@emilia-protocol/verify 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +37 -2
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -27,8 +27,43 @@ function sha256(input) {
27
27
  return crypto.createHash('sha256').update(input, 'utf8').digest('hex');
28
28
  }
29
29
 
30
- function canonicalize(obj) {
31
- return JSON.stringify(obj, Object.keys(obj).sort());
30
+ // Recursive canonical JSON — depth-first key sort at every level.
31
+ //
32
+ // The previous implementation
33
+ //
34
+ // JSON.stringify(obj, Object.keys(obj).sort())
35
+ //
36
+ // was a SHALLOW canonicalization. The second argument to JSON.stringify
37
+ // in array form is a property allowlist filter, NOT a sort order, and it
38
+ // does NOT recurse into nested objects to enforce key order at depth.
39
+ // Worse, it filters nested keys to only those names present in the
40
+ // top-level allowlist.
41
+ //
42
+ // Net effect of the shallow pattern: a verifier and a signer that both
43
+ // "sort keys before signing" could compute different canonical bytes for
44
+ // the same logical document, producing a false-negative signature
45
+ // failure. And nested fields (e.g. claim.context.risk_signals or
46
+ // claim.context.change.after_bank_hash) were not deterministically
47
+ // included in the signed material under the shallow algorithm.
48
+ //
49
+ // The fix below is the same recursive canonicalize() used by
50
+ // lib/guard-policies.js (hashCanonicalAction) on the server side, so
51
+ // signer and verifier produce byte-identical canonical material for any
52
+ // arbitrarily-nested object.
53
+ //
54
+ // Bug history: shipped in 1.0.0, fixed in 1.0.1. See package.json.
55
+ function canonicalize(value) {
56
+ if (value === null || value === undefined) return JSON.stringify(value);
57
+ if (Array.isArray(value)) {
58
+ return `[${value.map(canonicalize).join(',')}]`;
59
+ }
60
+ if (typeof value === 'object') {
61
+ return `{${Object.keys(value)
62
+ .sort()
63
+ .map((k) => JSON.stringify(k) + ':' + canonicalize(value[k]))
64
+ .join(',')}}`;
65
+ }
66
+ return JSON.stringify(value);
32
67
  }
33
68
 
34
69
  function hashPair(a, b) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emilia-protocol/verify",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Zero-dependency standalone verification for EP trust receipts, Merkle anchors, and commitment proofs. Uses only Node.js built-in crypto. No EP infrastructure required.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",