@utxopia/sdk 0.1.0-alpha.1

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 (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +171 -0
  3. package/package.json +108 -0
  4. package/packages/btc-client/src/esplora-client.ts +153 -0
  5. package/packages/btc-client/src/index.ts +3 -0
  6. package/packages/btc-client/src/op-return.ts +93 -0
  7. package/packages/btc-client/src/types.ts +112 -0
  8. package/packages/sdk/README.md +277 -0
  9. package/packages/sdk/dist/announcement-client.d.ts +64 -0
  10. package/packages/sdk/dist/announcement-client.js +337 -0
  11. package/packages/sdk/dist/auditor-ciphertext.d.ts +72 -0
  12. package/packages/sdk/dist/auditor-ciphertext.js +135 -0
  13. package/packages/sdk/dist/auditor.d.ts +115 -0
  14. package/packages/sdk/dist/auditor.js +292 -0
  15. package/packages/sdk/dist/bitcoin/ika.d.ts +47 -0
  16. package/packages/sdk/dist/bitcoin/ika.js +74 -0
  17. package/packages/sdk/dist/bitcoin/index.d.ts +2 -0
  18. package/packages/sdk/dist/bitcoin/index.js +1 -0
  19. package/packages/sdk/dist/bound-params.d.ts +96 -0
  20. package/packages/sdk/dist/bound-params.js +249 -0
  21. package/packages/sdk/dist/chadbuffer.d.ts +124 -0
  22. package/packages/sdk/dist/chadbuffer.js +405 -0
  23. package/packages/sdk/dist/claim-link.d.ts +30 -0
  24. package/packages/sdk/dist/claim-link.js +50 -0
  25. package/packages/sdk/dist/client.d.ts +233 -0
  26. package/packages/sdk/dist/client.js +416 -0
  27. package/packages/sdk/dist/commitment-tree.d.ts +268 -0
  28. package/packages/sdk/dist/commitment-tree.js +564 -0
  29. package/packages/sdk/dist/config.d.ts +177 -0
  30. package/packages/sdk/dist/config.js +426 -0
  31. package/packages/sdk/dist/core/esplora.d.ts +110 -0
  32. package/packages/sdk/dist/core/esplora.js +187 -0
  33. package/packages/sdk/dist/core/mempool.d.ts +60 -0
  34. package/packages/sdk/dist/core/mempool.js +107 -0
  35. package/packages/sdk/dist/crypto-babyjub.d.ts +105 -0
  36. package/packages/sdk/dist/crypto-babyjub.js +312 -0
  37. package/packages/sdk/dist/crypto-ed25519.d.ts +116 -0
  38. package/packages/sdk/dist/crypto-ed25519.js +235 -0
  39. package/packages/sdk/dist/crypto.d.ts +59 -0
  40. package/packages/sdk/dist/crypto.js +140 -0
  41. package/packages/sdk/dist/event-client.d.ts +64 -0
  42. package/packages/sdk/dist/event-client.js +165 -0
  43. package/packages/sdk/dist/events.d.ts +127 -0
  44. package/packages/sdk/dist/events.js +305 -0
  45. package/packages/sdk/dist/explorer.d.ts +86 -0
  46. package/packages/sdk/dist/explorer.js +197 -0
  47. package/packages/sdk/dist/index.d.ts +61 -0
  48. package/packages/sdk/dist/index.js +216 -0
  49. package/packages/sdk/dist/instructions.d.ts +1125 -0
  50. package/packages/sdk/dist/instructions.js +1760 -0
  51. package/packages/sdk/dist/keys.d.ts +467 -0
  52. package/packages/sdk/dist/keys.js +799 -0
  53. package/packages/sdk/dist/logger.d.ts +10 -0
  54. package/packages/sdk/dist/logger.js +40 -0
  55. package/packages/sdk/dist/magicblock.d.ts +90 -0
  56. package/packages/sdk/dist/magicblock.js +164 -0
  57. package/packages/sdk/dist/merkle.d.ts +82 -0
  58. package/packages/sdk/dist/merkle.js +141 -0
  59. package/packages/sdk/dist/note.d.ts +348 -0
  60. package/packages/sdk/dist/note.js +483 -0
  61. package/packages/sdk/dist/pda.d.ts +160 -0
  62. package/packages/sdk/dist/pda.js +384 -0
  63. package/packages/sdk/dist/pool-state.d.ts +100 -0
  64. package/packages/sdk/dist/pool-state.js +126 -0
  65. package/packages/sdk/dist/poseidon.d.ts +53 -0
  66. package/packages/sdk/dist/poseidon.js +136 -0
  67. package/packages/sdk/dist/prover/index.d.ts +10 -0
  68. package/packages/sdk/dist/prover/index.js +10 -0
  69. package/packages/sdk/dist/prover/mobile.d.ts +45 -0
  70. package/packages/sdk/dist/prover/mobile.js +227 -0
  71. package/packages/sdk/dist/prover/web.d.ts +136 -0
  72. package/packages/sdk/dist/prover/web.js +597 -0
  73. package/packages/sdk/dist/psbt.d.ts +73 -0
  74. package/packages/sdk/dist/psbt.js +202 -0
  75. package/packages/sdk/dist/selective-disclosure.d.ts +181 -0
  76. package/packages/sdk/dist/selective-disclosure.js +172 -0
  77. package/packages/sdk/dist/sender-memo.d.ts +149 -0
  78. package/packages/sdk/dist/sender-memo.js +250 -0
  79. package/packages/sdk/dist/sns-resolver.d.ts +121 -0
  80. package/packages/sdk/dist/sns-resolver.js +229 -0
  81. package/packages/sdk/dist/solana/connection.d.ts +77 -0
  82. package/packages/sdk/dist/solana/connection.js +133 -0
  83. package/packages/sdk/dist/solana/priority-fee.d.ts +78 -0
  84. package/packages/sdk/dist/solana/priority-fee.js +137 -0
  85. package/packages/sdk/dist/stealth.d.ts +429 -0
  86. package/packages/sdk/dist/stealth.js +703 -0
  87. package/packages/sdk/dist/taproot.d.ts +172 -0
  88. package/packages/sdk/dist/taproot.js +494 -0
  89. package/packages/sdk/dist/token-registry.d.ts +96 -0
  90. package/packages/sdk/dist/token-registry.js +122 -0
  91. package/packages/sdk/dist/utils/encoding.d.ts +19 -0
  92. package/packages/sdk/dist/utils/encoding.js +29 -0
  93. package/packages/sdk/dist/vk-registry.d.ts +100 -0
  94. package/packages/sdk/dist/vk-registry.js +212 -0
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Poseidon Hash - BN254 compatible with circom circuits and Solana's sol_poseidon
3
+ *
4
+ * Uses poseidon-lite: pure JavaScript, zero dependencies, works in
5
+ * Browser, Node.js, and React Native (no WASM required).
6
+ *
7
+ * UNIFIED MODEL:
8
+ * - Commitment = Poseidon(pub_key_x, amount)
9
+ * - Nullifier = Poseidon(priv_key, leaf_index)
10
+ * - Nullifier Hash = Poseidon(nullifier)
11
+ */
12
+ import { poseidon1, poseidon2, poseidon3, poseidon4, poseidon5, poseidon6, poseidon7, poseidon8, poseidon9, poseidon10, poseidon11, poseidon12, poseidon13, poseidon14, poseidon15, poseidon16, } from "poseidon-lite";
13
+ // Lookup table for poseidon hash by input count. JoinSplit currently needs at
14
+ // most 12 (2 fixed fields + N+M<=10); the larger arities remain available to
15
+ // other SDK callers.
16
+ const poseidonFns = [
17
+ undefined, // 0 — unused
18
+ poseidon1, // 1 input
19
+ poseidon2, // 2 inputs
20
+ poseidon3, // 3 inputs
21
+ poseidon4, // 4 inputs
22
+ poseidon5, // 5 inputs
23
+ poseidon6, // 6 inputs
24
+ poseidon7, // 7 inputs
25
+ poseidon8, // 8 inputs
26
+ poseidon9, // 9 inputs
27
+ poseidon10, // 10 inputs
28
+ poseidon11, // 11 inputs
29
+ poseidon12, // 12 inputs
30
+ poseidon13, // 13 inputs
31
+ poseidon14, // 14 inputs
32
+ poseidon15, // 15 inputs
33
+ poseidon16, // 16 inputs
34
+ ];
35
+ /**
36
+ * Initialize poseidon (no-op — poseidon-lite needs no initialization)
37
+ * Kept for backward API compatibility.
38
+ */
39
+ export async function initPoseidon() {
40
+ // poseidon-lite is synchronous and ready immediately
41
+ }
42
+ /**
43
+ * Whether Poseidon can hash right now.
44
+ *
45
+ * Always true: poseidon-lite is pure synchronous JS with nothing to load, so `initPoseidon()`
46
+ * exists only so callers written against a WASM-backed hasher keep working. Kept honest rather
47
+ * than tracking a flag — the previous version lived in note.ts over its own state and reported
48
+ * false no matter what you called.
49
+ */
50
+ export function isPoseidonReady() {
51
+ return true;
52
+ }
53
+ /*
54
+ * The async forms of these hashes are gone: poseidonHash, computeUnifiedCommitment,
55
+ * computeNullifier, hashNullifier, computeMPK, computeNPK, computeJoinSplitCommitment and
56
+ * computeJoinSplitNullifier were each `return xSync(...)`. poseidon-lite is pure synchronous JS,
57
+ * so awaiting them promised work that never happened and pushed async up through every caller
58
+ * for nothing — which is why all eight were unused outside this file while the *Sync forms are
59
+ * called 130-odd times. Use the *Sync functions.
60
+ */
61
+ /**
62
+ * Synchronous Poseidon hash (no initialization required with poseidon-lite)
63
+ */
64
+ export function poseidonHashSync(inputs) {
65
+ const n = inputs.length;
66
+ if (n < 1 || n > 16) {
67
+ throw new Error(`Poseidon: unsupported input count ${n} (expected 1-16)`);
68
+ }
69
+ return poseidonFns[n](inputs);
70
+ }
71
+ // BN254 scalar field prime
72
+ export const BN254_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
73
+ // ============================================================================
74
+ // Unified Model Functions (Primary API) - Async versions
75
+ // ============================================================================
76
+ // ============================================================================
77
+ // Synchronous versions (internal use only - require prior initPoseidon call)
78
+ // These are used by prover.ts which needs sync computation for circuit inputs
79
+ // ============================================================================
80
+ export function computeUnifiedCommitmentSync(pubKeyX, amount) {
81
+ return poseidonHashSync([pubKeyX, amount]);
82
+ }
83
+ export function computeNullifierSync(privKey, leafIndex) {
84
+ return poseidonHashSync([privKey, leafIndex]);
85
+ }
86
+ export function hashNullifierSync(nullifier) {
87
+ return poseidonHashSync([nullifier]);
88
+ }
89
+ // ============================================================================
90
+ // JoinSplit Primitives (Railgun-aligned 3-key model)
91
+ // ============================================================================
92
+ export function computeMPKSync(pkX, pkY, nullifyingKey) {
93
+ return poseidonHashSync([pkX, pkY, nullifyingKey]);
94
+ }
95
+ export function computeNPKSync(mpk, random) {
96
+ return poseidonHashSync([mpk, random]);
97
+ }
98
+ export function computeJoinSplitCommitmentSync(npk, token, amount) {
99
+ return poseidonHashSync([npk, token, amount]);
100
+ }
101
+ export function computeJoinSplitNullifierSync(nullifyingKey, leafIndex) {
102
+ return poseidonHashSync([nullifyingKey, leafIndex]);
103
+ }
104
+ // ============================================================================
105
+ // Multi-Token Utilities
106
+ // ============================================================================
107
+ /**
108
+ * Reduce a 32-byte big-endian value to BN254 scalar field (mod p).
109
+ * Must match on-chain reduce_to_field_exact in crypto.rs.
110
+ */
111
+ export function reduceToField(bytes) {
112
+ // Convert big-endian bytes to bigint
113
+ let value = 0n;
114
+ for (const b of bytes) {
115
+ value = (value << 8n) | BigInt(b);
116
+ }
117
+ return value % BN254_SCALAR_FIELD;
118
+ }
119
+ /**
120
+ * Compute token_id from mint address: Poseidon(reduce_to_field(mint), 0)
121
+ *
122
+ * Must match on-chain compute_token_id in crypto.rs which uses
123
+ * poseidon2_hash(reduce_to_field_exact(mint), [0u8; 32]).
124
+ */
125
+ export function computeTokenId(mintBytes) {
126
+ const reduced = reduceToField(mintBytes);
127
+ return poseidonHashSync([reduced, 0n]);
128
+ }
129
+ /**
130
+ * Convenience: compute token_id from a Solana address string
131
+ */
132
+ export function computeTokenIdFromAddress(mintAddress) {
133
+ // Base58 decode — import from @solana/kit if available, otherwise use raw bytes
134
+ // For now, caller should pass raw bytes via computeTokenId
135
+ throw new Error("Use computeTokenId(mintBytes) with raw pubkey bytes");
136
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Prover Subpath
3
+ *
4
+ * Auto-detects platform and exports the appropriate prover backend.
5
+ * For explicit imports, use:
6
+ * - @utxopia/sdk/prover/web for browser/Node.js snarkjs Groth16 prover
7
+ * - @utxopia/sdk/prover/mobile for React Native Groth16 prover
8
+ */
9
+ export * from "./web";
10
+ export type { MerkleProofInput, ProofData, CircuitType, JoinSplitProofInputs, } from "./web";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Prover Subpath
3
+ *
4
+ * Auto-detects platform and exports the appropriate prover backend.
5
+ * For explicit imports, use:
6
+ * - @utxopia/sdk/prover/web for browser/Node.js snarkjs Groth16 prover
7
+ * - @utxopia/sdk/prover/mobile for React Native Groth16 prover
8
+ */
9
+ // Re-export everything from the web prover (default for browser/Node.js)
10
+ export * from "./web";
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Mobile Prover for React Native (JoinSplit Architecture)
3
+ *
4
+ * Uses mopro-ffi native Groth16 prover for iOS/Android.
5
+ * Circuit .zkey files are resolved via an injected circuit resolver
6
+ * (bundled for tier 1+2, on-demand from Cloudflare R2 for others).
7
+ */
8
+ export type { MerkleProofInput, ProofData, CircuitType, JoinSplitProofInputs, } from "./web";
9
+ import type { ProofData, CircuitType, JoinSplitProofInputs } from "./web";
10
+ type CircuitResolver = (circuitName: string, onProgress?: (progress: number) => void) => Promise<string>;
11
+ /**
12
+ * Inject the circuit file resolver (called from the mobile app layer).
13
+ * The resolver maps a circuit name like "joinsplit_2x2" to the local
14
+ * file-system path of the corresponding .zkey file.
15
+ */
16
+ export declare function setCircuitResolver(resolver: CircuitResolver): void;
17
+ /**
18
+ * Initialise the native mopro-ffi prover.
19
+ * Safe to call multiple times — subsequent calls are no-ops.
20
+ */
21
+ export declare function initProver(): Promise<void>;
22
+ /**
23
+ * Check whether the native prover is available in the current runtime.
24
+ */
25
+ export declare function isProverAvailable(): Promise<boolean>;
26
+ /**
27
+ * Generate a JoinSplit Groth16 proof using the native mopro-ffi prover.
28
+ *
29
+ * @param inputs - JoinSplit proof inputs (typed) OR pre-formatted inputs object
30
+ * @param circuitType - Override circuit variant (default: derived from inputs)
31
+ * @param onProgress - Optional progress callback (0..1)
32
+ */
33
+ export declare function generateJoinSplitProof(inputs: JoinSplitProofInputs | any, circuitType?: string, onProgress?: (progress: number) => void): Promise<ProofData>;
34
+ /**
35
+ * Convert a ProofData to raw 256-byte proof for on-chain submission.
36
+ */
37
+ export declare function proofToBytes(proof: ProofData): Uint8Array;
38
+ /**
39
+ * Check if a circuit variant is available (resolver can find the .zkey).
40
+ */
41
+ export declare function circuitExists(circuitType?: CircuitType | string): Promise<boolean>;
42
+ /**
43
+ * Reset module state (for testing or hot-reload scenarios).
44
+ */
45
+ export declare function cleanup(): Promise<void>;
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Mobile Prover for React Native (JoinSplit Architecture)
3
+ *
4
+ * Uses mopro-ffi native Groth16 prover for iOS/Android.
5
+ * Circuit .zkey files are resolved via an injected circuit resolver
6
+ * (bundled for tier 1+2, on-demand from Cloudflare R2 for others).
7
+ */
8
+ import { computeJoinSplitNullifierSync, computeJoinSplitCommitmentSync, } from "../poseidon";
9
+ let moproModule = null;
10
+ let circuitResolver = null;
11
+ let proverReady = false;
12
+ // ---------------------------------------------------------------------------
13
+ // Configuration
14
+ // ---------------------------------------------------------------------------
15
+ /**
16
+ * Inject the circuit file resolver (called from the mobile app layer).
17
+ * The resolver maps a circuit name like "joinsplit_2x2" to the local
18
+ * file-system path of the corresponding .zkey file.
19
+ */
20
+ export function setCircuitResolver(resolver) {
21
+ circuitResolver = resolver;
22
+ }
23
+ // ---------------------------------------------------------------------------
24
+ // Initialisation
25
+ // ---------------------------------------------------------------------------
26
+ /**
27
+ * Initialise the native mopro-ffi prover.
28
+ * Safe to call multiple times — subsequent calls are no-ops.
29
+ */
30
+ export async function initProver() {
31
+ if (proverReady)
32
+ return;
33
+ try {
34
+ // Dynamic require so bundlers don't resolve this at compile time
35
+ // when the native module isn't installed yet.
36
+ moproModule = require("mopro-ffi");
37
+ }
38
+ catch {
39
+ throw new Error("mopro-ffi native module not found. " +
40
+ "Run the ubrn build step first (see MoproBindings/package.json).");
41
+ }
42
+ proverReady = true;
43
+ console.log("[MobileProver] mopro-ffi native prover initialised");
44
+ }
45
+ /**
46
+ * Check whether the native prover is available in the current runtime.
47
+ */
48
+ export async function isProverAvailable() {
49
+ try {
50
+ if (!moproModule) {
51
+ moproModule = require("mopro-ffi");
52
+ }
53
+ return true;
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ // ---------------------------------------------------------------------------
60
+ // Proof generation
61
+ // ---------------------------------------------------------------------------
62
+ /**
63
+ * Generate a JoinSplit Groth16 proof using the native mopro-ffi prover.
64
+ *
65
+ * @param inputs - JoinSplit proof inputs (typed) OR pre-formatted inputs object
66
+ * @param circuitType - Override circuit variant (default: derived from inputs)
67
+ * @param onProgress - Optional progress callback (0..1)
68
+ */
69
+ export async function generateJoinSplitProof(inputs, circuitType, onProgress) {
70
+ if (!proverReady)
71
+ await initProver();
72
+ if (!circuitResolver) {
73
+ throw new Error("Circuit resolver not set. Call setCircuitResolver() before generating proofs.");
74
+ }
75
+ // Determine circuit variant
76
+ const nIn = inputs.nInputs ?? inputs.inputs?.length ?? 1;
77
+ const nOut = inputs.nOutputs ?? inputs.outputs?.length ?? 2;
78
+ const variant = circuitType ?? `joinsplit_${nIn}x${nOut}`;
79
+ console.log(`[MobileProver] Generating ${variant} Groth16 proof...`);
80
+ const startTime = Date.now();
81
+ // Resolve the .zkey path (may trigger a download)
82
+ onProgress?.(0);
83
+ const zkeyPath = await circuitResolver(variant, onProgress);
84
+ onProgress?.(0.1);
85
+ // Format inputs for mopro's flat Record<string, string[]> format
86
+ const flatInputs = formatCircuitInputs(inputs, nIn, nOut);
87
+ // Call the native prover
88
+ onProgress?.(0.2);
89
+ const result = await moproModule.generateCircomProof(zkeyPath, flatInputs);
90
+ onProgress?.(0.9);
91
+ const elapsed = Date.now() - startTime;
92
+ console.log(`[MobileProver] Proof generated in ${elapsed}ms`);
93
+ // Convert mopro result to our ProofData format
94
+ const proofData = convertMoproResult(result);
95
+ onProgress?.(1);
96
+ return proofData;
97
+ }
98
+ // ---------------------------------------------------------------------------
99
+ // Input formatting
100
+ // ---------------------------------------------------------------------------
101
+ /**
102
+ * Convert JoinSplitProofInputs to mopro's flat Record<string, string[]>.
103
+ *
104
+ * mopro expects every value as a string array — scalars become single-element
105
+ * arrays, vectors become multi-element arrays.
106
+ */
107
+ function formatCircuitInputs(inputs, nIn, nOut) {
108
+ const flat = {};
109
+ // If inputs are already in flat format, pass through
110
+ if (!inputs.merkleRoot && !inputs.inputs) {
111
+ const result = {};
112
+ for (const [key, value] of Object.entries(inputs)) {
113
+ if (Array.isArray(value)) {
114
+ result[key] = value.map((v) => String(v));
115
+ }
116
+ else {
117
+ result[key] = [String(value)];
118
+ }
119
+ }
120
+ return result;
121
+ }
122
+ // Typed JoinSplitProofInputs — convert to circuit signal names
123
+ const typed = inputs;
124
+ flat["merkleRoot"] = [typed.merkleRoot.toString()];
125
+ flat["boundParamsHash"] = [typed.boundParamsHash.toString()];
126
+ flat["token"] = [typed.token.toString()];
127
+ flat["publicKey"] = typed.publicKey.map((p) => p.toString());
128
+ flat["signature"] = typed.signature.map((s) => s.toString());
129
+ flat["nullifyingKey"] = [typed.nullifyingKey.toString()];
130
+ // Input arrays
131
+ flat["randomIn"] = typed.inputs.map((i) => i.random.toString());
132
+ flat["valueIn"] = typed.inputs.map((i) => i.value.toString());
133
+ flat["leavesIndices"] = typed.inputs.map((i) => i.leafIndex.toString());
134
+ // Output arrays
135
+ flat["npkOut"] = typed.outputs.map((o) => o.npk.toString());
136
+ flat["valueOut"] = typed.outputs.map((o) => o.value.toString());
137
+ // Compute nullifiers: Poseidon(nullifyingKey, leafIndex)
138
+ const nullifiers = [];
139
+ for (const inp of typed.inputs) {
140
+ nullifiers.push(computeJoinSplitNullifierSync(typed.nullifyingKey, inp.leafIndex));
141
+ }
142
+ flat["nullifiers"] = nullifiers.map((n) => n.toString());
143
+ // Compute output commitments: Poseidon(npk, token, value)
144
+ const commitmentsOut = [];
145
+ for (const out of typed.outputs) {
146
+ commitmentsOut.push(computeJoinSplitCommitmentSync(out.npk, typed.token, out.value));
147
+ }
148
+ flat["commitmentsOut"] = commitmentsOut.map((c) => c.toString());
149
+ // Flattened siblings pathElements[i][j]; circuit derives path bits from leavesIndices.
150
+ const pathElements = [];
151
+ for (const inp of typed.inputs) {
152
+ for (const sibling of inp.merkleProof.siblings) {
153
+ pathElements.push(sibling.toString());
154
+ }
155
+ }
156
+ flat["pathElements"] = pathElements;
157
+ return flat;
158
+ }
159
+ // ---------------------------------------------------------------------------
160
+ // Result conversion
161
+ // ---------------------------------------------------------------------------
162
+ /**
163
+ * Convert mopro's CircomProofResult to our ProofData format.
164
+ *
165
+ * mopro returns: { proof: { a: {x, y}, b: {x: [x0,x1], y: [y0,y1]}, c: {x, y} }, inputs: string[] }
166
+ * We need: { proof: Uint8Array(256), publicInputs: string[] }
167
+ */
168
+ function convertMoproResult(result) {
169
+ const { proof, inputs: publicInputs } = result;
170
+ const bytes = new Uint8Array(256);
171
+ // G1 point A (64 bytes): x, y
172
+ writeBigIntBE(bytes, 0, BigInt(proof.a.x), 32);
173
+ writeBigIntBE(bytes, 32, BigInt(proof.a.y), 32);
174
+ // G2 point B (128 bytes): [x_imag, x_real, y_imag, y_real]
175
+ // mopro b.x = [x0, x1], b.y = [y0, y1]
176
+ // On-chain layout: x_imag(x1), x_real(x0), y_imag(y1), y_real(y0)
177
+ writeBigIntBE(bytes, 64, BigInt(proof.b.x[1]), 32);
178
+ writeBigIntBE(bytes, 96, BigInt(proof.b.x[0]), 32);
179
+ writeBigIntBE(bytes, 128, BigInt(proof.b.y[1]), 32);
180
+ writeBigIntBE(bytes, 160, BigInt(proof.b.y[0]), 32);
181
+ // G1 point C (64 bytes): x, y
182
+ writeBigIntBE(bytes, 192, BigInt(proof.c.x), 32);
183
+ writeBigIntBE(bytes, 224, BigInt(proof.c.y), 32);
184
+ return {
185
+ proof: bytes,
186
+ publicInputs: publicInputs ?? [],
187
+ };
188
+ }
189
+ function writeBigIntBE(buf, offset, value, length) {
190
+ for (let i = length - 1; i >= 0; i--) {
191
+ buf[offset + i] = Number(value & 0xffn);
192
+ value >>= 8n;
193
+ }
194
+ }
195
+ // ---------------------------------------------------------------------------
196
+ // Utilities
197
+ // ---------------------------------------------------------------------------
198
+ /**
199
+ * Convert a ProofData to raw 256-byte proof for on-chain submission.
200
+ */
201
+ export function proofToBytes(proof) {
202
+ return proof.proof;
203
+ }
204
+ /**
205
+ * Check if a circuit variant is available (resolver can find the .zkey).
206
+ */
207
+ export async function circuitExists(circuitType) {
208
+ if (!circuitResolver)
209
+ return false;
210
+ const name = circuitType ?? "joinsplit_2x2";
211
+ try {
212
+ await circuitResolver(name);
213
+ return true;
214
+ }
215
+ catch {
216
+ return false;
217
+ }
218
+ }
219
+ /**
220
+ * Reset module state (for testing or hot-reload scenarios).
221
+ */
222
+ export async function cleanup() {
223
+ moproModule = null;
224
+ circuitResolver = null;
225
+ proverReady = false;
226
+ console.log("[MobileProver] Cleaned up");
227
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * WASM-based Groth16 Proof Generator for UTXOPIA
3
+ *
4
+ * Universal prover that works in both Browser and Node.js environments.
5
+ * Uses Groth16 proofs via snarkjs with lazy loading.
6
+ *
7
+ * JOINSPLIT MODEL:
8
+ * - Commitment = Poseidon(npk, token, amount)
9
+ * - Nullifier = Poseidon(nullifyingKey, leafIndex)
10
+ * - Signature = EdDSA-Poseidon over (merkleRoot, boundParamsHash, nullifiers..., commitmentsOut...)
11
+ */
12
+ import type { Address } from "@solana/kit";
13
+ export interface MerkleProofInput {
14
+ siblings: bigint[];
15
+ indices: number[];
16
+ }
17
+ export interface ProofData {
18
+ proof: Uint8Array;
19
+ publicInputs: string[];
20
+ verificationKey?: Uint8Array;
21
+ }
22
+ export type CircuitType = `joinsplit_${number}x${number}`;
23
+ /** Names of non-JoinSplit auxiliary circuits (selective disclosure). */
24
+ export type AuxCircuitName = "ownership" | "range_sum" | "range_sum_4" | "range_sum_16";
25
+ /**
26
+ * Set the base path for circuit artifacts
27
+ */
28
+ export declare function setCircuitPath(path: string): void;
29
+ /**
30
+ * Get the current circuit base path
31
+ */
32
+ export declare function getCircuitPath(): string;
33
+ /**
34
+ * Pin the circuit artifacts this app is willing to prove with.
35
+ *
36
+ * Worth doing even though it costs a hash per download. The `.wasm` is the witness generator:
37
+ * it is handed the spending key, the nullifying key, note randomness, amounts and the full
38
+ * Merkle path in the clear. It arrives over plain `fetch`, where subresource integrity does not
39
+ * apply, and CDNs typically serve it `immutable, max-age=31536000` — so a substituted file keeps
40
+ * working out of browser and edge caches long after the origin is cleaned up. The proofs it
41
+ * produces still verify, so nothing fails and no user notices.
42
+ *
43
+ * Digests must come from the consuming build (they change whenever circuits are rebuilt), which
44
+ * is why the SDK cannot ship them. Once set, verification is enforced and fails closed: an
45
+ * artifact with no recorded digest is refused rather than trusted.
46
+ */
47
+ export declare function setCircuitArtifactDigests(digests: Record<string, string> | null): void;
48
+ /**
49
+ * Download the selected JoinSplit artifacts into the browser cache and the
50
+ * prover's in-memory fastfile cache without generating a proof. Calling this
51
+ * while the user reviews a transaction removes CDN latency from the confirm
52
+ * path. Concurrent preloads and proofs share the same downloads.
53
+ */
54
+ export declare function preloadJoinSplitCircuit(nInputs: number, nOutputs: number): Promise<void>;
55
+ /**
56
+ * Initialize the prover (preloads snarkjs module)
57
+ */
58
+ export declare function initProver(): Promise<void>;
59
+ /**
60
+ * Generate a Groth16 proof for any circuit by name. Useful for non-JoinSplit
61
+ * circuits (ownership, range_sum) that follow the same artifact layout
62
+ * `<circuitBasePath>/<name>/<name>_js/<name>.wasm` + `<circuitBasePath>/<name>/<name>.zkey`.
63
+ *
64
+ * Returns `{ proof: 256 bytes, publicInputs }` matching `ProofData`.
65
+ */
66
+ export declare function generateGenericGroth16Proof(circuitName: string, inputs: Record<string, string | string[] | number[] | string[][] | number[][]>): Promise<ProofData>;
67
+ /**
68
+ * Check if prover is available in current environment
69
+ */
70
+ export declare function isProverAvailable(): Promise<boolean>;
71
+ /**
72
+ * Verify a Groth16 proof against a verifying key + public signals.
73
+ *
74
+ * Accepts the snarkjs-native shapes:
75
+ * - `vkey`: the JSON emitted by `snarkjs zkey export verificationkey`
76
+ * - `publicSignals`: array of decimal strings
77
+ * - `proof`: `{ pi_a, pi_b, pi_c, protocol, curve }` as produced by `snarkjs.groth16.fullProve`
78
+ *
79
+ * Lazy-loads snarkjs so calling code doesn't pay the bundle cost unless it
80
+ * verifies. Returns true on a valid proof, false on an invalid one; throws
81
+ * if snarkjs is missing or the inputs are structurally malformed.
82
+ */
83
+ export declare function verifyGroth16Proof(vkey: unknown, publicSignals: ReadonlyArray<string>, proof: unknown): Promise<boolean>;
84
+ /**
85
+ * JoinSplit proof inputs
86
+ */
87
+ export interface JoinSplitProofInputs {
88
+ nInputs: number;
89
+ nOutputs: number;
90
+ merkleRoot: bigint;
91
+ boundParamsHash: bigint;
92
+ token: bigint;
93
+ publicKey: [bigint, bigint];
94
+ signature: [bigint, bigint, bigint];
95
+ nullifyingKey: bigint;
96
+ inputs: Array<{
97
+ random: bigint;
98
+ value: bigint;
99
+ leafIndex: bigint;
100
+ merkleProof: {
101
+ siblings: bigint[];
102
+ indices: number[];
103
+ };
104
+ }>;
105
+ outputs: Array<{
106
+ npk: bigint;
107
+ value: bigint;
108
+ }>;
109
+ }
110
+ /**
111
+ * Generate a JoinSplit proof
112
+ *
113
+ * Unified prover for all JoinSplit variants.
114
+ * Selects the joinsplit_NxM circuit based on nInputs/nOutputs.
115
+ */
116
+ export declare function generateJoinSplitProof(inputs: JoinSplitProofInputs): Promise<ProofData>;
117
+ /**
118
+ * Check if circuit artifacts exist for a given circuit type
119
+ */
120
+ export declare function circuitExists(circuitType: CircuitType): Promise<boolean>;
121
+ /**
122
+ * Convert proof to raw bytes for on-chain submission
123
+ */
124
+ export declare function proofToBytes(proof: ProofData): Uint8Array;
125
+ /**
126
+ * Cleanup all cached resources
127
+ */
128
+ export declare function cleanup(): Promise<void>;
129
+ /**
130
+ * Groth16 verifier program ID (from current config)
131
+ */
132
+ export declare function getGroth16VerifierProgramId(): Address;
133
+ /**
134
+ * Build instruction data for Groth16 verification
135
+ */
136
+ export declare function buildVerifyInstructionData(proof: Uint8Array, publicSignals: string[], vkHash: string): Uint8Array;