codequill 0.8.1-beta.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 +121 -0
- package/dist/commands/attest.js +442 -0
- package/dist/commands/attest.js.map +1 -0
- package/dist/commands/backup.js +370 -0
- package/dist/commands/backup.js.map +1 -0
- package/dist/commands/claim.js +104 -0
- package/dist/commands/claim.js.map +1 -0
- package/dist/commands/log.js +188 -0
- package/dist/commands/log.js.map +1 -0
- package/dist/commands/login.js +147 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/prove.js +244 -0
- package/dist/commands/prove.js.map +1 -0
- package/dist/commands/publish.js +243 -0
- package/dist/commands/publish.js.map +1 -0
- package/dist/commands/pull.js +174 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/quota.js +94 -0
- package/dist/commands/quota.js.map +1 -0
- package/dist/commands/revoke.js +97 -0
- package/dist/commands/revoke.js.map +1 -0
- package/dist/commands/snapshot.js +128 -0
- package/dist/commands/snapshot.js.map +1 -0
- package/dist/commands/status.js +234 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/verifyAttestation.js +212 -0
- package/dist/commands/verifyAttestation.js.map +1 -0
- package/dist/commands/verifyProof.js +145 -0
- package/dist/commands/verifyProof.js.map +1 -0
- package/dist/commands/wait.js +36 -0
- package/dist/commands/wait.js.map +1 -0
- package/dist/commands/who.js +55 -0
- package/dist/commands/who.js.map +1 -0
- package/dist/commands/why.js +412 -0
- package/dist/commands/why.js.map +1 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -0
- package/dist/launcher.js +69 -0
- package/dist/launcher.js.map +1 -0
- package/dist/services/api.js +51 -0
- package/dist/services/api.js.map +1 -0
- package/dist/services/apiClient.js +166 -0
- package/dist/services/apiClient.js.map +1 -0
- package/dist/services/authStore.js +84 -0
- package/dist/services/authStore.js.map +1 -0
- package/dist/services/config.js +19 -0
- package/dist/services/config.js.map +1 -0
- package/dist/services/confirm.js +58 -0
- package/dist/services/confirm.js.map +1 -0
- package/dist/services/crypto.js +38 -0
- package/dist/services/crypto.js.map +1 -0
- package/dist/services/errors.js +17 -0
- package/dist/services/errors.js.map +1 -0
- package/dist/services/fs.js +25 -0
- package/dist/services/fs.js.map +1 -0
- package/dist/services/git.js +121 -0
- package/dist/services/git.js.map +1 -0
- package/dist/services/manifests/attestationManifest.js +35 -0
- package/dist/services/manifests/attestationManifest.js.map +1 -0
- package/dist/services/manifests/proofManifest.js +151 -0
- package/dist/services/manifests/proofManifest.js.map +1 -0
- package/dist/services/manifests/snapshotManifest.js +214 -0
- package/dist/services/manifests/snapshotManifest.js.map +1 -0
- package/dist/services/merkle.js +92 -0
- package/dist/services/merkle.js.map +1 -0
- package/dist/services/paths.js +16 -0
- package/dist/services/paths.js.map +1 -0
- package/dist/services/snapshotIndex.js +401 -0
- package/dist/services/snapshotIndex.js.map +1 -0
- package/dist/services/txWaiter.js +84 -0
- package/dist/services/txWaiter.js.map +1 -0
- package/dist/services/ui.js +98 -0
- package/dist/services/ui.js.map +1 -0
- package/dist/services/utilities.js +45 -0
- package/dist/services/utilities.js.map +1 -0
- package/dist/services/zip.js +24 -0
- package/dist/services/zip.js.map +1 -0
- package/dist/types/api.js +2 -0
- package/dist/types/api.js.map +1 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { chainName } from "../utilities.js";
|
|
2
|
+
import { leafHash, parentHash, normalizePath } from "../merkle.js";
|
|
3
|
+
function isHex32(x) {
|
|
4
|
+
return typeof x === "string" && /^[0-9a-f]{64}$/i.test(x);
|
|
5
|
+
}
|
|
6
|
+
export function buildProofManifest(args) {
|
|
7
|
+
const core = buildProofFromManifest(args.snapshotManifest, args.targetPathHash);
|
|
8
|
+
// 1) Verify local file matches snapshot record (public file hash)
|
|
9
|
+
const fileHashInSnapshot = String(core.file?.hash ?? "").toLowerCase();
|
|
10
|
+
const current = String(args.currentFileHash ?? "").toLowerCase();
|
|
11
|
+
if (!isHex32(fileHashInSnapshot)) {
|
|
12
|
+
throw new Error(`Invalid proof build: snapshot file hash is not 32-byte hex`);
|
|
13
|
+
}
|
|
14
|
+
if (!isHex32(current)) {
|
|
15
|
+
throw new Error(`Invalid input: currentFileHash is not 32-byte hex`);
|
|
16
|
+
}
|
|
17
|
+
if (fileHashInSnapshot !== current) {
|
|
18
|
+
throw new Error(`File content does not match snapshot.\n\n` +
|
|
19
|
+
`Snapshot hash: ${fileHashInSnapshot}\n` +
|
|
20
|
+
`Current hash: ${current}\n`);
|
|
21
|
+
}
|
|
22
|
+
// 2) Optional on-chain context
|
|
23
|
+
let statement = String(core.statement ?? "");
|
|
24
|
+
const meta = args.snapshotMeta ?? null;
|
|
25
|
+
let snapshot = undefined;
|
|
26
|
+
if (meta?.snapshot_id) {
|
|
27
|
+
snapshot = {
|
|
28
|
+
snapshot_id: String(meta.snapshot_id),
|
|
29
|
+
published_at: meta.published_at,
|
|
30
|
+
chain_id: meta.chain_id,
|
|
31
|
+
tx_hash: meta.tx_hash ? String(meta.tx_hash) : undefined,
|
|
32
|
+
explorer_url: meta.explorer_url ? String(meta.explorer_url) : undefined,
|
|
33
|
+
publisher_wallet: meta.wallet_address ? String(meta.wallet_address) : undefined,
|
|
34
|
+
manifest_cid: meta.manifest_cid ? String(meta.manifest_cid) : undefined,
|
|
35
|
+
};
|
|
36
|
+
const chain = chainName(meta.chain_id) ?? (meta.chain_id ? `chain ${meta.chain_id}` : "unknown chain");
|
|
37
|
+
const pubAt = meta.published_at ? new Date(meta.published_at * 1000).toUTCString() : "unknown time";
|
|
38
|
+
const wallet = meta.wallet_address ? String(meta.wallet_address) : "unknown wallet";
|
|
39
|
+
statement += ` The snapshot was published on ${chain} at ${pubAt} by authority wallet ${wallet}.`;
|
|
40
|
+
}
|
|
41
|
+
// 3) Privacy block + optional disclosure (kept inside this function, as requested)
|
|
42
|
+
const disclosed = !!args.disclosePathPlaintext;
|
|
43
|
+
const privacy = {
|
|
44
|
+
disclosed_path: disclosed,
|
|
45
|
+
path_binding: disclosed ? "prover_asserted" : "not_provided",
|
|
46
|
+
...(disclosed
|
|
47
|
+
? { path_plaintext: normalizePath(args.pathPlaintext ?? args.targetPathNorm) }
|
|
48
|
+
: {}),
|
|
49
|
+
};
|
|
50
|
+
if (disclosed) {
|
|
51
|
+
const norm = privacy.path_plaintext;
|
|
52
|
+
statement += ` (Path provided by prover: "${norm}" — not publicly verifiable without decrypted path map.)`;
|
|
53
|
+
}
|
|
54
|
+
// 4) Return final proof manifest (now includes privacy)
|
|
55
|
+
return {
|
|
56
|
+
...core,
|
|
57
|
+
statement,
|
|
58
|
+
...(snapshot ? { snapshot } : {}),
|
|
59
|
+
privacy,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function buildProofFromManifest(manifest, targetPathHash) {
|
|
63
|
+
if (!manifest?.files || !Array.isArray(manifest.files)) {
|
|
64
|
+
throw new Error("Invalid manifest: missing files[]");
|
|
65
|
+
}
|
|
66
|
+
if (!manifest?.merkle_root || typeof manifest.merkle_root !== "string") {
|
|
67
|
+
throw new Error("Invalid manifest: missing merkle_root");
|
|
68
|
+
}
|
|
69
|
+
const repo = String(manifest.repo ?? "");
|
|
70
|
+
const commit = String(manifest.commit ?? "");
|
|
71
|
+
const merkle_root = String(manifest.merkle_root).toLowerCase();
|
|
72
|
+
const targetHash = String(targetPathHash ?? "").toLowerCase();
|
|
73
|
+
if (!isHex32(targetHash)) {
|
|
74
|
+
throw new Error("targetPathHash must be 64 hex chars (no 0x)");
|
|
75
|
+
}
|
|
76
|
+
// Parse manifest records
|
|
77
|
+
const records = manifest.files.map((f) => ({
|
|
78
|
+
path_hash: String(f.path_hash).toLowerCase(),
|
|
79
|
+
hash: String(f.hash).toLowerCase(),
|
|
80
|
+
}));
|
|
81
|
+
// Find matching file record by path_hash
|
|
82
|
+
const rec = records.find((r) => r.path_hash === targetHash);
|
|
83
|
+
if (!rec) {
|
|
84
|
+
throw new Error(`File not found in manifest (by path_hash).\n` +
|
|
85
|
+
`Provided path_hash: ${targetHash}\n` +
|
|
86
|
+
`Tip: you must compute path_hash using the decrypted path map (salt is encrypted in snapshot.path_map_enc).`);
|
|
87
|
+
}
|
|
88
|
+
// Build leaf list for all entries
|
|
89
|
+
const leafItems = records.map((r) => ({
|
|
90
|
+
leaf: leafHash(r.path_hash, r.hash),
|
|
91
|
+
path_hash: r.path_hash,
|
|
92
|
+
hash: r.hash,
|
|
93
|
+
}));
|
|
94
|
+
// Sort leaves lexicographically by leaf hash (must match merkle.ts)
|
|
95
|
+
leafItems.sort((a, b) => (a.leaf < b.leaf ? -1 : a.leaf > b.leaf ? 1 : 0));
|
|
96
|
+
const targetLeaf = leafHash(rec.path_hash, rec.hash);
|
|
97
|
+
const idx = leafItems.findIndex((x) => x.leaf === targetLeaf);
|
|
98
|
+
if (idx === -1)
|
|
99
|
+
throw new Error("Internal error: target leaf not found after sorting");
|
|
100
|
+
// Build proof upward
|
|
101
|
+
let index = idx;
|
|
102
|
+
let level = leafItems.map((x) => x.leaf);
|
|
103
|
+
const proofSteps = [];
|
|
104
|
+
while (level.length > 1) {
|
|
105
|
+
const isRightNode = index % 2 === 1;
|
|
106
|
+
const pairIndex = isRightNode ? index - 1 : index + 1;
|
|
107
|
+
const sibling = pairIndex < level.length ? level[pairIndex] : level[index]; // duplicate-last
|
|
108
|
+
proofSteps.push({
|
|
109
|
+
side: isRightNode ? "left" : "right",
|
|
110
|
+
hash: sibling,
|
|
111
|
+
});
|
|
112
|
+
const next = [];
|
|
113
|
+
for (let i = 0; i < level.length; i += 2) {
|
|
114
|
+
const l = level[i];
|
|
115
|
+
const r = i + 1 < level.length ? level[i + 1] : level[i];
|
|
116
|
+
next.push(parentHash(l, r));
|
|
117
|
+
}
|
|
118
|
+
level = next;
|
|
119
|
+
index = Math.floor(index / 2);
|
|
120
|
+
}
|
|
121
|
+
const computedRoot = String(level[0]).toLowerCase();
|
|
122
|
+
if (computedRoot !== merkle_root) {
|
|
123
|
+
throw new Error(`Manifest root mismatch: computed ${computedRoot} != manifest ${merkle_root}`);
|
|
124
|
+
}
|
|
125
|
+
const statement = `This document proves that the leaf derived from (path_hash="${rec.path_hash}", file_hash="${rec.hash}") ` +
|
|
126
|
+
`was included in the snapshot of repository "${repo}" at commit "${commit}", ` +
|
|
127
|
+
`whose Merkle root is ${merkle_root}, according to the CodeQuill snapshot specification v1.`;
|
|
128
|
+
return {
|
|
129
|
+
version: "codequill-proof:v1",
|
|
130
|
+
statement,
|
|
131
|
+
repo,
|
|
132
|
+
commit,
|
|
133
|
+
merkle_root,
|
|
134
|
+
file: {
|
|
135
|
+
path_hash: rec.path_hash,
|
|
136
|
+
hash: rec.hash,
|
|
137
|
+
},
|
|
138
|
+
leaf: targetLeaf,
|
|
139
|
+
proof: proofSteps,
|
|
140
|
+
hashing: {
|
|
141
|
+
algo: "keccak256",
|
|
142
|
+
pathHash: "hmac-sha256(path_salt, utf8(path)) (path_salt encrypted in snapshot.path_map_enc)",
|
|
143
|
+
file: "keccak(fileBytes)",
|
|
144
|
+
leaf: "keccak(0x00||pathHash||fileHash)",
|
|
145
|
+
node: "keccak(0x01||left||right)",
|
|
146
|
+
leafSort: "lexicographic hex of leaf hash",
|
|
147
|
+
oddRule: "duplicate_last",
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=proofManifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proofManifest.js","sourceRoot":"","sources":["../../../src/services/manifests/proofManifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,OAAO,EAAE,QAAQ,EAAwB,UAAU,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA0DzF,SAAS,OAAO,CAAC,CAAM;IACnB,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAUlC;IACG,MAAM,IAAI,GAAsB,sBAAsB,CAAC,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;IAEnG,kEAAkE;IAClE,MAAM,kBAAkB,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACvE,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAEjE,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;IAClF,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,kBAAkB,KAAK,OAAO,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CACX,2CAA2C;YAC3C,kBAAkB,kBAAkB,IAAI;YACxC,kBAAkB,OAAO,IAAI,CAChC,CAAC;IACN,CAAC;IAED,+BAA+B;IAC/B,IAAI,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC;IAEvC,IAAI,QAAQ,GAA4C,SAAS,CAAC;IAClE,IAAI,IAAI,EAAE,WAAW,EAAE,CAAC;QACpB,QAAQ,GAAG;YACP,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC;YACrC,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;YACxD,YAAY,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS;YACvE,gBAAgB,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,SAAS;YAC/E,YAAY,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS;SAC1E,CAAC;QAEF,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;QACvG,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC;QACpG,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC;QAEpF,SAAS,IAAI,kCAAkC,KAAK,OAAO,KAAK,wBAAwB,MAAM,GAAG,CAAC;IACtG,CAAC;IAED,mFAAmF;IACnF,MAAM,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC;IAC/C,MAAM,OAAO,GAAiB;QAC1B,cAAc,EAAE,SAAS;QACzB,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,cAAc;QAC5D,GAAG,CAAC,SAAS;YACT,CAAC,CAAC,EAAE,cAAc,EAAE,aAAa,CAAC,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,cAAc,CAAC,EAAE;YAC9E,CAAC,CAAC,EAAE,CAAC;KACZ,CAAC;IAEF,IAAI,SAAS,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,OAAO,CAAC,cAAe,CAAC;QACrC,SAAS,IAAI,+BAA+B,IAAI,0DAA0D,CAAC;IAC/G,CAAC;IAED,wDAAwD;IACxD,OAAO;QACH,GAAG,IAAI;QACP,SAAS;QACT,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjC,OAAO;KACV,CAAC;AACN,CAAC;AAED,SAAS,sBAAsB,CAAC,QAA0B,EAAE,cAAsB;IAC9E,IAAI,CAAC,QAAQ,EAAE,KAAK,IAAI,CAAC,KAAK,CAAC,OAAO,CAAE,QAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACzD,CAAC;IACD,IAAI,CAAC,QAAQ,EAAE,WAAW,IAAI,OAAQ,QAAgB,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;QAC9E,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC7D,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,CAAE,QAAgB,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,MAAM,CAAE,QAAgB,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IACtD,MAAM,WAAW,GAAG,MAAM,CAAE,QAAgB,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC;IAExE,MAAM,UAAU,GAAG,MAAM,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9D,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACnE,CAAC;IAED,yBAAyB;IACzB,MAAM,OAAO,GAAgD,QAAgB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC;QACjG,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE;QAC5C,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE;KACrC,CAAC,CAAC,CAAC;IAEJ,yCAAyC;IACzC,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,UAAU,CAAC,CAAC;IAC5D,IAAI,CAAC,GAAG,EAAE,CAAC;QACP,MAAM,IAAI,KAAK,CACX,8CAA8C;YAC9C,uBAAuB,UAAU,IAAI;YACrC,4GAA4G,CAC/G,CAAC;IACN,CAAC;IAED,kCAAkC;IAClC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAClC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC;QACnC,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,IAAI,EAAE,CAAC,CAAC,IAAI;KACf,CAAC,CAAC,CAAC;IAEJ,oEAAoE;IACpE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAE3E,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IACrD,MAAM,GAAG,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;IAC9D,IAAI,GAAG,KAAK,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IAEvF,qBAAqB;IACrB,IAAI,KAAK,GAAG,GAAG,CAAC;IAChB,IAAI,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,UAAU,GAAsB,EAAE,CAAC;IAEzC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,WAAW,GAAG,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC;QACpC,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;QAEtD,MAAM,OAAO,GAAG,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,iBAAiB;QAC7F,UAAU,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;YACpC,IAAI,EAAE,OAAO;SAChB,CAAC,CAAC;QAEH,MAAM,IAAI,GAAa,EAAE,CAAC;QAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACvC,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACnB,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACzD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAChC,CAAC;QAED,KAAK,GAAG,IAAI,CAAC;QACb,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACpD,IAAI,YAAY,KAAK,WAAW,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,oCAAoC,YAAY,gBAAgB,WAAW,EAAE,CAAC,CAAC;IACnG,CAAC;IAED,MAAM,SAAS,GACX,+DAA+D,GAAG,CAAC,SAAS,iBAAiB,GAAG,CAAC,IAAI,KAAK;QAC1G,+CAA+C,IAAI,gBAAgB,MAAM,KAAK;QAC9E,wBAAwB,WAAW,yDAAyD,CAAC;IAEjG,OAAO;QACH,OAAO,EAAE,oBAAoB;QAC7B,SAAS;QACT,IAAI;QACJ,MAAM;QACN,WAAW;QACX,IAAI,EAAE;YACF,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,IAAI,EAAE,GAAG,CAAC,IAAI;SACjB;QACD,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,UAAU;QACjB,OAAO,EAAE;YACL,IAAI,EAAE,WAAW;YACjB,QAAQ,EAAE,mFAAmF;YAC7F,IAAI,EAAE,mBAAmB;YACzB,IAAI,EAAE,kCAAkC;YACxC,IAAI,EAAE,2BAA2B;YACjC,QAAQ,EAAE,gCAAgC;YAC1C,OAAO,EAAE,gBAAgB;SAC5B;KACJ,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import crypto, { createCipheriv, randomBytes } from "node:crypto";
|
|
2
|
+
import sodium from "libsodium-wrappers";
|
|
3
|
+
import { getCliVersion, randomSaltHex32 } from "../utilities.js";
|
|
4
|
+
import { merkleRootFromFiles, merkleContentRootFromFileHashes, normalizePath } from "../merkle.js";
|
|
5
|
+
import { keccakHex } from "../crypto.js";
|
|
6
|
+
import { resolvePath } from "../paths.js";
|
|
7
|
+
import { readFileUtf8 } from "../fs.js";
|
|
8
|
+
import { listFilesAtCommit, readFileAtCommit } from "../git.js";
|
|
9
|
+
export async function buildSnapshotManifest(params) {
|
|
10
|
+
const { repoRoot, repoName, commitHash } = params;
|
|
11
|
+
const concurrency = params.concurrency ?? 8;
|
|
12
|
+
if (!params.workspaceEncPublic?.public_key_b64) {
|
|
13
|
+
throw new Error("Missing workspaceEncPublic.public_key_b64 (required to encrypt path map).");
|
|
14
|
+
}
|
|
15
|
+
const pathSalt = params.pathSalt ?? randomSaltHex32();
|
|
16
|
+
const files = await listFilesAtCommit(repoRoot, commitHash);
|
|
17
|
+
if (!files.length)
|
|
18
|
+
throw new Error(`Commit ${commitHash} contains no files (nothing to snapshot).`);
|
|
19
|
+
// Ensure unique paths
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
for (const p of files) {
|
|
22
|
+
if (seen.has(p))
|
|
23
|
+
throw new Error(`Duplicate file path detected in tree: ${p}`);
|
|
24
|
+
seen.add(p);
|
|
25
|
+
}
|
|
26
|
+
const limiter = pLimit(Math.max(1, concurrency));
|
|
27
|
+
const records = new Array(files.length);
|
|
28
|
+
const pathMapEntries = new Array(files.length);
|
|
29
|
+
// We'll also gather file hashes for content_root computation
|
|
30
|
+
const contentHashes = new Array(files.length);
|
|
31
|
+
await Promise.all(files.map((p, idx) => limiter(async () => {
|
|
32
|
+
const normalized = normalizePath(p);
|
|
33
|
+
const bytes = await readFileAtCommit(repoRoot, commitHash, normalized);
|
|
34
|
+
const fileHash = keccakHex(bytes);
|
|
35
|
+
const path_hash = pathHashHmacSha256(normalized, pathSalt);
|
|
36
|
+
records[idx] = { path_hash, hash: fileHash };
|
|
37
|
+
pathMapEntries[idx] = [path_hash, normalized];
|
|
38
|
+
contentHashes[idx] = fileHash;
|
|
39
|
+
})));
|
|
40
|
+
// Deterministic manifest order
|
|
41
|
+
records.sort((a, b) => (a.path_hash < b.path_hash ? -1 : a.path_hash > b.path_hash ? 1 : 0));
|
|
42
|
+
// Deterministic path map order too (sort by path_hash)
|
|
43
|
+
pathMapEntries.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
|
|
44
|
+
// Private Merkle root (path_hash + fileHash leaves)
|
|
45
|
+
const root = merkleRootFromFiles(records);
|
|
46
|
+
// Public content-only root (fileHash-only leaves)
|
|
47
|
+
const content_root = merkleContentRootFromFileHashes(contentHashes);
|
|
48
|
+
// Encrypt path map (includes salt) for owner UX
|
|
49
|
+
const pathMapPlain = {
|
|
50
|
+
version: "codequill-pathmap:v1",
|
|
51
|
+
salt_hex: pathSalt,
|
|
52
|
+
entries: pathMapEntries, // [path_hash, normalized_path]
|
|
53
|
+
};
|
|
54
|
+
const path_map_enc = await encryptJsonAsEnvelopeV1(pathMapPlain, params.workspaceEncPublic.public_key_b64, {
|
|
55
|
+
workspace_id: params.workspaceEncPublic.workspace_id,
|
|
56
|
+
key_id: params.workspaceEncPublic.key_id,
|
|
57
|
+
});
|
|
58
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
59
|
+
const cliVersion = getCliVersion();
|
|
60
|
+
const manifest = {
|
|
61
|
+
version: "codequill-snapshot:v1",
|
|
62
|
+
statement: buildSnapshotStatement({
|
|
63
|
+
repo: repoName,
|
|
64
|
+
commit: commitHash,
|
|
65
|
+
merkleRoot: root,
|
|
66
|
+
contentRoot: content_root,
|
|
67
|
+
}),
|
|
68
|
+
repo: repoName,
|
|
69
|
+
commit: commitHash,
|
|
70
|
+
merkle_root: root,
|
|
71
|
+
content_root,
|
|
72
|
+
files: records,
|
|
73
|
+
hashing: {
|
|
74
|
+
algo: "keccak256",
|
|
75
|
+
pathHash: "hmac-sha256(path_salt, utf8(path))",
|
|
76
|
+
file: "keccak(fileBytes)",
|
|
77
|
+
leaf: "keccak(0x00||pathHash||fileHash)",
|
|
78
|
+
node: "keccak(0x01||left||right)",
|
|
79
|
+
leafSort: "lexicographic hex of leaf hash",
|
|
80
|
+
oddRule: "duplicate_last",
|
|
81
|
+
pathSalt: {
|
|
82
|
+
storage: "encrypted",
|
|
83
|
+
location: "path_map_enc",
|
|
84
|
+
payload: "codequill-pathmap:v1",
|
|
85
|
+
field: "salt_hex",
|
|
86
|
+
},
|
|
87
|
+
contentRoot: {
|
|
88
|
+
leaf: "keccak(0x00||fileHash)",
|
|
89
|
+
node: "keccak(0x01||left||right)",
|
|
90
|
+
leafSort: "lexicographic hex of leaf hash",
|
|
91
|
+
oddRule: "duplicate_last",
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
path_map_enc,
|
|
95
|
+
timestamp,
|
|
96
|
+
generator: {
|
|
97
|
+
name: "codequill-cli",
|
|
98
|
+
version: cliVersion,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
return { manifest, merkleRoot: root, contentRoot: content_root, fileCount: records.length };
|
|
102
|
+
}
|
|
103
|
+
export async function loadManifestFromFile(filePath) {
|
|
104
|
+
const p = resolvePath(filePath);
|
|
105
|
+
const raw = await readFileUtf8(p);
|
|
106
|
+
const obj = JSON.parse(raw);
|
|
107
|
+
if (!isValidManifest(obj))
|
|
108
|
+
throw new Error("Invalid manifest: missing required fields.");
|
|
109
|
+
return obj;
|
|
110
|
+
}
|
|
111
|
+
function isValidManifest(obj) {
|
|
112
|
+
return (!!obj &&
|
|
113
|
+
typeof obj === "object" &&
|
|
114
|
+
typeof obj.repo === "string" &&
|
|
115
|
+
typeof obj.commit === "string" &&
|
|
116
|
+
typeof obj.merkle_root === "string" &&
|
|
117
|
+
typeof obj.content_root === "string" &&
|
|
118
|
+
!!obj.path_map_enc &&
|
|
119
|
+
typeof obj.path_map_enc === "object" &&
|
|
120
|
+
typeof obj.path_map_enc.ciphertext_b64 === "string" &&
|
|
121
|
+
Array.isArray(obj.files));
|
|
122
|
+
}
|
|
123
|
+
function buildSnapshotStatement(args) {
|
|
124
|
+
return (`This document is a CodeQuill snapshot manifest for repository "${args.repo}" ` +
|
|
125
|
+
`at commit "${args.commit}". It lists private path hashes (HMAC-SHA256 with encrypted salt stored in path_map_enc) ` +
|
|
126
|
+
`and file content hashes used to compute the private Merkle root ${args.merkleRoot}. ` +
|
|
127
|
+
`It also includes a public content-only root ${args.contentRoot} for verification without any path data, ` +
|
|
128
|
+
`according to the CodeQuill snapshot specification v1.`);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* path_hash = HMAC-SHA256(key = path_salt_bytes, msg = utf8(normalized_path))
|
|
132
|
+
* - saltHex is 64 hex chars (32 bytes)
|
|
133
|
+
* - returns lowercase hex (64 chars)
|
|
134
|
+
*/
|
|
135
|
+
function pathHashHmacSha256(normalizedPath, saltHex) {
|
|
136
|
+
const key = Buffer.from(saltHex, "hex");
|
|
137
|
+
if (key.length !== 32)
|
|
138
|
+
throw new Error("path_salt must be 32 bytes (64 hex chars).");
|
|
139
|
+
return crypto
|
|
140
|
+
.createHmac("sha256", key)
|
|
141
|
+
.update(normalizedPath, "utf8")
|
|
142
|
+
.digest("hex");
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Encrypt JSON payload as codequill-envelope:v1:
|
|
146
|
+
* - AES-256-GCM for payload
|
|
147
|
+
* - X25519 sealed box wrap for DEK
|
|
148
|
+
* Stores ciphertext in-base64 inside manifest (since it's small).
|
|
149
|
+
*/
|
|
150
|
+
async function encryptJsonAsEnvelopeV1(payload, workspacePublicKeyB64, recipient) {
|
|
151
|
+
const plaintext = Buffer.from(JSON.stringify(payload), "utf8");
|
|
152
|
+
const dek = randomBytes(32);
|
|
153
|
+
const iv = randomBytes(12);
|
|
154
|
+
const { ciphertext, tag } = aes256gcmEncrypt(plaintext, dek, iv);
|
|
155
|
+
const wrappedDek = await wrapDekForWorkspace(workspacePublicKeyB64, dek);
|
|
156
|
+
return {
|
|
157
|
+
scheme: "codequill-envelope:v1",
|
|
158
|
+
cipher: "aes-256-gcm",
|
|
159
|
+
iv_b64: iv.toString("base64"),
|
|
160
|
+
tag_b64: tag.toString("base64"),
|
|
161
|
+
dek_wrap: "x25519-sealedbox",
|
|
162
|
+
wrapped_dek_b64: wrappedDek.ciphertext_b64,
|
|
163
|
+
ciphertext_b64: ciphertext.toString("base64"),
|
|
164
|
+
recipient: {
|
|
165
|
+
type: "workspace",
|
|
166
|
+
workspace_id: recipient.workspace_id,
|
|
167
|
+
key_id: recipient.key_id,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function aes256gcmEncrypt(plaintext, key32, iv12) {
|
|
172
|
+
if (key32.length !== 32)
|
|
173
|
+
throw new Error("AES-256-GCM key must be 32 bytes.");
|
|
174
|
+
if (iv12.length !== 12)
|
|
175
|
+
throw new Error("AES-256-GCM iv must be 12 bytes.");
|
|
176
|
+
const cipher = createCipheriv("aes-256-gcm", key32, iv12);
|
|
177
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
178
|
+
const tag = cipher.getAuthTag();
|
|
179
|
+
return { ciphertext, tag };
|
|
180
|
+
}
|
|
181
|
+
async function wrapDekForWorkspace(publicKeyB64, dek) {
|
|
182
|
+
await sodium.ready;
|
|
183
|
+
const pub = Buffer.from(publicKeyB64, "base64");
|
|
184
|
+
if (pub.length !== 32)
|
|
185
|
+
throw new Error("Workspace public key must be 32 bytes (X25519).");
|
|
186
|
+
const wrapped = sodium.crypto_box_seal(new Uint8Array(dek), new Uint8Array(pub));
|
|
187
|
+
return {
|
|
188
|
+
version: "x25519-sealedbox",
|
|
189
|
+
ciphertext_b64: Buffer.from(wrapped).toString("base64"),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function pLimit(concurrency) {
|
|
193
|
+
let active = 0;
|
|
194
|
+
const queue = [];
|
|
195
|
+
const next = () => {
|
|
196
|
+
active--;
|
|
197
|
+
const fn = queue.shift();
|
|
198
|
+
if (fn)
|
|
199
|
+
fn();
|
|
200
|
+
};
|
|
201
|
+
return function (fn) {
|
|
202
|
+
return new Promise((resolve, reject) => {
|
|
203
|
+
const run = () => {
|
|
204
|
+
active++;
|
|
205
|
+
fn().then((x) => { next(); resolve(x); }, (e) => { next(); reject(e); });
|
|
206
|
+
};
|
|
207
|
+
if (active < concurrency)
|
|
208
|
+
run();
|
|
209
|
+
else
|
|
210
|
+
queue.push(run);
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=snapshotManifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshotManifest.js","sourceRoot":"","sources":["../../../src/services/manifests/snapshotManifest.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,MAAM,MAAM,oBAAoB,CAAC;AAExC,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,+BAA+B,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACnG,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAkFhE,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,MAO3C;IACG,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;IAClD,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,CAAC,CAAC;IAE5C,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,cAAc,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,2EAA2E,CAAC,CAAC;IACjG,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,eAAe,EAAE,CAAC;IAEtD,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAC5D,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,UAAU,UAAU,2CAA2C,CAAC,CAAC;IAEpG,sBAAsB;IACtB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACpB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,EAAE,CAAC,CAAC;QAC/E,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAChB,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC;IACjD,MAAM,OAAO,GAA+C,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACpF,MAAM,cAAc,GAA4B,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAExE,6DAA6D;IAC7D,MAAM,aAAa,GAAa,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAExD,MAAM,OAAO,CAAC,GAAG,CACb,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,CACjB,OAAO,CAAC,KAAK,IAAI,EAAE;QACf,MAAM,UAAU,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;QACpC,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,QAAQ,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;QAEvE,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAClC,MAAM,SAAS,GAAG,kBAAkB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAE3D,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;QAC7C,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;QAC9C,aAAa,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC;IAClC,CAAC,CAAC,CACL,CACJ,CAAC;IAEF,+BAA+B;IAC/B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAE7F,uDAAuD;IACvD,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAExE,oDAAoD;IACpD,MAAM,IAAI,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAE1C,kDAAkD;IAClD,MAAM,YAAY,GAAG,+BAA+B,CAAC,aAAa,CAAC,CAAC;IAEpE,gDAAgD;IAChD,MAAM,YAAY,GAAG;QACjB,OAAO,EAAE,sBAAsB;QAC/B,QAAQ,EAAE,QAAQ;QAClB,OAAO,EAAE,cAAc,EAAE,+BAA+B;KAC3D,CAAC;IAEF,MAAM,YAAY,GAAG,MAAM,uBAAuB,CAC9C,YAAY,EACZ,MAAM,CAAC,kBAAkB,CAAC,cAAc,EACxC;QACI,YAAY,EAAE,MAAM,CAAC,kBAAkB,CAAC,YAAY;QACpD,MAAM,EAAE,MAAM,CAAC,kBAAkB,CAAC,MAAM;KAC3C,CACJ,CAAC;IAEF,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAChD,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IAEnC,MAAM,QAAQ,GAAqB;QAC/B,OAAO,EAAE,uBAAuB;QAChC,SAAS,EAAE,sBAAsB,CAAC;YAC9B,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,UAAU;YAClB,UAAU,EAAE,IAAI;YAChB,WAAW,EAAE,YAAY;SAC5B,CAAC;QACF,IAAI,EAAE,QAAQ;QACd,MAAM,EAAE,UAAU;QAElB,WAAW,EAAE,IAAI;QACjB,YAAY;QAEZ,KAAK,EAAE,OAAO;QACd,OAAO,EAAE;YACL,IAAI,EAAE,WAAW;YACjB,QAAQ,EAAE,oCAAoC;YAC9C,IAAI,EAAE,mBAAmB;YACzB,IAAI,EAAE,kCAAkC;YACxC,IAAI,EAAE,2BAA2B;YACjC,QAAQ,EAAE,gCAAgC;YAC1C,OAAO,EAAE,gBAAgB;YAEzB,QAAQ,EAAE;gBACN,OAAO,EAAE,WAAW;gBACpB,QAAQ,EAAE,cAAc;gBACxB,OAAO,EAAE,sBAAsB;gBAC/B,KAAK,EAAE,UAAU;aACpB;YAED,WAAW,EAAE;gBACT,IAAI,EAAE,wBAAwB;gBAC9B,IAAI,EAAE,2BAA2B;gBACjC,QAAQ,EAAE,gCAAgC;gBAC1C,OAAO,EAAE,gBAAgB;aAC5B;SACJ;QACD,YAAY;QACZ,SAAS;QACT,SAAS,EAAE;YACP,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,UAAU;SACtB;KACJ,CAAC;IAEF,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;AAChG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,QAAgB;IACvD,MAAM,CAAC,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IAChC,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,CAAC,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC5B,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IACzF,OAAO,GAAuB,CAAC;AACnC,CAAC;AAED,SAAS,eAAe,CAAC,GAAQ;IAC7B,OAAO,CACH,CAAC,CAAC,GAAG;QACL,OAAO,GAAG,KAAK,QAAQ;QACvB,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ;QAC5B,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;QAC9B,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ;QACnC,OAAO,GAAG,CAAC,YAAY,KAAK,QAAQ;QACpC,CAAC,CAAC,GAAG,CAAC,YAAY;QAClB,OAAO,GAAG,CAAC,YAAY,KAAK,QAAQ;QACpC,OAAO,GAAG,CAAC,YAAY,CAAC,cAAc,KAAK,QAAQ;QACnD,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAC3B,CAAC;AACN,CAAC;AAED,SAAS,sBAAsB,CAAC,IAA+E;IAC3G,OAAO,CACH,kEAAkE,IAAI,CAAC,IAAI,IAAI;QAC/E,cAAc,IAAI,CAAC,MAAM,2FAA2F;QACpH,mEAAmE,IAAI,CAAC,UAAU,IAAI;QACtF,+CAA+C,IAAI,CAAC,WAAW,2CAA2C;QAC1G,uDAAuD,CAC1D,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,cAAsB,EAAE,OAAe;IAC/D,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACxC,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAErF,OAAO,MAAM;SACR,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC;SACzB,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC;SAC9B,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,uBAAuB,CAClC,OAAY,EACZ,qBAA6B,EAC7B,SAAqD;IAErD,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;IAE/D,MAAM,GAAG,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC5B,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAE3B,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,gBAAgB,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IACjE,MAAM,UAAU,GAAG,MAAM,mBAAmB,CAAC,qBAAqB,EAAE,GAAG,CAAC,CAAC;IAEzE,OAAO;QACH,MAAM,EAAE,uBAAuB;QAC/B,MAAM,EAAE,aAAa;QACrB,MAAM,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC7B,OAAO,EAAE,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAE/B,QAAQ,EAAE,kBAAkB;QAC5B,eAAe,EAAE,UAAU,CAAC,cAAc;QAE1C,cAAc,EAAE,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAE7C,SAAS,EAAE;YACP,IAAI,EAAE,WAAW;YACjB,YAAY,EAAE,SAAS,CAAC,YAAY;YACpC,MAAM,EAAE,SAAS,CAAC,MAAM;SAC3B;KACJ,CAAC;AACN,CAAC;AAED,SAAS,gBAAgB,CACrB,SAAiB,EACjB,KAAa,EACb,IAAY;IAEZ,IAAI,KAAK,CAAC,MAAM,KAAK,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IAC9E,IAAI,IAAI,CAAC,MAAM,KAAK,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IAE5E,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1D,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC7E,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAChC,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC;AAC/B,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,YAAoB,EAAE,GAAW;IAChE,MAAM,MAAM,CAAC,KAAK,CAAC;IAEnB,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IAChD,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IAE1F,MAAM,OAAO,GAAG,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IAEjF,OAAO;QACH,OAAO,EAAE,kBAA2B;QACpC,cAAc,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;KAC1D,CAAC;AACN,CAAC;AAED,SAAS,MAAM,CAAC,WAAmB;IAC/B,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,MAAM,KAAK,GAAmB,EAAE,CAAC;IACjC,MAAM,IAAI,GAAG,GAAG,EAAE;QACd,MAAM,EAAE,CAAC;QACT,MAAM,EAAE,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;QACzB,IAAI,EAAE;YAAE,EAAE,EAAE,CAAC;IACjB,CAAC,CAAC;IACF,OAAO,UAAa,EAAoB;QACpC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACnC,MAAM,GAAG,GAAG,GAAG,EAAE;gBACb,MAAM,EAAE,CAAC;gBACT,EAAE,EAAE,CAAC,IAAI,CACL,CAAC,CAAC,EAAE,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAC9B,CAAC,CAAC,EAAE,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAChC,CAAC;YACN,CAAC,CAAC;YACF,IAAI,MAAM,GAAG,WAAW;gBAAE,GAAG,EAAE,CAAC;;gBAC3B,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzB,CAAC,CAAC,CAAC;IACP,CAAC,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { keccakHex, hexToBytes, concatBytes } from './crypto.js';
|
|
2
|
+
export function normalizePath(p) {
|
|
3
|
+
let x = (p ?? '').trim();
|
|
4
|
+
x = x.replace(/\\/g, '/');
|
|
5
|
+
x = x.replace(/^\.\/+/, '');
|
|
6
|
+
x = x.replace(/\/{2,}/g, '/');
|
|
7
|
+
if (x.length > 1)
|
|
8
|
+
x = x.replace(/\/+$/, '');
|
|
9
|
+
if (!x)
|
|
10
|
+
throw new Error('Empty path not allowed');
|
|
11
|
+
return x;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* LEAF = keccak256( 0x00 || path_hash || file_hash )
|
|
15
|
+
*/
|
|
16
|
+
export function leafHash(pathHash, fileHash) {
|
|
17
|
+
const prefix = new Uint8Array([0x00]);
|
|
18
|
+
const leafBytes = concatBytes(concatBytes(prefix, hexToBytes(pathHash)), hexToBytes(fileHash));
|
|
19
|
+
return keccakHex(leafBytes);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* NODE = keccak256( 0x01 || left || right )
|
|
23
|
+
*/
|
|
24
|
+
export function parentHash(leftHash, rightHash) {
|
|
25
|
+
const prefix = new Uint8Array([0x01]);
|
|
26
|
+
const nodeBytes = concatBytes(concatBytes(prefix, hexToBytes(leftHash)), hexToBytes(rightHash));
|
|
27
|
+
return keccakHex(nodeBytes);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* LEAF = keccak256( 0x00 || pathHash32 || fileHash32 )
|
|
31
|
+
* NODE = keccak256( 0x01 || left32 || right32 )
|
|
32
|
+
*
|
|
33
|
+
* This is your PRIVATE root (path-hash salted).
|
|
34
|
+
*/
|
|
35
|
+
export function merkleRootFromFiles(files) {
|
|
36
|
+
if (!files.length)
|
|
37
|
+
throw new Error('Cannot build Merkle root: no files.');
|
|
38
|
+
const leaves = files.map((f) => {
|
|
39
|
+
const pathHash = f.path_hash;
|
|
40
|
+
const fileHash = f.hash;
|
|
41
|
+
const leafBytes = concatBytes(concatBytes(new Uint8Array([0x00]), hexToBytes(pathHash)), hexToBytes(fileHash));
|
|
42
|
+
return keccakHex(leafBytes);
|
|
43
|
+
});
|
|
44
|
+
leaves.sort();
|
|
45
|
+
let level = leaves.slice();
|
|
46
|
+
while (level.length > 1) {
|
|
47
|
+
const next = [];
|
|
48
|
+
for (let i = 0; i < level.length; i += 2) {
|
|
49
|
+
const left = level[i];
|
|
50
|
+
const right = i + 1 < level.length ? level[i + 1] : level[i];
|
|
51
|
+
const parentBytes = concatBytes(concatBytes(new Uint8Array([0x01]), hexToBytes(left)), hexToBytes(right));
|
|
52
|
+
next.push(keccakHex(parentBytes));
|
|
53
|
+
}
|
|
54
|
+
level = next;
|
|
55
|
+
}
|
|
56
|
+
return level[0];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* CONTENT-ONLY ROOT (PUBLIC VERIFICATION)
|
|
60
|
+
*
|
|
61
|
+
* Definitions:
|
|
62
|
+
* - file_hash = keccak(fileBytes)
|
|
63
|
+
* - leaf = keccak(0x00 || file_hash)
|
|
64
|
+
* - leaves sorted lexicographically (hex)
|
|
65
|
+
* - node = keccak(0x01 || left || right)
|
|
66
|
+
* - oddRule = duplicate_last
|
|
67
|
+
*
|
|
68
|
+
* This leaks NOTHING about paths and requires NO salt.
|
|
69
|
+
*/
|
|
70
|
+
export function merkleContentRootFromFileHashes(fileHashes) {
|
|
71
|
+
if (!fileHashes.length)
|
|
72
|
+
throw new Error('Cannot build content root: no files.');
|
|
73
|
+
// leaf = keccak(0x00 || fileHash)
|
|
74
|
+
const leaves = fileHashes.map((fileHash) => {
|
|
75
|
+
const leafBytes = concatBytes(new Uint8Array([0x00]), hexToBytes(fileHash));
|
|
76
|
+
return keccakHex(leafBytes);
|
|
77
|
+
});
|
|
78
|
+
leaves.sort();
|
|
79
|
+
let level = leaves.slice();
|
|
80
|
+
while (level.length > 1) {
|
|
81
|
+
const next = [];
|
|
82
|
+
for (let i = 0; i < level.length; i += 2) {
|
|
83
|
+
const left = level[i];
|
|
84
|
+
const right = i + 1 < level.length ? level[i + 1] : level[i];
|
|
85
|
+
const parentBytes = concatBytes(concatBytes(new Uint8Array([0x01]), hexToBytes(left)), hexToBytes(right));
|
|
86
|
+
next.push(keccakHex(parentBytes));
|
|
87
|
+
}
|
|
88
|
+
level = next;
|
|
89
|
+
}
|
|
90
|
+
return level[0];
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=merkle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merkle.js","sourceRoot":"","sources":["../../src/services/merkle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
export function toAbsolute(p) {
|
|
4
|
+
return path.isAbsolute(p) ? p : path.resolve(process.cwd(), p);
|
|
5
|
+
}
|
|
6
|
+
export function ensurePathExists(p) {
|
|
7
|
+
if (!fs.existsSync(p)) {
|
|
8
|
+
throw new Error(`Path does not exist: ${p}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function resolvePath(inputPath) {
|
|
12
|
+
const p = inputPath ? toAbsolute(inputPath) : process.cwd();
|
|
13
|
+
ensurePathExists(p);
|
|
14
|
+
return p;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/services/paths.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,CAAS;IACxC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,EAAE,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,SAAkB;IAC5C,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IAC5D,gBAAgB,CAAC,CAAC,CAAC,CAAC;IACpB,OAAO,CAAC,CAAC;AACX,CAAC"}
|