@mikeargento/bitgraph 1.1.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/LICENSE +9 -0
- package/NOTICE +14 -0
- package/README.md +175 -0
- package/dist/__tests__/canonical.test.d.ts +2 -0
- package/dist/__tests__/canonical.test.d.ts.map +1 -0
- package/dist/__tests__/canonical.test.js +162 -0
- package/dist/__tests__/canonical.test.js.map +1 -0
- package/dist/__tests__/commit-service.integration.test.d.ts +2 -0
- package/dist/__tests__/commit-service.integration.test.d.ts.map +1 -0
- package/dist/__tests__/commit-service.integration.test.js +95 -0
- package/dist/__tests__/commit-service.integration.test.js.map +1 -0
- package/dist/__tests__/constructor.test.d.ts +2 -0
- package/dist/__tests__/constructor.test.d.ts.map +1 -0
- package/dist/__tests__/constructor.test.js +267 -0
- package/dist/__tests__/constructor.test.js.map +1 -0
- package/dist/__tests__/hello-world.test.d.ts +2 -0
- package/dist/__tests__/hello-world.test.d.ts.map +1 -0
- package/dist/__tests__/hello-world.test.js +9 -0
- package/dist/__tests__/hello-world.test.js.map +1 -0
- package/dist/__tests__/policy-enforcement.test.d.ts +2 -0
- package/dist/__tests__/policy-enforcement.test.d.ts.map +1 -0
- package/dist/__tests__/policy-enforcement.test.js +188 -0
- package/dist/__tests__/policy-enforcement.test.js.map +1 -0
- package/dist/__tests__/proof-hash-regression.test.d.ts +2 -0
- package/dist/__tests__/proof-hash-regression.test.d.ts.map +1 -0
- package/dist/__tests__/proof-hash-regression.test.js +134 -0
- package/dist/__tests__/proof-hash-regression.test.js.map +1 -0
- package/dist/__tests__/proof-hash.test.d.ts +2 -0
- package/dist/__tests__/proof-hash.test.d.ts.map +1 -0
- package/dist/__tests__/proof-hash.test.js +134 -0
- package/dist/__tests__/proof-hash.test.js.map +1 -0
- package/dist/__tests__/verifier.test.d.ts +2 -0
- package/dist/__tests__/verifier.test.d.ts.map +1 -0
- package/dist/__tests__/verifier.test.js +457 -0
- package/dist/__tests__/verifier.test.js.map +1 -0
- package/dist/canonical.d.ts +54 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +119 -0
- package/dist/canonical.js.map +1 -0
- package/dist/constructor.d.ts +66 -0
- package/dist/constructor.d.ts.map +1 -0
- package/dist/constructor.js +339 -0
- package/dist/constructor.js.map +1 -0
- package/dist/host.d.ts +138 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +3 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/policy.d.ts +68 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +176 -0
- package/dist/policy.js.map +1 -0
- package/dist/proof-hash.d.ts +9 -0
- package/dist/proof-hash.d.ts.map +1 -0
- package/dist/proof-hash.js +75 -0
- package/dist/proof-hash.js.map +1 -0
- package/dist/types.d.ts +705 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/verifier.d.ts +27 -0
- package/dist/verifier.d.ts.map +1 -0
- package/dist/verifier.js +844 -0
- package/dist/verifier.js.map +1 -0
- package/package.json +40 -0
- package/src/__tests__/canonical.test.ts +253 -0
- package/src/__tests__/commit-service.integration.test.ts +117 -0
- package/src/__tests__/constructor.test.ts +340 -0
- package/src/__tests__/hello-world.test.ts +10 -0
- package/src/__tests__/policy-enforcement.test.ts +226 -0
- package/src/__tests__/proof-hash-regression.test.ts +147 -0
- package/src/__tests__/proof-hash.test.ts +148 -0
- package/src/__tests__/verifier.test.ts +541 -0
- package/src/constructor.ts +435 -0
- package/src/host.ts +153 -0
- package/src/index.ts +51 -0
- package/src/policy.ts +239 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Copyright (c) 2024-2026 Mike Argento. All rights reserved.
|
|
2
|
+
|
|
3
|
+
The source code and related materials in this repository are proprietary and are not licensed for copying, modification, distribution, sublicensing, or commercial use except pursuant to a separate written agreement with the copyright owner or as expressly granted below.
|
|
4
|
+
|
|
5
|
+
Verification grant. Permission is granted, free of charge and irrevocably, to any person obtaining a copy of this software, to copy, build, and run the software solely for the purposes of verifying BitGraph proofs and of reproducing and auditing the published enclave measurements. This permission does not extend to constructing proofs, operating a proof-issuing service, or any other use.
|
|
6
|
+
|
|
7
|
+
Certain prior versions of this software were distributed under the Apache License, Version 2.0. Nothing in this notice is intended to revoke rights validly granted with respect to those prior versions.
|
|
8
|
+
|
|
9
|
+
Patent pending. Verification of BitGraph proofs is and remains permissionless.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
BitGraph
|
|
2
|
+
Copyright 2024-2026 Mike Argento
|
|
3
|
+
|
|
4
|
+
This product includes software from the following third-party projects:
|
|
5
|
+
|
|
6
|
+
@noble/ed25519
|
|
7
|
+
Copyright (c) 2019 Paul Miller (https://paulmillr.com)
|
|
8
|
+
License: MIT
|
|
9
|
+
https://github.com/paulmillr/noble-ed25519
|
|
10
|
+
|
|
11
|
+
@noble/hashes
|
|
12
|
+
Copyright (c) 2022 Paul Miller (https://paulmillr.com)
|
|
13
|
+
License: MIT
|
|
14
|
+
https://github.com/paulmillr/noble-hashes
|
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# BitGraph
|
|
2
|
+
|
|
3
|
+
**Portable cryptographic proof of construction for digital artifacts.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@mikeargento/bitgraph)
|
|
6
|
+
[](https://bitgraph.ing)
|
|
7
|
+
[](https://bitgraph.ing/docs)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
Just as a photograph captures photons through the constraint of a single frame of film, a BitGraph captures bits through the constraint of a single mathematical slot.
|
|
12
|
+
|
|
13
|
+
Origin can be enforced or it can be claimed. Most digital provenance systems claim it. They produce an artifact and then attach a signature, a timestamp, or a metadata block describing where the artifact came from. The claim arrives after the artifact already exists, which is the wrong end of the timeline.
|
|
14
|
+
|
|
15
|
+
BitGraph enforces origin. A measured trusted execution environment creates an unpredictable cryptographic slot before the artifact's hash is known. The artifact's hash arrives later and is bound into the slot. The slot is consumed and cannot be reused. What emerges is not a description of provenance but a proof of construction.
|
|
16
|
+
|
|
17
|
+
> This exact digital state was committed through this measured process, in this order, under these constraints.
|
|
18
|
+
|
|
19
|
+
## The primitive
|
|
20
|
+
|
|
21
|
+
Nonce first. Hash second. Atomic binding third.
|
|
22
|
+
|
|
23
|
+
The TEE generates hardware entropy inside the enclave. That entropy becomes a slot, signed with the enclave's key, with an identity no attacker could have precomputed. The slot exists as a cryptographic object before any artifact hash has been seen.
|
|
24
|
+
|
|
25
|
+
The artifact hash arrives. The TEE binds the hash into the slot, signs the binding, and advances its internal order. The slot becomes consumed.
|
|
26
|
+
|
|
27
|
+
> UNUSED slot exists first. Artifact hash enters later. TEE binds the hash to the slot. Slot becomes CONSUMED. Proof travels with the artifact.
|
|
28
|
+
|
|
29
|
+
The atomicity is the whole guarantee. The slot is allocated and signed before the hash is known. The slot can be consumed exactly once by a single binding operation. The artifact itself can be produced anywhere, by any process, using any tools. What matters is that when the hash arrives, the slot is already there waiting.
|
|
30
|
+
|
|
31
|
+
Most systems say: "Here is a file hash. Now let's sign it." BitGraph says: "Here is a pre-existing origin slot. Now this file hash has occupied it."
|
|
32
|
+
|
|
33
|
+
## Why nonce-first matters
|
|
34
|
+
|
|
35
|
+
If a nonce, timestamp, or credential is added after the hash is already witnessed, it is just a label. It can prove someone signed something. It can prove a record existed by some moment. It cannot constrain the artifact's origin, because the artifact already existed before the nonce entered the picture.
|
|
36
|
+
|
|
37
|
+
That leaves a forgery window. A malicious actor can prepare old hashes, replay prior material, backfill records, or attach fresh randomness to something never produced through the claimed path. The label looks valid. The construction was never constrained.
|
|
38
|
+
|
|
39
|
+
BitGraph closes the window by requiring the slot to exist first. The slot is not evidence added afterward. It is the condition the artifact must satisfy.
|
|
40
|
+
|
|
41
|
+
## What a BitGraph proof contains
|
|
42
|
+
|
|
43
|
+
A BitGraph proof is a portable proof object, typically JSON, that travels with the artifact. It can include:
|
|
44
|
+
|
|
45
|
+
| Component | Purpose |
|
|
46
|
+
|---|---|
|
|
47
|
+
| Artifact hash | Identifies the exact file or digital state |
|
|
48
|
+
| Nonce | The pre-existing causal slot |
|
|
49
|
+
| Slot counter | Shows the slot was allocated before the commit |
|
|
50
|
+
| Commit counter | Shows the artifact consumed the slot later |
|
|
51
|
+
| Epoch ID | Groups an ordered run of commitments |
|
|
52
|
+
| Previous hash link | Connects proofs into a chain |
|
|
53
|
+
| Signer public key | Identifies the proof-signing authority |
|
|
54
|
+
| Signature | Verifies the proof was issued by the enclave-controlled key |
|
|
55
|
+
| TEE measurement | Shows what code and environment produced the proof |
|
|
56
|
+
| Attestation | Shows the proof came from measured hardware |
|
|
57
|
+
| Public anchor | Tethers BitGraph logical time to a public reference |
|
|
58
|
+
|
|
59
|
+
The result is not "a file was signed." It is: this hash was committed into this causal slot, by this measured environment, at this position in logical order, under this signing identity.
|
|
60
|
+
|
|
61
|
+
## Logical time
|
|
62
|
+
|
|
63
|
+
Every proof has order. Every slot and commit has a position. The system can prove that this happened after that, that this slot existed before this hash was bound, that this proof came before the next, that this epoch has an internal cryptographic history.
|
|
64
|
+
|
|
65
|
+
BitGraph proves causal order first. Clock time is optional.
|
|
66
|
+
|
|
67
|
+
## Ethereum: the backward seal
|
|
68
|
+
|
|
69
|
+
BitGraph's internal ordering does not require Ethereum. The chain creates internal order through slot allocation, consumption, counters, signatures, and chained proof history. Ethereum anchors add a different property on top: a public backward seal that any third party can independently verify.
|
|
70
|
+
|
|
71
|
+
An Ethereum block hash that becomes available after the artifact has been committed could not have been known at the moment of commitment. This produces an entropy sandwich:
|
|
72
|
+
|
|
73
|
+
1. Private TEE entropy before the artifact.
|
|
74
|
+
2. Artifact commitment in the middle.
|
|
75
|
+
3. Public blockchain entropy after it.
|
|
76
|
+
|
|
77
|
+
The artifact was committed after the TEE-created slot existed and before the later Ethereum block was knowable. That bounds the commitment in adversary-resistant entropy, witnessed in a public timeline anyone can check years later.
|
|
78
|
+
|
|
79
|
+
Ethereum is not asked to prove the artifact's origin. BitGraph does that. Ethereum provides the backward seal that makes the commitment publicly verifiable.
|
|
80
|
+
|
|
81
|
+
## Multi-TEE breach detection
|
|
82
|
+
|
|
83
|
+
BitGraph's architecture supports running three independent TEEs in parallel as a tripwire, not a consensus system. Three TEEs witness the same input and produce three individual proofs with different nonces, signatures, and attestations. They agree on the meaningful result: same artifact hash, same policy decision, valid measurements, valid signatures, expected ordering behavior.
|
|
84
|
+
|
|
85
|
+
If one diverges, the system does not need to know which one is compromised. It only needs to know something is wrong. The batch is quarantined, the affected range is marked suspect, the epoch is rotated, and downstream verification accounts for the gap.
|
|
86
|
+
|
|
87
|
+
This is not "trust this TEE forever." It is: compromise is assumed, silence is what gets eliminated.
|
|
88
|
+
|
|
89
|
+
## The trust model
|
|
90
|
+
|
|
91
|
+
BitGraph does not depend on blind trust in any single component. Not the operator, the TEE, Ethereum, the clock, a certificate authority, or a live server. Each layer adds an independently verifiable property.
|
|
92
|
+
|
|
93
|
+
| Layer | What it contributes |
|
|
94
|
+
|---|---|
|
|
95
|
+
| TEE | Measured execution and protected key use |
|
|
96
|
+
| Nonce-first slot | Causal precondition |
|
|
97
|
+
| Atomic binding | Prevents post-hoc attachment |
|
|
98
|
+
| Counters | Internal logical order |
|
|
99
|
+
| Proof chain | Historical continuity |
|
|
100
|
+
| Ethereum anchor | Public backward seal |
|
|
101
|
+
| Multi-TEE redundancy | Breach visibility |
|
|
102
|
+
| Epoch rotation | Damage containment |
|
|
103
|
+
| Portable verification | Independence from the original server |
|
|
104
|
+
|
|
105
|
+
## What BitGraph applies to
|
|
106
|
+
|
|
107
|
+
BitGraph works on any digital state that can be hashed. The same primitive applies whether the artifact is a photograph, a contract, a model output, a dataset, or a software release.
|
|
108
|
+
|
|
109
|
+
**Media.** Photos, videos, audio, edited files, generative outputs. The question shifts from "is this real?" to "what origin path does this artifact satisfy?"
|
|
110
|
+
|
|
111
|
+
**AI outputs.** Model results bound to authenticated identity and causal position without requiring the model to run inside an enclave.
|
|
112
|
+
|
|
113
|
+
**Software supply chain.** Build artifacts, releases, model weights, and deployment packages bound to a measured construction path.
|
|
114
|
+
|
|
115
|
+
**Legal and clinical records.** Contracts, filings, telehealth session manifests, lab results, and consent forms with independently verifiable causal ordering.
|
|
116
|
+
|
|
117
|
+
**Research and IP.** Datasets, experimental outputs, and possession proofs that commit to a hash without requiring the file to leave the user's device.
|
|
118
|
+
|
|
119
|
+
## How BitGraph differs from existing approaches
|
|
120
|
+
|
|
121
|
+
BitGraph is often confused with adjacent systems. The differences are structural:
|
|
122
|
+
|
|
123
|
+
| System | Says | BitGraph says |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| Signatures | This key signed this data | This key was controlled by a measured environment that consumed an unused slot |
|
|
126
|
+
| Timestamps | This hash existed by time T | This hash consumed a pre-existing slot at this position in causal order |
|
|
127
|
+
| C2PA | Here are signed claims about this content | Here is the construction path this content satisfied |
|
|
128
|
+
| Blockchains | Public ordering of shared transactions | Private origin coordinates with optional public anchoring |
|
|
129
|
+
|
|
130
|
+
Signatures, timestamps, content credentials, and blockchains all answer "who claimed what, when?" BitGraph answers "what construction path did this exact artifact satisfy?" They are complementary, not competing. A signature can be inside a BitGraph proof. A timestamp can decorate one. Content credentials can ride alongside one. None of them, alone, do what BitGraph does.
|
|
131
|
+
|
|
132
|
+
## Multiple copies of the same original
|
|
133
|
+
|
|
134
|
+
Physical originality depends on singularity. There is one canvas, one negative, one signed paper. Digital files broke that because perfect copies are indistinguishable from the source.
|
|
135
|
+
|
|
136
|
+
BitGraph introduces a different category. A digital artifact can be copied without losing its original provenance. The proof travels with the bytes or alongside them. Instead of every copy being a degraded copy, BitGraph allows multiple copies of the same original. Originality moves from physical container to causal proof. Singularity is no longer required for originality.
|
|
137
|
+
|
|
138
|
+
## The simplest version
|
|
139
|
+
|
|
140
|
+
A measured TEE creates a random unused slot before the artifact hash arrives. The hash arrives. The TEE binds it to the slot, consumes the slot, signs the result, and links it into an ordered chain. Three TEEs can run in parallel so silent compromise becomes visible. The same mechanism periodically commits an Ethereum block hash, sealing everything before it in a public timeline.
|
|
141
|
+
|
|
142
|
+
The result is a provenance system that does not say "someone signed this." It says: this exact artifact occupied this origin coordinate.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Quickstart
|
|
147
|
+
|
|
148
|
+
Try it live: drop a file at [bitgraph.ing](https://bitgraph.ing). The file never leaves your device; only its SHA-256 hash is sent to the enclave.
|
|
149
|
+
|
|
150
|
+
Verify a proof in code:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
npm install @mikeargento/bitgraph
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
import { verify } from "@mikeargento/bitgraph";
|
|
158
|
+
|
|
159
|
+
const result = await verify({ proof, bytes });
|
|
160
|
+
if (result.ok) {
|
|
161
|
+
// signature, slot binding, attestation, and chain link all checked
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
See [bitgraph.ing/docs](https://bitgraph.ing/docs) for the full proof format, verification checklist, attestation handling, and self-host instructions.
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
Copyright 2024-2026 Mike Argento. All rights reserved. Patent Pending.
|
|
170
|
+
|
|
171
|
+
This repository is source-available, not open-source. The code is published so anyone can read, audit, and reproduce the enclave build, but it is proprietary: copying, modification, distribution, and commercial use require a separate written agreement with the copyright owner. See [LICENSE](LICENSE).
|
|
172
|
+
|
|
173
|
+
Certain prior versions were distributed under the Apache License, Version 2.0; rights validly granted with respect to those prior versions are not affected.
|
|
174
|
+
|
|
175
|
+
Verification of BitGraph proofs is and remains permissionless. Anyone can verify a proof without asking permission: the standalone verifier is published as [@mikeargento/bitgraph-verify](https://www.npmjs.com/package/@mikeargento/bitgraph-verify) under the MIT license, and the [LICENSE](LICENSE) here additionally grants express, irrevocable permission to copy, build, and run this repository's code for verifying proofs and for reproducing and auditing the published enclave measurements.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/canonical.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Copyright (c) Mike Argento. All rights reserved. See LICENSE.
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import * as assert from "node:assert/strict";
|
|
4
|
+
import { canonicalize, canonicalizeToString, constantTimeEqual, } from "../index.js";
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// canonicalizeToString
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
describe("canonicalizeToString", () => {
|
|
9
|
+
// Primitives
|
|
10
|
+
test("null", () => assert.equal(canonicalizeToString(null), "null"));
|
|
11
|
+
test("true", () => assert.equal(canonicalizeToString(true), "true"));
|
|
12
|
+
test("false", () => assert.equal(canonicalizeToString(false), "false"));
|
|
13
|
+
test("integer", () => assert.equal(canonicalizeToString(42), "42"));
|
|
14
|
+
test("float", () => assert.equal(canonicalizeToString(3.14), "3.14"));
|
|
15
|
+
test("negative", () => assert.equal(canonicalizeToString(-1), "-1"));
|
|
16
|
+
test("zero", () => assert.equal(canonicalizeToString(0), "0"));
|
|
17
|
+
test("string", () => assert.equal(canonicalizeToString("hello"), '"hello"'));
|
|
18
|
+
test("empty string", () => assert.equal(canonicalizeToString(""), '""'));
|
|
19
|
+
// Object key ordering
|
|
20
|
+
test("sorts keys lexicographically", () => {
|
|
21
|
+
assert.equal(canonicalizeToString({ z: 1, a: 2, m: 3 }), '{"a":2,"m":3,"z":1}');
|
|
22
|
+
});
|
|
23
|
+
test("uppercase sorts before lowercase (Unicode code-point order)", () => {
|
|
24
|
+
assert.equal(canonicalizeToString({ b: 2, A: 1 }), '{"A":1,"b":2}');
|
|
25
|
+
});
|
|
26
|
+
test("sorts nested object keys recursively", () => {
|
|
27
|
+
assert.equal(canonicalizeToString({ outer: { z: 1, a: 2 }, b: 3 }), '{"b":3,"outer":{"a":2,"z":1}}');
|
|
28
|
+
});
|
|
29
|
+
test("empty object", () => assert.equal(canonicalizeToString({}), "{}"));
|
|
30
|
+
test("same output regardless of insertion order", () => {
|
|
31
|
+
const a = { z: 1, a: 2 };
|
|
32
|
+
const b = { a: 2, z: 1 };
|
|
33
|
+
assert.equal(canonicalizeToString(a), canonicalizeToString(b));
|
|
34
|
+
});
|
|
35
|
+
// Arrays
|
|
36
|
+
test("preserves array element order", () => {
|
|
37
|
+
assert.equal(canonicalizeToString([3, 1, 2]), "[3,1,2]");
|
|
38
|
+
});
|
|
39
|
+
test("sorts keys inside array elements", () => {
|
|
40
|
+
assert.equal(canonicalizeToString([{ b: 2, a: 1 }]), '[{"a":1,"b":2}]');
|
|
41
|
+
});
|
|
42
|
+
test("empty array", () => assert.equal(canonicalizeToString([]), "[]"));
|
|
43
|
+
test("nested arrays preserve order", () => {
|
|
44
|
+
assert.equal(canonicalizeToString([[1, 2], [3, 4]]), "[[1,2],[3,4]]");
|
|
45
|
+
});
|
|
46
|
+
// Undefined handling
|
|
47
|
+
test("skips undefined values in objects", () => {
|
|
48
|
+
const obj = { a: 1, c: 3 };
|
|
49
|
+
obj["b"] = undefined;
|
|
50
|
+
assert.equal(canonicalizeToString(obj), '{"a":1,"c":3}');
|
|
51
|
+
});
|
|
52
|
+
// Rejection of non-serializable types
|
|
53
|
+
test("throws TypeError for undefined", () => {
|
|
54
|
+
assert.throws(() => canonicalizeToString(undefined), TypeError);
|
|
55
|
+
});
|
|
56
|
+
test("throws TypeError for function", () => {
|
|
57
|
+
assert.throws(() => canonicalizeToString((() => { })), TypeError);
|
|
58
|
+
});
|
|
59
|
+
test("throws TypeError for symbol", () => {
|
|
60
|
+
assert.throws(() => canonicalizeToString(Symbol("x")), TypeError);
|
|
61
|
+
});
|
|
62
|
+
test("throws TypeError for BigInt", () => {
|
|
63
|
+
assert.throws(() => canonicalizeToString(BigInt(42)), TypeError);
|
|
64
|
+
});
|
|
65
|
+
test("throws TypeError for BigInt inside object", () => {
|
|
66
|
+
assert.throws(() => canonicalizeToString({ n: BigInt(1) }), TypeError);
|
|
67
|
+
});
|
|
68
|
+
// NaN / Infinity → null (standard JSON.stringify behavior)
|
|
69
|
+
test("NaN serializes as null", () => {
|
|
70
|
+
assert.equal(canonicalizeToString(NaN), "null");
|
|
71
|
+
});
|
|
72
|
+
test("Infinity serializes as null", () => {
|
|
73
|
+
assert.equal(canonicalizeToString(Infinity), "null");
|
|
74
|
+
});
|
|
75
|
+
test("-Infinity serializes as null", () => {
|
|
76
|
+
assert.equal(canonicalizeToString(-Infinity), "null");
|
|
77
|
+
});
|
|
78
|
+
// Determinism
|
|
79
|
+
test("is deterministic across repeated calls", () => {
|
|
80
|
+
const obj = { c: 3, b: 2, a: 1 };
|
|
81
|
+
assert.equal(canonicalizeToString(obj), canonicalizeToString(obj));
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// canonicalize
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
describe("canonicalize", () => {
|
|
88
|
+
test("returns a Uint8Array", () => {
|
|
89
|
+
assert.ok(canonicalize({}) instanceof Uint8Array);
|
|
90
|
+
});
|
|
91
|
+
test("UTF-8 encodes the canonical JSON string", () => {
|
|
92
|
+
const bytes = canonicalize({ hello: "world" });
|
|
93
|
+
assert.equal(new TextDecoder().decode(bytes), '{"hello":"world"}');
|
|
94
|
+
});
|
|
95
|
+
test("matches TextEncoder(canonicalizeToString(obj))", () => {
|
|
96
|
+
const obj = { z: 1, a: 2 };
|
|
97
|
+
const bytes = canonicalize(obj);
|
|
98
|
+
const str = canonicalizeToString(obj);
|
|
99
|
+
assert.deepEqual(bytes, new TextEncoder().encode(str));
|
|
100
|
+
});
|
|
101
|
+
test("empty object → two bytes ('{' and '}')", () => {
|
|
102
|
+
assert.deepEqual(canonicalize({}), new TextEncoder().encode("{}"));
|
|
103
|
+
});
|
|
104
|
+
test("large payload round-trips through canonicalizeToString", () => {
|
|
105
|
+
const large = {};
|
|
106
|
+
for (let i = 0; i < 1000; i++)
|
|
107
|
+
large[`key_${i}`] = i;
|
|
108
|
+
const bytes = canonicalize(large);
|
|
109
|
+
const str = canonicalizeToString(large);
|
|
110
|
+
assert.deepEqual(bytes, new TextEncoder().encode(str));
|
|
111
|
+
});
|
|
112
|
+
test("throws TypeError for BigInt nested inside object", () => {
|
|
113
|
+
assert.throws(() => canonicalize({ n: BigInt(1) }), TypeError);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// constantTimeEqual
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
describe("constantTimeEqual", () => {
|
|
120
|
+
test("equal non-empty arrays", () => {
|
|
121
|
+
assert.equal(constantTimeEqual(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 3])), true);
|
|
122
|
+
});
|
|
123
|
+
test("different content same length", () => {
|
|
124
|
+
assert.equal(constantTimeEqual(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 4])), false);
|
|
125
|
+
});
|
|
126
|
+
test("different lengths", () => {
|
|
127
|
+
assert.equal(constantTimeEqual(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2])), false);
|
|
128
|
+
});
|
|
129
|
+
test("empty arrays are equal", () => {
|
|
130
|
+
assert.equal(constantTimeEqual(new Uint8Array([]), new Uint8Array([])), true);
|
|
131
|
+
});
|
|
132
|
+
test("empty vs non-empty", () => {
|
|
133
|
+
assert.equal(constantTimeEqual(new Uint8Array([]), new Uint8Array([0])), false);
|
|
134
|
+
});
|
|
135
|
+
test("single byte equal", () => {
|
|
136
|
+
assert.equal(constantTimeEqual(new Uint8Array([0xff]), new Uint8Array([0xff])), true);
|
|
137
|
+
});
|
|
138
|
+
test("single byte different", () => {
|
|
139
|
+
assert.equal(constantTimeEqual(new Uint8Array([0xff]), new Uint8Array([0xfe])), false);
|
|
140
|
+
});
|
|
141
|
+
test("large equal arrays", () => {
|
|
142
|
+
const a = new Uint8Array(1024).fill(0xab);
|
|
143
|
+
const b = new Uint8Array(1024).fill(0xab);
|
|
144
|
+
assert.equal(constantTimeEqual(a, b), true);
|
|
145
|
+
});
|
|
146
|
+
test("large arrays differing in last byte", () => {
|
|
147
|
+
const a = new Uint8Array(1024).fill(0xab);
|
|
148
|
+
const b = new Uint8Array(1024).fill(0xab);
|
|
149
|
+
b[1023] = 0xac;
|
|
150
|
+
assert.equal(constantTimeEqual(a, b), false);
|
|
151
|
+
});
|
|
152
|
+
test("large arrays differing in first byte", () => {
|
|
153
|
+
const a = new Uint8Array(1024).fill(0xab);
|
|
154
|
+
const b = new Uint8Array(1024).fill(0xab);
|
|
155
|
+
b[0] = 0xac;
|
|
156
|
+
assert.equal(constantTimeEqual(a, b), false);
|
|
157
|
+
});
|
|
158
|
+
test("all-zeros equal", () => {
|
|
159
|
+
assert.equal(constantTimeEqual(new Uint8Array(32), new Uint8Array(32)), true);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
//# sourceMappingURL=canonical.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.test.js","sourceRoot":"","sources":["../../src/__tests__/canonical.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit-service.integration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/commit-service.integration.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Copyright (c) Mike Argento. All rights reserved. See LICENSE.
|
|
2
|
+
/**
|
|
3
|
+
* Integration test: HTTP commit service (mock server)
|
|
4
|
+
*
|
|
5
|
+
* Starts server/commit-service/dist/mock/mock-server.js, exercises the full
|
|
6
|
+
* commit → verify cycle over HTTP, and confirms that tampered proofs are rejected.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, test, before, after } from "node:test";
|
|
9
|
+
import * as assert from "node:assert/strict";
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { join, dirname } from "node:path";
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
// Two levels up from dist/__tests__/ reaches the project root.
|
|
16
|
+
const MOCK_SERVER_PATH = join(__dirname, "../../server/commit-service/dist/mock/mock-server.js");
|
|
17
|
+
const PORT = 18787;
|
|
18
|
+
const BASE_URL = `http://localhost:${PORT}`;
|
|
19
|
+
async function waitForServer(timeoutMs = 8000) {
|
|
20
|
+
const deadline = Date.now() + timeoutMs;
|
|
21
|
+
while (Date.now() < deadline) {
|
|
22
|
+
try {
|
|
23
|
+
const res = await fetch(`${BASE_URL}/health`);
|
|
24
|
+
if (res.ok)
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// not ready yet
|
|
29
|
+
}
|
|
30
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
31
|
+
}
|
|
32
|
+
throw new Error(`Mock server did not become ready within ${timeoutMs}ms`);
|
|
33
|
+
}
|
|
34
|
+
function sha256B64(text) {
|
|
35
|
+
return createHash("sha256").update(text, "utf8").digest("base64");
|
|
36
|
+
}
|
|
37
|
+
describe("HTTP commit service integration", () => {
|
|
38
|
+
let server;
|
|
39
|
+
before(async () => {
|
|
40
|
+
server = spawn(process.execPath, [MOCK_SERVER_PATH], {
|
|
41
|
+
env: { ...process.env, PORT: String(PORT) },
|
|
42
|
+
stdio: "pipe",
|
|
43
|
+
});
|
|
44
|
+
await waitForServer();
|
|
45
|
+
});
|
|
46
|
+
after(() => {
|
|
47
|
+
server.kill();
|
|
48
|
+
});
|
|
49
|
+
test("happy path: commit + verify succeeds", async () => {
|
|
50
|
+
const digestB64 = sha256B64("hello bitgraph integration test");
|
|
51
|
+
// Commit
|
|
52
|
+
const commitRes = await fetch(`${BASE_URL}/commit`, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: { "Content-Type": "application/json" },
|
|
55
|
+
body: JSON.stringify({ digests: [{ digestB64, hashAlg: "sha256" }] }),
|
|
56
|
+
});
|
|
57
|
+
assert.equal(commitRes.status, 200, "POST /commit should return 200");
|
|
58
|
+
const proofs = (await commitRes.json());
|
|
59
|
+
assert.ok(Array.isArray(proofs) && proofs.length === 1, "expected one proof");
|
|
60
|
+
const proof = proofs[0];
|
|
61
|
+
assert.equal(proof.version, "bitgraph/1");
|
|
62
|
+
assert.equal(proof.artifact.digestB64, digestB64);
|
|
63
|
+
// Verify
|
|
64
|
+
const verifyRes = await fetch(`${BASE_URL}/verify`, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: { "Content-Type": "application/json" },
|
|
67
|
+
body: JSON.stringify({ proof }),
|
|
68
|
+
});
|
|
69
|
+
assert.equal(verifyRes.status, 200, "POST /verify should return 200");
|
|
70
|
+
const result = (await verifyRes.json());
|
|
71
|
+
assert.equal(result.valid, true, `signature verification failed: ${result.reason}`);
|
|
72
|
+
});
|
|
73
|
+
test("tamper path: modified digest bytes are rejected", async () => {
|
|
74
|
+
const digestB64 = sha256B64("tamper test payload");
|
|
75
|
+
const commitRes = await fetch(`${BASE_URL}/commit`, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: { "Content-Type": "application/json" },
|
|
78
|
+
body: JSON.stringify({ digests: [{ digestB64, hashAlg: "sha256" }] }),
|
|
79
|
+
});
|
|
80
|
+
assert.equal(commitRes.status, 200);
|
|
81
|
+
const proofs = (await commitRes.json());
|
|
82
|
+
// Deep-clone and tamper the artifact digest
|
|
83
|
+
const tampered = JSON.parse(JSON.stringify(proofs[0]));
|
|
84
|
+
tampered.artifact.digestB64 = sha256B64("completely different content");
|
|
85
|
+
const verifyRes = await fetch(`${BASE_URL}/verify`, {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: { "Content-Type": "application/json" },
|
|
88
|
+
body: JSON.stringify({ proof: tampered }),
|
|
89
|
+
});
|
|
90
|
+
assert.equal(verifyRes.status, 200);
|
|
91
|
+
const result = (await verifyRes.json());
|
|
92
|
+
assert.equal(result.valid, false, "tampered proof should fail verification");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
//# sourceMappingURL=commit-service.integration.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit-service.integration.test.js","sourceRoot":"","sources":["../../src/__tests__/commit-service.integration.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constructor.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/constructor.test.ts"],"names":[],"mappings":""}
|