1import { keccak_256 } from "@noble/hashes/sha3";
2
3// Minimal RFC-8785 JCS canonicalization
4export function jcs(obj: any): string {
5 if (obj === null) return "null";
6 if (typeof obj === "number") return JSON.stringify(obj);
7 if (typeof obj === "string" || typeof obj === "boolean")
8 return JSON.stringify(obj);
9 if (Array.isArray(obj))
10 return "[" + obj.map(jcs).join(",") + "]";
11
12 const keys = Object.keys(obj).sort();
13 return "{" + keys.map(k =>
14 JSON.stringify(k) + ":" + jcs(obj[k])
15 ).join(",") + "}";
16}
17
18export function hashPayload(
19 payloadNoNonce: any,
20 nonceHex: string
21): `0x${string}` {
22 const encoder = new TextEncoder();
23 const a = encoder.encode(jcs(payloadNoNonce));
24 const b = Uint8Array.from(
25 Buffer.from(nonceHex.replace(/^0x/, ''), 'hex')
26 );
27 const cat = new Uint8Array(a.length + b.length);
28 cat.set(a, 0);
29 cat.set(b, a.length);
30 const h = keccak_256(cat);
31 return ("0x" + Buffer.from(h).toString("hex")) as `0x${string}`;
32}1import json
2from eth_hash.auto import keccak
3
4def jcs(obj):
5 if obj is None: return "null"
6 if isinstance(obj, bool): return "true" if obj else "false"
7 if isinstance(obj, (int, float)):
8 return json.dumps(obj, separators=(",", ":"))
9 if isinstance(obj, str):
10 return json.dumps(obj, ensure_ascii=False, separators=(",", ":"))
11 if isinstance(obj, list):
12 return "[" + ",".join(jcs(x) for x in obj) + "]"
13
14 keys = sorted(obj.keys())
15 return "{" + ",".join(
16 json.dumps(k, ensure_ascii=False) + ":" + jcs(obj[k])
17 for k in keys
18 ) + "}"
19
20def hash_payload(payload_no_nonce: dict, nonce_hex: str) -> str:
21 canonical = jcs(payload_no_nonce).encode("utf-8")
22 nonce = bytes.fromhex(
23 nonce_hex[2:] if nonce_hex.startswith("0x") else nonce_hex
24 )
25 return "0x" + keccak(canonical + nonce).hex()1# install: node >=18
2npx chaingit/chaintick-verifier verify --id <signalId>
3
4# or local file
5npx chaingit/chaintick-verifier verify --payload payload.json --nonce 0x...