@nobulex/proof 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.
@@ -0,0 +1,58 @@
1
+ import type { HashHex } from '@nobulex/crypto';
2
+ import type { ComplianceProof, ProofVerificationResult, ProofGenerationOptions, AuditEntryData } from './types';
3
+ export type { ComplianceProof, ProofVerificationResult, ProofGenerationOptions, AuditEntryData, } from './types';
4
+ export { poseidonHash, hashToField, fieldToHex, FIELD_PRIME } from './poseidon';
5
+ /**
6
+ * Compute a Poseidon commitment over a sequence of audit log entries.
7
+ *
8
+ * The commitment is built by chaining: start with a zero accumulator,
9
+ * then for each entry, hash (accumulator, entryFieldElement) with Poseidon.
10
+ * This produces a sequential commitment that depends on the order and
11
+ * content of every entry.
12
+ *
13
+ * @param entries - The audit entries to commit to
14
+ * @returns Hex-encoded Poseidon commitment
15
+ */
16
+ export declare function computeAuditCommitment(entries: AuditEntryData[]): HashHex;
17
+ /**
18
+ * Compute a Poseidon commitment over a constraint definition string.
19
+ *
20
+ * First hashes the string with SHA-256 to get a fixed-size digest,
21
+ * converts that to a field element, then applies Poseidon.
22
+ *
23
+ * @param constraints - The constraint definitions (CCL source or canonical string)
24
+ * @returns Hex-encoded Poseidon commitment
25
+ */
26
+ export declare function computeConstraintCommitment(constraints: string): HashHex;
27
+ /**
28
+ * Generate a compliance proof attesting that the given audit entries
29
+ * are consistent with the covenant's constraints.
30
+ *
31
+ * For v0.1, this produces a Poseidon hash commitment proof:
32
+ * - Computes audit log commitment (chained Poseidon over entry hashes)
33
+ * - Computes constraint commitment (Poseidon of SHA-256 of constraints)
34
+ * - Generates proof = Poseidon(auditLogCommitment, constraintCommitment, covenantId)
35
+ * - Public inputs = [covenantId, auditLogCommitment, constraintCommitment, entryCount]
36
+ *
37
+ * In future versions (groth16, plonk), the proof will be a full ZK-SNARK.
38
+ *
39
+ * @param options - Proof generation parameters
40
+ * @returns A ComplianceProof object
41
+ */
42
+ export declare function generateComplianceProof(options: ProofGenerationOptions): Promise<ComplianceProof>;
43
+ /**
44
+ * Verify a compliance proof by checking its structural integrity and
45
+ * recomputing the proof value from public inputs.
46
+ *
47
+ * Verification steps:
48
+ * 1. Check proof format (version, required fields)
49
+ * 2. Check public inputs are well-formed (length = 4)
50
+ * 3. Audit log commitment is consistent (publicInputs[1] matches auditLogCommitment, covenantId matches)
51
+ * 4. Constraint commitment matches (publicInputs[2] matches constraintCommitment, entryCount matches)
52
+ * 5. Proof verifies against verification circuit (recompute and compare)
53
+ *
54
+ * @param proof - The ComplianceProof to verify
55
+ * @returns Detailed verification result
56
+ */
57
+ export declare function verifyComplianceProof(proof: ComplianceProof): Promise<ProofVerificationResult>;
58
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAG/C,OAAO,KAAK,EACV,eAAe,EACf,uBAAuB,EACvB,sBAAsB,EACtB,cAAc,EACf,MAAM,SAAS,CAAC;AAGjB,YAAY,EACV,eAAe,EACf,uBAAuB,EACvB,sBAAsB,EACtB,cAAc,GACf,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAMhF;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,OAAO,CAgBzE;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAQxE;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,eAAe,CAAC,CA4C1B;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,uBAAuB,CAAC,CAwHlC"}
package/dist/index.js ADDED
@@ -0,0 +1,326 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
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: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ FIELD_PRIME: () => FIELD_PRIME,
24
+ computeAuditCommitment: () => computeAuditCommitment,
25
+ computeConstraintCommitment: () => computeConstraintCommitment,
26
+ fieldToHex: () => fieldToHex,
27
+ generateComplianceProof: () => generateComplianceProof,
28
+ hashToField: () => hashToField,
29
+ poseidonHash: () => poseidonHash,
30
+ verifyComplianceProof: () => verifyComplianceProof
31
+ });
32
+ module.exports = __toCommonJS(index_exports);
33
+ var import_crypto2 = require("@nobulex/crypto");
34
+
35
+ // src/poseidon.ts
36
+ var import_crypto = require("@nobulex/crypto");
37
+ var FIELD_PRIME = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
38
+ var T = 3;
39
+ var FULL_ROUNDS = 8;
40
+ var PARTIAL_ROUNDS = 57;
41
+ var TOTAL_ROUNDS = FULL_ROUNDS + PARTIAL_ROUNDS;
42
+ function generateRoundConstants() {
43
+ const constants = [];
44
+ for (let round = 0; round < TOTAL_ROUNDS; round++) {
45
+ const roundConsts = [];
46
+ for (let j = 0; j < T; j++) {
47
+ const idx = round * T + j;
48
+ const hash = (0, import_crypto.sha256String)(`poseidon_rc_${idx}`);
49
+ const value = BigInt("0x" + hash) % FIELD_PRIME;
50
+ roundConsts.push(value);
51
+ }
52
+ constants.push(roundConsts);
53
+ }
54
+ return constants;
55
+ }
56
+ var ROUND_CONSTANTS = generateRoundConstants();
57
+ function modInverse(a, p) {
58
+ a = (a % p + p) % p;
59
+ if (a === 0n) {
60
+ throw new Error("No inverse for zero");
61
+ }
62
+ let [old_r, r] = [a, p];
63
+ let [old_s, s] = [1n, 0n];
64
+ while (r !== 0n) {
65
+ const quotient = old_r / r;
66
+ [old_r, r] = [r, old_r - quotient * r];
67
+ [old_s, s] = [s, old_s - quotient * s];
68
+ }
69
+ return (old_s % p + p) % p;
70
+ }
71
+ function buildMDSMatrix() {
72
+ const matrix = [];
73
+ for (let i = 0; i < T; i++) {
74
+ const row = [];
75
+ for (let j = 0; j < T; j++) {
76
+ const xi = BigInt(i + 1);
77
+ const yj = BigInt(T + j + 1);
78
+ const sum = (xi + yj) % FIELD_PRIME;
79
+ row.push(modInverse(sum, FIELD_PRIME));
80
+ }
81
+ matrix.push(row);
82
+ }
83
+ return matrix;
84
+ }
85
+ var MDS_MATRIX = buildMDSMatrix();
86
+ function sbox(x) {
87
+ const x2 = x * x % FIELD_PRIME;
88
+ const x4 = x2 * x2 % FIELD_PRIME;
89
+ return x4 * x % FIELD_PRIME;
90
+ }
91
+ function addRoundConstants(state, round) {
92
+ const rc = ROUND_CONSTANTS[round];
93
+ return state.map((s, i) => (s + rc[i]) % FIELD_PRIME);
94
+ }
95
+ function mdsMultiply(state) {
96
+ const result = new Array(T).fill(0n);
97
+ for (let i = 0; i < T; i++) {
98
+ let acc = 0n;
99
+ for (let j = 0; j < T; j++) {
100
+ acc = (acc + MDS_MATRIX[i][j] * state[j]) % FIELD_PRIME;
101
+ }
102
+ result[i] = acc;
103
+ }
104
+ return result;
105
+ }
106
+ function fullRound(state, round) {
107
+ let s = addRoundConstants(state, round);
108
+ s = s.map(sbox);
109
+ return mdsMultiply(s);
110
+ }
111
+ function partialRound(state, round) {
112
+ let s = addRoundConstants(state, round);
113
+ s[0] = sbox(s[0]);
114
+ return mdsMultiply(s);
115
+ }
116
+ function poseidonHash(inputs) {
117
+ if (inputs.length === 0) {
118
+ throw new Error("Poseidon hash requires at least one input");
119
+ }
120
+ for (let i = 0; i < inputs.length; i++) {
121
+ const input = inputs[i];
122
+ if (input < 0n || input >= FIELD_PRIME) {
123
+ throw new Error(
124
+ `Input ${i} is out of field range: must be in [0, FIELD_PRIME)`
125
+ );
126
+ }
127
+ }
128
+ const rate = T - 1;
129
+ const padded = [...inputs];
130
+ padded.push(1n);
131
+ while (padded.length % rate !== 0) {
132
+ padded.push(0n);
133
+ }
134
+ let state = new Array(T).fill(0n);
135
+ for (let chunk = 0; chunk < padded.length; chunk += rate) {
136
+ for (let i = 0; i < rate; i++) {
137
+ state[i] = (state[i] + padded[chunk + i]) % FIELD_PRIME;
138
+ }
139
+ state = poseidonPermutation(state);
140
+ }
141
+ return state[0];
142
+ }
143
+ function poseidonPermutation(state) {
144
+ let s = [...state];
145
+ let round = 0;
146
+ const halfFull = FULL_ROUNDS / 2;
147
+ for (let i = 0; i < halfFull; i++) {
148
+ s = fullRound(s, round);
149
+ round++;
150
+ }
151
+ for (let i = 0; i < PARTIAL_ROUNDS; i++) {
152
+ s = partialRound(s, round);
153
+ round++;
154
+ }
155
+ for (let i = 0; i < halfFull; i++) {
156
+ s = fullRound(s, round);
157
+ round++;
158
+ }
159
+ return s;
160
+ }
161
+ function hashToField(hash) {
162
+ if (hash.length < 2) {
163
+ throw new Error("Hash string too short for field conversion");
164
+ }
165
+ const value = BigInt("0x" + hash);
166
+ return value % FIELD_PRIME;
167
+ }
168
+ function fieldToHex(value) {
169
+ if (value < 0n || value >= FIELD_PRIME) {
170
+ throw new Error("Value out of field range");
171
+ }
172
+ return value.toString(16).padStart(64, "0");
173
+ }
174
+
175
+ // src/index.ts
176
+ function computeAuditCommitment(entries) {
177
+ if (entries.length === 0) {
178
+ return fieldToHex(poseidonHash([0n]));
179
+ }
180
+ let accumulator = 0n;
181
+ for (const entry of entries) {
182
+ const entryField = hashToField(entry.hash);
183
+ accumulator = poseidonHash([accumulator, entryField]);
184
+ }
185
+ return fieldToHex(accumulator);
186
+ }
187
+ function computeConstraintCommitment(constraints) {
188
+ const constraintHash = (0, import_crypto2.sha256String)(constraints);
189
+ const constraintField = hashToField(constraintHash);
190
+ const commitment = poseidonHash([constraintField]);
191
+ return fieldToHex(commitment);
192
+ }
193
+ async function generateComplianceProof(options) {
194
+ const { covenantId, constraints, auditEntries, proofSystem = "poseidon_hash" } = options;
195
+ if (!covenantId || covenantId.length === 0) {
196
+ throw new Error("covenantId is required");
197
+ }
198
+ if (!constraints || constraints.length === 0) {
199
+ throw new Error("constraints string is required");
200
+ }
201
+ const auditLogCommitment = computeAuditCommitment(auditEntries);
202
+ const constraintCommitment = computeConstraintCommitment(constraints);
203
+ const auditField = hashToField(auditLogCommitment);
204
+ const constraintField = hashToField(constraintCommitment);
205
+ const covenantField = hashToField(covenantId);
206
+ const proofValue = poseidonHash([auditField, constraintField, covenantField]);
207
+ const proofHex = fieldToHex(proofValue);
208
+ const publicInputs = [
209
+ covenantId,
210
+ auditLogCommitment,
211
+ constraintCommitment,
212
+ String(auditEntries.length)
213
+ ];
214
+ return {
215
+ version: "1.0",
216
+ covenantId,
217
+ auditLogCommitment,
218
+ constraintCommitment,
219
+ proof: proofHex,
220
+ publicInputs,
221
+ proofSystem,
222
+ generatedAt: (0, import_crypto2.timestamp)(),
223
+ entryCount: auditEntries.length
224
+ };
225
+ }
226
+ async function verifyComplianceProof(proof) {
227
+ const errors = [];
228
+ if (proof.version !== "1.0") {
229
+ errors.push(`Unsupported proof version: ${proof.version}`);
230
+ }
231
+ if (!proof.covenantId || proof.covenantId.length === 0) {
232
+ errors.push("Missing covenantId");
233
+ }
234
+ if (!proof.auditLogCommitment || proof.auditLogCommitment.length === 0) {
235
+ errors.push("Missing auditLogCommitment");
236
+ }
237
+ if (!proof.constraintCommitment || proof.constraintCommitment.length === 0) {
238
+ errors.push("Missing constraintCommitment");
239
+ }
240
+ if (!proof.proof || proof.proof.length === 0) {
241
+ errors.push("Missing proof value");
242
+ }
243
+ if (proof.proofSystem !== "poseidon_hash" && proof.proofSystem !== "groth16" && proof.proofSystem !== "plonk") {
244
+ errors.push(`Unsupported proof system: ${proof.proofSystem}`);
245
+ }
246
+ if (typeof proof.entryCount !== "number" || proof.entryCount < 0) {
247
+ errors.push(`Invalid entryCount: ${proof.entryCount}`);
248
+ }
249
+ if (!proof.generatedAt || proof.generatedAt.length === 0) {
250
+ errors.push("Missing generatedAt timestamp");
251
+ }
252
+ if (!Array.isArray(proof.publicInputs)) {
253
+ errors.push("publicInputs must be an array");
254
+ return {
255
+ valid: false,
256
+ covenantId: proof.covenantId ?? "",
257
+ entryCount: proof.entryCount ?? 0,
258
+ errors
259
+ };
260
+ }
261
+ if (proof.publicInputs.length !== 4) {
262
+ errors.push(
263
+ `publicInputs must have exactly 4 elements, got ${proof.publicInputs.length}`
264
+ );
265
+ }
266
+ if (proof.publicInputs.length === 4) {
267
+ const [piCovenantId, piAuditCommitment, piConstraintCommitment, piEntryCount] = proof.publicInputs;
268
+ if (piCovenantId !== proof.covenantId) {
269
+ errors.push(
270
+ `publicInputs[0] (covenantId) mismatch: expected ${proof.covenantId}, got ${piCovenantId}`
271
+ );
272
+ }
273
+ if (piAuditCommitment !== proof.auditLogCommitment) {
274
+ errors.push(
275
+ `publicInputs[1] (auditLogCommitment) mismatch: expected ${proof.auditLogCommitment}, got ${piAuditCommitment}`
276
+ );
277
+ }
278
+ }
279
+ if (proof.publicInputs.length === 4) {
280
+ const [, , piConstraintCommitment, piEntryCount] = proof.publicInputs;
281
+ if (piConstraintCommitment !== proof.constraintCommitment) {
282
+ errors.push(
283
+ `publicInputs[2] (constraintCommitment) mismatch: expected ${proof.constraintCommitment}, got ${piConstraintCommitment}`
284
+ );
285
+ }
286
+ if (piEntryCount !== String(proof.entryCount)) {
287
+ errors.push(
288
+ `publicInputs[3] (entryCount) mismatch: expected ${proof.entryCount}, got ${piEntryCount}`
289
+ );
290
+ }
291
+ }
292
+ if (proof.proofSystem === "poseidon_hash" && errors.length === 0) {
293
+ try {
294
+ const auditField = hashToField(proof.auditLogCommitment);
295
+ const constraintField = hashToField(proof.constraintCommitment);
296
+ const covenantField = hashToField(proof.covenantId);
297
+ const expectedProof = poseidonHash([auditField, constraintField, covenantField]);
298
+ const expectedHex = fieldToHex(expectedProof);
299
+ if (expectedHex !== proof.proof) {
300
+ errors.push(
301
+ `Proof value mismatch: recomputed ${expectedHex}, got ${proof.proof}`
302
+ );
303
+ }
304
+ } catch (err) {
305
+ const message = err instanceof Error ? err.message : String(err);
306
+ errors.push(`Error recomputing proof: ${message}`);
307
+ }
308
+ }
309
+ return {
310
+ valid: errors.length === 0,
311
+ covenantId: proof.covenantId ?? "",
312
+ entryCount: proof.entryCount ?? 0,
313
+ errors
314
+ };
315
+ }
316
+ // Annotate the CommonJS export names for ESM import in node:
317
+ 0 && (module.exports = {
318
+ FIELD_PRIME,
319
+ computeAuditCommitment,
320
+ computeConstraintCommitment,
321
+ fieldToHex,
322
+ generateComplianceProof,
323
+ hashToField,
324
+ poseidonHash,
325
+ verifyComplianceProof
326
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,294 @@
1
+ // src/index.ts
2
+ import { sha256String as sha256String2, timestamp } from "@nobulex/crypto";
3
+
4
+ // src/poseidon.ts
5
+ import { sha256String } from "@nobulex/crypto";
6
+ var FIELD_PRIME = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
7
+ var T = 3;
8
+ var FULL_ROUNDS = 8;
9
+ var PARTIAL_ROUNDS = 57;
10
+ var TOTAL_ROUNDS = FULL_ROUNDS + PARTIAL_ROUNDS;
11
+ function generateRoundConstants() {
12
+ const constants = [];
13
+ for (let round = 0; round < TOTAL_ROUNDS; round++) {
14
+ const roundConsts = [];
15
+ for (let j = 0; j < T; j++) {
16
+ const idx = round * T + j;
17
+ const hash = sha256String(`poseidon_rc_${idx}`);
18
+ const value = BigInt("0x" + hash) % FIELD_PRIME;
19
+ roundConsts.push(value);
20
+ }
21
+ constants.push(roundConsts);
22
+ }
23
+ return constants;
24
+ }
25
+ var ROUND_CONSTANTS = generateRoundConstants();
26
+ function modInverse(a, p) {
27
+ a = (a % p + p) % p;
28
+ if (a === 0n) {
29
+ throw new Error("No inverse for zero");
30
+ }
31
+ let [old_r, r] = [a, p];
32
+ let [old_s, s] = [1n, 0n];
33
+ while (r !== 0n) {
34
+ const quotient = old_r / r;
35
+ [old_r, r] = [r, old_r - quotient * r];
36
+ [old_s, s] = [s, old_s - quotient * s];
37
+ }
38
+ return (old_s % p + p) % p;
39
+ }
40
+ function buildMDSMatrix() {
41
+ const matrix = [];
42
+ for (let i = 0; i < T; i++) {
43
+ const row = [];
44
+ for (let j = 0; j < T; j++) {
45
+ const xi = BigInt(i + 1);
46
+ const yj = BigInt(T + j + 1);
47
+ const sum = (xi + yj) % FIELD_PRIME;
48
+ row.push(modInverse(sum, FIELD_PRIME));
49
+ }
50
+ matrix.push(row);
51
+ }
52
+ return matrix;
53
+ }
54
+ var MDS_MATRIX = buildMDSMatrix();
55
+ function sbox(x) {
56
+ const x2 = x * x % FIELD_PRIME;
57
+ const x4 = x2 * x2 % FIELD_PRIME;
58
+ return x4 * x % FIELD_PRIME;
59
+ }
60
+ function addRoundConstants(state, round) {
61
+ const rc = ROUND_CONSTANTS[round];
62
+ return state.map((s, i) => (s + rc[i]) % FIELD_PRIME);
63
+ }
64
+ function mdsMultiply(state) {
65
+ const result = new Array(T).fill(0n);
66
+ for (let i = 0; i < T; i++) {
67
+ let acc = 0n;
68
+ for (let j = 0; j < T; j++) {
69
+ acc = (acc + MDS_MATRIX[i][j] * state[j]) % FIELD_PRIME;
70
+ }
71
+ result[i] = acc;
72
+ }
73
+ return result;
74
+ }
75
+ function fullRound(state, round) {
76
+ let s = addRoundConstants(state, round);
77
+ s = s.map(sbox);
78
+ return mdsMultiply(s);
79
+ }
80
+ function partialRound(state, round) {
81
+ let s = addRoundConstants(state, round);
82
+ s[0] = sbox(s[0]);
83
+ return mdsMultiply(s);
84
+ }
85
+ function poseidonHash(inputs) {
86
+ if (inputs.length === 0) {
87
+ throw new Error("Poseidon hash requires at least one input");
88
+ }
89
+ for (let i = 0; i < inputs.length; i++) {
90
+ const input = inputs[i];
91
+ if (input < 0n || input >= FIELD_PRIME) {
92
+ throw new Error(
93
+ `Input ${i} is out of field range: must be in [0, FIELD_PRIME)`
94
+ );
95
+ }
96
+ }
97
+ const rate = T - 1;
98
+ const padded = [...inputs];
99
+ padded.push(1n);
100
+ while (padded.length % rate !== 0) {
101
+ padded.push(0n);
102
+ }
103
+ let state = new Array(T).fill(0n);
104
+ for (let chunk = 0; chunk < padded.length; chunk += rate) {
105
+ for (let i = 0; i < rate; i++) {
106
+ state[i] = (state[i] + padded[chunk + i]) % FIELD_PRIME;
107
+ }
108
+ state = poseidonPermutation(state);
109
+ }
110
+ return state[0];
111
+ }
112
+ function poseidonPermutation(state) {
113
+ let s = [...state];
114
+ let round = 0;
115
+ const halfFull = FULL_ROUNDS / 2;
116
+ for (let i = 0; i < halfFull; i++) {
117
+ s = fullRound(s, round);
118
+ round++;
119
+ }
120
+ for (let i = 0; i < PARTIAL_ROUNDS; i++) {
121
+ s = partialRound(s, round);
122
+ round++;
123
+ }
124
+ for (let i = 0; i < halfFull; i++) {
125
+ s = fullRound(s, round);
126
+ round++;
127
+ }
128
+ return s;
129
+ }
130
+ function hashToField(hash) {
131
+ if (hash.length < 2) {
132
+ throw new Error("Hash string too short for field conversion");
133
+ }
134
+ const value = BigInt("0x" + hash);
135
+ return value % FIELD_PRIME;
136
+ }
137
+ function fieldToHex(value) {
138
+ if (value < 0n || value >= FIELD_PRIME) {
139
+ throw new Error("Value out of field range");
140
+ }
141
+ return value.toString(16).padStart(64, "0");
142
+ }
143
+
144
+ // src/index.ts
145
+ function computeAuditCommitment(entries) {
146
+ if (entries.length === 0) {
147
+ return fieldToHex(poseidonHash([0n]));
148
+ }
149
+ let accumulator = 0n;
150
+ for (const entry of entries) {
151
+ const entryField = hashToField(entry.hash);
152
+ accumulator = poseidonHash([accumulator, entryField]);
153
+ }
154
+ return fieldToHex(accumulator);
155
+ }
156
+ function computeConstraintCommitment(constraints) {
157
+ const constraintHash = sha256String2(constraints);
158
+ const constraintField = hashToField(constraintHash);
159
+ const commitment = poseidonHash([constraintField]);
160
+ return fieldToHex(commitment);
161
+ }
162
+ async function generateComplianceProof(options) {
163
+ const { covenantId, constraints, auditEntries, proofSystem = "poseidon_hash" } = options;
164
+ if (!covenantId || covenantId.length === 0) {
165
+ throw new Error("covenantId is required");
166
+ }
167
+ if (!constraints || constraints.length === 0) {
168
+ throw new Error("constraints string is required");
169
+ }
170
+ const auditLogCommitment = computeAuditCommitment(auditEntries);
171
+ const constraintCommitment = computeConstraintCommitment(constraints);
172
+ const auditField = hashToField(auditLogCommitment);
173
+ const constraintField = hashToField(constraintCommitment);
174
+ const covenantField = hashToField(covenantId);
175
+ const proofValue = poseidonHash([auditField, constraintField, covenantField]);
176
+ const proofHex = fieldToHex(proofValue);
177
+ const publicInputs = [
178
+ covenantId,
179
+ auditLogCommitment,
180
+ constraintCommitment,
181
+ String(auditEntries.length)
182
+ ];
183
+ return {
184
+ version: "1.0",
185
+ covenantId,
186
+ auditLogCommitment,
187
+ constraintCommitment,
188
+ proof: proofHex,
189
+ publicInputs,
190
+ proofSystem,
191
+ generatedAt: timestamp(),
192
+ entryCount: auditEntries.length
193
+ };
194
+ }
195
+ async function verifyComplianceProof(proof) {
196
+ const errors = [];
197
+ if (proof.version !== "1.0") {
198
+ errors.push(`Unsupported proof version: ${proof.version}`);
199
+ }
200
+ if (!proof.covenantId || proof.covenantId.length === 0) {
201
+ errors.push("Missing covenantId");
202
+ }
203
+ if (!proof.auditLogCommitment || proof.auditLogCommitment.length === 0) {
204
+ errors.push("Missing auditLogCommitment");
205
+ }
206
+ if (!proof.constraintCommitment || proof.constraintCommitment.length === 0) {
207
+ errors.push("Missing constraintCommitment");
208
+ }
209
+ if (!proof.proof || proof.proof.length === 0) {
210
+ errors.push("Missing proof value");
211
+ }
212
+ if (proof.proofSystem !== "poseidon_hash" && proof.proofSystem !== "groth16" && proof.proofSystem !== "plonk") {
213
+ errors.push(`Unsupported proof system: ${proof.proofSystem}`);
214
+ }
215
+ if (typeof proof.entryCount !== "number" || proof.entryCount < 0) {
216
+ errors.push(`Invalid entryCount: ${proof.entryCount}`);
217
+ }
218
+ if (!proof.generatedAt || proof.generatedAt.length === 0) {
219
+ errors.push("Missing generatedAt timestamp");
220
+ }
221
+ if (!Array.isArray(proof.publicInputs)) {
222
+ errors.push("publicInputs must be an array");
223
+ return {
224
+ valid: false,
225
+ covenantId: proof.covenantId ?? "",
226
+ entryCount: proof.entryCount ?? 0,
227
+ errors
228
+ };
229
+ }
230
+ if (proof.publicInputs.length !== 4) {
231
+ errors.push(
232
+ `publicInputs must have exactly 4 elements, got ${proof.publicInputs.length}`
233
+ );
234
+ }
235
+ if (proof.publicInputs.length === 4) {
236
+ const [piCovenantId, piAuditCommitment, piConstraintCommitment, piEntryCount] = proof.publicInputs;
237
+ if (piCovenantId !== proof.covenantId) {
238
+ errors.push(
239
+ `publicInputs[0] (covenantId) mismatch: expected ${proof.covenantId}, got ${piCovenantId}`
240
+ );
241
+ }
242
+ if (piAuditCommitment !== proof.auditLogCommitment) {
243
+ errors.push(
244
+ `publicInputs[1] (auditLogCommitment) mismatch: expected ${proof.auditLogCommitment}, got ${piAuditCommitment}`
245
+ );
246
+ }
247
+ }
248
+ if (proof.publicInputs.length === 4) {
249
+ const [, , piConstraintCommitment, piEntryCount] = proof.publicInputs;
250
+ if (piConstraintCommitment !== proof.constraintCommitment) {
251
+ errors.push(
252
+ `publicInputs[2] (constraintCommitment) mismatch: expected ${proof.constraintCommitment}, got ${piConstraintCommitment}`
253
+ );
254
+ }
255
+ if (piEntryCount !== String(proof.entryCount)) {
256
+ errors.push(
257
+ `publicInputs[3] (entryCount) mismatch: expected ${proof.entryCount}, got ${piEntryCount}`
258
+ );
259
+ }
260
+ }
261
+ if (proof.proofSystem === "poseidon_hash" && errors.length === 0) {
262
+ try {
263
+ const auditField = hashToField(proof.auditLogCommitment);
264
+ const constraintField = hashToField(proof.constraintCommitment);
265
+ const covenantField = hashToField(proof.covenantId);
266
+ const expectedProof = poseidonHash([auditField, constraintField, covenantField]);
267
+ const expectedHex = fieldToHex(expectedProof);
268
+ if (expectedHex !== proof.proof) {
269
+ errors.push(
270
+ `Proof value mismatch: recomputed ${expectedHex}, got ${proof.proof}`
271
+ );
272
+ }
273
+ } catch (err) {
274
+ const message = err instanceof Error ? err.message : String(err);
275
+ errors.push(`Error recomputing proof: ${message}`);
276
+ }
277
+ }
278
+ return {
279
+ valid: errors.length === 0,
280
+ covenantId: proof.covenantId ?? "",
281
+ entryCount: proof.entryCount ?? 0,
282
+ errors
283
+ };
284
+ }
285
+ export {
286
+ FIELD_PRIME,
287
+ computeAuditCommitment,
288
+ computeConstraintCommitment,
289
+ fieldToHex,
290
+ generateComplianceProof,
291
+ hashToField,
292
+ poseidonHash,
293
+ verifyComplianceProof
294
+ };
@@ -0,0 +1,33 @@
1
+ import type { HashHex } from '@nobulex/crypto';
2
+ /**
3
+ * BN254 (alt_bn128) scalar field prime.
4
+ * This is the standard prime used in Ethereum precompile-compatible ZK circuits.
5
+ */
6
+ export declare const FIELD_PRIME = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
7
+ /**
8
+ * Poseidon hash function (t=3, rate=2).
9
+ *
10
+ * Accepts an array of field elements and returns a single field element.
11
+ * For inputs longer than 2, we use a sponge construction: absorb 2 elements
12
+ * at a time into the first 2 state positions, then squeeze 1 element out.
13
+ *
14
+ * @param inputs - Array of bigint field elements (each must be < FIELD_PRIME)
15
+ * @returns A single bigint field element (the hash)
16
+ */
17
+ export declare function poseidonHash(inputs: bigint[]): bigint;
18
+ /**
19
+ * Convert a hex-encoded hash (e.g., SHA-256 output) to a BN254 field element.
20
+ *
21
+ * Takes the first 31 bytes (248 bits) of the hash to ensure the result
22
+ * is always less than FIELD_PRIME (~254 bits but with leading structure).
23
+ * Then reduces mod FIELD_PRIME for safety.
24
+ *
25
+ * @param hash - Hex-encoded hash string (at least 62 hex chars / 31 bytes)
26
+ * @returns A bigint in the BN254 scalar field
27
+ */
28
+ export declare function hashToField(hash: HashHex): bigint;
29
+ /**
30
+ * Convert a bigint field element to a hex string (zero-padded to 64 chars).
31
+ */
32
+ export declare function fieldToHex(value: bigint): string;
33
+ //# sourceMappingURL=poseidon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"poseidon.d.ts","sourceRoot":"","sources":["../src/poseidon.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,WAAW,iFACwD,CAAC;AAqKjF;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CA0CrD;AAiCD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CASjD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKhD"}
@@ -0,0 +1,67 @@
1
+ import type { HashHex } from '@nobulex/crypto';
2
+ /**
3
+ * A zero-knowledge compliance proof attesting that audit log entries
4
+ * satisfy the constraints defined in a covenant.
5
+ */
6
+ export interface ComplianceProof {
7
+ /** Schema version for forward compatibility */
8
+ version: '1.0';
9
+ /** The covenant this proof is generated for */
10
+ covenantId: HashHex;
11
+ /** Poseidon commitment over all audit log entries */
12
+ auditLogCommitment: HashHex;
13
+ /** Poseidon commitment over the constraint set */
14
+ constraintCommitment: HashHex;
15
+ /** The proof value (Poseidon hash of commitments for v0.1) */
16
+ proof: string;
17
+ /** Public inputs visible to any verifier */
18
+ publicInputs: string[];
19
+ /** Which proof system was used */
20
+ proofSystem: 'poseidon_hash' | 'groth16' | 'plonk';
21
+ /** ISO 8601 timestamp when the proof was generated */
22
+ generatedAt: string;
23
+ /** Number of audit entries covered by this proof */
24
+ entryCount: number;
25
+ }
26
+ /**
27
+ * Result of verifying a compliance proof.
28
+ */
29
+ export interface ProofVerificationResult {
30
+ /** Whether the proof is valid */
31
+ valid: boolean;
32
+ /** The covenant the proof claims to cover */
33
+ covenantId: HashHex;
34
+ /** Number of entries covered */
35
+ entryCount: number;
36
+ /** Detailed error messages if verification failed */
37
+ errors: string[];
38
+ }
39
+ /**
40
+ * Options for generating a compliance proof.
41
+ */
42
+ export interface ProofGenerationOptions {
43
+ /** The covenant ID to bind this proof to */
44
+ covenantId: HashHex;
45
+ /** The constraint definitions (CCL source or canonical string) */
46
+ constraints: string;
47
+ /** Audit log entries to prove compliance over */
48
+ auditEntries: AuditEntryData[];
49
+ /** Proof system to use (only poseidon_hash in v0.1) */
50
+ proofSystem?: 'poseidon_hash';
51
+ }
52
+ /**
53
+ * Minimal audit entry data needed for proof generation.
54
+ */
55
+ export interface AuditEntryData {
56
+ /** The action that was taken */
57
+ action: string;
58
+ /** The resource the action targeted */
59
+ resource: string;
60
+ /** The enforcement outcome */
61
+ outcome: 'EXECUTED' | 'DENIED' | 'IMPOSSIBLE';
62
+ /** ISO 8601 timestamp of the action */
63
+ timestamp: string;
64
+ /** SHA-256 hash of the full audit entry */
65
+ hash: HashHex;
66
+ }
67
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/C;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,+CAA+C;IAC/C,OAAO,EAAE,KAAK,CAAC;IACf,+CAA+C;IAC/C,UAAU,EAAE,OAAO,CAAC;IACpB,qDAAqD;IACrD,kBAAkB,EAAE,OAAO,CAAC;IAC5B,kDAAkD;IAClD,oBAAoB,EAAE,OAAO,CAAC;IAC9B,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,kCAAkC;IAClC,WAAW,EAAE,eAAe,GAAG,SAAS,GAAG,OAAO,CAAC;IACnD,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,iCAAiC;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,6CAA6C;IAC7C,UAAU,EAAE,OAAO,CAAC;IACpB,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,4CAA4C;IAC5C,UAAU,EAAE,OAAO,CAAC;IACpB,kEAAkE;IAClE,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,uDAAuD;IACvD,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,OAAO,EAAE,UAAU,GAAG,QAAQ,GAAG,YAAY,CAAC;IAC9C,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,IAAI,EAAE,OAAO,CAAC;CACf"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@nobulex/proof",
3
+ "version": "0.1.0",
4
+ "description": "Proof generation and verification for the Stele covenant framework",
5
+ "license": "MIT",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "sideEffects": false,
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/agbusiness195/Stele.git",
30
+ "directory": "packages/proof"
31
+ },
32
+ "keywords": [
33
+ "stele",
34
+ "covenant",
35
+ "ai-accountability",
36
+ "proof",
37
+ "verification",
38
+ "merkle"
39
+ ],
40
+ "scripts": {
41
+ "build": "tsup src/index.ts --format cjs,esm && tsc --emitDeclarationOnly --outDir dist",
42
+ "typecheck": "tsc --noEmit",
43
+ "bundle": "tsup src/index.ts --format cjs,esm",
44
+ "dev": "tsup src/index.ts --format cjs,esm --watch",
45
+ "prepublishOnly": "npm run build"
46
+ },
47
+ "dependencies": {
48
+ "@nobulex/crypto": "0.1.0",
49
+ "@nobulex/ccl": "0.1.0"
50
+ }
51
+ }