@prooflog/node 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,592 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __commonJS = (cb, mod) => function __require() {
8
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
+ };
10
+ var __copyProps = (to, from, except, desc2) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc2 = __getOwnPropDesc(from, key)) || desc2.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
26
+
27
+ // ../../node_modules/.pnpm/fast-json-stable-stringify@2.1.0/node_modules/fast-json-stable-stringify/index.js
28
+ var require_fast_json_stable_stringify = __commonJS({
29
+ "../../node_modules/.pnpm/fast-json-stable-stringify@2.1.0/node_modules/fast-json-stable-stringify/index.js"(exports, module) {
30
+ "use strict";
31
+ module.exports = function(data, opts) {
32
+ if (!opts) opts = {};
33
+ if (typeof opts === "function") opts = { cmp: opts };
34
+ var cycles = typeof opts.cycles === "boolean" ? opts.cycles : false;
35
+ var cmp = opts.cmp && /* @__PURE__ */ (function(f) {
36
+ return function(node) {
37
+ return function(a, b) {
38
+ var aobj = { key: a, value: node[a] };
39
+ var bobj = { key: b, value: node[b] };
40
+ return f(aobj, bobj);
41
+ };
42
+ };
43
+ })(opts.cmp);
44
+ var seen = [];
45
+ return (function stringify2(node) {
46
+ if (node && node.toJSON && typeof node.toJSON === "function") {
47
+ node = node.toJSON();
48
+ }
49
+ if (node === void 0) return;
50
+ if (typeof node == "number") return isFinite(node) ? "" + node : "null";
51
+ if (typeof node !== "object") return JSON.stringify(node);
52
+ var i, out;
53
+ if (Array.isArray(node)) {
54
+ out = "[";
55
+ for (i = 0; i < node.length; i++) {
56
+ if (i) out += ",";
57
+ out += stringify2(node[i]) || "null";
58
+ }
59
+ return out + "]";
60
+ }
61
+ if (node === null) return "null";
62
+ if (seen.indexOf(node) !== -1) {
63
+ if (cycles) return JSON.stringify("__cycle__");
64
+ throw new TypeError("Converting circular structure to JSON");
65
+ }
66
+ var seenIndex = seen.push(node) - 1;
67
+ var keys = Object.keys(node).sort(cmp && cmp(node));
68
+ out = "";
69
+ for (i = 0; i < keys.length; i++) {
70
+ var key = keys[i];
71
+ var value = stringify2(node[key]);
72
+ if (!value) continue;
73
+ if (out) out += ",";
74
+ out += JSON.stringify(key) + ":" + value;
75
+ }
76
+ seen.splice(seenIndex, 1);
77
+ return "{" + out + "}";
78
+ })(data);
79
+ };
80
+ }
81
+ });
82
+
83
+ // src/client.ts
84
+ import { drizzle } from "drizzle-orm/neon-http";
85
+ import { neon } from "@neondatabase/serverless";
86
+ import { eq, desc, asc, and, gt } from "drizzle-orm";
87
+
88
+ // ../db/src/schema.ts
89
+ import {
90
+ pgTable,
91
+ uuid,
92
+ text,
93
+ timestamp,
94
+ bigint,
95
+ jsonb,
96
+ unique
97
+ } from "drizzle-orm/pg-core";
98
+ var organisations = pgTable("organisations", {
99
+ id: uuid("id").primaryKey().defaultRandom(),
100
+ name: text("name").notNull(),
101
+ plan: text("plan").notNull().default("free"),
102
+ createdAt: timestamp("created_at").notNull().defaultNow()
103
+ });
104
+ var auditLogs = pgTable(
105
+ "audit_logs",
106
+ {
107
+ id: uuid("id").primaryKey().defaultRandom(),
108
+ organisationId: uuid("organisation_id").notNull().references(() => organisations.id),
109
+ sequence: bigint("sequence", { mode: "number" }).notNull(),
110
+ action: text("action").notNull(),
111
+ actor: jsonb("actor").notNull(),
112
+ target: jsonb("target"),
113
+ metadata: jsonb("metadata"),
114
+ hash: text("hash").notNull(),
115
+ previousHash: text("previous_hash").notNull(),
116
+ createdAt: timestamp("created_at").notNull().defaultNow()
117
+ },
118
+ (t) => [
119
+ unique("audit_logs_org_sequence_idx").on(t.organisationId, t.sequence)
120
+ ]
121
+ );
122
+
123
+ // ../../node_modules/.pnpm/@noble+hashes@2.2.0/node_modules/@noble/hashes/utils.js
124
+ function isBytes(a) {
125
+ return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
126
+ }
127
+ function abytes(value, length, title = "") {
128
+ const bytes = isBytes(value);
129
+ const len = value?.length;
130
+ const needsLen = length !== void 0;
131
+ if (!bytes || needsLen && len !== length) {
132
+ const prefix = title && `"${title}" `;
133
+ const ofLen = needsLen ? ` of length ${length}` : "";
134
+ const got = bytes ? `length=${len}` : `type=${typeof value}`;
135
+ const message = prefix + "expected Uint8Array" + ofLen + ", got " + got;
136
+ if (!bytes)
137
+ throw new TypeError(message);
138
+ throw new RangeError(message);
139
+ }
140
+ return value;
141
+ }
142
+ function aexists(instance, checkFinished = true) {
143
+ if (instance.destroyed)
144
+ throw new Error("Hash instance has been destroyed");
145
+ if (checkFinished && instance.finished)
146
+ throw new Error("Hash#digest() has already been called");
147
+ }
148
+ function aoutput(out, instance) {
149
+ abytes(out, void 0, "digestInto() output");
150
+ const min = instance.outputLen;
151
+ if (out.length < min) {
152
+ throw new RangeError('"digestInto() output" expected to be of length >=' + min);
153
+ }
154
+ }
155
+ function clean(...arrays) {
156
+ for (let i = 0; i < arrays.length; i++) {
157
+ arrays[i].fill(0);
158
+ }
159
+ }
160
+ function createView(arr) {
161
+ return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
162
+ }
163
+ function rotr(word, shift) {
164
+ return word << 32 - shift | word >>> shift;
165
+ }
166
+ var hasHexBuiltin = /* @__PURE__ */ (() => (
167
+ // @ts-ignore
168
+ typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function"
169
+ ))();
170
+ var hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
171
+ function bytesToHex(bytes) {
172
+ abytes(bytes);
173
+ if (hasHexBuiltin)
174
+ return bytes.toHex();
175
+ let hex = "";
176
+ for (let i = 0; i < bytes.length; i++) {
177
+ hex += hexes[bytes[i]];
178
+ }
179
+ return hex;
180
+ }
181
+ function createHasher(hashCons, info = {}) {
182
+ const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
183
+ const tmp = hashCons(void 0);
184
+ hashC.outputLen = tmp.outputLen;
185
+ hashC.blockLen = tmp.blockLen;
186
+ hashC.canXOF = tmp.canXOF;
187
+ hashC.create = (opts) => hashCons(opts);
188
+ Object.assign(hashC, info);
189
+ return Object.freeze(hashC);
190
+ }
191
+ var oidNist = (suffix) => ({
192
+ // Current NIST hashAlgs suffixes used here fit in one DER subidentifier octet.
193
+ // Larger suffix values would need base-128 OID encoding and a different length byte.
194
+ oid: Uint8Array.from([6, 9, 96, 134, 72, 1, 101, 3, 4, 2, suffix])
195
+ });
196
+
197
+ // ../../node_modules/.pnpm/@noble+hashes@2.2.0/node_modules/@noble/hashes/_md.js
198
+ function Chi(a, b, c) {
199
+ return a & b ^ ~a & c;
200
+ }
201
+ function Maj(a, b, c) {
202
+ return a & b ^ a & c ^ b & c;
203
+ }
204
+ var HashMD = class {
205
+ blockLen;
206
+ outputLen;
207
+ canXOF = false;
208
+ padOffset;
209
+ isLE;
210
+ // For partial updates less than block size
211
+ buffer;
212
+ view;
213
+ finished = false;
214
+ length = 0;
215
+ pos = 0;
216
+ destroyed = false;
217
+ constructor(blockLen, outputLen, padOffset, isLE) {
218
+ this.blockLen = blockLen;
219
+ this.outputLen = outputLen;
220
+ this.padOffset = padOffset;
221
+ this.isLE = isLE;
222
+ this.buffer = new Uint8Array(blockLen);
223
+ this.view = createView(this.buffer);
224
+ }
225
+ update(data) {
226
+ aexists(this);
227
+ abytes(data);
228
+ const { view, buffer, blockLen } = this;
229
+ const len = data.length;
230
+ for (let pos = 0; pos < len; ) {
231
+ const take = Math.min(blockLen - this.pos, len - pos);
232
+ if (take === blockLen) {
233
+ const dataView = createView(data);
234
+ for (; blockLen <= len - pos; pos += blockLen)
235
+ this.process(dataView, pos);
236
+ continue;
237
+ }
238
+ buffer.set(data.subarray(pos, pos + take), this.pos);
239
+ this.pos += take;
240
+ pos += take;
241
+ if (this.pos === blockLen) {
242
+ this.process(view, 0);
243
+ this.pos = 0;
244
+ }
245
+ }
246
+ this.length += data.length;
247
+ this.roundClean();
248
+ return this;
249
+ }
250
+ digestInto(out) {
251
+ aexists(this);
252
+ aoutput(out, this);
253
+ this.finished = true;
254
+ const { buffer, view, blockLen, isLE } = this;
255
+ let { pos } = this;
256
+ buffer[pos++] = 128;
257
+ clean(this.buffer.subarray(pos));
258
+ if (this.padOffset > blockLen - pos) {
259
+ this.process(view, 0);
260
+ pos = 0;
261
+ }
262
+ for (let i = pos; i < blockLen; i++)
263
+ buffer[i] = 0;
264
+ view.setBigUint64(blockLen - 8, BigInt(this.length * 8), isLE);
265
+ this.process(view, 0);
266
+ const oview = createView(out);
267
+ const len = this.outputLen;
268
+ if (len % 4)
269
+ throw new Error("_sha2: outputLen must be aligned to 32bit");
270
+ const outLen = len / 4;
271
+ const state = this.get();
272
+ if (outLen > state.length)
273
+ throw new Error("_sha2: outputLen bigger than state");
274
+ for (let i = 0; i < outLen; i++)
275
+ oview.setUint32(4 * i, state[i], isLE);
276
+ }
277
+ digest() {
278
+ const { buffer, outputLen } = this;
279
+ this.digestInto(buffer);
280
+ const res = buffer.slice(0, outputLen);
281
+ this.destroy();
282
+ return res;
283
+ }
284
+ _cloneInto(to) {
285
+ to ||= new this.constructor();
286
+ to.set(...this.get());
287
+ const { blockLen, buffer, length, finished, destroyed, pos } = this;
288
+ to.destroyed = destroyed;
289
+ to.finished = finished;
290
+ to.length = length;
291
+ to.pos = pos;
292
+ if (length % blockLen)
293
+ to.buffer.set(buffer);
294
+ return to;
295
+ }
296
+ clone() {
297
+ return this._cloneInto();
298
+ }
299
+ };
300
+ var SHA256_IV = /* @__PURE__ */ Uint32Array.from([
301
+ 1779033703,
302
+ 3144134277,
303
+ 1013904242,
304
+ 2773480762,
305
+ 1359893119,
306
+ 2600822924,
307
+ 528734635,
308
+ 1541459225
309
+ ]);
310
+
311
+ // ../../node_modules/.pnpm/@noble+hashes@2.2.0/node_modules/@noble/hashes/sha2.js
312
+ var SHA256_K = /* @__PURE__ */ Uint32Array.from([
313
+ 1116352408,
314
+ 1899447441,
315
+ 3049323471,
316
+ 3921009573,
317
+ 961987163,
318
+ 1508970993,
319
+ 2453635748,
320
+ 2870763221,
321
+ 3624381080,
322
+ 310598401,
323
+ 607225278,
324
+ 1426881987,
325
+ 1925078388,
326
+ 2162078206,
327
+ 2614888103,
328
+ 3248222580,
329
+ 3835390401,
330
+ 4022224774,
331
+ 264347078,
332
+ 604807628,
333
+ 770255983,
334
+ 1249150122,
335
+ 1555081692,
336
+ 1996064986,
337
+ 2554220882,
338
+ 2821834349,
339
+ 2952996808,
340
+ 3210313671,
341
+ 3336571891,
342
+ 3584528711,
343
+ 113926993,
344
+ 338241895,
345
+ 666307205,
346
+ 773529912,
347
+ 1294757372,
348
+ 1396182291,
349
+ 1695183700,
350
+ 1986661051,
351
+ 2177026350,
352
+ 2456956037,
353
+ 2730485921,
354
+ 2820302411,
355
+ 3259730800,
356
+ 3345764771,
357
+ 3516065817,
358
+ 3600352804,
359
+ 4094571909,
360
+ 275423344,
361
+ 430227734,
362
+ 506948616,
363
+ 659060556,
364
+ 883997877,
365
+ 958139571,
366
+ 1322822218,
367
+ 1537002063,
368
+ 1747873779,
369
+ 1955562222,
370
+ 2024104815,
371
+ 2227730452,
372
+ 2361852424,
373
+ 2428436474,
374
+ 2756734187,
375
+ 3204031479,
376
+ 3329325298
377
+ ]);
378
+ var SHA256_W = /* @__PURE__ */ new Uint32Array(64);
379
+ var SHA2_32B = class extends HashMD {
380
+ constructor(outputLen) {
381
+ super(64, outputLen, 8, false);
382
+ }
383
+ get() {
384
+ const { A, B, C, D, E, F, G, H } = this;
385
+ return [A, B, C, D, E, F, G, H];
386
+ }
387
+ // prettier-ignore
388
+ set(A, B, C, D, E, F, G, H) {
389
+ this.A = A | 0;
390
+ this.B = B | 0;
391
+ this.C = C | 0;
392
+ this.D = D | 0;
393
+ this.E = E | 0;
394
+ this.F = F | 0;
395
+ this.G = G | 0;
396
+ this.H = H | 0;
397
+ }
398
+ process(view, offset) {
399
+ for (let i = 0; i < 16; i++, offset += 4)
400
+ SHA256_W[i] = view.getUint32(offset, false);
401
+ for (let i = 16; i < 64; i++) {
402
+ const W15 = SHA256_W[i - 15];
403
+ const W2 = SHA256_W[i - 2];
404
+ const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3;
405
+ const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10;
406
+ SHA256_W[i] = s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0;
407
+ }
408
+ let { A, B, C, D, E, F, G, H } = this;
409
+ for (let i = 0; i < 64; i++) {
410
+ const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
411
+ const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0;
412
+ const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
413
+ const T2 = sigma0 + Maj(A, B, C) | 0;
414
+ H = G;
415
+ G = F;
416
+ F = E;
417
+ E = D + T1 | 0;
418
+ D = C;
419
+ C = B;
420
+ B = A;
421
+ A = T1 + T2 | 0;
422
+ }
423
+ A = A + this.A | 0;
424
+ B = B + this.B | 0;
425
+ C = C + this.C | 0;
426
+ D = D + this.D | 0;
427
+ E = E + this.E | 0;
428
+ F = F + this.F | 0;
429
+ G = G + this.G | 0;
430
+ H = H + this.H | 0;
431
+ this.set(A, B, C, D, E, F, G, H);
432
+ }
433
+ roundClean() {
434
+ clean(SHA256_W);
435
+ }
436
+ destroy() {
437
+ this.destroyed = true;
438
+ this.set(0, 0, 0, 0, 0, 0, 0, 0);
439
+ clean(this.buffer);
440
+ }
441
+ };
442
+ var _SHA256 = class extends SHA2_32B {
443
+ // We cannot use array here since array allows indexing by variable
444
+ // which means optimizer/compiler cannot use registers.
445
+ A = SHA256_IV[0] | 0;
446
+ B = SHA256_IV[1] | 0;
447
+ C = SHA256_IV[2] | 0;
448
+ D = SHA256_IV[3] | 0;
449
+ E = SHA256_IV[4] | 0;
450
+ F = SHA256_IV[5] | 0;
451
+ G = SHA256_IV[6] | 0;
452
+ H = SHA256_IV[7] | 0;
453
+ constructor() {
454
+ super(32);
455
+ }
456
+ };
457
+ var sha256 = /* @__PURE__ */ createHasher(
458
+ () => new _SHA256(),
459
+ /* @__PURE__ */ oidNist(1)
460
+ );
461
+
462
+ // ../crypto/src/hash.ts
463
+ var import_fast_json_stable_stringify = __toESM(require_fast_json_stable_stringify());
464
+ var GENESIS_HASH = "0".repeat(64);
465
+ function computeHash(event, previousHash) {
466
+ const payload = (0, import_fast_json_stable_stringify.default)({
467
+ organisationId: event.organisationId,
468
+ sequence: event.sequence,
469
+ action: event.action,
470
+ actor: event.actor,
471
+ target: event.target ?? null,
472
+ metadata: event.metadata ?? null,
473
+ createdAt: event.createdAt,
474
+ previousHash
475
+ });
476
+ return bytesToHex(sha256(new TextEncoder().encode(payload)));
477
+ }
478
+
479
+ // src/client.ts
480
+ var ProofLog = class {
481
+ db;
482
+ constructor(config) {
483
+ if (!config.databaseUrl) throw new Error("databaseUrl is required");
484
+ const sql = neon(config.databaseUrl);
485
+ this.db = drizzle(sql);
486
+ }
487
+ /**
488
+ * Pushes a new audit log event directly to the database.
489
+ * Handles concurrency retries internally.
490
+ */
491
+ async ingest(organisationId, options) {
492
+ const maxRetries = 3;
493
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
494
+ try {
495
+ const lastEntry = await this.db.select().from(auditLogs).where(eq(auditLogs.organisationId, organisationId)).orderBy(desc(auditLogs.sequence)).limit(1);
496
+ const previousHash = lastEntry.length ? lastEntry[0].hash : GENESIS_HASH;
497
+ const sequence = lastEntry.length ? lastEntry[0].sequence + 1 : 1;
498
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
499
+ const hash = computeHash(
500
+ {
501
+ organisationId,
502
+ sequence,
503
+ action: options.action,
504
+ actor: options.actor,
505
+ target: options.target ?? null,
506
+ metadata: options.metadata ?? null,
507
+ createdAt
508
+ },
509
+ previousHash
510
+ );
511
+ await this.db.insert(auditLogs).values({
512
+ organisationId,
513
+ sequence,
514
+ action: options.action,
515
+ actor: options.actor,
516
+ target: options.target ?? null,
517
+ metadata: options.metadata ?? null,
518
+ hash,
519
+ previousHash,
520
+ createdAt: new Date(createdAt)
521
+ });
522
+ return { sequence, hash };
523
+ } catch (error) {
524
+ if (error.code === "23505" || error.message?.includes("23505") || error.message?.includes("unique constraint")) {
525
+ if (attempt === maxRetries - 1) {
526
+ throw new Error("Failed to ingest audit log due to high concurrency. Please try again.");
527
+ }
528
+ continue;
529
+ }
530
+ throw error;
531
+ }
532
+ }
533
+ throw new Error("Unreachable");
534
+ }
535
+ /**
536
+ * Triggers a cryptographic verification of the audit log chain for the organisation in batches.
537
+ */
538
+ async verify(organisationId) {
539
+ const batchSize = 1e3;
540
+ let hasMore = true;
541
+ let currentSequence = 0;
542
+ let totalEntries = 0;
543
+ let expectedPreviousHash = GENESIS_HASH;
544
+ while (hasMore) {
545
+ const batch = await this.db.select().from(auditLogs).where(
546
+ and(
547
+ eq(auditLogs.organisationId, organisationId),
548
+ gt(auditLogs.sequence, currentSequence)
549
+ )
550
+ ).orderBy(asc(auditLogs.sequence)).limit(batchSize);
551
+ if (batch.length === 0) {
552
+ hasMore = false;
553
+ break;
554
+ }
555
+ for (const entry of batch) {
556
+ if (entry.previousHash !== expectedPreviousHash) {
557
+ return {
558
+ valid: false,
559
+ totalEntries,
560
+ tamperedAt: entry.sequence,
561
+ reason: `Chain broken at sequence ${entry.sequence}`
562
+ };
563
+ }
564
+ const recomputed = computeHash({
565
+ organisationId: entry.organisationId,
566
+ sequence: entry.sequence,
567
+ action: entry.action,
568
+ actor: entry.actor,
569
+ target: entry.target,
570
+ metadata: entry.metadata,
571
+ createdAt: entry.createdAt.toISOString()
572
+ }, entry.previousHash);
573
+ if (recomputed !== entry.hash) {
574
+ return {
575
+ valid: false,
576
+ totalEntries,
577
+ tamperedAt: entry.sequence,
578
+ reason: `Hash mismatch at sequence ${entry.sequence} \u2014 data tampered`
579
+ };
580
+ }
581
+ expectedPreviousHash = entry.hash;
582
+ currentSequence = entry.sequence;
583
+ totalEntries++;
584
+ }
585
+ }
586
+ return { valid: true, totalEntries };
587
+ }
588
+ };
589
+ export {
590
+ ProofLog
591
+ };
592
+ //# sourceMappingURL=index.mjs.map