pactium 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +92 -0
  3. package/README.zh-CN.md +90 -0
  4. package/SECURITY.md +7 -0
  5. package/bin/pactium.mjs +121 -0
  6. package/docs/LICOLITE-ASPECT.md +57 -0
  7. package/docs/README.md +13 -0
  8. package/docs/TERM.md +289 -0
  9. package/docs/architecture/ARCHITECTURE.md +62 -0
  10. package/docs/protocols/PROFILE.md +124 -0
  11. package/docs/protocols/PROTOCOLS.md +62 -0
  12. package/examples/record-operation.mjs +26 -0
  13. package/package.json +69 -0
  14. package/src/README.md +13 -0
  15. package/src/aspects/licolite/aspect.js +278 -0
  16. package/src/aspects/licolite/constants.js +13 -0
  17. package/src/aspects/licolite/evidence.js +47 -0
  18. package/src/aspects/licolite/index.d.ts +51 -0
  19. package/src/aspects/licolite/index.js +19 -0
  20. package/src/aspects/licolite/signing.js +78 -0
  21. package/src/canonical/value.js +40 -0
  22. package/src/core/append-condition.js +102 -0
  23. package/src/core/pactium-core.js +1073 -0
  24. package/src/core/tracking-cursor.js +68 -0
  25. package/src/http.js +99 -0
  26. package/src/index-engine/snapshot-merkle-index.js +994 -0
  27. package/src/index.d.ts +244 -0
  28. package/src/index.js +73 -0
  29. package/src/ledger/signed-head.js +204 -0
  30. package/src/ledger/transparency-log.js +702 -0
  31. package/src/maintenance/task-engine.js +36 -0
  32. package/src/proof/bundle-format.js +265 -0
  33. package/src/proof/bundle.js +77 -0
  34. package/src/proof/envelope.js +548 -0
  35. package/src/proof/registry.js +18 -0
  36. package/src/protocol/constants.js +69 -0
  37. package/src/protocol/hashing.js +47 -0
  38. package/src/quality/profile-runner.js +291 -0
  39. package/src/repair/planner.js +62 -0
  40. package/src/shared/records.js +32 -0
  41. package/src/storage/local-json-storage-port.js +360 -0
  42. package/src/verification/failure.js +31 -0
@@ -0,0 +1,36 @@
1
+ import { PACTIUM_PROTOCOL } from "../protocol/constants.js";
2
+ import { normalizeCanonicalValue } from "../canonical/value.js";
3
+ import { createId } from "../protocol/hashing.js";
4
+
5
+ export function createMaintenanceTaskEngine({ pactium = null } = {}) {
6
+ function planTask(taskType, input = {}) {
7
+ return {
8
+ protocol: PACTIUM_PROTOCOL,
9
+ taskId: createId("maintenance_task", { taskType, input }),
10
+ taskType,
11
+ input: normalizeCanonicalValue(input),
12
+ scheduler: "host-owned",
13
+ deterministic: true
14
+ };
15
+ }
16
+ async function runTask(task) {
17
+ if (task.taskType === "doctor" && pactium) {
18
+ return {
19
+ protocol: PACTIUM_PROTOCOL,
20
+ taskId: task.taskId,
21
+ ok: true,
22
+ result: await pactium.doctor()
23
+ };
24
+ }
25
+ return {
26
+ protocol: PACTIUM_PROTOCOL,
27
+ taskId: task.taskId,
28
+ ok: true,
29
+ result: {
30
+ plannedOnly: task.taskType !== "doctor",
31
+ daemon: false
32
+ }
33
+ };
34
+ }
35
+ return Object.freeze({ protocol: PACTIUM_PROTOCOL, planTask, runTask });
36
+ }
@@ -0,0 +1,265 @@
1
+ import { canonicalDecode } from "../canonical/value.js";
2
+ import { PACTIUM_PROOF_BUNDLE_TYPE } from "../protocol/constants.js";
3
+ import { cidForBytes, hashBytes } from "../protocol/hashing.js";
4
+ import { asArray, asRecord } from "../shared/records.js";
5
+ import { createVerificationFailure } from "../verification/failure.js";
6
+
7
+ export function decodeVarint(bytes, offset = 0) {
8
+ let value = 0;
9
+ let shift = 0;
10
+ let cursor = offset;
11
+ while (cursor < bytes.length) {
12
+ const byte = bytes[cursor];
13
+ value += (byte & 0x7f) * (2 ** shift);
14
+ cursor += 1;
15
+ if ((byte & 0x80) === 0) return { value, nextOffset: cursor };
16
+ shift += 7;
17
+ if (shift > 56) throw new Error("Bundle varint is too large.");
18
+ }
19
+ throw new Error("Bundle varint is truncated.");
20
+ }
21
+
22
+ function emptyResolver(indexFailures = []) {
23
+ const readFailures = [];
24
+ return {
25
+ blockCids: new Set(),
26
+ indexFailures,
27
+ readFailures,
28
+ get failures() {
29
+ return [...indexFailures, ...readFailures];
30
+ },
31
+ has(cid) {
32
+ return false;
33
+ },
34
+ get(cid) {
35
+ return null;
36
+ },
37
+ verifyAll() {
38
+ return { blocks: [], failures: this.failures };
39
+ }
40
+ };
41
+ }
42
+
43
+ function bundleFailure(code, evidenceRef, message = "", repairable = false) {
44
+ return createVerificationFailure({
45
+ layer: "proof-bundle",
46
+ code,
47
+ message,
48
+ evidenceRef,
49
+ repairable
50
+ });
51
+ }
52
+
53
+ function decodeIndexedMetadata({ bytes, item, maxHeaderSize, maxBlockSize }) {
54
+ const failures = [];
55
+ const offset = Number(item.offset || 0);
56
+ if (offset < 0 || offset >= bytes.length) {
57
+ return {
58
+ metadata: null,
59
+ failures: [bundleFailure("bad_bundle_offset", String(offset), "", true)]
60
+ };
61
+ }
62
+ let decoded;
63
+ try {
64
+ decoded = decodeVarint(bytes, offset);
65
+ } catch (error) {
66
+ return {
67
+ metadata: null,
68
+ failures: [bundleFailure(
69
+ "bad_bundle_varint",
70
+ String(offset),
71
+ error instanceof Error ? error.message : "Bundle record varint could not be decoded."
72
+ )]
73
+ };
74
+ }
75
+ const recordLength = Number(item.recordLength || 0);
76
+ const headerLength = Number(item.headerLength || 0);
77
+ const payloadLength = Number(item.byteLength || 0);
78
+ if (decoded.value !== recordLength || recordLength !== headerLength + payloadLength) {
79
+ return {
80
+ metadata: null,
81
+ failures: [bundleFailure("bad_bundle_record_length", item.cid)]
82
+ };
83
+ }
84
+ if (headerLength > maxHeaderSize) failures.push(bundleFailure("oversized_bundle_header", item.cid));
85
+ if (payloadLength > maxBlockSize) failures.push(bundleFailure("oversized_bundle_block", item.cid));
86
+ const headerStart = decoded.nextOffset;
87
+ const payloadStart = headerStart + headerLength;
88
+ const payloadEnd = payloadStart + payloadLength;
89
+ if (payloadEnd > bytes.length) {
90
+ return {
91
+ metadata: null,
92
+ failures: [bundleFailure("bad_bundle_offset", String(offset), "", true)]
93
+ };
94
+ }
95
+ const headerBytes = bytes.subarray(headerStart, payloadStart);
96
+ let header;
97
+ try {
98
+ header = asRecord(canonicalDecode(headerBytes));
99
+ } catch (error) {
100
+ return {
101
+ metadata: null,
102
+ failures: [bundleFailure(
103
+ "bad_bundle_header",
104
+ item.cid,
105
+ error instanceof Error ? error.message : "Bundle record header could not be decoded."
106
+ )]
107
+ };
108
+ }
109
+ if (header.cid !== item.cid ||
110
+ header.payloadHash !== item.payloadHash ||
111
+ Number(header.byteLength || 0) !== payloadLength) {
112
+ return {
113
+ metadata: null,
114
+ failures: [bundleFailure("bad_bundle_index", item.cid)]
115
+ };
116
+ }
117
+ if (failures.length > 0) return { metadata: null, failures };
118
+ return {
119
+ failures,
120
+ metadata: {
121
+ header,
122
+ payloadStart,
123
+ payloadEnd,
124
+ payloadLength
125
+ }
126
+ };
127
+ }
128
+
129
+ function decodeIndexedBlock({ bytes, item, metadata, maxHeaderSize, maxBlockSize }) {
130
+ const decoded = metadata || decodeIndexedMetadata({ bytes, item, maxHeaderSize, maxBlockSize });
131
+ if (!decoded.metadata || decoded.failures.length > 0) {
132
+ return { block: null, failures: decoded.failures };
133
+ }
134
+ const { header, payloadStart, payloadEnd, payloadLength } = decoded.metadata;
135
+ const payloadBytes = bytes.subarray(payloadStart, payloadEnd);
136
+ const payloadHash = `sha256:${hashBytes(payloadBytes)}`;
137
+ if (payloadHash !== item.payloadHash || cidForBytes(payloadBytes) !== item.cid) {
138
+ return {
139
+ block: null,
140
+ failures: [bundleFailure("bad_bundle_index", item.cid)]
141
+ };
142
+ }
143
+ return {
144
+ failures: [],
145
+ block: {
146
+ protocol: header.protocol,
147
+ cid: header.cid,
148
+ codec: header.codec,
149
+ kind: header.kind,
150
+ refs: asArray(header.refs),
151
+ byteLength: payloadLength,
152
+ payloadHash,
153
+ payloadBase64: payloadBytes.toString("base64")
154
+ }
155
+ };
156
+ }
157
+
158
+ export function createIndexedBundleResolver(bundle, {
159
+ maxHeaderSize = 16 * 1024,
160
+ maxBlockSize = 64 * 1024 * 1024
161
+ } = {}) {
162
+ if (bundle?.bundleType !== PACTIUM_PROOF_BUNDLE_TYPE) {
163
+ return emptyResolver([
164
+ bundleFailure(
165
+ "malformed_bundle",
166
+ String(bundle?.bundleType || ""),
167
+ "Proof Bundle must use the indexed bundle type."
168
+ )
169
+ ]);
170
+ }
171
+ if (!bundle.binaryBase64) {
172
+ return emptyResolver([
173
+ bundleFailure(
174
+ "missing_bundle_binary",
175
+ PACTIUM_PROOF_BUNDLE_TYPE,
176
+ "Proof Bundle is missing indexed binary records.",
177
+ true
178
+ )
179
+ ]);
180
+ }
181
+ const bytes = Buffer.from(String(bundle.binaryBase64 || ""), "base64");
182
+ const offsets = new Set();
183
+ const cids = new Set();
184
+ const entries = asArray(bundle.index).map((item, ordinal) => ({ item, ordinal }));
185
+ const indexByCid = new Map();
186
+ const cache = new Map();
187
+ const metadataCache = new Map();
188
+ const payloadCache = new Map();
189
+ const indexFailures = [];
190
+ const readFailures = [];
191
+ function metadataFor(entry) {
192
+ if (metadataCache.has(entry.ordinal)) return metadataCache.get(entry.ordinal);
193
+ const decoded = decodeIndexedMetadata({
194
+ bytes,
195
+ item: entry.item,
196
+ maxHeaderSize,
197
+ maxBlockSize
198
+ });
199
+ metadataCache.set(entry.ordinal, decoded);
200
+ return decoded;
201
+ }
202
+ function decodePayload(entry) {
203
+ if (payloadCache.has(entry.ordinal)) return payloadCache.get(entry.ordinal);
204
+ const decoded = decodeIndexedBlock({
205
+ bytes,
206
+ item: entry.item,
207
+ metadata: metadataFor(entry),
208
+ maxHeaderSize,
209
+ maxBlockSize
210
+ });
211
+ if (decoded.failures.length > 0 && metadataFor(entry).failures.length === 0) {
212
+ readFailures.push(...decoded.failures);
213
+ }
214
+ payloadCache.set(entry.ordinal, decoded.block);
215
+ return decoded.block;
216
+ }
217
+ for (const entry of entries) {
218
+ const { item } = entry;
219
+ const offset = Number(item.offset || 0);
220
+ if (offsets.has(offset)) {
221
+ indexFailures.push(bundleFailure("duplicate_bundle_offset", String(offset)));
222
+ }
223
+ offsets.add(offset);
224
+ if (cids.has(item.cid)) {
225
+ indexFailures.push(bundleFailure("duplicate_bundle_cid", item.cid));
226
+ } else {
227
+ indexByCid.set(item.cid, entry);
228
+ }
229
+ cids.add(item.cid);
230
+ if (offset < 0 || offset >= bytes.length) {
231
+ indexFailures.push(bundleFailure("bad_bundle_offset", String(offset), "", true));
232
+ }
233
+ indexFailures.push(...metadataFor(entry).failures);
234
+ }
235
+ return {
236
+ blockCids: new Set(indexByCid.keys()),
237
+ indexFailures,
238
+ readFailures,
239
+ get failures() {
240
+ return [...indexFailures, ...readFailures];
241
+ },
242
+ has(cid) {
243
+ return indexByCid.has(cid);
244
+ },
245
+ get(cid) {
246
+ if (!indexByCid.has(cid)) return null;
247
+ if (cache.has(cid)) return cache.get(cid);
248
+ const block = decodePayload(indexByCid.get(cid));
249
+ cache.set(cid, block);
250
+ return block;
251
+ },
252
+ verifyAll() {
253
+ const blocks = [];
254
+ for (const entry of entries) {
255
+ const block = decodePayload(entry);
256
+ if (block) blocks.push(block);
257
+ }
258
+ return { blocks, failures: this.failures };
259
+ }
260
+ };
261
+ }
262
+
263
+ export function indexedBlocksFromBundle(bundle, options = {}) {
264
+ return createIndexedBundleResolver(bundle, options).verifyAll();
265
+ }
@@ -0,0 +1,77 @@
1
+ import { PACTIUM_PROOF_BUNDLE_TYPE, PACTIUM_PROTOCOL } from "../protocol/constants.js";
2
+ import { asArray } from "../shared/records.js";
3
+ import { protocolHash } from "../protocol/hashing.js";
4
+ import { createVerificationFailure } from "../verification/failure.js";
5
+ import { createIndexedBundleResolver } from "./bundle-format.js";
6
+ import { verifyProofEnvelope } from "./envelope.js";
7
+
8
+ export async function verifyProofBundle(bundle, options = {}) {
9
+ if (!bundle || bundle.protocol !== PACTIUM_PROTOCOL || bundle.bundleType !== PACTIUM_PROOF_BUNDLE_TYPE) {
10
+ return {
11
+ protocol: PACTIUM_PROTOCOL,
12
+ ok: false,
13
+ failures: [createVerificationFailure({
14
+ layer: "proof-bundle",
15
+ code: "malformed_bundle",
16
+ message: "Proof Bundle is missing or has the wrong protocol."
17
+ })]
18
+ };
19
+ }
20
+ const failures = [];
21
+ const maxHeaderSize = Number(options.maxHeaderSize || 16 * 1024);
22
+ const maxBlockSize = Number(options.maxBlockSize || 64 * 1024 * 1024);
23
+ const resolver = createIndexedBundleResolver(bundle, { maxHeaderSize, maxBlockSize });
24
+ failures.push(...resolver.indexFailures);
25
+ const blockCids = resolver.blockCids;
26
+ const expectedBundleHash = protocolHash("proof.bundle", {
27
+ manifest: bundle.manifest,
28
+ envelope: bundle.envelope,
29
+ index: asArray(bundle.index).map((item) => ({
30
+ cid: item.cid,
31
+ offset: item.offset,
32
+ recordLength: item.recordLength,
33
+ headerLength: item.headerLength,
34
+ byteLength: item.byteLength,
35
+ payloadHash: item.payloadHash
36
+ }))
37
+ });
38
+ if (bundle.bundleHash && bundle.bundleHash !== expectedBundleHash) {
39
+ failures.push(createVerificationFailure({
40
+ layer: "proof-bundle",
41
+ code: "bad_bundle_hash",
42
+ message: "Proof Bundle hash does not match its indexed contents.",
43
+ evidenceRef: bundle.bundleHash
44
+ }));
45
+ }
46
+ for (const required of asArray(bundle.manifest?.requiredBlocks)) {
47
+ if (!blockCids.has(required)) {
48
+ failures.push(createVerificationFailure({
49
+ layer: "proof-bundle",
50
+ code: "missing_bundle_block",
51
+ message: "Proof Bundle is missing a required block.",
52
+ evidenceRef: required,
53
+ repairable: true
54
+ }));
55
+ } else {
56
+ resolver.get(required);
57
+ }
58
+ }
59
+ if (options.verifyAllBlocks === true) resolver.verifyAll();
60
+ const envelopeResult = await verifyProofEnvelope(bundle.envelope, {
61
+ bundle,
62
+ bundleResolver: resolver,
63
+ includeBundleResolverFailures: false,
64
+ supportedCriticalExtensions: options.supportedCriticalExtensions || [],
65
+ proofVerifiers: options.proofVerifiers || {},
66
+ requireAllProofs: options.requireAllProofs !== false,
67
+ verifierManifest: options.verifierManifest || null,
68
+ ledgerHeadSignatures: options.ledgerHeadSignatures || []
69
+ });
70
+ return {
71
+ protocol: PACTIUM_PROTOCOL,
72
+ bundleHash: bundle.bundleHash,
73
+ ok: failures.length === 0 && envelopeResult.ok,
74
+ failures: [...failures, ...resolver.readFailures, ...envelopeResult.failures],
75
+ envelope: envelopeResult
76
+ };
77
+ }