@mikeargento/bitgraph-verify 1.1.2 → 1.2.0
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.
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/proof-hash.d.ts +30 -1
- package/dist/proof-hash.d.ts.map +1 -1
- package/dist/proof-hash.js +92 -14
- package/dist/proof-hash.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/proof-hash.ts +93 -14
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
export type { BitGraphProof, BitGraphPolicy, VerificationPolicy, SignedBody, EnforcementTier, Attribution, PolicyBinding, SlotAllocation, ActorIdentity, AuthorizationPayload, WebAuthnAuthorization, AgencyEnvelope, } from "./types.js";
|
|
9
9
|
export { verify, verifyProofIntegrity, resetEpochLinkState } from "./verifier.js";
|
|
10
10
|
export type { VerifyResult, ProofIntegrityResult } from "./verifier.js";
|
|
11
|
-
export { computeProofHash, computeChainHash } from "./proof-hash.js";
|
|
11
|
+
export { computeProofHash, computeChainHash, buildSignedBody, computeSignedBodyHash } from "./proof-hash.js";
|
|
12
12
|
export { canonicalize, canonicalizeToString, constantTimeEqual } from "./canonical.js";
|
|
13
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,YAAY,EACV,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,UAAU,EACV,eAAe,EACf,WAAW,EACX,aAAa,EACb,cAAc,EACd,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,MAAM,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAClF,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAExE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,YAAY,EACV,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,UAAU,EACV,eAAe,EACf,WAAW,EACX,aAAa,EACb,cAAc,EACd,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,MAAM,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAClF,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAExE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE7G,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
|
|
2
2
|
export { verify, verifyProofIntegrity, resetEpochLinkState } from "./verifier.js";
|
|
3
|
-
export { computeProofHash, computeChainHash } from "./proof-hash.js";
|
|
3
|
+
export { computeProofHash, computeChainHash, buildSignedBody, computeSignedBodyHash } from "./proof-hash.js";
|
|
4
4
|
export { canonicalize, canonicalizeToString, constantTimeEqual } from "./canonical.js";
|
|
5
5
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAyBrF,OAAO,EAAE,MAAM,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAGlF,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAyBrF,OAAO,EAAE,MAAM,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAGlF,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE7G,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/proof-hash.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { BitGraphProof } from "./types.js";
|
|
2
2
|
/**
|
|
3
|
-
* Compute the
|
|
3
|
+
* Compute the ledger identity hash of a BitGraph proof.
|
|
4
|
+
*
|
|
5
|
+
* Hashes the frozen signed-body subset described at the top of this file.
|
|
6
|
+
* This is NOT the value the Ed25519 signature covers and NOT the attestation
|
|
7
|
+
* user_data; for that, reconstruct the full signed body (verifier.ts step 3).
|
|
4
8
|
*
|
|
5
9
|
* @param proof - The full BitGraphProof object (or equivalent Record)
|
|
6
10
|
* @returns Base64-standard encoded SHA-256 hash
|
|
@@ -27,4 +31,29 @@ export declare function computeProofHash(proof: BitGraphProof | Record<string, u
|
|
|
27
31
|
* @returns Base64-standard encoded SHA-256 hash
|
|
28
32
|
*/
|
|
29
33
|
export declare function computeChainHash(proof: BitGraphProof | Record<string, unknown>): string;
|
|
34
|
+
/**
|
|
35
|
+
* Reconstruct the FULL canonical signed body: the bytes the Ed25519 signature
|
|
36
|
+
* actually covers, and the bytes whose SHA-256 the enclave places in the Nitro
|
|
37
|
+
* attestation's `user_data`.
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ THIS IS HASH (2) IN THE LIST AT THE TOP OF THIS FILE, AND IT IS NOT
|
|
40
|
+
* computeProofHash. For a proof carrying neither `actor` nor `policy` the two
|
|
41
|
+
* coincide, which is why substituting one for the other survives every
|
|
42
|
+
* ordinary proof and fails only on agency or policy proofs — a bug that hides
|
|
43
|
+
* until the day the feature ships. That is exactly what happened: the offline
|
|
44
|
+
* auditor compared the attestation's user_data against computeProofHash and
|
|
45
|
+
* reported every declared recording as belonging to "some other proof",
|
|
46
|
+
* turning a valid proof into a FALSE (2026-08-18, found on the first real
|
|
47
|
+
* declaration, ledger position #12,010).
|
|
48
|
+
*
|
|
49
|
+
* Signature verification and attestation binding MUST both use this function
|
|
50
|
+
* so they cannot drift apart again.
|
|
51
|
+
*/
|
|
52
|
+
export declare function buildSignedBody(proof: BitGraphProof | Record<string, unknown>): Record<string, unknown>;
|
|
53
|
+
/**
|
|
54
|
+
* base64(SHA-256(canonical full signed body)). The value the enclave writes
|
|
55
|
+
* into the attestation's user_data, and the digest the Ed25519 signature
|
|
56
|
+
* covers. See buildSignedBody for why this is never computeProofHash.
|
|
57
|
+
*/
|
|
58
|
+
export declare function computeSignedBodyHash(proof: BitGraphProof | Record<string, unknown>): string;
|
|
30
59
|
//# sourceMappingURL=proof-hash.d.ts.map
|
package/dist/proof-hash.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proof-hash.d.ts","sourceRoot":"","sources":["../src/proof-hash.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"proof-hash.d.ts","sourceRoot":"","sources":["../src/proof-hash.ts"],"names":[],"mappings":"AAoEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CA2BvF;AAsBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAavF;AAcD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqBvG;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAE5F"}
|
package/dist/proof-hash.js
CHANGED
|
@@ -4,28 +4,53 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Canonical, deterministic proof hash computation.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Ed25519 signature covers, making the hash verifiable.
|
|
10
|
-
*
|
|
11
|
-
* Signed body fields:
|
|
7
|
+
* This is the LEDGER IDENTITY of a proof. It hashes a fixed SUBSET of the
|
|
8
|
+
* signed body:
|
|
12
9
|
* - version, artifact, commit
|
|
13
10
|
* - publicKeyB64 (from signer)
|
|
14
11
|
* - enforcement, measurement (from environment)
|
|
15
12
|
* - attribution (if present)
|
|
16
13
|
* - attestationFormat (if attestation present)
|
|
17
14
|
*
|
|
18
|
-
*
|
|
19
|
-
* - S3 ledger key generation
|
|
15
|
+
* Used for:
|
|
16
|
+
* - S3 ledger key generation — proofs/{epoch}/{counter}-{proofHash}.json
|
|
20
17
|
* - Ethereum anchor binding
|
|
21
18
|
* - Proof deduplication and canonical identity
|
|
22
19
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
20
|
+
* ── THE FIELD LIST ABOVE IS FROZEN. DO NOT ADD TO IT. ──
|
|
21
|
+
*
|
|
22
|
+
* It is tempting to "fix" this function by adding `actor` and `policy`, which
|
|
23
|
+
* ARE in the signed body the enclave signs. Doing so is a silent, permanent
|
|
24
|
+
* break: this value is baked into every S3 object key already written, and
|
|
25
|
+
* ledger verify() compares a recomputed value against the stored `proofHash`
|
|
26
|
+
* field (packages/ledger/src/verify.ts). Widening the subset changes the hash
|
|
27
|
+
* of every existing agency-bearing or policy-bearing proof, orphaning its key
|
|
28
|
+
* and failing its hash check forever. The subset is a compatibility contract,
|
|
29
|
+
* not an approximation of the signature.
|
|
30
|
+
*
|
|
31
|
+
* BitGraph has THREE distinct proof hashes. Confusing them is the single
|
|
32
|
+
* easiest way to break the system, so they are spelled out here:
|
|
33
|
+
*
|
|
34
|
+
* 1. computeProofHash (this function) — signed-body SUBSET.
|
|
35
|
+
* Ledger identity and S3 key. Frozen. Not a signature-equivalent value.
|
|
36
|
+
*
|
|
37
|
+
* 2. SHA-256 of the FULL canonical signed body — what the Ed25519 signature
|
|
38
|
+
* actually covers, and what the enclave puts in the Nitro attestation's
|
|
39
|
+
* user_data. Reconstructed by verifier.ts (step 3) and, in the browser,
|
|
40
|
+
* by buildSignedBody()/proofHashB64() in website/src/lib/bitgraph.ts.
|
|
41
|
+
* For a proof with no `actor` and no `policy` this coincides with (1);
|
|
42
|
+
* for agency or policy proofs it does NOT. Use this for signature and
|
|
43
|
+
* attestation checks. Never use (1).
|
|
44
|
+
*
|
|
45
|
+
* 3. computeChainHash (below) — the WHOLE proof, minus ledger-added fields.
|
|
46
|
+
* This is what the enclave writes into the next proof's commit.prevB64
|
|
47
|
+
* and into epochLink.prevProofHashB64. Matching prevB64 against (1)
|
|
48
|
+
* silently fails to link real chains.
|
|
49
|
+
*
|
|
50
|
+
* packages/hosted/src/bitcoin-anchor.ts carries a deliberate inline copy of
|
|
51
|
+
* (1) because Railway cannot resolve the monorepo package. It mirrors this
|
|
52
|
+
* field list exactly and must continue to; if this list ever does change,
|
|
53
|
+
* that copy changes with it in the same commit.
|
|
29
54
|
*
|
|
30
55
|
* Algorithm:
|
|
31
56
|
* 1. Extract signed body fields
|
|
@@ -40,7 +65,11 @@
|
|
|
40
65
|
import { canonicalize } from "./canonical.js";
|
|
41
66
|
import { sha256 } from "@noble/hashes/sha256";
|
|
42
67
|
/**
|
|
43
|
-
* Compute the
|
|
68
|
+
* Compute the ledger identity hash of a BitGraph proof.
|
|
69
|
+
*
|
|
70
|
+
* Hashes the frozen signed-body subset described at the top of this file.
|
|
71
|
+
* This is NOT the value the Ed25519 signature covers and NOT the attestation
|
|
72
|
+
* user_data; for that, reconstruct the full signed body (verifier.ts step 3).
|
|
44
73
|
*
|
|
45
74
|
* @param proof - The full BitGraphProof object (or equivalent Record)
|
|
46
75
|
* @returns Base64-standard encoded SHA-256 hash
|
|
@@ -134,4 +163,53 @@ function base64(hash) {
|
|
|
134
163
|
}
|
|
135
164
|
return btoa(binary);
|
|
136
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Reconstruct the FULL canonical signed body: the bytes the Ed25519 signature
|
|
168
|
+
* actually covers, and the bytes whose SHA-256 the enclave places in the Nitro
|
|
169
|
+
* attestation's `user_data`.
|
|
170
|
+
*
|
|
171
|
+
* ⚠️ THIS IS HASH (2) IN THE LIST AT THE TOP OF THIS FILE, AND IT IS NOT
|
|
172
|
+
* computeProofHash. For a proof carrying neither `actor` nor `policy` the two
|
|
173
|
+
* coincide, which is why substituting one for the other survives every
|
|
174
|
+
* ordinary proof and fails only on agency or policy proofs — a bug that hides
|
|
175
|
+
* until the day the feature ships. That is exactly what happened: the offline
|
|
176
|
+
* auditor compared the attestation's user_data against computeProofHash and
|
|
177
|
+
* reported every declared recording as belonging to "some other proof",
|
|
178
|
+
* turning a valid proof into a FALSE (2026-08-18, found on the first real
|
|
179
|
+
* declaration, ledger position #12,010).
|
|
180
|
+
*
|
|
181
|
+
* Signature verification and attestation binding MUST both use this function
|
|
182
|
+
* so they cannot drift apart again.
|
|
183
|
+
*/
|
|
184
|
+
export function buildSignedBody(proof) {
|
|
185
|
+
const p = proof;
|
|
186
|
+
const signer = p.signer;
|
|
187
|
+
const env = p.environment;
|
|
188
|
+
const body = {
|
|
189
|
+
version: p.version,
|
|
190
|
+
artifact: p.artifact,
|
|
191
|
+
commit: p.commit,
|
|
192
|
+
publicKeyB64: signer?.publicKeyB64,
|
|
193
|
+
enforcement: env?.enforcement,
|
|
194
|
+
measurement: env?.measurement,
|
|
195
|
+
};
|
|
196
|
+
if (env?.attestation !== undefined)
|
|
197
|
+
body.attestationFormat = env.attestation.format;
|
|
198
|
+
const agency = p.agency;
|
|
199
|
+
if (agency !== undefined)
|
|
200
|
+
body.actor = agency.actor;
|
|
201
|
+
if (p.policy !== undefined)
|
|
202
|
+
body.policy = p.policy;
|
|
203
|
+
if (p.attribution !== undefined)
|
|
204
|
+
body.attribution = p.attribution;
|
|
205
|
+
return body;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* base64(SHA-256(canonical full signed body)). The value the enclave writes
|
|
209
|
+
* into the attestation's user_data, and the digest the Ed25519 signature
|
|
210
|
+
* covers. See buildSignedBody for why this is never computeProofHash.
|
|
211
|
+
*/
|
|
212
|
+
export function computeSignedBodyHash(proof) {
|
|
213
|
+
return base64(sha256(canonicalize(buildSignedBody(proof))));
|
|
214
|
+
}
|
|
137
215
|
//# sourceMappingURL=proof-hash.js.map
|
package/dist/proof-hash.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proof-hash.js","sourceRoot":"","sources":["../src/proof-hash.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAErF
|
|
1
|
+
{"version":3,"file":"proof-hash.js","sourceRoot":"","sources":["../src/proof-hash.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -26,6 +26,6 @@ export type {
|
|
|
26
26
|
export { verify, verifyProofIntegrity, resetEpochLinkState } from "./verifier.js";
|
|
27
27
|
export type { VerifyResult, ProofIntegrityResult } from "./verifier.js";
|
|
28
28
|
|
|
29
|
-
export { computeProofHash, computeChainHash } from "./proof-hash.js";
|
|
29
|
+
export { computeProofHash, computeChainHash, buildSignedBody, computeSignedBodyHash } from "./proof-hash.js";
|
|
30
30
|
|
|
31
31
|
export { canonicalize, canonicalizeToString, constantTimeEqual } from "./canonical.js";
|
package/src/proof-hash.ts
CHANGED
|
@@ -5,28 +5,53 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Canonical, deterministic proof hash computation.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Ed25519 signature covers, making the hash verifiable.
|
|
11
|
-
*
|
|
12
|
-
* Signed body fields:
|
|
8
|
+
* This is the LEDGER IDENTITY of a proof. It hashes a fixed SUBSET of the
|
|
9
|
+
* signed body:
|
|
13
10
|
* - version, artifact, commit
|
|
14
11
|
* - publicKeyB64 (from signer)
|
|
15
12
|
* - enforcement, measurement (from environment)
|
|
16
13
|
* - attribution (if present)
|
|
17
14
|
* - attestationFormat (if attestation present)
|
|
18
15
|
*
|
|
19
|
-
*
|
|
20
|
-
* - S3 ledger key generation
|
|
16
|
+
* Used for:
|
|
17
|
+
* - S3 ledger key generation — proofs/{epoch}/{counter}-{proofHash}.json
|
|
21
18
|
* - Ethereum anchor binding
|
|
22
19
|
* - Proof deduplication and canonical identity
|
|
23
20
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
21
|
+
* ── THE FIELD LIST ABOVE IS FROZEN. DO NOT ADD TO IT. ──
|
|
22
|
+
*
|
|
23
|
+
* It is tempting to "fix" this function by adding `actor` and `policy`, which
|
|
24
|
+
* ARE in the signed body the enclave signs. Doing so is a silent, permanent
|
|
25
|
+
* break: this value is baked into every S3 object key already written, and
|
|
26
|
+
* ledger verify() compares a recomputed value against the stored `proofHash`
|
|
27
|
+
* field (packages/ledger/src/verify.ts). Widening the subset changes the hash
|
|
28
|
+
* of every existing agency-bearing or policy-bearing proof, orphaning its key
|
|
29
|
+
* and failing its hash check forever. The subset is a compatibility contract,
|
|
30
|
+
* not an approximation of the signature.
|
|
31
|
+
*
|
|
32
|
+
* BitGraph has THREE distinct proof hashes. Confusing them is the single
|
|
33
|
+
* easiest way to break the system, so they are spelled out here:
|
|
34
|
+
*
|
|
35
|
+
* 1. computeProofHash (this function) — signed-body SUBSET.
|
|
36
|
+
* Ledger identity and S3 key. Frozen. Not a signature-equivalent value.
|
|
37
|
+
*
|
|
38
|
+
* 2. SHA-256 of the FULL canonical signed body — what the Ed25519 signature
|
|
39
|
+
* actually covers, and what the enclave puts in the Nitro attestation's
|
|
40
|
+
* user_data. Reconstructed by verifier.ts (step 3) and, in the browser,
|
|
41
|
+
* by buildSignedBody()/proofHashB64() in website/src/lib/bitgraph.ts.
|
|
42
|
+
* For a proof with no `actor` and no `policy` this coincides with (1);
|
|
43
|
+
* for agency or policy proofs it does NOT. Use this for signature and
|
|
44
|
+
* attestation checks. Never use (1).
|
|
45
|
+
*
|
|
46
|
+
* 3. computeChainHash (below) — the WHOLE proof, minus ledger-added fields.
|
|
47
|
+
* This is what the enclave writes into the next proof's commit.prevB64
|
|
48
|
+
* and into epochLink.prevProofHashB64. Matching prevB64 against (1)
|
|
49
|
+
* silently fails to link real chains.
|
|
50
|
+
*
|
|
51
|
+
* packages/hosted/src/bitcoin-anchor.ts carries a deliberate inline copy of
|
|
52
|
+
* (1) because Railway cannot resolve the monorepo package. It mirrors this
|
|
53
|
+
* field list exactly and must continue to; if this list ever does change,
|
|
54
|
+
* that copy changes with it in the same commit.
|
|
30
55
|
*
|
|
31
56
|
* Algorithm:
|
|
32
57
|
* 1. Extract signed body fields
|
|
@@ -44,7 +69,11 @@ import { sha256 } from "@noble/hashes/sha256";
|
|
|
44
69
|
import type { BitGraphProof } from "./types.js";
|
|
45
70
|
|
|
46
71
|
/**
|
|
47
|
-
* Compute the
|
|
72
|
+
* Compute the ledger identity hash of a BitGraph proof.
|
|
73
|
+
*
|
|
74
|
+
* Hashes the frozen signed-body subset described at the top of this file.
|
|
75
|
+
* This is NOT the value the Ed25519 signature covers and NOT the attestation
|
|
76
|
+
* user_data; for that, reconstruct the full signed body (verifier.ts step 3).
|
|
48
77
|
*
|
|
49
78
|
* @param proof - The full BitGraphProof object (or equivalent Record)
|
|
50
79
|
* @returns Base64-standard encoded SHA-256 hash
|
|
@@ -144,3 +173,53 @@ function base64(hash: Uint8Array): string {
|
|
|
144
173
|
}
|
|
145
174
|
return btoa(binary);
|
|
146
175
|
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Reconstruct the FULL canonical signed body: the bytes the Ed25519 signature
|
|
179
|
+
* actually covers, and the bytes whose SHA-256 the enclave places in the Nitro
|
|
180
|
+
* attestation's `user_data`.
|
|
181
|
+
*
|
|
182
|
+
* ⚠️ THIS IS HASH (2) IN THE LIST AT THE TOP OF THIS FILE, AND IT IS NOT
|
|
183
|
+
* computeProofHash. For a proof carrying neither `actor` nor `policy` the two
|
|
184
|
+
* coincide, which is why substituting one for the other survives every
|
|
185
|
+
* ordinary proof and fails only on agency or policy proofs — a bug that hides
|
|
186
|
+
* until the day the feature ships. That is exactly what happened: the offline
|
|
187
|
+
* auditor compared the attestation's user_data against computeProofHash and
|
|
188
|
+
* reported every declared recording as belonging to "some other proof",
|
|
189
|
+
* turning a valid proof into a FALSE (2026-08-18, found on the first real
|
|
190
|
+
* declaration, ledger position #12,010).
|
|
191
|
+
*
|
|
192
|
+
* Signature verification and attestation binding MUST both use this function
|
|
193
|
+
* so they cannot drift apart again.
|
|
194
|
+
*/
|
|
195
|
+
export function buildSignedBody(proof: BitGraphProof | Record<string, unknown>): Record<string, unknown> {
|
|
196
|
+
const p = proof as Record<string, unknown>;
|
|
197
|
+
const signer = p.signer as { publicKeyB64?: string } | undefined;
|
|
198
|
+
const env = p.environment as
|
|
199
|
+
| { enforcement?: string; measurement?: string; attestation?: { format?: string } }
|
|
200
|
+
| undefined;
|
|
201
|
+
|
|
202
|
+
const body: Record<string, unknown> = {
|
|
203
|
+
version: p.version,
|
|
204
|
+
artifact: p.artifact,
|
|
205
|
+
commit: p.commit,
|
|
206
|
+
publicKeyB64: signer?.publicKeyB64,
|
|
207
|
+
enforcement: env?.enforcement,
|
|
208
|
+
measurement: env?.measurement,
|
|
209
|
+
};
|
|
210
|
+
if (env?.attestation !== undefined) body.attestationFormat = env.attestation.format;
|
|
211
|
+
const agency = p.agency as { actor?: unknown } | undefined;
|
|
212
|
+
if (agency !== undefined) body.actor = agency.actor;
|
|
213
|
+
if (p.policy !== undefined) body.policy = p.policy;
|
|
214
|
+
if (p.attribution !== undefined) body.attribution = p.attribution;
|
|
215
|
+
return body;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* base64(SHA-256(canonical full signed body)). The value the enclave writes
|
|
220
|
+
* into the attestation's user_data, and the digest the Ed25519 signature
|
|
221
|
+
* covers. See buildSignedBody for why this is never computeProofHash.
|
|
222
|
+
*/
|
|
223
|
+
export function computeSignedBodyHash(proof: BitGraphProof | Record<string, unknown>): string {
|
|
224
|
+
return base64(sha256(canonicalize(buildSignedBody(proof) as unknown as BitGraphProof)));
|
|
225
|
+
}
|