forge-trust-chain 0.3.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.
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Chain — Temporal chain of TrustAtoms and Merkle blocks.
3
+ *
4
+ * The "page" level: atoms are letters, Merkle trees are sentences,
5
+ * and the chain is the full document — ordered in time, linked by hashes.
6
+ *
7
+ * This is the in-memory runtime. For persistence, see store.
8
+ */
9
+
10
+ import { createAtom, verifyAtom, verifyChain, formatAtom } from "./trust-atom.js";
11
+ import { treeFromAtoms, createBlock, getMerkleProof, verifyMerkleProof } from "./merkle.js";
12
+
13
+ export class TrustChain {
14
+ constructor(identity) {
15
+ this.identity = identity;
16
+ this.atoms = [];
17
+ this.blocks = [];
18
+ }
19
+
20
+ /* ---- Record ---- */
21
+
22
+ /**
23
+ * Append a new state transition to the chain.
24
+ */
25
+ record({ action, from, to }) {
26
+ const prev =
27
+ this.atoms.length > 0
28
+ ? this.atoms[this.atoms.length - 1].proof
29
+ : "genesis";
30
+
31
+ const atom = createAtom({
32
+ who: this.identity,
33
+ from,
34
+ action,
35
+ to,
36
+ prev,
37
+ });
38
+
39
+ this.atoms.push(atom);
40
+ return atom;
41
+ }
42
+
43
+ /**
44
+ * Seal current atoms into a Merkle block.
45
+ * Call periodically (e.g. every N atoms or every M seconds).
46
+ */
47
+ seal() {
48
+ if (this.atoms.length === 0) return null;
49
+
50
+ const prevBlockHash =
51
+ this.blocks.length > 0
52
+ ? this.blocks[this.blocks.length - 1].block_hash
53
+ : "genesis";
54
+
55
+ // Seal only atoms not yet in a block
56
+ const unsealed = this.atoms.slice(this._lastSealedIndex() + 1);
57
+ if (unsealed.length === 0) return null;
58
+
59
+ const block = createBlock(unsealed, prevBlockHash);
60
+ block.atom_range = [
61
+ this._lastSealedIndex() + 1,
62
+ this.atoms.length - 1,
63
+ ];
64
+ this.blocks.push(block);
65
+ return block;
66
+ }
67
+
68
+ /* ---- Verify ---- */
69
+
70
+ /** Verify entire atom chain integrity — O(n). */
71
+ verify() {
72
+ return verifyChain(this.atoms);
73
+ }
74
+
75
+ /** Generate Merkle proof for a specific atom index. */
76
+ proveAtom(globalIndex) {
77
+ // Find which block contains this atom
78
+ const block = this.blocks.find(
79
+ (b) => globalIndex >= b.atom_range[0] && globalIndex <= b.atom_range[1]
80
+ );
81
+ if (!block) return null;
82
+
83
+ const localIndex = globalIndex - block.atom_range[0];
84
+ const proof = getMerkleProof(block.layers, localIndex);
85
+ return {
86
+ atom: this.atoms[globalIndex],
87
+ merkle_proof: proof,
88
+ merkle_root: block.root,
89
+ block_hash: block.block_hash,
90
+ };
91
+ }
92
+
93
+ /** Verify a Merkle proof for an atom. */
94
+ verifyProof(atomProofHash, merkleProof, expectedRoot) {
95
+ return verifyMerkleProof(atomProofHash, merkleProof, expectedRoot);
96
+ }
97
+
98
+ /* ---- Query ---- */
99
+
100
+ get length() {
101
+ return this.atoms.length;
102
+ }
103
+
104
+ last() {
105
+ return this.atoms[this.atoms.length - 1] || null;
106
+ }
107
+
108
+ /** Export chain as portable JSON (no raw data). */
109
+ export() {
110
+ return {
111
+ identity_hash: this.atoms[0]?.who || null,
112
+ atom_count: this.atoms.length,
113
+ block_count: this.blocks.length,
114
+ atoms: this.atoms.map(({ _raw, ...a }) => a),
115
+ blocks: this.blocks.map(({ layers, ...b }) => b),
116
+ exported_at: Date.now(),
117
+ };
118
+ }
119
+
120
+ /** Print chain summary to console. */
121
+ summary() {
122
+ const lines = [
123
+ `Chain: ${this.identity}`,
124
+ `Atoms: ${this.atoms.length}`,
125
+ `Blocks: ${this.blocks.length}`,
126
+ `Integrity: ${this.verify().valid ? "✓ VALID" : "✗ BROKEN"}`,
127
+ "",
128
+ "Recent atoms:",
129
+ ];
130
+ const recent = this.atoms.slice(-5);
131
+ const offset = this.atoms.length - recent.length;
132
+ for (let i = 0; i < recent.length; i++) {
133
+ lines.push(" " + formatAtom(recent[i], offset + i));
134
+ }
135
+ return lines.join("\n");
136
+ }
137
+
138
+ /* ---- Internal ---- */
139
+
140
+ _lastSealedIndex() {
141
+ if (this.blocks.length === 0) return -1;
142
+ return this.blocks[this.blocks.length - 1].atom_range[1];
143
+ }
144
+ }
145
+
146
+ /* ================================================================
147
+ CROSS-CHAIN DISPUTE RESOLUTION
148
+ ================================================================ */
149
+
150
+ /**
151
+ * Compare two chains for the same system and find divergence.
152
+ * This is the "cryptographic double-entry bookkeeping" from the whitepaper.
153
+ */
154
+ export function findDivergence(chainA, chainB) {
155
+ const minLen = Math.min(chainA.atoms.length, chainB.atoms.length);
156
+
157
+ for (let i = 0; i < minLen; i++) {
158
+ const a = chainA.atoms[i];
159
+ const b = chainB.atoms[i];
160
+
161
+ // Same action at same time should produce same from/to hashes
162
+ if (a.action !== b.action || a.from !== b.from || a.to !== b.to) {
163
+ return {
164
+ diverged: true,
165
+ at_index: i,
166
+ time_a: a.when,
167
+ time_b: b.when,
168
+ action_match: a.action === b.action,
169
+ state_match: a.from === b.from && a.to === b.to,
170
+ };
171
+ }
172
+ }
173
+
174
+ // Length difference = one party has operations the other doesn't
175
+ if (chainA.atoms.length !== chainB.atoms.length) {
176
+ return {
177
+ diverged: true,
178
+ at_index: minLen,
179
+ reason: "length_mismatch",
180
+ length_a: chainA.atoms.length,
181
+ length_b: chainB.atoms.length,
182
+ };
183
+ }
184
+
185
+ return { diverged: false };
186
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Merkle Tree — Efficient batch verification for TrustAtoms.
3
+ *
4
+ * Single atom in batch: O(log n) via Merkle proof
5
+ * Entire batch: O(1) via root hash
6
+ *
7
+ * This is the "paragraph" level: atoms are letters, Merkle tree is the sentence.
8
+ */
9
+
10
+ import { hash } from "./trust-pixel.js";
11
+
12
+ /* ================================================================
13
+ BUILD
14
+ ================================================================ */
15
+
16
+ /**
17
+ * Build a Merkle tree from an array of TrustAtom proof hashes.
18
+ *
19
+ * Returns { root, layers } where:
20
+ * root = single hash representing the entire batch
21
+ * layers = array of arrays, from leaves (bottom) to root (top)
22
+ */
23
+ export function buildMerkleTree(proofs) {
24
+ if (proofs.length === 0) return { root: hash("empty"), layers: [[]] };
25
+ if (proofs.length === 1) return { root: proofs[0], layers: [proofs] };
26
+
27
+ const layers = [proofs.slice()]; // layer 0 = leaves
28
+
29
+ let current = proofs.slice();
30
+ while (current.length > 1) {
31
+ const next = [];
32
+ for (let i = 0; i < current.length; i += 2) {
33
+ if (i + 1 < current.length) {
34
+ next.push(hash(current[i] + current[i + 1]));
35
+ } else {
36
+ // Odd node: promote as-is (self-pair)
37
+ next.push(hash(current[i] + current[i]));
38
+ }
39
+ }
40
+ layers.push(next);
41
+ current = next;
42
+ }
43
+
44
+ return { root: current[0], layers };
45
+ }
46
+
47
+ /* ================================================================
48
+ PROOF — Selective disclosure (ZKP application)
49
+ ================================================================ */
50
+
51
+ /**
52
+ * Generate a Merkle proof for a leaf at given index.
53
+ *
54
+ * Returns an array of { hash, direction } pairs.
55
+ * "direction" tells the verifier which side to concatenate on.
56
+ */
57
+ export function getMerkleProof(layers, leafIndex) {
58
+ const proof = [];
59
+ let idx = leafIndex;
60
+
61
+ for (let layer = 0; layer < layers.length - 1; layer++) {
62
+ const nodes = layers[layer];
63
+ const isRight = idx % 2 === 1;
64
+ const siblingIdx = isRight ? idx - 1 : idx + 1;
65
+
66
+ if (siblingIdx < nodes.length) {
67
+ proof.push({
68
+ hash: nodes[siblingIdx],
69
+ direction: isRight ? "left" : "right",
70
+ });
71
+ } else {
72
+ // No sibling (odd node), paired with itself
73
+ proof.push({
74
+ hash: nodes[idx],
75
+ direction: isRight ? "left" : "right",
76
+ });
77
+ }
78
+
79
+ idx = Math.floor(idx / 2);
80
+ }
81
+
82
+ return proof;
83
+ }
84
+
85
+ /**
86
+ * Verify a Merkle proof — O(log n).
87
+ *
88
+ * Proves that a specific leaf hash belongs to the tree
89
+ * WITHOUT revealing any other leaves.
90
+ */
91
+ export function verifyMerkleProof(leafHash, proof, expectedRoot) {
92
+ let current = leafHash;
93
+
94
+ for (const step of proof) {
95
+ if (step.direction === "left") {
96
+ current = hash(step.hash + current);
97
+ } else {
98
+ current = hash(current + step.hash);
99
+ }
100
+ }
101
+
102
+ return current === expectedRoot;
103
+ }
104
+
105
+ /* ================================================================
106
+ CONVENIENCE
107
+ ================================================================ */
108
+
109
+ /**
110
+ * Build tree from atoms (extracts proof hashes automatically).
111
+ */
112
+ export function treeFromAtoms(atoms) {
113
+ return buildMerkleTree(atoms.map((a) => a.proof));
114
+ }
115
+
116
+ /**
117
+ * Create a time-block: a Merkle tree with metadata.
118
+ */
119
+ export function createBlock(atoms, prevBlockHash = "genesis") {
120
+ const tree = treeFromAtoms(atoms);
121
+ const blockHash = hash(tree.root + prevBlockHash + Date.now());
122
+
123
+ return {
124
+ root: tree.root,
125
+ layers: tree.layers,
126
+ atom_count: atoms.length,
127
+ prev_block: prevBlockHash,
128
+ block_hash: blockHash,
129
+ created_at: Date.now(),
130
+ };
131
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * TrustAtom — One verifiable state transition.
3
+ *
4
+ * A Turing machine's minimum operation: State A → Action → State B
5
+ * A TrustAtom records exactly this, with identity and chain integrity.
6
+ *
7
+ * Structure:
8
+ * who hash(identity) — who did it
9
+ * from hash(state_before) — starting state
10
+ * action hash(operation) — what was done
11
+ * to hash(state_after) — ending state
12
+ * when timestamp — when
13
+ * prev previous atom hash — chain link (array for DAG)
14
+ * proof hash(all above) — the atom's fingerprint
15
+ *
16
+ * Every field is a TrustPixel's hash. The atom itself is a composition.
17
+ */
18
+
19
+ import { hash, hashMany, selfWitness } from "./trust-pixel.js";
20
+
21
+ /* ================================================================
22
+ CREATE
23
+ ================================================================ */
24
+
25
+ /**
26
+ * @param {object} params
27
+ * @param {string} params.who - Identity string (will be hashed)
28
+ * @param {any} params.from - Pre-state (will be hashed)
29
+ * @param {string} params.action - Operation description (will be hashed)
30
+ * @param {any} params.to - Post-state (will be hashed)
31
+ * @param {string|string[]} [params.prev] - Previous atom proof(s), or "genesis"
32
+ * @returns {object} TrustAtom
33
+ */
34
+ export function createAtom({ who, from, action, to, prev = "genesis" }) {
35
+ const when = Date.now();
36
+
37
+ // Normalise prev to array (DAG-ready)
38
+ const prevArr = Array.isArray(prev) ? prev : [prev];
39
+
40
+ const atom = {
41
+ who: hash(who),
42
+ from: hash(from),
43
+ action: hash(action),
44
+ to: hash(to),
45
+ when,
46
+ prev: prevArr,
47
+ };
48
+
49
+ // Proof = hash of all fields concatenated in deterministic order
50
+ atom.proof = hashMany(
51
+ atom.who,
52
+ atom.from,
53
+ atom.action,
54
+ atom.to,
55
+ atom.when,
56
+ ...atom.prev
57
+ );
58
+
59
+ // Keep raw values for debugging / local use (not transmitted)
60
+ atom._raw = { who, action, when };
61
+
62
+ return atom;
63
+ }
64
+
65
+ /* ================================================================
66
+ VERIFY
67
+ ================================================================ */
68
+
69
+ /** Verify a single atom's self-consistency — O(1). */
70
+ export function verifyAtom(atom) {
71
+ const expected = hashMany(
72
+ atom.who,
73
+ atom.from,
74
+ atom.action,
75
+ atom.to,
76
+ atom.when,
77
+ ...atom.prev
78
+ );
79
+ return expected === atom.proof;
80
+ }
81
+
82
+ /** Verify a chain of atoms — O(n). */
83
+ export function verifyChain(atoms) {
84
+ if (atoms.length === 0) return { valid: true, broken_at: -1 };
85
+
86
+ for (let i = 0; i < atoms.length; i++) {
87
+ // 1. Self-consistency
88
+ if (!verifyAtom(atoms[i])) {
89
+ return { valid: false, broken_at: i, reason: "proof_mismatch" };
90
+ }
91
+
92
+ // 2. Chain link (skip genesis)
93
+ if (i > 0) {
94
+ if (!atoms[i].prev.includes(atoms[i - 1].proof)) {
95
+ return { valid: false, broken_at: i, reason: "chain_break" };
96
+ }
97
+ }
98
+
99
+ // 3. Temporal order
100
+ if (i > 0 && atoms[i].when < atoms[i - 1].when) {
101
+ return { valid: false, broken_at: i, reason: "time_reversal" };
102
+ }
103
+ }
104
+
105
+ return { valid: true, broken_at: -1 };
106
+ }
107
+
108
+ /* ================================================================
109
+ UTILITY
110
+ ================================================================ */
111
+
112
+ /** Strip raw data for transmission (only hashes travel). */
113
+ export function stripAtom(atom) {
114
+ const { _raw, ...clean } = atom;
115
+ return clean;
116
+ }
117
+
118
+ /** Pretty-print an atom for CLI. */
119
+ export function formatAtom(atom, index = 0) {
120
+ const who = atom._raw?.who || atom.who.slice(0, 12) + "…";
121
+ const act = atom._raw?.action || atom.action.slice(0, 12) + "…";
122
+ const time = new Date(atom.when).toISOString();
123
+ const proof = atom.proof.slice(0, 16) + "…";
124
+ return `#${index} [${time}] ${who} → ${act} proof:${proof}`;
125
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * TrustPixel — The smallest indivisible unit of trust.
3
+ *
4
+ * First principle: Trust = Certainty × Existence
5
+ *
6
+ * Certainty → SHA-256 hash (mathematical: deterministic, irreversible)
7
+ * Existence → Witness (physical: an independent copy survives deletion)
8
+ *
9
+ * A hash without a witness can be silently deleted.
10
+ * A witness without a hash can be forged.
11
+ * Together they produce an undeniable fact.
12
+ */
13
+
14
+ import { createHash } from "node:crypto";
15
+
16
+ /* ================================================================
17
+ CERTAINTY — the mathematical half
18
+ ================================================================ */
19
+
20
+ export function hash(input) {
21
+ if (input === undefined || input === null) input = "";
22
+ if (typeof input === "object")
23
+ input = JSON.stringify(input, Object.keys(input).sort());
24
+ return createHash("sha256").update(String(input)).digest("hex");
25
+ }
26
+
27
+ export function hashMany(...parts) {
28
+ return hash(
29
+ parts
30
+ .map((p) =>
31
+ typeof p === "object" ? JSON.stringify(p, Object.keys(p).sort()) : String(p)
32
+ )
33
+ .join("|")
34
+ );
35
+ }
36
+
37
+ /* ================================================================
38
+ EXISTENCE — the physical / social half
39
+ ================================================================ */
40
+
41
+ export function selfWitness(loc = "local") {
42
+ return { type: "self", location: loc, at: Date.now() };
43
+ }
44
+
45
+ export function bilateralWitness(counterparty, loc = "remote") {
46
+ return { type: "bilateral", counterparty, location: loc, at: Date.now() };
47
+ }
48
+
49
+ export function publicWitness(service, receipt = null) {
50
+ return { type: "public", service, receipt, at: Date.now() };
51
+ }
52
+
53
+ export function anchoredWitness(chain, txid) {
54
+ return { type: "anchored", chain, txid, at: Date.now() };
55
+ }
56
+
57
+ /* ================================================================
58
+ TRUST PIXEL — Certainty × Existence
59
+ ================================================================ */
60
+
61
+ export function createPixel(content, witnesses) {
62
+ const h = hash(content);
63
+ if (!witnesses || witnesses.length === 0) witnesses = [selfWitness()];
64
+ return { hash: h, witnesses, created_at: Date.now() };
65
+ }
66
+
67
+ export function verifyPixel(pixel, content) {
68
+ const certain = hash(content) === pixel.hash;
69
+ const exists = pixel.witnesses && pixel.witnesses.length > 0;
70
+ return { valid: certain && exists, certain, exists, strongest: bestWitness(pixel) };
71
+ }
72
+
73
+ export function bestWitness(pixel) {
74
+ const rank = { anchored: 4, public: 3, bilateral: 2, self: 1 };
75
+ let best = { level: 0, label: "none" };
76
+ for (const w of pixel.witnesses || []) {
77
+ const r = rank[w.type] || 0;
78
+ if (r > best.level) best = { level: r, label: w.type };
79
+ }
80
+ return best;
81
+ }