@mikeargento/bitgraph 1.9.0 → 1.9.2
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/LICENSE +1 -1
- package/README.md +58 -35
- package/dist/constructor.js +1 -1
- package/dist/constructor.js.map +1 -1
- package/dist/fuse-cli.js +1 -1
- package/dist/fuse-cli.js.map +1 -1
- package/dist/fuse.js +1 -1
- package/dist/fuse.js.map +1 -1
- package/dist/host.js +1 -1
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/policy.js +1 -1
- package/dist/policy.js.map +1 -1
- package/package.json +23 -8
- package/src/constructor.ts +1 -1
- package/src/fuse-cli.ts +1 -1
- package/src/fuse.ts +1 -1
- package/src/host.ts +1 -1
- package/src/index.ts +3 -2
- package/src/policy.ts +1 -1
- package/dist/__tests__/audit-anchors.test.d.ts +0 -2
- package/dist/__tests__/audit-anchors.test.d.ts.map +0 -1
- package/dist/__tests__/audit-anchors.test.js +0 -152
- package/dist/__tests__/audit-anchors.test.js.map +0 -1
- package/dist/__tests__/audit-anomalies.test.d.ts +0 -2
- package/dist/__tests__/audit-anomalies.test.d.ts.map +0 -1
- package/dist/__tests__/audit-anomalies.test.js +0 -663
- package/dist/__tests__/audit-anomalies.test.js.map +0 -1
- package/dist/__tests__/audit-attestation.test.d.ts +0 -2
- package/dist/__tests__/audit-attestation.test.d.ts.map +0 -1
- package/dist/__tests__/audit-attestation.test.js +0 -403
- package/dist/__tests__/audit-attestation.test.js.map +0 -1
- package/dist/__tests__/audit-bench-smoke.test.d.ts +0 -2
- package/dist/__tests__/audit-bench-smoke.test.d.ts.map +0 -1
- package/dist/__tests__/audit-bench-smoke.test.js +0 -87
- package/dist/__tests__/audit-bench-smoke.test.js.map +0 -1
- package/dist/__tests__/audit-boundary.test.d.ts +0 -20
- package/dist/__tests__/audit-boundary.test.d.ts.map +0 -1
- package/dist/__tests__/audit-boundary.test.js +0 -84
- package/dist/__tests__/audit-boundary.test.js.map +0 -1
- package/dist/__tests__/audit-cli.test.d.ts +0 -2
- package/dist/__tests__/audit-cli.test.d.ts.map +0 -1
- package/dist/__tests__/audit-cli.test.js +0 -239
- package/dist/__tests__/audit-cli.test.js.map +0 -1
- package/dist/__tests__/audit-dependencies.test.d.ts +0 -2
- package/dist/__tests__/audit-dependencies.test.d.ts.map +0 -1
- package/dist/__tests__/audit-dependencies.test.js +0 -116
- package/dist/__tests__/audit-dependencies.test.js.map +0 -1
- package/dist/__tests__/audit-fixtures.d.ts +0 -265
- package/dist/__tests__/audit-fixtures.d.ts.map +0 -1
- package/dist/__tests__/audit-fixtures.js +0 -511
- package/dist/__tests__/audit-fixtures.js.map +0 -1
- package/dist/__tests__/audit-ingest.test.d.ts +0 -2
- package/dist/__tests__/audit-ingest.test.d.ts.map +0 -1
- package/dist/__tests__/audit-ingest.test.js +0 -473
- package/dist/__tests__/audit-ingest.test.js.map +0 -1
- package/dist/__tests__/audit-memory-ingest.test.d.ts +0 -12
- package/dist/__tests__/audit-memory-ingest.test.d.ts.map +0 -1
- package/dist/__tests__/audit-memory-ingest.test.js +0 -135
- package/dist/__tests__/audit-memory-ingest.test.js.map +0 -1
- package/dist/__tests__/audit-property.test.d.ts +0 -2
- package/dist/__tests__/audit-property.test.d.ts.map +0 -1
- package/dist/__tests__/audit-property.test.js +0 -771
- package/dist/__tests__/audit-property.test.js.map +0 -1
- package/dist/__tests__/audit-real-fixtures.test.d.ts +0 -2
- package/dist/__tests__/audit-real-fixtures.test.d.ts.map +0 -1
- package/dist/__tests__/audit-real-fixtures.test.js +0 -132
- package/dist/__tests__/audit-real-fixtures.test.js.map +0 -1
- package/dist/__tests__/audit-real-live.test.d.ts +0 -2
- package/dist/__tests__/audit-real-live.test.d.ts.map +0 -1
- package/dist/__tests__/audit-real-live.test.js +0 -107
- package/dist/__tests__/audit-real-live.test.js.map +0 -1
- package/dist/__tests__/audit-reconstruct.test.d.ts +0 -2
- package/dist/__tests__/audit-reconstruct.test.d.ts.map +0 -1
- package/dist/__tests__/audit-reconstruct.test.js +0 -287
- package/dist/__tests__/audit-reconstruct.test.js.map +0 -1
- package/dist/__tests__/audit-report.test.d.ts +0 -2
- package/dist/__tests__/audit-report.test.d.ts.map +0 -1
- package/dist/__tests__/audit-report.test.js +0 -186
- package/dist/__tests__/audit-report.test.js.map +0 -1
- package/dist/__tests__/audit-roundtrip.test.d.ts +0 -2
- package/dist/__tests__/audit-roundtrip.test.d.ts.map +0 -1
- package/dist/__tests__/audit-roundtrip.test.js +0 -368
- package/dist/__tests__/audit-roundtrip.test.js.map +0 -1
- package/dist/__tests__/audit-temporal.test.d.ts +0 -2
- package/dist/__tests__/audit-temporal.test.d.ts.map +0 -1
- package/dist/__tests__/audit-temporal.test.js +0 -364
- package/dist/__tests__/audit-temporal.test.js.map +0 -1
- package/dist/__tests__/audit-tiers.test.d.ts +0 -2
- package/dist/__tests__/audit-tiers.test.d.ts.map +0 -1
- package/dist/__tests__/audit-tiers.test.js +0 -282
- package/dist/__tests__/audit-tiers.test.js.map +0 -1
- package/dist/__tests__/audit-witness-real.test.d.ts +0 -19
- package/dist/__tests__/audit-witness-real.test.d.ts.map +0 -1
- package/dist/__tests__/audit-witness-real.test.js +0 -82
- package/dist/__tests__/audit-witness-real.test.js.map +0 -1
- package/dist/__tests__/audit-witness.test.d.ts +0 -2
- package/dist/__tests__/audit-witness.test.d.ts.map +0 -1
- package/dist/__tests__/audit-witness.test.js +0 -308
- package/dist/__tests__/audit-witness.test.js.map +0 -1
- package/dist/__tests__/canonical.test.d.ts +0 -2
- package/dist/__tests__/canonical.test.d.ts.map +0 -1
- package/dist/__tests__/canonical.test.js +0 -162
- package/dist/__tests__/canonical.test.js.map +0 -1
- package/dist/__tests__/chain-hash-anchor.test.d.ts +0 -22
- package/dist/__tests__/chain-hash-anchor.test.d.ts.map +0 -1
- package/dist/__tests__/chain-hash-anchor.test.js +0 -55
- package/dist/__tests__/chain-hash-anchor.test.js.map +0 -1
- package/dist/__tests__/commit-service.integration.test.d.ts +0 -2
- package/dist/__tests__/commit-service.integration.test.d.ts.map +0 -1
- package/dist/__tests__/commit-service.integration.test.js +0 -95
- package/dist/__tests__/commit-service.integration.test.js.map +0 -1
- package/dist/__tests__/constructor.test.d.ts +0 -2
- package/dist/__tests__/constructor.test.d.ts.map +0 -1
- package/dist/__tests__/constructor.test.js +0 -267
- package/dist/__tests__/constructor.test.js.map +0 -1
- package/dist/__tests__/declared-proof-fixture.d.ts +0 -66
- package/dist/__tests__/declared-proof-fixture.d.ts.map +0 -1
- package/dist/__tests__/declared-proof-fixture.js +0 -66
- package/dist/__tests__/declared-proof-fixture.js.map +0 -1
- package/dist/__tests__/fuse-placement.test.d.ts +0 -2
- package/dist/__tests__/fuse-placement.test.d.ts.map +0 -1
- package/dist/__tests__/fuse-placement.test.js +0 -31
- package/dist/__tests__/fuse-placement.test.js.map +0 -1
- package/dist/__tests__/fuse-roundtrip.test.d.ts +0 -2
- package/dist/__tests__/fuse-roundtrip.test.d.ts.map +0 -1
- package/dist/__tests__/fuse-roundtrip.test.js +0 -241
- package/dist/__tests__/fuse-roundtrip.test.js.map +0 -1
- package/dist/__tests__/fuse-sdk.test.d.ts +0 -2
- package/dist/__tests__/fuse-sdk.test.d.ts.map +0 -1
- package/dist/__tests__/fuse-sdk.test.js +0 -117
- package/dist/__tests__/fuse-sdk.test.js.map +0 -1
- package/dist/__tests__/fuse-set-sdk.test.d.ts +0 -2
- package/dist/__tests__/fuse-set-sdk.test.d.ts.map +0 -1
- package/dist/__tests__/fuse-set-sdk.test.js +0 -721
- package/dist/__tests__/fuse-set-sdk.test.js.map +0 -1
- package/dist/__tests__/fuse-set.test.d.ts +0 -2
- package/dist/__tests__/fuse-set.test.d.ts.map +0 -1
- package/dist/__tests__/fuse-set.test.js +0 -868
- package/dist/__tests__/fuse-set.test.js.map +0 -1
- package/dist/__tests__/fuse-set2.test.d.ts +0 -2
- package/dist/__tests__/fuse-set2.test.d.ts.map +0 -1
- package/dist/__tests__/fuse-set2.test.js +0 -294
- package/dist/__tests__/fuse-set2.test.js.map +0 -1
- package/dist/__tests__/fuse-vectors.test.d.ts +0 -2
- package/dist/__tests__/fuse-vectors.test.d.ts.map +0 -1
- package/dist/__tests__/fuse-vectors.test.js +0 -175
- package/dist/__tests__/fuse-vectors.test.js.map +0 -1
- package/dist/__tests__/fuse-verify.test.d.ts +0 -2
- package/dist/__tests__/fuse-verify.test.d.ts.map +0 -1
- package/dist/__tests__/fuse-verify.test.js +0 -250
- package/dist/__tests__/fuse-verify.test.js.map +0 -1
- package/dist/__tests__/hello-world.test.d.ts +0 -2
- package/dist/__tests__/hello-world.test.d.ts.map +0 -1
- package/dist/__tests__/hello-world.test.js +0 -9
- package/dist/__tests__/hello-world.test.js.map +0 -1
- package/dist/__tests__/mock-proof-fixture.d.ts +0 -15
- package/dist/__tests__/mock-proof-fixture.d.ts.map +0 -1
- package/dist/__tests__/mock-proof-fixture.js +0 -34
- package/dist/__tests__/mock-proof-fixture.js.map +0 -1
- package/dist/__tests__/policy-enforcement.test.d.ts +0 -2
- package/dist/__tests__/policy-enforcement.test.d.ts.map +0 -1
- package/dist/__tests__/policy-enforcement.test.js +0 -188
- package/dist/__tests__/policy-enforcement.test.js.map +0 -1
- package/dist/__tests__/proof-hash-regression.test.d.ts +0 -2
- package/dist/__tests__/proof-hash-regression.test.d.ts.map +0 -1
- package/dist/__tests__/proof-hash-regression.test.js +0 -95
- package/dist/__tests__/proof-hash-regression.test.js.map +0 -1
- package/dist/__tests__/proof-hash.test.d.ts +0 -2
- package/dist/__tests__/proof-hash.test.d.ts.map +0 -1
- package/dist/__tests__/proof-hash.test.js +0 -116
- package/dist/__tests__/proof-hash.test.js.map +0 -1
- package/dist/__tests__/proof-integrity.test.d.ts +0 -2
- package/dist/__tests__/proof-integrity.test.d.ts.map +0 -1
- package/dist/__tests__/proof-integrity.test.js +0 -426
- package/dist/__tests__/proof-integrity.test.js.map +0 -1
- package/dist/__tests__/realistic-proof-fixture.d.ts +0 -56
- package/dist/__tests__/realistic-proof-fixture.d.ts.map +0 -1
- package/dist/__tests__/realistic-proof-fixture.js +0 -57
- package/dist/__tests__/realistic-proof-fixture.js.map +0 -1
- package/dist/__tests__/verifier.test.d.ts +0 -2
- package/dist/__tests__/verifier.test.d.ts.map +0 -1
- package/dist/__tests__/verifier.test.js +0 -531
- package/dist/__tests__/verifier.test.js.map +0 -1
- package/dist/canonical.d.ts +0 -54
- package/dist/canonical.d.ts.map +0 -1
- package/dist/canonical.js +0 -119
- package/dist/canonical.js.map +0 -1
- package/dist/proof-hash.d.ts +0 -9
- package/dist/proof-hash.d.ts.map +0 -1
- package/dist/proof-hash.js +0 -75
- package/dist/proof-hash.js.map +0 -1
- package/dist/types.d.ts +0 -705
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/dist/verifier.d.ts +0 -27
- package/dist/verifier.d.ts.map +0 -1
- package/dist/verifier.js +0 -844
- package/dist/verifier.js.map +0 -1
- package/src/__tests__/audit-anchors.test.ts +0 -188
- package/src/__tests__/audit-anomalies.test.ts +0 -815
- package/src/__tests__/audit-attestation.test.ts +0 -529
- package/src/__tests__/audit-bench-smoke.test.ts +0 -108
- package/src/__tests__/audit-boundary.test.ts +0 -96
- package/src/__tests__/audit-cli.test.ts +0 -288
- package/src/__tests__/audit-dependencies.test.ts +0 -141
- package/src/__tests__/audit-fixtures.ts +0 -736
- package/src/__tests__/audit-ingest.test.ts +0 -575
- package/src/__tests__/audit-memory-ingest.test.ts +0 -156
- package/src/__tests__/audit-property.test.ts +0 -892
- package/src/__tests__/audit-real-fixtures.test.ts +0 -153
- package/src/__tests__/audit-real-live.test.ts +0 -128
- package/src/__tests__/audit-reconstruct.test.ts +0 -353
- package/src/__tests__/audit-report.test.ts +0 -222
- package/src/__tests__/audit-roundtrip.test.ts +0 -464
- package/src/__tests__/audit-temporal.test.ts +0 -446
- package/src/__tests__/audit-tiers.test.ts +0 -343
- package/src/__tests__/audit-witness-real.test.ts +0 -92
- package/src/__tests__/audit-witness.test.ts +0 -345
- package/src/__tests__/canonical.test.ts +0 -253
- package/src/__tests__/chain-hash-anchor.test.ts +0 -67
- package/src/__tests__/commit-service.integration.test.ts +0 -117
- package/src/__tests__/constructor.test.ts +0 -340
- package/src/__tests__/declared-proof-fixture.ts +0 -65
- package/src/__tests__/fuse-placement.test.ts +0 -32
- package/src/__tests__/fuse-roundtrip.test.ts +0 -243
- package/src/__tests__/fuse-sdk.test.ts +0 -142
- package/src/__tests__/fuse-set-sdk.test.ts +0 -852
- package/src/__tests__/fuse-set.test.ts +0 -1005
- package/src/__tests__/fuse-set2.test.ts +0 -343
- package/src/__tests__/fuse-vectors.test.ts +0 -218
- package/src/__tests__/fuse-verify.test.ts +0 -278
- package/src/__tests__/hello-world.test.ts +0 -10
- package/src/__tests__/mock-proof-fixture.ts +0 -36
- package/src/__tests__/policy-enforcement.test.ts +0 -226
- package/src/__tests__/proof-hash-regression.test.ts +0 -107
- package/src/__tests__/proof-hash.test.ts +0 -129
- package/src/__tests__/proof-integrity.test.ts +0 -541
- package/src/__tests__/real-fixtures/anchor-after-112762.json +0 -47
- package/src/__tests__/real-fixtures/anchor-with-ethereum-112758.json +0 -47
- package/src/__tests__/real-fixtures/live-proof-0.json +0 -42
- package/src/__tests__/real-fixtures/live-proof-1.json +0 -43
- package/src/__tests__/real-fixtures/live-proof-2.json +0 -43
- package/src/__tests__/real-fixtures/successor-112760.json +0 -38
- package/src/__tests__/real-fixtures/witness-25525146.json +0 -6
- package/src/__tests__/real-fixtures/witness-25525147.json +0 -6
- package/src/__tests__/realistic-proof-fixture.ts +0 -57
- package/src/__tests__/verifier.test.ts +0 -624
|
@@ -1,529 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Mike Argento. All rights reserved. See LICENSE.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Offline attestation validation tests.
|
|
5
|
-
*
|
|
6
|
-
* Real path: REALISTIC_PROOF carries a TRUNCATED attestation blob; the
|
|
7
|
-
* validator must report document-present but validation-failed (parse
|
|
8
|
-
* error) honestly, never a pass.
|
|
9
|
-
*
|
|
10
|
-
* Synthetic path: a minimal valid COSE_Sign1 with a self-signed P-384
|
|
11
|
-
* certificate chain is built in-test with node:crypto webcrypto and
|
|
12
|
-
* hand-rolled DER/CBOR, exercising signature verification, chain
|
|
13
|
-
* walking, trust-root anchoring, validity windows at the document's own
|
|
14
|
-
* timestamp, PCR0 matching, and user_data binding, pass and fail. The
|
|
15
|
-
* synthetic chain also proves the DEFAULT trust root is the bundled AWS
|
|
16
|
-
* Nitro root: without an explicit override, a synthetic chain fails at
|
|
17
|
-
* the root check.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { describe, it, before } from "node:test";
|
|
21
|
-
import * as assert from "node:assert/strict";
|
|
22
|
-
import { webcrypto } from "node:crypto";
|
|
23
|
-
|
|
24
|
-
/** Node's webcrypto key type without relying on DOM lib globals. */
|
|
25
|
-
type CryptoKey = Parameters<typeof webcrypto.subtle.exportKey>[1];
|
|
26
|
-
import {
|
|
27
|
-
analyzeAuthorities,
|
|
28
|
-
ingestBundle,
|
|
29
|
-
validateAttestations,
|
|
30
|
-
validateNitroAttestationDocument,
|
|
31
|
-
verifyObservedProofs,
|
|
32
|
-
} from "@mikeargento/bitgraph-audit";
|
|
33
|
-
import { computeProofHash } from "@mikeargento/bitgraph-verify";
|
|
34
|
-
import type { BitGraphProof } from "@mikeargento/bitgraph-verify";
|
|
35
|
-
import { REALISTIC_PROOF } from "./realistic-proof-fixture.js";
|
|
36
|
-
import {
|
|
37
|
-
b64,
|
|
38
|
-
makeChainIdProof,
|
|
39
|
-
makeKey,
|
|
40
|
-
makeTempDir,
|
|
41
|
-
proofJson,
|
|
42
|
-
signBody,
|
|
43
|
-
utf8,
|
|
44
|
-
writeBundleDir,
|
|
45
|
-
} from "./audit-fixtures.js";
|
|
46
|
-
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
// Minimal CBOR encoder (test side only)
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
|
|
51
|
-
function concat(parts: Uint8Array[]): Uint8Array {
|
|
52
|
-
const total = parts.reduce((sum, p) => sum + p.length, 0);
|
|
53
|
-
const out = new Uint8Array(total);
|
|
54
|
-
let off = 0;
|
|
55
|
-
for (const p of parts) {
|
|
56
|
-
out.set(p, off);
|
|
57
|
-
off += p.length;
|
|
58
|
-
}
|
|
59
|
-
return out;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function cborHead(major: number, length: number): Uint8Array {
|
|
63
|
-
const m = major << 5;
|
|
64
|
-
if (length < 24) return new Uint8Array([m | length]);
|
|
65
|
-
if (length < 256) return new Uint8Array([m | 24, length]);
|
|
66
|
-
if (length < 65536) return new Uint8Array([m | 25, length >> 8, length & 0xff]);
|
|
67
|
-
if (length < 4294967296) {
|
|
68
|
-
return new Uint8Array([
|
|
69
|
-
m | 26,
|
|
70
|
-
(length >>> 24) & 0xff,
|
|
71
|
-
(length >>> 16) & 0xff,
|
|
72
|
-
(length >>> 8) & 0xff,
|
|
73
|
-
length & 0xff,
|
|
74
|
-
]);
|
|
75
|
-
}
|
|
76
|
-
const out = new Uint8Array(9);
|
|
77
|
-
out[0] = m | 27;
|
|
78
|
-
let v = BigInt(length);
|
|
79
|
-
for (let i = 8; i >= 1; i--) {
|
|
80
|
-
out[i] = Number(v & 0xffn);
|
|
81
|
-
v >>= 8n;
|
|
82
|
-
}
|
|
83
|
-
return out;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function cborInt(n: number): Uint8Array {
|
|
87
|
-
return n >= 0 ? cborHead(0, n) : cborHead(1, -1 - n);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function cborBytes(bytes: Uint8Array): Uint8Array {
|
|
91
|
-
return concat([cborHead(2, bytes.length), bytes]);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function cborText(s: string): Uint8Array {
|
|
95
|
-
const body = utf8(s);
|
|
96
|
-
return concat([cborHead(3, body.length), body]);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function cborArray(items: Uint8Array[]): Uint8Array {
|
|
100
|
-
return concat([cborHead(4, items.length), ...items]);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function cborMap(entries: Array<[Uint8Array, Uint8Array]>): Uint8Array {
|
|
104
|
-
return concat([cborHead(5, entries.length), ...entries.flat()]);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ---------------------------------------------------------------------------
|
|
108
|
-
// Minimal DER builders (test side only)
|
|
109
|
-
// ---------------------------------------------------------------------------
|
|
110
|
-
|
|
111
|
-
function derLen(n: number): Uint8Array {
|
|
112
|
-
if (n < 128) return new Uint8Array([n]);
|
|
113
|
-
const bytes: number[] = [];
|
|
114
|
-
let v = n;
|
|
115
|
-
while (v > 0) {
|
|
116
|
-
bytes.unshift(v & 0xff);
|
|
117
|
-
v = Math.floor(v / 256);
|
|
118
|
-
}
|
|
119
|
-
return new Uint8Array([0x80 | bytes.length, ...bytes]);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function der(tag: number, content: Uint8Array): Uint8Array {
|
|
123
|
-
return concat([new Uint8Array([tag]), derLen(content.length), content]);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function derSeq(...parts: Uint8Array[]): Uint8Array {
|
|
127
|
-
return der(0x30, concat(parts));
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const OID_ECDSA_SHA384 = der(0x06, new Uint8Array([0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03]));
|
|
131
|
-
const OID_EC_PUBLIC_KEY = der(0x06, new Uint8Array([0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01]));
|
|
132
|
-
const OID_SECP384R1 = der(0x06, new Uint8Array([0x2b, 0x81, 0x04, 0x00, 0x22]));
|
|
133
|
-
|
|
134
|
-
function derUtcTime(text: string): Uint8Array {
|
|
135
|
-
return der(0x17, utf8(text));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function derBitString(content: Uint8Array): Uint8Array {
|
|
139
|
-
return der(0x03, concat([new Uint8Array([0x00]), content]));
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function derIntFromBytes(bytes: Uint8Array): Uint8Array {
|
|
143
|
-
let body = bytes;
|
|
144
|
-
let start = 0;
|
|
145
|
-
while (start < body.length - 1 && body[start] === 0x00) start++;
|
|
146
|
-
body = body.subarray(start);
|
|
147
|
-
if ((body[0] as number) & 0x80) body = concat([new Uint8Array([0x00]), body]);
|
|
148
|
-
return der(0x02, body);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function rawSigToDer(raw: Uint8Array): Uint8Array {
|
|
152
|
-
return derSeq(derIntFromBytes(raw.subarray(0, 48)), derIntFromBytes(raw.subarray(48, 96)));
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// ---------------------------------------------------------------------------
|
|
156
|
-
// Synthetic P-384 chain and attestation documents
|
|
157
|
-
// ---------------------------------------------------------------------------
|
|
158
|
-
|
|
159
|
-
interface TestKeyPair {
|
|
160
|
-
privateKey: CryptoKey;
|
|
161
|
-
publicRaw: Uint8Array;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async function makeP384(): Promise<TestKeyPair> {
|
|
165
|
-
const kp = await webcrypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-384" }, true, [
|
|
166
|
-
"sign",
|
|
167
|
-
"verify",
|
|
168
|
-
]);
|
|
169
|
-
const publicRaw = new Uint8Array(await webcrypto.subtle.exportKey("raw", kp.publicKey));
|
|
170
|
-
return { privateKey: kp.privateKey, publicRaw };
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
async function signP384(priv: CryptoKey, message: Uint8Array): Promise<Uint8Array> {
|
|
174
|
-
return new Uint8Array(
|
|
175
|
-
await webcrypto.subtle.sign(
|
|
176
|
-
{ name: "ECDSA", hash: "SHA-384" },
|
|
177
|
-
priv,
|
|
178
|
-
message as Uint8Array<ArrayBuffer>
|
|
179
|
-
)
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async function makeCert(
|
|
184
|
-
subjectPublicRaw: Uint8Array,
|
|
185
|
-
signerPrivate: CryptoKey,
|
|
186
|
-
notBefore = "200101000000Z",
|
|
187
|
-
notAfter = "490101000000Z"
|
|
188
|
-
): Promise<Uint8Array> {
|
|
189
|
-
const spki = derSeq(derSeq(OID_EC_PUBLIC_KEY, OID_SECP384R1), derBitString(subjectPublicRaw));
|
|
190
|
-
const tbs = derSeq(
|
|
191
|
-
der(0x02, new Uint8Array([0x01])), // serialNumber
|
|
192
|
-
derSeq(OID_ECDSA_SHA384), // signature algorithm
|
|
193
|
-
derSeq(), // issuer (empty)
|
|
194
|
-
derSeq(derUtcTime(notBefore), derUtcTime(notAfter)), // validity
|
|
195
|
-
derSeq(), // subject (empty)
|
|
196
|
-
spki
|
|
197
|
-
);
|
|
198
|
-
const sig = await signP384(signerPrivate, tbs);
|
|
199
|
-
return derSeq(tbs, derSeq(OID_ECDSA_SHA384), derBitString(rawSigToDer(sig)));
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const PCR0_BYTES = new Uint8Array(48).fill(0xab);
|
|
203
|
-
const PCR0_HEX = "ab".repeat(48);
|
|
204
|
-
const DOC_TIMESTAMP = Date.UTC(2026, 0, 1);
|
|
205
|
-
|
|
206
|
-
async function makeDocB64(opts: {
|
|
207
|
-
leafPrivate: CryptoKey;
|
|
208
|
-
leafCert: Uint8Array;
|
|
209
|
-
cabundle: Uint8Array[];
|
|
210
|
-
userData?: Uint8Array;
|
|
211
|
-
timestamp?: number;
|
|
212
|
-
pcr0?: Uint8Array;
|
|
213
|
-
}): Promise<string> {
|
|
214
|
-
const entries: Array<[Uint8Array, Uint8Array]> = [
|
|
215
|
-
[cborText("module_id"), cborText("i-0123456789abcdef0-enc0123456789abcdef")],
|
|
216
|
-
[cborText("digest"), cborText("SHA384")],
|
|
217
|
-
[cborText("timestamp"), cborInt(opts.timestamp ?? DOC_TIMESTAMP)],
|
|
218
|
-
[
|
|
219
|
-
cborText("pcrs"),
|
|
220
|
-
cborMap([
|
|
221
|
-
[cborInt(0), cborBytes(opts.pcr0 ?? PCR0_BYTES)],
|
|
222
|
-
[cborInt(1), cborBytes(new Uint8Array(48))], // all-zero PCR, filtered out
|
|
223
|
-
]),
|
|
224
|
-
],
|
|
225
|
-
[cborText("certificate"), cborBytes(opts.leafCert)],
|
|
226
|
-
[cborText("cabundle"), cborArray(opts.cabundle.map(cborBytes))],
|
|
227
|
-
...(opts.userData !== undefined
|
|
228
|
-
? ([[cborText("user_data"), cborBytes(opts.userData)]] as Array<[Uint8Array, Uint8Array]>)
|
|
229
|
-
: []),
|
|
230
|
-
];
|
|
231
|
-
const payload = cborMap(entries);
|
|
232
|
-
const protectedHeader = cborMap([[cborInt(1), cborInt(-35)]]); // alg: ES384
|
|
233
|
-
const sigStructure = cborArray([
|
|
234
|
-
cborText("Signature1"),
|
|
235
|
-
cborBytes(protectedHeader),
|
|
236
|
-
cborBytes(new Uint8Array(0)),
|
|
237
|
-
cborBytes(payload),
|
|
238
|
-
]);
|
|
239
|
-
const signature = await signP384(opts.leafPrivate, sigStructure);
|
|
240
|
-
const cose = cborArray([
|
|
241
|
-
cborBytes(protectedHeader),
|
|
242
|
-
cborMap([]),
|
|
243
|
-
cborBytes(payload),
|
|
244
|
-
cborBytes(signature),
|
|
245
|
-
]);
|
|
246
|
-
return b64(cose);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// ---------------------------------------------------------------------------
|
|
250
|
-
// Shared synthetic fixture
|
|
251
|
-
// ---------------------------------------------------------------------------
|
|
252
|
-
|
|
253
|
-
let root: TestKeyPair;
|
|
254
|
-
let leaf: TestKeyPair;
|
|
255
|
-
let rootCert: Uint8Array;
|
|
256
|
-
let leafCert: Uint8Array;
|
|
257
|
-
/** A verifier-valid proof whose attestation binds user_data to its own canonical hash. */
|
|
258
|
-
let boundProof: { proof: BitGraphProof; proofHash: string };
|
|
259
|
-
|
|
260
|
-
before(async () => {
|
|
261
|
-
root = await makeP384();
|
|
262
|
-
leaf = await makeP384();
|
|
263
|
-
rootCert = await makeCert(root.publicRaw, root.privateKey); // self-signed
|
|
264
|
-
leafCert = await makeCert(leaf.publicRaw, root.privateKey);
|
|
265
|
-
|
|
266
|
-
const key = await makeKey();
|
|
267
|
-
const bytes = utf8("attested payload");
|
|
268
|
-
const digestB64 = b64(
|
|
269
|
-
new Uint8Array(await webcrypto.subtle.digest("SHA-256", bytes as Uint8Array<ArrayBuffer>))
|
|
270
|
-
);
|
|
271
|
-
const proof = await signBody(
|
|
272
|
-
key,
|
|
273
|
-
{ hashAlg: "sha256", digestB64 },
|
|
274
|
-
{ nonceB64: b64(crypto.getRandomValues(new Uint8Array(16))) },
|
|
275
|
-
PCR0_HEX,
|
|
276
|
-
{ attestation: { format: "aws-nitro", reportB64: "cGxhY2Vob2xkZXI=" } }
|
|
277
|
-
);
|
|
278
|
-
const proofHash = computeProofHash(proof);
|
|
279
|
-
const docB64 = await makeDocB64({
|
|
280
|
-
leafPrivate: leaf.privateKey,
|
|
281
|
-
leafCert,
|
|
282
|
-
cabundle: [rootCert],
|
|
283
|
-
userData: new Uint8Array(Buffer.from(proofHash, "base64")),
|
|
284
|
-
});
|
|
285
|
-
(proof.environment.attestation as { reportB64: string }).reportB64 = docB64;
|
|
286
|
-
assert.equal(
|
|
287
|
-
computeProofHash(proof),
|
|
288
|
-
proofHash,
|
|
289
|
-
"reportB64 lives outside the signed body; swapping it never changes the canonical hash"
|
|
290
|
-
);
|
|
291
|
-
boundProof = { proof, proofHash };
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
// ---------------------------------------------------------------------------
|
|
295
|
-
// Real fixture path: truncated blob must fail honestly
|
|
296
|
-
// ---------------------------------------------------------------------------
|
|
297
|
-
|
|
298
|
-
describe("audit attestation: truncated real fixture reports honestly", () => {
|
|
299
|
-
it("REALISTIC_PROOF: document present, validation failed with a parse error, never a pass", async () => {
|
|
300
|
-
const dir = await makeTempDir("bg-audit-att-real-");
|
|
301
|
-
await writeBundleDir(dir, { "proofs/realistic.json": JSON.stringify(REALISTIC_PROOF) });
|
|
302
|
-
const ingest = await ingestBundle(dir);
|
|
303
|
-
await verifyObservedProofs(ingest);
|
|
304
|
-
|
|
305
|
-
const analysis = await validateAttestations(ingest);
|
|
306
|
-
assert.equal(analysis.records.length, 1);
|
|
307
|
-
const record = analysis.records[0]!;
|
|
308
|
-
assert.equal(record.declaredMeasurementPresent, true);
|
|
309
|
-
assert.equal(record.documentPresent, true);
|
|
310
|
-
assert.equal(record.attestationFormat, "aws-nitro");
|
|
311
|
-
assert.equal(record.documentValidated, false);
|
|
312
|
-
assert.ok(record.validationFailure !== undefined, "a precise parse failure is reported");
|
|
313
|
-
assert.equal(record.pcr0MatchesDeclared, undefined, "no comparison against an unvalidated document");
|
|
314
|
-
assert.equal(record.userDataBoundToProof, undefined);
|
|
315
|
-
assert.ok(record.checks.every((c) => !(c.name === "PCR0 Match" && c.pass)));
|
|
316
|
-
assert.equal(analysis.counts.documentsFailed, 1);
|
|
317
|
-
assert.equal(analysis.counts.documentsValidated, 0);
|
|
318
|
-
assert.ok(analysis.findings.some((f) => f.code === "attestation-invalid"));
|
|
319
|
-
});
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
// ---------------------------------------------------------------------------
|
|
323
|
-
// Synthetic path: low-level validator
|
|
324
|
-
// ---------------------------------------------------------------------------
|
|
325
|
-
|
|
326
|
-
describe("audit attestation: synthetic document, low-level validator", () => {
|
|
327
|
-
it("validates a correct document end-to-end (signature, chain, root, window, PCR0, user_data)", async () => {
|
|
328
|
-
const docB64 = (boundProof.proof.environment.attestation as { reportB64: string }).reportB64;
|
|
329
|
-
const result = await validateNitroAttestationDocument(docB64, {
|
|
330
|
-
expectedPcr0: PCR0_HEX,
|
|
331
|
-
expectedUserDataB64: boundProof.proofHash,
|
|
332
|
-
trustedRootCaDer: rootCert,
|
|
333
|
-
});
|
|
334
|
-
assert.equal(result.documentValid, true, result.failure);
|
|
335
|
-
assert.equal(result.pcr0, PCR0_HEX);
|
|
336
|
-
assert.equal(result.pcrs[1], undefined, "all-zero PCRs are treated as absent");
|
|
337
|
-
assert.equal(result.pcr0Matches, true);
|
|
338
|
-
assert.equal(result.userDataMatches, true);
|
|
339
|
-
assert.equal(result.timestamp, DOC_TIMESTAMP);
|
|
340
|
-
assert.equal(result.certChainLength, 2);
|
|
341
|
-
assert.ok(result.moduleId !== undefined);
|
|
342
|
-
assert.ok(result.checks.every((c) => c.pass));
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
it("defaults to the bundled AWS Nitro root: a synthetic chain fails the root check", async () => {
|
|
346
|
-
const docB64 = (boundProof.proof.environment.attestation as { reportB64: string }).reportB64;
|
|
347
|
-
const result = await validateNitroAttestationDocument(docB64, {
|
|
348
|
-
expectedPcr0: PCR0_HEX,
|
|
349
|
-
expectedUserDataB64: boundProof.proofHash,
|
|
350
|
-
});
|
|
351
|
-
assert.equal(result.documentValid, false);
|
|
352
|
-
const rootCheck = result.checks.find((c) => c.name === "AWS Nitro Root CA");
|
|
353
|
-
assert.ok(rootCheck !== undefined && rootCheck.pass === false);
|
|
354
|
-
assert.equal(result.pcr0Matches, undefined, "no comparisons on a failed document");
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
it("a corrupted COSE signature fails at the signature step", async () => {
|
|
358
|
-
const docB64 = (boundProof.proof.environment.attestation as { reportB64: string }).reportB64;
|
|
359
|
-
const bytes = Buffer.from(docB64, "base64");
|
|
360
|
-
bytes[bytes.length - 1] = (bytes[bytes.length - 1] as number) ^ 0x01;
|
|
361
|
-
const result = await validateNitroAttestationDocument(bytes.toString("base64"), {
|
|
362
|
-
trustedRootCaDer: rootCert,
|
|
363
|
-
});
|
|
364
|
-
assert.equal(result.documentValid, false);
|
|
365
|
-
const sigCheck = result.checks.find((c) => c.name === "ECDSA P-384 Signature");
|
|
366
|
-
assert.ok(sigCheck !== undefined && sigCheck.pass === false);
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
it("a certificate not signed by its parent fails the chain walk", async () => {
|
|
370
|
-
const evil = await makeP384();
|
|
371
|
-
const orphanLeafCert = await makeCert(leaf.publicRaw, evil.privateKey);
|
|
372
|
-
const docB64 = await makeDocB64({
|
|
373
|
-
leafPrivate: leaf.privateKey,
|
|
374
|
-
leafCert: orphanLeafCert,
|
|
375
|
-
cabundle: [rootCert],
|
|
376
|
-
});
|
|
377
|
-
const result = await validateNitroAttestationDocument(docB64, { trustedRootCaDer: rootCert });
|
|
378
|
-
assert.equal(result.documentValid, false);
|
|
379
|
-
const chainCheck = result.checks.find((c) => c.name === "Certificate Chain");
|
|
380
|
-
assert.ok(chainCheck !== undefined && chainCheck.pass === false);
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
it("evaluates certificate validity windows at the document's own timestamp", async () => {
|
|
384
|
-
const expiredLeafCert = await makeCert(
|
|
385
|
-
leaf.publicRaw,
|
|
386
|
-
root.privateKey,
|
|
387
|
-
"200101000000Z",
|
|
388
|
-
"210101000000Z"
|
|
389
|
-
);
|
|
390
|
-
const docB64 = await makeDocB64({
|
|
391
|
-
leafPrivate: leaf.privateKey,
|
|
392
|
-
leafCert: expiredLeafCert,
|
|
393
|
-
cabundle: [rootCert],
|
|
394
|
-
timestamp: DOC_TIMESTAMP, // 2026: outside [2020, 2021]
|
|
395
|
-
});
|
|
396
|
-
const result = await validateNitroAttestationDocument(docB64, { trustedRootCaDer: rootCert });
|
|
397
|
-
assert.equal(result.documentValid, false);
|
|
398
|
-
const windowCheck = result.checks.find((c) => c.name === "Certificate Validity Window");
|
|
399
|
-
assert.ok(windowCheck !== undefined && windowCheck.pass === false);
|
|
400
|
-
assert.match(windowCheck.detail, /does not contain the document timestamp/);
|
|
401
|
-
});
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
// ---------------------------------------------------------------------------
|
|
405
|
-
// Pipeline: separate facts, findings, authority groups
|
|
406
|
-
// ---------------------------------------------------------------------------
|
|
407
|
-
|
|
408
|
-
describe("audit attestation: pipeline facts stay separate", () => {
|
|
409
|
-
it("pass case populates the per-proof record and AuthorityGroup.attested", async () => {
|
|
410
|
-
const plain = await makeChainIdProof({ counter: "2", epochId: "E-ATT" });
|
|
411
|
-
const dir = await makeTempDir("bg-audit-att-pipe-");
|
|
412
|
-
await writeBundleDir(dir, {
|
|
413
|
-
"proofs/bound.json": proofJson(boundProof.proof),
|
|
414
|
-
"proofs/plain.json": proofJson(plain.proof),
|
|
415
|
-
"proofs/realistic.json": JSON.stringify(REALISTIC_PROOF),
|
|
416
|
-
});
|
|
417
|
-
const ingest = await ingestBundle(dir);
|
|
418
|
-
await verifyObservedProofs(ingest);
|
|
419
|
-
const authority = analyzeAuthorities(ingest);
|
|
420
|
-
|
|
421
|
-
const analysis = await validateAttestations(ingest, authority, { trustedRootCaDer: rootCert });
|
|
422
|
-
|
|
423
|
-
const bound = analysis.records.find((r) => r.proofHash === boundProof.proofHash)!;
|
|
424
|
-
assert.equal(bound.documentValidated, true, bound.validationFailure);
|
|
425
|
-
assert.equal(bound.attestedPcr0, PCR0_HEX);
|
|
426
|
-
assert.equal(bound.pcr0MatchesDeclared, true);
|
|
427
|
-
assert.equal(bound.userDataBoundToProof, true);
|
|
428
|
-
|
|
429
|
-
const plainRecord = analysis.records.find((r) => r.proofHash === computeProofHash(plain.proof))!;
|
|
430
|
-
assert.equal(plainRecord.documentPresent, false);
|
|
431
|
-
assert.equal(plainRecord.documentValidated, false);
|
|
432
|
-
assert.equal(plainRecord.checks.length, 0);
|
|
433
|
-
|
|
434
|
-
const boundGroup = authority.groups.find((g) => g.proofHashes.includes(boundProof.proofHash))!;
|
|
435
|
-
assert.ok(boundGroup.attested !== undefined);
|
|
436
|
-
assert.equal(boundGroup.attested.status, "validated");
|
|
437
|
-
assert.equal(boundGroup.attested.attestedMeasurement, PCR0_HEX);
|
|
438
|
-
assert.equal(boundGroup.attested.matchesDeclared, true);
|
|
439
|
-
assert.equal(boundGroup.attested.validatedProofCount, 1);
|
|
440
|
-
assert.equal(boundGroup.attested.failedProofCount, 0);
|
|
441
|
-
|
|
442
|
-
const realisticGroup = authority.groups.find((g) =>
|
|
443
|
-
g.proofHashes.includes(computeProofHash(REALISTIC_PROOF))
|
|
444
|
-
)!;
|
|
445
|
-
assert.ok(realisticGroup.attested !== undefined);
|
|
446
|
-
assert.equal(realisticGroup.attested.status, "validation-failed");
|
|
447
|
-
assert.equal(realisticGroup.attested.attestedMeasurement, undefined);
|
|
448
|
-
|
|
449
|
-
const plainGroup = authority.groups.find((g) =>
|
|
450
|
-
g.proofHashes.includes(computeProofHash(plain.proof))
|
|
451
|
-
)!;
|
|
452
|
-
assert.equal(plainGroup.attestationPresent, false);
|
|
453
|
-
assert.equal(plainGroup.attested, undefined, "nothing to validate, nothing claimed");
|
|
454
|
-
|
|
455
|
-
assert.equal(analysis.counts.documentsValidated, 1);
|
|
456
|
-
assert.equal(analysis.counts.documentsFailed, 1);
|
|
457
|
-
assert.equal(analysis.counts.pcr0Matches, 1);
|
|
458
|
-
assert.equal(analysis.counts.userDataBound, 1);
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
it("attested PCR0 disagreeing with the declared measurement is a separate recorded fact", async () => {
|
|
462
|
-
const key = await makeKey();
|
|
463
|
-
const declared = "ff".repeat(48); // declared measurement differs from the attested PCR0
|
|
464
|
-
const proof = await signBody(
|
|
465
|
-
key,
|
|
466
|
-
{ hashAlg: "sha256", digestB64: b64(new Uint8Array(32).fill(7)) },
|
|
467
|
-
{ nonceB64: b64(crypto.getRandomValues(new Uint8Array(16))) },
|
|
468
|
-
declared,
|
|
469
|
-
{ attestation: { format: "aws-nitro", reportB64: "cGxhY2Vob2xkZXI=" } }
|
|
470
|
-
);
|
|
471
|
-
const proofHash = computeProofHash(proof);
|
|
472
|
-
const docB64 = await makeDocB64({
|
|
473
|
-
leafPrivate: leaf.privateKey,
|
|
474
|
-
leafCert,
|
|
475
|
-
cabundle: [rootCert],
|
|
476
|
-
userData: new Uint8Array(Buffer.from(proofHash, "base64")),
|
|
477
|
-
});
|
|
478
|
-
(proof.environment.attestation as { reportB64: string }).reportB64 = docB64;
|
|
479
|
-
|
|
480
|
-
const dir = await makeTempDir("bg-audit-att-pcr-");
|
|
481
|
-
await writeBundleDir(dir, { "proofs/p.json": proofJson(proof) });
|
|
482
|
-
const ingest = await ingestBundle(dir);
|
|
483
|
-
await verifyObservedProofs(ingest);
|
|
484
|
-
const authority = analyzeAuthorities(ingest);
|
|
485
|
-
const analysis = await validateAttestations(ingest, authority, { trustedRootCaDer: rootCert });
|
|
486
|
-
|
|
487
|
-
const record = analysis.records[0]!;
|
|
488
|
-
assert.equal(record.documentValidated, true, "the document itself is genuine");
|
|
489
|
-
assert.equal(record.pcr0MatchesDeclared, false);
|
|
490
|
-
assert.equal(record.userDataBoundToProof, true);
|
|
491
|
-
assert.ok(analysis.findings.some((f) => f.code === "attestation-measurement-mismatch"));
|
|
492
|
-
const group = authority.groups[0]!;
|
|
493
|
-
assert.equal(group.attested?.status, "validated");
|
|
494
|
-
assert.equal(group.attested?.matchesDeclared, false);
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
it("a genuine attestation lifted from another proof fails only the user_data binding", async () => {
|
|
498
|
-
const key = await makeKey();
|
|
499
|
-
const proof = await signBody(
|
|
500
|
-
key,
|
|
501
|
-
{ hashAlg: "sha256", digestB64: b64(new Uint8Array(32).fill(9)) },
|
|
502
|
-
{ nonceB64: b64(crypto.getRandomValues(new Uint8Array(16))) },
|
|
503
|
-
PCR0_HEX,
|
|
504
|
-
{ attestation: { format: "aws-nitro", reportB64: "cGxhY2Vob2xkZXI=" } }
|
|
505
|
-
);
|
|
506
|
-
// The document is valid and attests the right PCR0, but its user_data
|
|
507
|
-
// binds a DIFFERENT proof's hash.
|
|
508
|
-
const docB64 = await makeDocB64({
|
|
509
|
-
leafPrivate: leaf.privateKey,
|
|
510
|
-
leafCert,
|
|
511
|
-
cabundle: [rootCert],
|
|
512
|
-
userData: crypto.getRandomValues(new Uint8Array(32)),
|
|
513
|
-
});
|
|
514
|
-
(proof.environment.attestation as { reportB64: string }).reportB64 = docB64;
|
|
515
|
-
|
|
516
|
-
const dir = await makeTempDir("bg-audit-att-lift-");
|
|
517
|
-
await writeBundleDir(dir, { "proofs/p.json": proofJson(proof) });
|
|
518
|
-
const ingest = await ingestBundle(dir);
|
|
519
|
-
await verifyObservedProofs(ingest);
|
|
520
|
-
const analysis = await validateAttestations(ingest, undefined, { trustedRootCaDer: rootCert });
|
|
521
|
-
|
|
522
|
-
const record = analysis.records[0]!;
|
|
523
|
-
assert.equal(record.documentValidated, true);
|
|
524
|
-
assert.equal(record.pcr0MatchesDeclared, true);
|
|
525
|
-
assert.equal(record.userDataBoundToProof, false);
|
|
526
|
-
assert.ok(analysis.findings.some((f) => f.code === "attestation-user-data-mismatch"));
|
|
527
|
-
assert.ok(!analysis.findings.some((f) => f.code === "attestation-invalid"));
|
|
528
|
-
});
|
|
529
|
-
});
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Mike Argento. All rights reserved. See LICENSE.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Smoke-scale benchmark (Phase 4e): a 2,000-proof protocol-correct chain
|
|
5
|
-
* audited end-to-end under a generous wall-clock ceiling and a sane
|
|
6
|
-
* memory bound.
|
|
7
|
-
*
|
|
8
|
-
* This is the CI-sized companion of packages/audit/scripts/bench-audit.mjs
|
|
9
|
-
* (the full 50,000-proof benchmark, run via `npm run bench` in
|
|
10
|
-
* packages/audit). It exists to catch accidental quadratic regressions in
|
|
11
|
-
* ingest, link resolution, position maps, or report building without long
|
|
12
|
-
* runtimes: at 2,000 proofs an O(N^2) pipeline stage costs millions of
|
|
13
|
-
* extra operations and blows straight through the generous ceiling, while
|
|
14
|
-
* the honest linear pipeline finishes in a few seconds on any hardware.
|
|
15
|
-
* No exact timings are asserted, only sanity bounds; verification is
|
|
16
|
-
* never weakened (every proof carries a real Ed25519 signature and is
|
|
17
|
-
* fully checked at the integrity tier).
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { describe, it } from "node:test";
|
|
21
|
-
import * as assert from "node:assert/strict";
|
|
22
|
-
import { rm } from "node:fs/promises";
|
|
23
|
-
import { buildJsonReport, buildMarkdownReport, computeExitFlags, runAudit } from "@mikeargento/bitgraph-audit";
|
|
24
|
-
import {
|
|
25
|
-
healthyPairs,
|
|
26
|
-
makeCounterChain,
|
|
27
|
-
makeTempDir,
|
|
28
|
-
proofJson,
|
|
29
|
-
writeBundleDir,
|
|
30
|
-
} from "./audit-fixtures.js";
|
|
31
|
-
|
|
32
|
-
const PROOF_COUNT = 2_000;
|
|
33
|
-
/** Generous: the pipeline runs this in single-digit seconds on 2020s hardware. */
|
|
34
|
-
const WALL_CLOCK_CEILING_MS = 180_000;
|
|
35
|
-
/** Sanity bound on RSS growth across the audit; quadratic blowups are far larger. */
|
|
36
|
-
const RSS_GROWTH_CEILING_BYTES = 1024 * 1024 * 1024;
|
|
37
|
-
|
|
38
|
-
describe("audit benchmark smoke: 2,000-proof chain under sanity bounds", () => {
|
|
39
|
-
it("audits a 2,000-proof healthy chain cleanly within the ceilings", async () => {
|
|
40
|
-
const dir = await makeTempDir("bitgraph-audit-bench-smoke-");
|
|
41
|
-
try {
|
|
42
|
-
// Generation is untimed setup: real signatures through the real path.
|
|
43
|
-
const chain = await makeCounterChain({
|
|
44
|
-
epochId: "bench-smoke-epoch",
|
|
45
|
-
pairs: healthyPairs(PROOF_COUNT),
|
|
46
|
-
chainId: "bitgraph:main",
|
|
47
|
-
payloadPrefix: "bench-smoke",
|
|
48
|
-
});
|
|
49
|
-
const files: Record<string, string> = {};
|
|
50
|
-
for (let i = 0; i < chain.proofs.length; i++) {
|
|
51
|
-
files[`proofs/proof-${String(i).padStart(6, "0")}.json`] = proofJson(
|
|
52
|
-
chain.proofs[i]!.proof
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
await writeBundleDir(dir, files);
|
|
56
|
-
|
|
57
|
-
const rssBefore = process.memoryUsage().rss;
|
|
58
|
-
let rssPeak = rssBefore;
|
|
59
|
-
const sampler = setInterval(() => {
|
|
60
|
-
const rss = process.memoryUsage().rss;
|
|
61
|
-
if (rss > rssPeak) rssPeak = rss;
|
|
62
|
-
}, 50);
|
|
63
|
-
sampler.unref();
|
|
64
|
-
|
|
65
|
-
const startedAt = process.hrtime.bigint();
|
|
66
|
-
const result = await runAudit(dir);
|
|
67
|
-
const report = buildJsonReport(result);
|
|
68
|
-
const markdown = buildMarkdownReport(result);
|
|
69
|
-
const elapsedMs = Number(process.hrtime.bigint() - startedAt) / 1e6;
|
|
70
|
-
clearInterval(sampler);
|
|
71
|
-
const rssAfter = process.memoryUsage().rss;
|
|
72
|
-
if (rssAfter > rssPeak) rssPeak = rssAfter;
|
|
73
|
-
|
|
74
|
-
// Correctness at scale: one continuous chain, zero anomalies.
|
|
75
|
-
assert.equal(result.ingest.counts.observed, PROOF_COUNT);
|
|
76
|
-
assert.equal(result.verification.failed, 0);
|
|
77
|
-
assert.equal(result.verification.artifactUnavailable, PROOF_COUNT);
|
|
78
|
-
assert.equal(result.reconstruction.partitions.length, 1);
|
|
79
|
-
assert.equal(result.reconstruction.partitions[0]?.components.length, 1);
|
|
80
|
-
assert.equal(result.anomalies.anomalies.length, 0);
|
|
81
|
-
assert.equal(result.anomalies.divergences.length, 0);
|
|
82
|
-
assert.equal(report.summary.chainIntact, true);
|
|
83
|
-
assert.equal(computeExitFlags(result).code, 0);
|
|
84
|
-
assert.ok(markdown.length > 0);
|
|
85
|
-
|
|
86
|
-
// Sanity bounds only; real numbers live in the full 50k benchmark.
|
|
87
|
-
assert.ok(
|
|
88
|
-
elapsedMs < WALL_CLOCK_CEILING_MS,
|
|
89
|
-
`full audit of ${PROOF_COUNT} proofs took ${Math.round(elapsedMs)}ms; ` +
|
|
90
|
-
`ceiling ${WALL_CLOCK_CEILING_MS}ms (likely a quadratic regression)`
|
|
91
|
-
);
|
|
92
|
-
const rssGrowth = rssPeak - rssBefore;
|
|
93
|
-
assert.ok(
|
|
94
|
-
rssGrowth < RSS_GROWTH_CEILING_BYTES,
|
|
95
|
-
`RSS grew ${Math.round(rssGrowth / (1024 * 1024))} MiB during the audit; ` +
|
|
96
|
-
`ceiling ${Math.round(RSS_GROWTH_CEILING_BYTES / (1024 * 1024))} MiB`
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
// Leave a breadcrumb in the test log without asserting exact times.
|
|
100
|
-
console.log(
|
|
101
|
-
`[bench-smoke] ${PROOF_COUNT} proofs: audit+reports ${Math.round(elapsedMs)}ms, ` +
|
|
102
|
-
`rss growth ${Math.round(rssGrowth / (1024 * 1024))} MiB`
|
|
103
|
-
);
|
|
104
|
-
} finally {
|
|
105
|
-
await rm(dir, { recursive: true, force: true });
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
});
|