Skip to content

feat: CID readiness boundary — normalization, digest binding, size policy - #837

Open
GazzyLee wants to merge 1 commit into
DigiNodes:mainfrom
GazzyLee:cid-readiness-boundary
Open

GazzyLee wants to merge 1 commit into
DigiNodes:mainfrom
GazzyLee:cid-readiness-boundary

Conversation

@GazzyLee

@GazzyLee GazzyLee commented Oct 17, 2026 •

Copy link
Copy Markdown

Summary

Implements the post-Wave-9 source and hosted-gate audit's focused contract readiness boundary for Content Identifier (CID) handling in EvidenceManager.sol. Defines accepted CID versions/codecs/hashes, byte/string normalization, digest binding, size policy, and duplicate behavior. Adds comprehensive test coverage for valid/invalid CID, alternate encoding, Unicode, duplicate content, digest mismatch, zero/oversized metadata, and fuzz scenarios.

Problem Context

The post-Wave-9 audit identified a contract readiness boundary around CID handling in the legacy EvidenceManager module. The previous implementation:

  • Stored raw CID strings without normalization, allowing equivalent CIDs (e.g. "Z…" vs "z…") to hash to different identifiers and bypass duplicate detection
  • Had no digest binding, so a CID could not be cryptographically tied to its off-chain content
  • Lacked formal size policy documentation and zero-digest rejection

Technical Scope

1. Accepted CID Versions / Codecs / Hashes

Version Prefix Encoding Hash Status
CIDv0 Qm base58btc SHA2-256 ✅ Accepted
CIDv1 b / B base32 any ✅ Accepted
CIDv1 z / Z base58btc any ✅ Accepted
CIDv1 f / F base16 any ✅ Accepted
CIDv1 k / K base36 any ✅ Accepted
CIDv1 u / U base64url any ✅ Accepted
CIDv1 m / M base64 any ✅ Accepted
CIDv1 t / T base32hexpad any ✅ Accepted
— any other — — ❌ UnsupportedCIDVersion()

Exposed as public constants:

  • CID_PREFIX_V0 = "Qm"
  • CID_PREFIX_V1_LOWERCASE = "bzfkumt"

2. Byte/String Normalization

New _normalizeCID() internal pure function:

  • CIDv0 ("Qm…"): returned unchanged (already canonical)
  • CIDv1: first byte lowercased if uppercase (B→b, Z→z, F→f, K→k, U→u, M→m, T→t); rest of string unchanged

Equivalent CIDs (e.g. "ZDJ7…" vs "zdj7…") resolve to the same canonical string before hashing or storage, ensuring deterministic duplicate detection.

3. Digest Binding

addEvidence now accepts a bytes32 contentDigest parameter:

  • Zero digests revert with ZeroDigest()
  • Bidirectional consistency enforcement per claim:
    • A CID may only be bound to one content digest (_cidToDigest)
    • A content digest may only be bound to one CID (_digestToCid)
  • Digest/CID mismatch reverts with ContentDigestMismatch()

4. Size Policy

Policy Value Error
Minimum CID length 2 bytes (MIN_CID_LENGTH) InvalidCID()
Maximum CID length 512 bytes (MAX_CID_LENGTH) CIDTooLarge()
Max evidence per claim 100 (MAX_EVIDENCE_PER_CLAIM) EvidenceCapacityExceeded()

5. Duplicate Behavior

  • Duplicates detected on the canonical (normalized) CID hash per claim
  • Same CID on a different claim is permitted (scoped duplicate detection)
  • Same CID re-submitted by a different user to the same claim still reverts DuplicateEvidence()

Events

EvidenceAdded now exposes canonical identifiers:

event EvidenceAdded(
    uint256 indexed claimId,
    uint256 indexed evidenceId,
    address indexed uploader,
    string cid,           // canonical (normalized) CID string
    bytes32 indexed cidHash,   // keccak256(canonical CID) — canonical on-chain identifier
    bytes32 contentDigest      // bound content digest
);

cidHash is indexed for efficient log filtering by consumers (Indexer, Frontend, Verification Engine, Dispute Resolution, Settlement Engine, Reputation System).

Files Changed

File Change
contracts/EvidenceManager.sol Core contract: normalization, digest binding, size policy, duplicate behavior, new errors, expanded event
test/EvidenceManager.test.ts Test suite: all existing tests updated + new test groups
config/networks.ts New: missing network configuration module required by hardhat.config.ts

Test Coverage

Valid / Invalid CID

  • CIDv0, CIDv1 base32, base58btc, base16 accepted
  • Empty string, single char, oversized (>512 bytes) rejected
  • Unknown prefixes (x, 1, http://, etc.) rejected with UnsupportedCIDVersion()
  • Q without m second char rejected

Alternate Encoding (Normalization)

  • Uppercase CIDv1 prefixes (Z, B, F) accepted and normalized to lowercase
  • Equivalent CIDs (uppercase vs lowercase) resolve to same canonical identifier → duplicate rejected
  • isCIDRegistered and getContentDigest normalize input before lookup

Unicode Validation

  • Non-ASCII UTF-8 characters (é, emoji) rejected with InvalidCID()
  • Control characters (NUL, tab) rejected

Duplicate Content

  • Same CID re-submitted to same claim → DuplicateEvidence()
  • Same CID on different claim → permitted
  • Different CID on same claim → permitted
  • Duplicate by different uploader → still rejected

Mismatched Digest

  • Same CID + different digest → ContentDigestMismatch()
  • Same digest + different CID → ContentDigestMismatch()
  • Same CID + same digest on different claims → permitted
  • getContentDigest returns bound digest; zero for unregistered

Zero / Oversized Metadata

  • Zero content digest → ZeroDigest()
  • CID below MIN_CID_LENGTH → InvalidCID()
  • CID above MAX_CID_LENGTH → CIDTooLarge()
  • CID at exactly MAX_CID_LENGTH (512 bytes) → accepted

Fuzz Tests

  • 20 random valid CIDv0 strings accepted with consistent hashing
  • 11 random invalid prefixes all rejected with UnsupportedCIDVersion()
  • 10 random wrong digests against same CID all rejected with ContentDigestMismatch()

Acceptance Criteria Mapping

Criterion Status
Equivalent CIDs normalize deterministically ✅ _normalizeCID lowercases CIDv1 multibase prefix
Unsupported codecs/hashes fail ✅ _validateCIDPrefix rejects unknown prefixes
Digest/CID mismatch cannot register ✅ Bidirectional _cidToDigest / _digestToCid enforcement
Events expose canonical identifiers ✅ EvidenceAdded emits cidHash (indexed) and contentDigest

Constraints Preserved

  • ✅ Approved V2 Optimism/EVM topology — no cross-chain or Stellar dependencies
  • ✅ Exact arithmetic — all unchecked blocks preserved, no arithmetic changes
  • ✅ Governance/timelock authority — AccessControl roles unchanged (PAUSER_ROLE, CLAIM_REGISTRY_ROLE)
  • ✅ Scoped pause model — whenNotPaused modifier preserved
  • ✅ Deterministic artifacts — keccak256 of canonical CID string is deterministic
  • ✅ Consumer interface handoffs — getEvidence, getEvidenceByClaim, isCIDRegistered signatures preserved; new getContentDigest additive only
  • ✅ Never weakens a failing security gate — all existing revert conditions preserved; new gates are additive

Dependencies

Non-Goals

  • Unrelated refactors
  • Mainnet deployment
  • Applying Stellar Wave before maintainer activation

Verification

  • Existing EvidenceManager.test.ts tests updated for new contentDigest parameter and expanded EvidenceAdded event
  • New test groups: alternate encoding normalization, Unicode validation, digest binding, size policy, fuzz
  • No other Solidity contracts call addEvidence directly (verified via grep)
  • Frontend does not call addEvidence directly (only UI label string in messages/en.json)

Closes #724

…licy

Define accepted CID versions/codecs/hashes (CIDv0 Qm, CIDv1 multibase b/z/f/k/u/m/t),
normalize CIDv1 multibase prefixes to lowercase canonical form, bind each CID to a
caller-supplied content digest with bidirectional consistency enforcement, enforce
MIN/MAX CID length bounds, and detect duplicates on the canonical CID hash per claim.
Events expose canonical cidHash and contentDigest. Adds comprehensive tests for
valid/invalid CID, alternate encoding, Unicode, duplicate content, digest mismatch,
zero/oversized metadata, and fuzz scenarios.

Closes DigiNodes#724
@GazzyLee
GazzyLee requested a review from dDevAhmed as a code owner October 17, 2026 15:24
@coderabbitai

coderabbitai Bot commented Oct 17, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository: DigiNodes/truthbounty-contract/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 1b3408eb-f903-429a-aa82-a69057708fae
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PREWAVE-SC-048 — Canonicalize Evidence CID and Digest Validation

1 participant