3. Canonicalization & Hashing

Summary: the same reality must hash to the same bytes. EDMA only accepts evidence that is reduced to a canonical JSON and hashed to an evidenceHash any verifier can reproduce exactly. Ambiguous formatting (pretty-printing, locale numbers, unit drift) is rejected before PoV.

Why this matters

Canonicalization removes ambiguity. Hashing turns that unambiguous payload into a portable fingerprint. With a stable evidenceHash and a route-agnostic claimId, independent attestors—and the PoV Gate—can agree on truth without coordination.

Canonical JSON (normative)

Use UTF-8 encoding, no BOM. Keys are ASCII-sorted at every object level. Emit minified JSON (only ’,’ and ’:’ separators; no spaces/newlines). Numbers are integers unless you explicitly send a decimal string (do not send floats or scientific notation). Time is UNIX UTC seconds in start_ts and end_ts. Energy is Wh in quantity_wh. 32-byte values are lowercase hex with 0x prefix.

Minimal energy window object:

{"batch_id":"0x<32-byte>","device_id":"0x<32-byte>","end_ts":1698883200,"nonce":"0x<32-byte>","quantity_wh":10000,"start_ts":1698796800}

Optional but helpful fields include grid_node, firmware, method or method_hash, samples, clock_offset_ms, source_file_hash.

From canonical JSON to hashes

Compute the digest exactly as written here. Any formatting difference produces a different hash.

Compute evidenceHash:

evidenceHash = SHA-256( utf8(canonical_json) )  // bytes32, hex, 0x-prefixed, lowercase

Compute route-agnostic claimId: (prevents cross-market reuse of the same evidence)

bytes32 claimId = keccak256(
  abi.encodePacked(deviceId, startTs, endTs, quantityWh, evidenceHash)
);

Why both: evidenceHash binds to the exact bytes of your canonical JSON; claimId binds that evidence to deviceId, the time window, and the energy quantity for One-Claim exclusivity.

Where to canonicalize and sign

Do it at the source (meter or trusted gateway) immediately after aggregation. Submit already-canonical JSON to ingestion. Sign the canonical JSON bytes (or their SHA-256) with the device key, and include the signature in headers. The server verifies signature, checks window rules, recomputes the hash, and only then proceeds to attest.

Common pitfalls (reject upstream)

Floating numbers (e.g., 10000.0), scientific notation (e.g., 1e4), locale formats (comma decimals), unsorted keys, unintended array ordering, kWh instead of quantity_wh in Wh, local time instead of UTC, mixed hex casing or missing 0x.

Test vector (use in CI)

evidenceHash: 0xdca11c7b4c9347db3134f5cced0610541b863c22cc26325d9c28e7e41870c34d

(Your SDKs must reproduce this value byte-for-byte.)

Helper code

TypeScript:

Python:

Conformance (simple checklist)

Write canonical JSON; compute and sign evidenceHash at the source; include batch_id, device_id, start_ts, end_ts, quantity_wh, nonce; reject overlaps and non-UTC; persist the exact canonical JSON off-chain; compute the same claimId in all SDKs; call the Gate in the same transaction that mints or settles.

Drawing

Last updated