@mikeargento/bitgraph 1.1.1 → 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.
Files changed (52) hide show
  1. package/dist/__tests__/fuse-roundtrip.test.d.ts +2 -0
  2. package/dist/__tests__/fuse-roundtrip.test.d.ts.map +1 -0
  3. package/dist/__tests__/fuse-roundtrip.test.js +172 -0
  4. package/dist/__tests__/fuse-roundtrip.test.js.map +1 -0
  5. package/dist/__tests__/fuse-sdk.test.d.ts +2 -0
  6. package/dist/__tests__/fuse-sdk.test.d.ts.map +1 -0
  7. package/dist/__tests__/fuse-sdk.test.js +117 -0
  8. package/dist/__tests__/fuse-sdk.test.js.map +1 -0
  9. package/dist/__tests__/fuse-vectors.test.d.ts +2 -0
  10. package/dist/__tests__/fuse-vectors.test.d.ts.map +1 -0
  11. package/dist/__tests__/fuse-vectors.test.js +175 -0
  12. package/dist/__tests__/fuse-vectors.test.js.map +1 -0
  13. package/dist/__tests__/fuse-verify.test.d.ts +2 -0
  14. package/dist/__tests__/fuse-verify.test.d.ts.map +1 -0
  15. package/dist/__tests__/fuse-verify.test.js +250 -0
  16. package/dist/__tests__/fuse-verify.test.js.map +1 -0
  17. package/dist/fuse-cli.d.ts +3 -0
  18. package/dist/fuse-cli.d.ts.map +1 -0
  19. package/dist/fuse-cli.js +210 -0
  20. package/dist/fuse-cli.js.map +1 -0
  21. package/dist/fuse.d.ts +75 -0
  22. package/dist/fuse.d.ts.map +1 -0
  23. package/dist/fuse.js +273 -0
  24. package/dist/fuse.js.map +1 -0
  25. package/dist/index.d.ts +2 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +4 -0
  28. package/dist/index.js.map +1 -1
  29. package/package.json +12 -7
  30. package/src/__tests__/fuse-roundtrip.test.ts +174 -0
  31. package/src/__tests__/fuse-sdk.test.ts +142 -0
  32. package/src/__tests__/fuse-vectors.test.ts +218 -0
  33. package/src/__tests__/fuse-verify.test.ts +278 -0
  34. package/src/fuse-cli.ts +187 -0
  35. package/src/fuse.ts +356 -0
  36. package/src/index.ts +6 -0
  37. package/dist/canonical.d.ts +0 -54
  38. package/dist/canonical.d.ts.map +0 -1
  39. package/dist/canonical.js +0 -119
  40. package/dist/canonical.js.map +0 -1
  41. package/dist/proof-hash.d.ts +0 -9
  42. package/dist/proof-hash.d.ts.map +0 -1
  43. package/dist/proof-hash.js +0 -75
  44. package/dist/proof-hash.js.map +0 -1
  45. package/dist/types.d.ts +0 -705
  46. package/dist/types.d.ts.map +0 -1
  47. package/dist/types.js +0 -3
  48. package/dist/types.js.map +0 -1
  49. package/dist/verifier.d.ts +0 -27
  50. package/dist/verifier.d.ts.map +0 -1
  51. package/dist/verifier.js +0 -844
  52. package/dist/verifier.js.map +0 -1
package/dist/fuse.js ADDED
@@ -0,0 +1,273 @@
1
+ // Copyright (c) Mike Argento. All rights reserved. See LICENSE.
2
+ /**
3
+ * fuse(builder, options): the producer interface of the bitgraph-fuse/1
4
+ * profile (working name; outwardly this is simply BitGraph).
5
+ *
6
+ * The four beats, in order, with nothing else in between:
7
+ * 1. nonce: allocate a slot; the enclave signs a record that contains no
8
+ * artifact data and hands back a nonce that is a bearer ticket
9
+ * until it is consumed.
10
+ * 2. fuse: hand the COMMITMENT to that record (never the raw nonce) to the
11
+ * builder, which writes it into the artifact it is producing and
12
+ * returns the finished bytes.
13
+ * 3. hash: SHA-256 of the fused bytes.
14
+ * 4. fill: commit that digest under the same slot, with the placement id
15
+ * and the origin digest in the signed attribution.
16
+ *
17
+ * What this module never does: write the nonce anywhere but process memory,
18
+ * put it in a message, or fall back to an ordinary recording when the fused
19
+ * commit fails. A failure is reported as a failure and the slot expires on
20
+ * its own.
21
+ *
22
+ * Transport: the site's proxy routes by default (POST /api/fuse/allocate and
23
+ * /api/fuse/commit, behind the anchor-first gate), configurable so a licensee
24
+ * can point it at a parent directly.
25
+ */
26
+ import { sha256 } from "@noble/hashes/sha256";
27
+ import { buildFrame, bytesToBase64, bytesToHex, computeSlotCommitment, computeSlotRecordHash, fuseAttribution, getPlacement, verifyFuse, base64ToBytes, } from "@mikeargento/bitgraph-verify";
28
+ export class FuseError extends Error {
29
+ code;
30
+ status;
31
+ constructor(code, message, status = null) {
32
+ super(message);
33
+ this.name = "FuseError";
34
+ this.code = code;
35
+ this.status = status;
36
+ }
37
+ }
38
+ /** A builder for a registered placement over an existing original (Forms A and B), or for a bare Form C payload. */
39
+ export function builderFor(placement, original) {
40
+ const p = getPlacement(placement);
41
+ if (p === undefined)
42
+ throw new FuseError("bad-placement", `placement "${placement}" is not registered`);
43
+ return ({ commitment, originDigest }) => {
44
+ if (original !== undefined)
45
+ return p.build({ original, originDigest: originDigest ?? sha256(original), commitment });
46
+ return p.build(originDigest !== undefined ? { originDigest, commitment } : { commitment });
47
+ };
48
+ }
49
+ const DEFAULTS = {
50
+ baseUrl: "https://bitgraph.ing",
51
+ allocatePath: "/api/fuse/allocate",
52
+ commitPath: "/api/fuse/commit",
53
+ lookupPath: "/api/proofs/",
54
+ timeoutMs: 30_000,
55
+ recoveryAttempts: 5,
56
+ recoveryDelayMs: 1_500,
57
+ };
58
+ const B64_32 = /^[A-Za-z0-9+/]{43}=$/;
59
+ const B64_64 = /^[A-Za-z0-9+/]{86}==$/;
60
+ function isSlotRecord(x) {
61
+ if (x === null || typeof x !== "object" || Array.isArray(x))
62
+ return false;
63
+ const s = x;
64
+ return (s.version === "bitgraph/slot/1" &&
65
+ typeof s.nonceB64 === "string" && B64_32.test(s.nonceB64) &&
66
+ typeof s.counter === "string" && /^(0|[1-9][0-9]*)$/.test(s.counter) &&
67
+ typeof s.epochId === "string" && typeof s.publicKeyB64 === "string" &&
68
+ typeof s.signatureB64 === "string" && B64_64.test(s.signatureB64));
69
+ }
70
+ function toUrlSafe(b64) {
71
+ return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
72
+ }
73
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
74
+ async function request(t, path, init) {
75
+ const f = t.fetch ?? fetch;
76
+ const headers = {};
77
+ if (init.body !== undefined)
78
+ headers["Content-Type"] = "application/json";
79
+ if (t.apiKey)
80
+ headers["Authorization"] = `Bearer ${t.apiKey}`;
81
+ let res;
82
+ try {
83
+ res = await f(`${t.baseUrl}${path}`, {
84
+ method: init.method,
85
+ headers,
86
+ ...(init.body !== undefined ? { body: JSON.stringify(init.body) } : {}),
87
+ signal: AbortSignal.timeout(t.timeoutMs),
88
+ });
89
+ }
90
+ catch (err) {
91
+ throw new FuseError("network", `request failed: ${err instanceof Error ? err.message : String(err)}`);
92
+ }
93
+ const text = await res.text();
94
+ let json = null;
95
+ try {
96
+ json = text.length > 0 ? JSON.parse(text) : null;
97
+ }
98
+ catch {
99
+ json = text;
100
+ }
101
+ return { status: res.status, json, headers: res.headers };
102
+ }
103
+ function messageOf(json, fallback) {
104
+ return json !== null && typeof json === "object" && typeof json.error === "string" ? json.error : fallback;
105
+ }
106
+ function codeOf(json) {
107
+ return json !== null && typeof json === "object" && typeof json.code === "string" ? json.code : null;
108
+ }
109
+ /**
110
+ * Read the proof back by digest after a lost or refused commit: the ONE proof
111
+ * whose commit.slotHashB64 is the hash of the slot record we hold. Any other
112
+ * proof of the same digest is a different recording under a different slot.
113
+ * Never allocates.
114
+ */
115
+ async function recover(t, artifactDigestB64, slot) {
116
+ const expectedSlotHash = bytesToBase64(computeSlotRecordHash(slot));
117
+ for (let attempt = 0; attempt < t.recoveryAttempts; attempt++) {
118
+ if (attempt > 0)
119
+ await sleep(t.recoveryDelayMs);
120
+ let r;
121
+ try {
122
+ r = await request(t, `${t.lookupPath}${encodeURIComponent(toUrlSafe(artifactDigestB64))}`, { method: "GET" });
123
+ }
124
+ catch {
125
+ continue;
126
+ }
127
+ if (r.status !== 200)
128
+ continue;
129
+ const proofs = r.json?.proofs;
130
+ if (!Array.isArray(proofs))
131
+ continue;
132
+ for (const entry of proofs) {
133
+ const p = entry?.proof;
134
+ if (p && p.commit?.slotHashB64 === expectedSlotHash && p.commit?.nonceB64 === slot.nonceB64)
135
+ return p;
136
+ }
137
+ }
138
+ return null;
139
+ }
140
+ /**
141
+ * Allocate, fuse, hash, fill. Returns the Frame with the unchanged proof, or
142
+ * throws a FuseError; it never returns an ordinary recording in place of a
143
+ * fused one.
144
+ */
145
+ export async function fuse(builder, options) {
146
+ const placement = getPlacement(options.placement);
147
+ if (placement === undefined)
148
+ throw new FuseError("bad-placement", `placement "${options.placement}" is not registered`);
149
+ if (placement.form !== "C" && options.original === undefined)
150
+ throw new FuseError("bad-input", `${placement.id} needs the original bytes`);
151
+ if (placement.form === "C" && options.original !== undefined)
152
+ throw new FuseError("bad-input", "produced/1 takes no original; pass originDigest to name a source");
153
+ if (options.originDigest !== undefined && options.originDigest.length !== 32)
154
+ throw new FuseError("bad-input", "originDigest must be 32 bytes");
155
+ const t = { ...DEFAULTS, ...(options.transport ?? {}) };
156
+ const originDigest = options.original !== undefined ? sha256(options.original) : options.originDigest;
157
+ const originDigestB64 = originDigest !== undefined ? bytesToBase64(originDigest) : null;
158
+ // 1. nonce
159
+ const alloc = await request(t, t.allocatePath, { method: "POST", body: {} });
160
+ if (alloc.status === 503 && codeOf(alloc.json) === "tee-restarting")
161
+ throw new FuseError("tee-restarting", messageOf(alloc.json, "the boundary is restarting"), 503);
162
+ if (alloc.status !== 200)
163
+ throw new FuseError("allocate-failed", messageOf(alloc.json, `allocation failed (${alloc.status})`), alloc.status);
164
+ const slotId = alloc.json?.slotId;
165
+ const slot = alloc.json?.slot;
166
+ if (!isSlotRecord(slot) || slotId !== slot.nonceB64)
167
+ throw new FuseError("allocate-failed", "the allocation response is not a slot record", alloc.status);
168
+ if (slot.chainId !== "bitgraph:main")
169
+ throw new FuseError("allocate-failed", "the slot is not on the anchored chain; a fused floor needs bitgraph:main");
170
+ // 2. fuse
171
+ const commitment = computeSlotCommitment(slot);
172
+ let fused;
173
+ try {
174
+ fused = await builder({ commitment, commitmentHex: bytesToHex(commitment), ...(originDigest !== undefined ? { originDigest } : {}), slot });
175
+ }
176
+ catch (err) {
177
+ throw new FuseError("builder-failed", `the builder threw: ${err instanceof Error ? err.message : String(err)}`);
178
+ }
179
+ if (!(fused instanceof Uint8Array))
180
+ throw new FuseError("builder-failed", "the builder must return a Uint8Array");
181
+ // Fail closed: never commit bytes that do not carry the commitment.
182
+ const located = placement.locate(fused);
183
+ if (located === null || bytesToHex(located.commitment) !== bytesToHex(commitment)) {
184
+ throw new FuseError("commitment-missing", `the fused bytes do not carry the ${placement.id} commitment; nothing was committed and the slot will expire`);
185
+ }
186
+ // 3. hash
187
+ const artifactDigest = sha256(fused);
188
+ const artifactDigestB64 = bytesToBase64(artifactDigest);
189
+ // 4. fill
190
+ const attribution = fuseAttribution(placement.id, originDigest);
191
+ const body = {
192
+ digests: [{ digestB64: artifactDigestB64, hashAlg: "sha256" }],
193
+ slotId: slot.nonceB64,
194
+ slot,
195
+ chainId: "bitgraph:main",
196
+ attribution,
197
+ };
198
+ if (options.agency !== undefined)
199
+ body.agency = options.agency;
200
+ let proof = null;
201
+ let recovered = false;
202
+ let commit = null;
203
+ try {
204
+ commit = await request(t, t.commitPath, { method: "POST", body });
205
+ }
206
+ catch (err) {
207
+ // The request may have reached the boundary. Read back before giving up; never allocate again.
208
+ proof = await recover(t, artifactDigestB64, slot);
209
+ if (proof === null)
210
+ throw err;
211
+ recovered = true;
212
+ }
213
+ if (proof === null && commit !== null) {
214
+ if (commit.status === 200) {
215
+ const j = commit.json;
216
+ proof = Array.isArray(j) ? (j[0] ?? null) : (j?.proof ?? null);
217
+ if (proof === null)
218
+ throw new FuseError("commit-refused", "the commit response carried no proof", 200);
219
+ }
220
+ else if (commit.status === 409 && codeOf(commit.json) === "slot-unavailable") {
221
+ proof = await recover(t, artifactDigestB64, slot);
222
+ if (proof === null)
223
+ throw new FuseError("slot-unavailable", messageOf(commit.json, "the slot is no longer available"), 409);
224
+ recovered = true;
225
+ }
226
+ else if (commit.status === 503 && codeOf(commit.json) === "tee-restarting") {
227
+ proof = await recover(t, artifactDigestB64, slot);
228
+ if (proof === null)
229
+ throw new FuseError("tee-restarting", messageOf(commit.json, "the boundary is restarting"), 503);
230
+ recovered = true;
231
+ }
232
+ else {
233
+ throw new FuseError("commit-refused", messageOf(commit.json, `commit refused (${commit.status})`), commit.status);
234
+ }
235
+ }
236
+ if (proof === null)
237
+ throw new FuseError("transport", "no proof");
238
+ // Never label as fused a proof under any other slot.
239
+ if (proof.slotAllocation?.nonceB64 !== slot.nonceB64 || proof.commit?.nonceB64 !== slot.nonceB64) {
240
+ throw new FuseError("slot-mismatch", "the boundary returned a proof under a different slot; nothing is labelled fused");
241
+ }
242
+ // A minted proof is verified by a reader before it is called a proof.
243
+ const verification = await verifyFuse({ proof, bytes: fused });
244
+ if (verification.category !== "FUSED_DIRECT") {
245
+ throw new FuseError("verification-failed", `the returned proof does not verify as fused: ${verification.category}${verification.reason ? ` (${verification.reason})` : ""}`);
246
+ }
247
+ const frame = buildFrame({
248
+ proof,
249
+ placement: placement.id,
250
+ artifactDigest,
251
+ ...(originDigest !== undefined ? { originDigest } : {}),
252
+ fusedFile: options.fusedFile ?? null,
253
+ ...(placement.form === "C" ? { fusePayload: fused } : {}),
254
+ });
255
+ const keep = options.keepFused ?? !placement.byteExact;
256
+ return {
257
+ frame,
258
+ proof,
259
+ artifactDigestB64,
260
+ originDigestB64,
261
+ ...(keep ? { fusedBytes: fused } : {}),
262
+ recovered,
263
+ verification,
264
+ };
265
+ }
266
+ /** Decode a standard-base64 digest, for callers holding one as text. */
267
+ export function digestFromBase64(b64) {
268
+ const d = base64ToBytes(b64);
269
+ if (d === null || d.length !== 32)
270
+ throw new FuseError("bad-input", "not a 32-byte base64 digest");
271
+ return d;
272
+ }
273
+ //# sourceMappingURL=fuse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fuse.js","sourceRoot":"","sources":["../src/fuse.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -15,6 +15,8 @@ export { computeProofHash } from "@mikeargento/bitgraph-verify";
15
15
  export { canonicalize, canonicalizeToString, constantTimeEqual } from "@mikeargento/bitgraph-verify";
16
16
  export type { HostCapabilities } from "./host.js";
17
17
  export { Constructor } from "./constructor.js";
18
+ export { fuse, builderFor, FuseError, digestFromBase64 } from "./fuse.js";
19
+ export type { FuseBuilder, BuilderInput, FuseOptions, FuseResult, FuseTransport, FuseErrorCode } from "./fuse.js";
18
20
  export { parsePolicy, hashPolicy, createPolicyBinding, validateAction, } from "./policy.js";
19
21
  export type { PolicyDocument, PolicyRules, ActionValidationResult, } from "./policy.js";
20
22
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAGH,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,8BAA8B,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAC3E,YAAY,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGrG,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGlD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EACL,WAAW,EACX,UAAU,EACV,mBAAmB,EACnB,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,cAAc,EACd,WAAW,EACX,sBAAsB,GACvB,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAGH,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,8BAA8B,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAC3E,YAAY,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGrG,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGlD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAK/C,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC1E,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAGlH,OAAO,EACL,WAAW,EACX,UAAU,EACV,mBAAmB,EACnB,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,cAAc,EACd,WAAW,EACX,sBAAsB,GACvB,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -4,6 +4,10 @@ export { computeProofHash } from "@mikeargento/bitgraph-verify";
4
4
  export { canonicalize, canonicalizeToString, constantTimeEqual } from "@mikeargento/bitgraph-verify";
5
5
  // Constructor (write path)
6
6
  export { Constructor } from "./constructor.js";
7
+ // The producer profile over the primitive (working name Fuse): allocate a
8
+ // slot, write a commitment to it into the artifact, hash, commit under the
9
+ // same slot. The resulting proof is ordinary bitgraph/1.
10
+ export { fuse, builderFor, FuseError, digestFromBase64 } from "./fuse.js";
7
11
  // Policy parsing, hashing, and validation
8
12
  export { parsePolicy, hashPolicy, createPolicyBinding, validateAction, } from "./policy.js";
9
13
  //# 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,gEAAgE;AA4BhE,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAE3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAKrG,2BAA2B;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,0CAA0C;AAC1C,OAAO,EACL,WAAW,EACX,UAAU,EACV,mBAAmB,EACnB,cAAc,GACf,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gEAAgE;AA4BhE,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAE3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAKrG,2BAA2B;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,0EAA0E;AAC1E,2EAA2E;AAC3E,yDAAyD;AACzD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAG1E,0CAA0C;AAC1C,OAAO,EACL,WAAW,EACX,UAAU,EACV,mBAAmB,EACnB,cAAc,GACf,MAAM,aAAa,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikeargento/bitgraph",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "description": "Portable cryptographic proof of construction for digital artifacts.",
6
6
  "type": "module",
@@ -17,21 +17,23 @@
17
17
  "src",
18
18
  "README.md",
19
19
  "LICENSE",
20
- "NOTICE"
20
+ "NOTICE",
21
+ "!src/__tests__/fuse-fixtures"
21
22
  ],
22
23
  "scripts": {
23
24
  "build": "npm run build --workspace=packages/verify && npm run build --workspace=packages/audit && npm run build --workspace=packages/player && npm run build --workspace=packages/mcp && tsc",
24
25
  "typecheck": "npm run build --workspace=packages/verify && npm run build --workspace=packages/audit && npm run typecheck --workspace=packages/mcp && tsc --noEmit",
25
- "test:core": "npm run build && node --test dist/__tests__/canonical.test.js dist/__tests__/constructor.test.js dist/__tests__/verifier.test.js dist/__tests__/proof-integrity.test.js dist/__tests__/audit-ingest.test.js dist/__tests__/audit-tiers.test.js dist/__tests__/audit-reconstruct.test.js dist/__tests__/audit-anomalies.test.js dist/__tests__/audit-anchors.test.js dist/__tests__/audit-witness.test.js dist/__tests__/audit-temporal.test.js dist/__tests__/audit-attestation.test.js dist/__tests__/audit-report.test.js dist/__tests__/audit-cli.test.js dist/__tests__/audit-property.test.js dist/__tests__/audit-real-fixtures.test.js dist/__tests__/audit-dependencies.test.js dist/__tests__/audit-bench-smoke.test.js dist/__tests__/audit-roundtrip.test.js dist/__tests__/audit-real-live.test.js dist/__tests__/chain-hash-anchor.test.js dist/__tests__/audit-boundary.test.js dist/__tests__/audit-witness-real.test.js dist/__tests__/audit-memory-ingest.test.js",
26
- "test": "npm run test:core"
26
+ "test:core": "npm run build && node --test dist/__tests__/canonical.test.js dist/__tests__/constructor.test.js dist/__tests__/verifier.test.js dist/__tests__/proof-integrity.test.js dist/__tests__/audit-ingest.test.js dist/__tests__/audit-tiers.test.js dist/__tests__/audit-reconstruct.test.js dist/__tests__/audit-anomalies.test.js dist/__tests__/audit-anchors.test.js dist/__tests__/audit-witness.test.js dist/__tests__/audit-temporal.test.js dist/__tests__/audit-attestation.test.js dist/__tests__/audit-report.test.js dist/__tests__/audit-cli.test.js dist/__tests__/audit-property.test.js dist/__tests__/audit-real-fixtures.test.js dist/__tests__/audit-dependencies.test.js dist/__tests__/audit-bench-smoke.test.js dist/__tests__/audit-roundtrip.test.js dist/__tests__/audit-real-live.test.js dist/__tests__/chain-hash-anchor.test.js dist/__tests__/audit-boundary.test.js dist/__tests__/audit-witness-real.test.js dist/__tests__/audit-memory-ingest.test.js dist/__tests__/fuse-vectors.test.js dist/__tests__/fuse-roundtrip.test.js dist/__tests__/fuse-verify.test.js dist/__tests__/fuse-sdk.test.js",
27
+ "test": "npm run test:core",
28
+ "prepack": "npm run build"
27
29
  },
28
30
  "dependencies": {
29
- "@mikeargento/bitgraph-verify": "^1.0.0",
31
+ "@mikeargento/bitgraph-verify": "^1.4.0",
30
32
  "@noble/ed25519": "^2.1.0",
31
33
  "@noble/hashes": "^1.4.0"
32
34
  },
33
35
  "devDependencies": {
34
- "@mikeargento/bitgraph-audit": "^0.1.0",
36
+ "@mikeargento/bitgraph-audit": "^0.4.0",
35
37
  "@types/node": "^20.0.0",
36
38
  "typescript": "^5.4.0"
37
39
  },
@@ -41,5 +43,8 @@
41
43
  "packages/mcp",
42
44
  "packages/player",
43
45
  "packages/titles"
44
- ]
46
+ ],
47
+ "bin": {
48
+ "bitgraph-fuse": "./dist/fuse-cli.js"
49
+ }
45
50
  }
@@ -0,0 +1,174 @@
1
+ // Copyright (c) Mike Argento. All rights reserved. See LICENSE.
2
+
3
+ /**
4
+ * BitGraph Fuse: reconstruction round trips (spec 15.3). A placement enters
5
+ * the registry only with this test passing: original plus proof rebuilds
6
+ * bytes whose hash equals the artifact digest, from fixed vectors.
7
+ */
8
+
9
+ import { describe, test } from "node:test";
10
+ import * as assert from "node:assert/strict";
11
+ import { readFileSync } from "node:fs";
12
+ import { fileURLToPath } from "node:url";
13
+ import { sha256 } from "@noble/hashes/sha256";
14
+ import { PLACEMENTS, getPlacement, computeSlotCommitment, bytesToBase64, buildFrame, parseFrame, readFrameMarker, readFuseAttribution, fuseAttribution } from "@mikeargento/bitgraph-verify";
15
+ import type { BitGraphProof } from "@mikeargento/bitgraph-verify";
16
+
17
+ const FIX = fileURLToPath(new URL("../../src/__tests__/fuse-fixtures/", import.meta.url));
18
+ const bytes = (name: string) => new Uint8Array(readFileSync(FIX + name));
19
+ const proofOf = (name: string) => JSON.parse(readFileSync(FIX + name, "utf8")) as BitGraphProof;
20
+
21
+ describe("registry", () => {
22
+ test("fixed order and ids", () => {
23
+ assert.deepEqual(PLACEMENTS.map((p) => p.id), ["trailer/1", "container/1", "produced/1"]);
24
+ assert.deepEqual(PLACEMENTS.map((p) => p.byteExact), [true, true, false]);
25
+ assert.equal(getPlacement("xmp/9"), undefined);
26
+ assert.equal(getPlacement(""), undefined);
27
+ });
28
+ });
29
+
30
+ describe("trailer/1", () => {
31
+ const p = getPlacement("trailer/1")!;
32
+ test("original + proof rebuilds the committed bytes", () => {
33
+ const proof = proofOf("trailer.proof.json");
34
+ const rebuilt = p.build({ original: bytes("original.txt"), commitment: computeSlotCommitment(proof.slotAllocation!) });
35
+ assert.deepEqual(rebuilt, bytes("fused-trailer.bin"));
36
+ assert.equal(bytesToBase64(sha256(rebuilt)), proof.artifact.digestB64);
37
+ });
38
+ test("locate returns the commitment and the exact original prefix", () => {
39
+ const proof = proofOf("trailer.proof.json");
40
+ const l = p.locate(bytes("fused-trailer.bin"))!;
41
+ assert.deepEqual(l.commitment, computeSlotCommitment(proof.slotAllocation!));
42
+ assert.deepEqual(l.originalBytes, bytes("original.txt"));
43
+ assert.equal(l.originDigest, undefined, "trailer/1 writes no origin digest");
44
+ });
45
+ test("locate refuses short input, wrong magic, non-zero reserved bytes", () => {
46
+ assert.equal(p.locate(new Uint8Array(47)), null);
47
+ const f = bytes("fused-trailer.bin");
48
+ const magic = f.slice(); magic[magic.length - 48] = 0x58;
49
+ assert.equal(p.locate(magic), null);
50
+ const reserved = f.slice(); reserved[reserved.length - 40] = 1;
51
+ assert.equal(p.locate(reserved), null);
52
+ assert.equal(p.locate(bytes("original.txt")), null, "an unfused file has no trailer");
53
+ });
54
+ test("build refuses a bad commitment length and a missing original", () => {
55
+ assert.throws(() => p.build({ commitment: new Uint8Array(32) }), /original/);
56
+ assert.throws(() => p.build({ original: new Uint8Array(1), commitment: new Uint8Array(31) }), /32 bytes/);
57
+ });
58
+ });
59
+
60
+ describe("container/1", () => {
61
+ const p = getPlacement("container/1")!;
62
+ test("original + proof rebuilds the committed archive byte for byte, twice", () => {
63
+ const proof = proofOf("container.proof.json");
64
+ const c = computeSlotCommitment(proof.slotAllocation!);
65
+ const a = p.build({ original: bytes("image.png"), commitment: c });
66
+ const b = p.build({ original: bytes("image.png"), commitment: c });
67
+ assert.deepEqual(a, b, "deterministic");
68
+ assert.deepEqual(a, bytes("fused-container.tar"));
69
+ assert.equal(bytesToBase64(sha256(a)), proof.artifact.digestB64);
70
+ });
71
+ test("locate returns commitment, origin digest and the original bytes", () => {
72
+ const proof = proofOf("container.proof.json");
73
+ const l = p.locate(bytes("fused-container.tar"))!;
74
+ assert.deepEqual(l.commitment, computeSlotCommitment(proof.slotAllocation!));
75
+ assert.deepEqual(l.originDigest, sha256(bytes("image.png")));
76
+ assert.deepEqual(l.originalBytes, bytes("image.png"));
77
+ });
78
+ test("locate refuses any deviation from the fixed layout", () => {
79
+ const f = bytes("fused-container.tar");
80
+ const mtime = f.slice(); mtime[136] = 0x31; // manifest header mtime
81
+ assert.equal(p.locate(mtime), null, "non-zero mtime");
82
+ const uid = f.slice(); uid[108] = 0x31;
83
+ assert.equal(p.locate(uid), null, "uid");
84
+ const truncated = f.subarray(0, f.length - 512);
85
+ assert.equal(p.locate(truncated), null, "missing end block");
86
+ const extra = new Uint8Array(f.length + 512); extra.set(f);
87
+ assert.equal(p.locate(extra), null, "extra trailing block");
88
+ // Swap entry order: rebuild with the original first is not this placement.
89
+ const swapped = f.slice(); swapped[0] = 0x78; // corrupt the manifest name
90
+ assert.equal(p.locate(swapped), null, "wrong entry name");
91
+ assert.equal(p.locate(bytes("image.png")), null, "not a tar");
92
+ assert.equal(p.locate(bytes("fused-trailer.bin")), null);
93
+ });
94
+ test("an archive with a wrong manifest origin does not locate", () => {
95
+ const proof = proofOf("container.proof.json");
96
+ const c = computeSlotCommitment(proof.slotAllocation!);
97
+ const lying = p.build({ original: bytes("image.png"), originDigest: sha256(bytes("original.txt")), commitment: c });
98
+ // build accepts a caller-supplied origin digest; locate returns what is written,
99
+ // and the verifier compares it against the marker and the original's hash.
100
+ const l = p.locate(lying)!;
101
+ assert.deepEqual(l.originDigest, sha256(bytes("original.txt")));
102
+ assert.notDeepEqual(l.originDigest, sha256(l.originalBytes!));
103
+ });
104
+ });
105
+
106
+ describe("produced/1", () => {
107
+ const p = getPlacement("produced/1")!;
108
+ test("build and locate agree, with and without an origin", () => {
109
+ for (const [name, proofName] of [["produced-origin.json", "produced-origin.proof.json"], ["produced-bare.json", "produced-bare.proof.json"]] as const) {
110
+ const proof = proofOf(proofName);
111
+ const l = p.locate(bytes(name))!;
112
+ assert.deepEqual(l.commitment, computeSlotCommitment(proof.slotAllocation!));
113
+ assert.equal(bytesToBase64(sha256(bytes(name))), proof.artifact.digestB64);
114
+ }
115
+ assert.equal(p.locate(bytes("original.txt")), null);
116
+ assert.throws(() => p.build({ original: new Uint8Array(1), commitment: new Uint8Array(32) }), /no original/);
117
+ });
118
+ });
119
+
120
+ describe("legacy files that carry a nonce line", () => {
121
+ test("a file with a 'nonce:' line and no trailer, container or payload locates nothing", () => {
122
+ // ~/BitGraph holds recordings whose bytes begin with a test label and a line
123
+ // "nonce: <hex>" (the proto pattern, minted 2026-06-27 and 2026-08-06). That
124
+ // line is content, not a commitment: every registered placement must miss.
125
+ const legacy = new TextEncoder().encode("BitGraph test subject folder-02 file-02\nnonce: e317096692576a308263bc283d66b42e\nuoJjpYb8igrEhhSlczKKTKDyw6guO2iU5iHdBRF+mPELQqBfjrO2WZYrSWyHEuKK\n");
126
+ for (const p of PLACEMENTS) assert.equal(p.locate(legacy), null, p.id);
127
+ });
128
+ });
129
+
130
+ describe("attribution and Frame", () => {
131
+ test("fused attribution carries placement in title and origin in message (standard base64)", () => {
132
+ const origin = sha256(bytes("original.txt"));
133
+ const a = fuseAttribution("trailer/1", origin);
134
+ assert.deepEqual(a, { name: "bitgraph-fuse/1", title: "trailer/1", message: bytesToBase64(origin) });
135
+ assert.deepEqual(fuseAttribution("produced/1"), { name: "bitgraph-fuse/1", title: "produced/1" });
136
+ const proof = proofOf("trailer.proof.json");
137
+ const m = readFuseAttribution(proof)!;
138
+ assert.equal(m.placement, "trailer/1");
139
+ assert.equal(m.placementSource, "attribution");
140
+ assert.equal(m.source, "attribution");
141
+ assert.deepEqual(m.originDigest, origin);
142
+ assert.equal(m.originSource, "attribution");
143
+ const bare = readFuseAttribution(proofOf("trailer-undeclared.proof.json"))!;
144
+ assert.equal(bare.originDigest, undefined);
145
+ assert.equal(bare.originSource, undefined);
146
+ assert.equal(readFuseAttribution(proofOf("recorded.proof.json")), null, "an ordinary proof has no marker");
147
+ });
148
+ test("a Frame parses regardless of outer formatting and the nested proof is untouched", () => {
149
+ const text = readFileSync(FIX + "trailer.bitgraph-fuse.json", "utf8");
150
+ const a = parseFrame(text)!;
151
+ const b = parseFrame(JSON.stringify(JSON.parse(text)))!;
152
+ assert.deepEqual(a, b);
153
+ assert.equal(a.manifest.placement, "trailer/1");
154
+ assert.deepEqual(a.proof, proofOf("trailer.proof.json"));
155
+ const marker = readFrameMarker(a);
156
+ assert.equal(marker.source, "manifest");
157
+ assert.equal(marker.placementSource, "manifest");
158
+ assert.deepEqual(marker.originDigest, sha256(bytes("original.txt")));
159
+ const undeclared = parseFrame({ type: "bitgraph-fuse/1", manifest: { artifact: { algorithm: "sha256", digest: "00".repeat(32) }, fusedFile: null }, proof: proofOf("trailer.proof.json") })!;
160
+ assert.equal(readFrameMarker(undeclared).placement, null, "a manifest may leave the placement undeclared");
161
+ assert.equal(parseFrame(proofOf("trailer.proof.json")), null, "a bare proof is not a Frame");
162
+ assert.equal(parseFrame("{not json"), null);
163
+ assert.equal(parseFrame({ type: "bitgraph-fuse/1", manifest: { placement: "trailer/1", artifact: { algorithm: "sha256", digest: "zz" }, fusedFile: null }, proof: {} }), null);
164
+ });
165
+ test("a Frame is never proof-shaped", () => {
166
+ const f = JSON.parse(readFileSync(FIX + "produced-origin.bitgraph-fuse.json", "utf8")) as Record<string, unknown>;
167
+ assert.equal(f.version, undefined);
168
+ assert.equal(f.artifact, undefined);
169
+ assert.equal(f.signer, undefined);
170
+ assert.equal(f.type, "bitgraph-fuse/1");
171
+ assert.ok(f.fusePayload, "Form C frames carry the parsed payload view");
172
+ assert.throws(() => buildFrame({ proof: proofOf("produced-origin.proof.json"), placement: "produced/1", artifactDigest: new Uint8Array(32), fusedFile: null, fusePayload: new Uint8Array([1]) }), /canonical/);
173
+ });
174
+ });