ClaimID formulas
Purpose: claim_id is the global, collision-resistant, route-agnostic identifier for a single monetizable fact. It guarantees one fact → one claim → one cashflow across all EDMA routes and mirrors.
A. Invariants (what the id must guarantee)
Uniqueness by evidence and identity: Same physical event with a different dossier (files, corrections) must not collide.
Determinism: Any honest client/server computing the id from the same inputs yields the same 32-byte value.
Domain separation: IDs from different lanes/schemas cannot collide.
Append-only lineage: Corrections/replacements mint a new claim_id and link replaces: old_claim_id. No mutation.
Cross-route safety: The same evidence cannot be used in Trade and Tokens at once.
B. Canonical definition (bytes and function)
We derive claim_id by hashing a domain-tagged, length-delimited tuple of typed fields.
claim_id = keccak256(
abi.encodePacked(
DOMAIN_TAG, // bytes8 = "EDMA/CLM"
VERSION_TAG, // bytes4 = 0x00000001
chainId_u256, // EVM chainId (uint256, ABI encoding)
laneTag, // bytes4: "TRAD" | "TOKN"
schemaHash, // bytes32 = keccak256(utf8(schema_id)), e.g. "TRADE.ON_BOARD.v1"
keyHash, // bytes32 = keccak256( key_blob ) (see Section D)
povHash // bytes32 = sha256(canonical_json_bytes) (Section 12)
)
)Why include povHash? Two dossiers with the same identity fields but different evidence (e.g., corrected BL/PSI) produce different claim_ids, preventing “same BL, different PDFs” abuse.
All strings are UTF-8. All hashes are 32 bytes. We rely on Solidity’s abi.encodePacked typing for length-delimited packing; do not hand-concatenate raw strings.
C. Normalization rules (before computing keyHash)
Schemas (Sec. 12.3): decide what fields compose the identity; these rules decide how to normalize them prior to hashing:
string_id fields (BL numbers, serials, ports, project IDs): Trim ASCII whitespace; collapse internal spaces; uppercase A–Z; NFC normalize; forbid control chars.
Reject if not matching schema’s pattern: (^[A-Z0-9._-]+$ unless tighter).
Timestamps: RFC-3339 UTC "YYYY-MM-DDTHH:MM:SSZ" (no milliseconds unless schema mandates).
Decimals: numbers encoded as strings with schema precision (e.g., "396000" Wh; "39.600" if precision=3). No leading +, no exponent.
Arrays: SET → sort ascending (binary compare) before hashing (e.g., container_ids). SEQ → preserve order (e.g., temperature log intervals).
Programs/regions/enums: canonical enumerations defined by schema.
If any normalization fails: clients must surface an error and the Gate will return E_FORMAT_INVALID or E_SET_NOT_SORTED.
D. keyHash construction (per schema)
We hash a key blob—the minimal identity for the event/unit—after normalization. Each schema specifies its fields.
D.1 Trade — On-Board & Sealed (TRADE.ON_BOARD.v1)
D.2 Trade — Customs Cleared (TRADE.CUSTOMS.v1)
Identity = customs entry no., country code, importer tax id.
D.3 Trade — Arrival & QA (TRADE.ARRIVAL_QA.v1)
Identity = ASN, DC id, time bucket (to the hour, unless schema says otherwise).
D.4 Tokens — Energy (1 MWh) (TOKENS.ENERGY.MWH.v1)
Identity = device id, start_ts, end_ts, quantity_Wh.
D.5 Tokens — Carbon (VCM program serial) (TOKENS.CARBON.VCM.v1)
Identity = program, project_id, vintage, unit_serial.
D.6 Registry Mirror (REG.MIRROR.v1) (mapping, not a second claim)
Mirrors bind an external serial to an existing claim_id (they do not mint a new one). For the mirror record we store:
E. Splits, merges, and replacements (lineage rules)
Split shipments (Trade): each sub-lot mints an EMT with its own claim_id.
key_fields := ( bl_number, seal_number, sha256(join_sorted(subset_container_ids)) )Note: include the subset; do not reuse the full list hash, or One-Claim will block the second child.Merge (downstream gates): a later gate may consume multiple child claim_ids; it lists them in the dossier; the downstream
claim_idis derived from its own identity (e.g., ASN/DC window). Children are marked consumed.Replacement (revocation): a corrective dossier computes a new
claim_id(new povHash and often new key fields) and stores replaces:old_claim_id. Proof pages show both entries; we never mutate the old claim.
F. Collision resistance & safety notes
Including schemaHash + povHash: provides domain separation and second-preimage resilience.
Using abi.encode/encodePacked: avoids manual delimiter mistakes; do not raw-concat strings.
For sets: hashing the sorted list (as a file digest) prevents order-based collisions.
Normalization: eliminates accidental collisions from casing/whitespace/locale.
G. Reference implementations
Solidity (library excerpt)
TypeScript (deterministic encoder)
(In production, use the SDK’s canonicalizer and provided helpers; the snippet shows types and packing, not full error handling.)
H. Worked examples (process, not full hex)
H.1 Trade — On-Board (TRADE.ON_BOARD.v1)
Normalize
keyHash =
keccak256("CLAIMKEY","TRADE.ON_BOARD.v1", BL, SEAL, containersSha256)schemaHash =
keccak256("TRADE.ON_BOARD.v1")povHash =
sha256(canonical_json_bytes)(includes file digests for bl.pdf, seal.jpg, etc.)claim_id =
keccak256( DOMAIN, V1, chainId, "TRAD", schemaHash, keyHash, povHash )
H.2 Tokens — Carbon (TOKENS.CARBON.VCM.v1)
Normalize:
program="VERRA", project_id="VCS-1234", vintage="2024", unit_serial="…-00012345"keyHash =
keccak256("CLAIMKEY","TOKENS.CARBON.VCM.v1", program, project_id, vintage, unit_serial)schemaHash =
keccak256("TOKENS.CARBON.VCM.v1")povHash =
sha256(canonical_json_bytes)claim_id =
keccak256( DOMAIN, V1, chainId, "TOKN", schemaHash, keyHash, povHash )
I. Validation & tests (you should pass these)
Determinism: recompute claim_id on different clients → identical results.
Normalization: casing/whitespace variants yield same id; local-time timestamps are rejected.
Set/sequence: changing order of container_ids (SET) doesn’t change id; changing order of a SEQ field must.
Evidence-sensitive: same key fields but different povHash → different claim_id.
Append-only: replacement dossier yields new claim_id; Explorer shows replaces linkage.
Cross-route: attempt to reuse the same key fields in Trade & Tokens → different lanes; One-Claim still blocks duplicates within each lane.
Plain recap
Last updated
