@bolyra/sdk 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +63 -0
  3. package/README.md +2 -2
  4. package/dist/delegation.d.ts +64 -16
  5. package/dist/delegation.d.ts.map +1 -1
  6. package/dist/delegation.js +200 -17
  7. package/dist/delegation.js.map +1 -1
  8. package/dist/errors.d.ts +12 -0
  9. package/dist/errors.d.ts.map +1 -1
  10. package/dist/errors.js +32 -1
  11. package/dist/errors.js.map +1 -1
  12. package/dist/handshake.d.ts +2 -0
  13. package/dist/handshake.d.ts.map +1 -1
  14. package/dist/handshake.js +55 -13
  15. package/dist/handshake.js.map +1 -1
  16. package/dist/identity.d.ts +24 -0
  17. package/dist/identity.d.ts.map +1 -1
  18. package/dist/identity.js +46 -0
  19. package/dist/identity.js.map +1 -1
  20. package/dist/index.d.ts +8 -3
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +26 -3
  23. package/dist/index.js.map +1 -1
  24. package/dist/model-binding.d.ts +113 -0
  25. package/dist/model-binding.d.ts.map +1 -0
  26. package/dist/model-binding.js +195 -0
  27. package/dist/model-binding.js.map +1 -0
  28. package/dist/offchain.d.ts +89 -0
  29. package/dist/offchain.d.ts.map +1 -0
  30. package/dist/offchain.js +300 -0
  31. package/dist/offchain.js.map +1 -0
  32. package/dist/prover.d.ts +21 -0
  33. package/dist/prover.d.ts.map +1 -0
  34. package/dist/prover.js +171 -0
  35. package/dist/prover.js.map +1 -0
  36. package/dist/types.d.ts +29 -0
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/utils.d.ts +4 -0
  39. package/dist/utils.d.ts.map +1 -1
  40. package/dist/utils.js +14 -0
  41. package/dist/utils.js.map +1 -1
  42. package/package.json +5 -3
  43. package/src/delegation.ts +268 -30
  44. package/src/errors.ts +46 -0
  45. package/src/handshake.ts +69 -20
  46. package/src/identity.ts +55 -1
  47. package/src/index.ts +29 -2
  48. package/src/offchain.ts +344 -0
  49. package/src/prover.ts +178 -0
  50. package/src/types.ts +32 -0
  51. package/src/utils.ts +23 -0
package/src/prover.ts ADDED
@@ -0,0 +1,178 @@
1
+ import * as snarkjs from 'snarkjs';
2
+ import * as path from 'path';
3
+ import * as fs from 'fs';
4
+ import * as os from 'os';
5
+ import { execFileSync, execFile } from 'child_process';
6
+ import { promisify } from 'util';
7
+
8
+ const execFileAsync = promisify(execFile);
9
+ import { Proof } from './types';
10
+
11
+ /**
12
+ * Prover backend selection.
13
+ * - 'auto' : use rapidsnark if available, else snarkjs
14
+ * - 'rapidsnark' : require rapidsnark, throw if missing
15
+ * - 'snarkjs' : always use snarkjs (slower, pure JS)
16
+ */
17
+ export type ProverBackend = 'auto' | 'rapidsnark' | 'snarkjs';
18
+
19
+ let cachedRapidsnarkPath: string | null | undefined = undefined;
20
+
21
+ // Cache witness calculators by wasm path (built once, reused for all proofs).
22
+ // Caching saves ~37ms/proof by avoiding WASM re-instantiation + file re-read.
23
+ //
24
+ // The calculator is a stateful WebAssembly instance — concurrent calls on the
25
+ // same instance race. We serialize per wasm path via a promise chain.
26
+ interface WitnessCalculator {
27
+ calculateWTNSBin(input: Record<string, unknown>, sanityCheck: number): Promise<Uint8Array>;
28
+ }
29
+ const wcCache = new Map<string, Promise<WitnessCalculator>>();
30
+ const wcQueue = new Map<string, Promise<unknown>>();
31
+
32
+ function getWitnessCalculator(wasmPath: string): Promise<WitnessCalculator> {
33
+ const cached = wcCache.get(wasmPath);
34
+ if (cached) return cached;
35
+ const promise = (async () => {
36
+ // witness_calculator.js sits next to the .wasm in circuit_js/
37
+ const wcDir = path.dirname(wasmPath);
38
+ const builderPath = path.join(wcDir, 'witness_calculator.js');
39
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
40
+ const builder = require(builderPath);
41
+ const wasmBuf = fs.readFileSync(wasmPath);
42
+ return (await builder(wasmBuf)) as WitnessCalculator;
43
+ })();
44
+ wcCache.set(wasmPath, promise);
45
+ return promise;
46
+ }
47
+
48
+ /**
49
+ * Compute a witness using the cached calculator for `wasmPath`, serialized so
50
+ * concurrent calls on the same wasm don't race on the shared WASM instance.
51
+ */
52
+ async function computeWitness(
53
+ wasmPath: string,
54
+ input: Record<string, unknown>,
55
+ ): Promise<Uint8Array> {
56
+ const wc = await getWitnessCalculator(wasmPath);
57
+ const prev = wcQueue.get(wasmPath) ?? Promise.resolve();
58
+ const next = prev.then(() => wc.calculateWTNSBin(input, 0));
59
+ // Keep the chain alive but swallow errors so a failing call doesn't poison the queue.
60
+ wcQueue.set(
61
+ wasmPath,
62
+ next.catch(() => undefined),
63
+ );
64
+ return next;
65
+ }
66
+
67
+ /** Find the rapidsnark prover binary, or return null if not available. */
68
+ function findRapidsnarkBinary(): string | null {
69
+ if (cachedRapidsnarkPath !== undefined) return cachedRapidsnarkPath;
70
+
71
+ // 1) Explicit env override
72
+ if (process.env.BOLYRA_RAPIDSNARK) {
73
+ if (fs.existsSync(process.env.BOLYRA_RAPIDSNARK)) {
74
+ cachedRapidsnarkPath = process.env.BOLYRA_RAPIDSNARK;
75
+ return cachedRapidsnarkPath;
76
+ }
77
+ }
78
+
79
+ // 2) Bundled in circuits/build/rapidsnark_prover (matches benchmark setup)
80
+ const bundled = path.join(__dirname, '../../circuits/build/rapidsnark_prover');
81
+ if (fs.existsSync(bundled)) {
82
+ cachedRapidsnarkPath = bundled;
83
+ return cachedRapidsnarkPath;
84
+ }
85
+
86
+ // 3) PATH lookup for `prover` or `rapidsnark`
87
+ for (const name of ['rapidsnark_prover', 'rapidsnark', 'prover']) {
88
+ try {
89
+ const out = execFileSync('which', [name], { encoding: 'utf8' }).trim();
90
+ if (out) {
91
+ cachedRapidsnarkPath = out;
92
+ return cachedRapidsnarkPath;
93
+ }
94
+ } catch {
95
+ // not in PATH
96
+ }
97
+ }
98
+
99
+ cachedRapidsnarkPath = null;
100
+ return null;
101
+ }
102
+
103
+ /** Generate a Groth16 proof using rapidsnark (witness gen via snarkjs WASM). */
104
+ async function proveWithRapidsnark(
105
+ input: Record<string, unknown>,
106
+ wasmPath: string,
107
+ zkeyPath: string,
108
+ binary: string,
109
+ ): Promise<Proof> {
110
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'bolyra-rs-'));
111
+ try {
112
+ const wtnsPath = path.join(tmp, 'witness.wtns');
113
+ const proofPath = path.join(tmp, 'proof.json');
114
+ const publicPath = path.join(tmp, 'public.json');
115
+
116
+ const wtnsBuf = await computeWitness(wasmPath, input);
117
+ fs.writeFileSync(wtnsPath, Buffer.from(wtnsBuf));
118
+ // Async exec so concurrent proofs (e.g., human + agent in a handshake)
119
+ // actually run in parallel instead of serializing on the event loop.
120
+ await execFileAsync(binary, [zkeyPath, wtnsPath, proofPath, publicPath]);
121
+
122
+ const proof = JSON.parse(fs.readFileSync(proofPath, 'utf8'));
123
+ const publicSignals = JSON.parse(fs.readFileSync(publicPath, 'utf8'));
124
+ return { proof, publicSignals };
125
+ } finally {
126
+ try {
127
+ fs.rmSync(tmp, { recursive: true, force: true });
128
+ } catch {
129
+ // best-effort cleanup
130
+ }
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Generate a Groth16 proof using the fastest available backend.
136
+ * rapidsnark is ~5x faster than snarkjs but requires the native binary.
137
+ *
138
+ * @param input - Circuit input (string-encoded bigints)
139
+ * @param wasmPath - Path to circuit_js/circuit.wasm (witness generator)
140
+ * @param zkeyPath - Path to circuit_final.zkey
141
+ * @param backend - 'auto' (default), 'rapidsnark', or 'snarkjs'
142
+ */
143
+ export async function proveGroth16(
144
+ input: Record<string, unknown>,
145
+ wasmPath: string,
146
+ zkeyPath: string,
147
+ backend: ProverBackend = 'auto',
148
+ ): Promise<Proof> {
149
+ if (backend === 'snarkjs') {
150
+ const { proof, publicSignals } = await snarkjs.groth16.fullProve(input, wasmPath, zkeyPath);
151
+ return { proof, publicSignals };
152
+ }
153
+
154
+ if (backend === 'rapidsnark') {
155
+ const bin = findRapidsnarkBinary();
156
+ if (!bin) {
157
+ throw new Error(
158
+ 'rapidsnark requested but not found. Set BOLYRA_RAPIDSNARK=/path/to/prover, place binary at circuits/build/rapidsnark_prover, or install on PATH.',
159
+ );
160
+ }
161
+ return proveWithRapidsnark(input, wasmPath, zkeyPath, bin);
162
+ }
163
+
164
+ // auto: try rapidsnark, fall back to snarkjs
165
+ const bin = findRapidsnarkBinary();
166
+ if (bin) {
167
+ return proveWithRapidsnark(input, wasmPath, zkeyPath, bin);
168
+ }
169
+ const { proof, publicSignals } = await snarkjs.groth16.fullProve(input, wasmPath, zkeyPath);
170
+ return { proof, publicSignals };
171
+ }
172
+
173
+ /** Returns the active backend that would be used (for diagnostics/logging). */
174
+ export function activeProverBackend(backend: ProverBackend = 'auto'): 'rapidsnark' | 'snarkjs' {
175
+ if (backend === 'snarkjs') return 'snarkjs';
176
+ if (backend === 'rapidsnark') return 'rapidsnark';
177
+ return findRapidsnarkBinary() ? 'rapidsnark' : 'snarkjs';
178
+ }
package/src/types.ts CHANGED
@@ -52,16 +52,48 @@ export interface DelegationResult {
52
52
  newScopeCommitment: bigint;
53
53
  /** Delegation nullifier (unique per delegation per session) */
54
54
  delegationNullifier: bigint;
55
+ /** Delegatee Merkle root — checked on-chain against agentRootExists (CIP-1) */
56
+ delegateeMerkleRoot: bigint;
55
57
  /** Hop number in the chain (0-indexed) */
56
58
  hopIndex: number;
57
59
  }
58
60
 
61
+ /** Optional Merkle inclusion proof for the delegatee's enrollment in agentTree.
62
+ * If omitted, delegate() defaults to the single-leaf pattern (depth 1, index 0,
63
+ * 20 zero siblings) — matches the conformance test layout. Real deployments
64
+ * pass the actual proof against the live agentTree.
65
+ */
66
+ export interface DelegateeMerkleProof {
67
+ length: number;
68
+ index: number;
69
+ /** Always length 20 (Delegation circuit MAX_DEPTH) */
70
+ siblings: bigint[];
71
+ }
72
+
59
73
  /** Proof with public signals ready for on-chain verification */
60
74
  export interface Proof {
61
75
  proof: any; // snarkjs proof object
62
76
  publicSignals: string[];
63
77
  }
64
78
 
79
+ /** Result of an off-chain handshake verification (batched for later on-chain checkpoint) */
80
+ export interface OffchainVerificationResult extends HandshakeResult {
81
+ /** Index of this session within the current batch */
82
+ batchIndex: number;
83
+ /** Merkle root of the batch at the time this result was added (undefined until batch is sealed) */
84
+ batchRoot?: bigint;
85
+ }
86
+
87
+ /** On-chain checkpoint representing a batch of off-chain verified sessions */
88
+ export interface BatchCheckpoint {
89
+ /** Poseidon Merkle root of all session commitments in the batch */
90
+ root: bigint;
91
+ /** Unix timestamp (seconds) when the batch was posted on-chain */
92
+ timestamp: number;
93
+ /** Number of sessions included in this batch */
94
+ sessionCount: number;
95
+ }
96
+
65
97
  /** Configuration for the SDK */
66
98
  export interface BolyraConfig {
67
99
  /** RPC URL for the target chain (default: Base Sepolia) */
package/src/utils.ts CHANGED
@@ -24,6 +24,29 @@ export async function poseidon2(a: bigint, b: bigint): Promise<bigint> {
24
24
  return _F.toObject(hash);
25
25
  }
26
26
 
27
+ /** Poseidon hash with 3 inputs. Returns a bigint. */
28
+ export async function poseidon3(
29
+ a: bigint,
30
+ b: bigint,
31
+ c: bigint,
32
+ ): Promise<bigint> {
33
+ await ensureCrypto();
34
+ const hash = _poseidon([a, b, c]);
35
+ return _F.toObject(hash);
36
+ }
37
+
38
+ /** Poseidon hash with 4 inputs. Returns a bigint. */
39
+ export async function poseidon4(
40
+ a: bigint,
41
+ b: bigint,
42
+ c: bigint,
43
+ d: bigint,
44
+ ): Promise<bigint> {
45
+ await ensureCrypto();
46
+ const hash = _poseidon([a, b, c, d]);
47
+ return _F.toObject(hash);
48
+ }
49
+
27
50
  /** Poseidon hash with 5 inputs. Returns a bigint. */
28
51
  export async function poseidon5(
29
52
  a: bigint,