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.
- package/LICENSE +21 -0
- package/README.md +368 -0
- package/package.json +55 -0
- package/src/cli/index.js +547 -0
- package/src/core/chain.js +186 -0
- package/src/core/merkle.js +131 -0
- package/src/core/trust-atom.js +125 -0
- package/src/core/trust-pixel.js +81 -0
- package/src/core/witness.js +377 -0
- package/src/mcp/server.js +534 -0
- package/src/scanner/index.js +437 -0
- package/src/store/store.js +133 -0
- package/src/test.js +266 -0
|
@@ -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
|
+
}
|