@utxopia/sdk 0.1.0-alpha.2 → 0.1.0-alpha.3
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/README.md +214 -108
- package/{packages/sdk/dist → dist}/client.d.ts +25 -1
- package/{packages/sdk/dist → dist}/client.js +36 -1
- package/{packages/sdk/dist → dist}/crypto-ed25519.d.ts +14 -0
- package/{packages/sdk/dist → dist}/crypto-ed25519.js +14 -0
- package/{packages/sdk/dist → dist}/index.d.ts +3 -3
- package/{packages/sdk/dist → dist}/index.js +3 -3
- package/{packages/sdk/dist → dist}/instructions.d.ts +28 -11
- package/{packages/sdk/dist → dist}/instructions.js +45 -15
- package/{packages/sdk/dist → dist}/stealth.d.ts +104 -9
- package/{packages/sdk/dist → dist}/stealth.js +146 -14
- package/{packages/sdk/dist → dist}/taproot.d.ts +39 -2
- package/{packages/sdk/dist → dist}/taproot.js +54 -2
- package/package.json +86 -63
- package/src/announcement-client.ts +457 -0
- package/src/auditor-ciphertext.ts +181 -0
- package/src/auditor.ts +409 -0
- package/src/bitcoin/ika.ts +103 -0
- package/src/bitcoin/index.ts +5 -0
- package/src/bound-params.ts +322 -0
- package/src/chadbuffer.ts +603 -0
- package/src/circomlibjs.d.ts +51 -0
- package/src/claim-link.ts +53 -0
- package/src/client.ts +638 -0
- package/src/commitment-tree.ts +736 -0
- package/src/config.ts +772 -0
- package/src/core/esplora.ts +332 -0
- package/src/core/mempool.ts +159 -0
- package/src/crypto-babyjub.ts +385 -0
- package/src/crypto-ed25519.ts +297 -0
- package/src/crypto.ts +199 -0
- package/src/event-client.ts +231 -0
- package/src/events.ts +384 -0
- package/src/explorer.ts +300 -0
- package/src/index.ts +902 -0
- package/src/instructions.ts +2820 -0
- package/src/keys.ts +1228 -0
- package/src/logger.ts +41 -0
- package/src/magicblock.ts +278 -0
- package/src/merkle.ts +197 -0
- package/src/note.ts +754 -0
- package/src/pda.ts +516 -0
- package/src/pool-state.ts +176 -0
- package/src/poseidon.ts +175 -0
- package/src/prover/index.ts +19 -0
- package/src/prover/mobile.ts +303 -0
- package/src/prover/web.ts +771 -0
- package/src/psbt.ts +333 -0
- package/src/selective-disclosure.ts +284 -0
- package/src/sender-memo.ts +343 -0
- package/src/snarkjs.d.ts +19 -0
- package/src/sns-resolver.ts +333 -0
- package/src/solana/connection.ts +189 -0
- package/src/solana/priority-fee.ts +201 -0
- package/src/spend-doc.ts +163 -0
- package/src/stealth.ts +1477 -0
- package/src/taproot.ts +707 -0
- package/src/token-registry.ts +207 -0
- package/src/utils/encoding.ts +33 -0
- package/src/vk-registry.ts +295 -0
- package/LICENSE +0 -21
- package/packages/btc-client/src/esplora-client.ts +0 -153
- package/packages/btc-client/src/index.ts +0 -3
- package/packages/btc-client/src/op-return.ts +0 -93
- package/packages/btc-client/src/types.ts +0 -112
- package/packages/sdk/README.md +0 -277
- /package/{packages/sdk/dist → dist}/announcement-client.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/announcement-client.js +0 -0
- /package/{packages/sdk/dist → dist}/auditor-ciphertext.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/auditor-ciphertext.js +0 -0
- /package/{packages/sdk/dist → dist}/auditor.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/auditor.js +0 -0
- /package/{packages/sdk/dist → dist}/bitcoin/ika.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/bitcoin/ika.js +0 -0
- /package/{packages/sdk/dist → dist}/bitcoin/index.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/bitcoin/index.js +0 -0
- /package/{packages/sdk/dist → dist}/bound-params.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/bound-params.js +0 -0
- /package/{packages/sdk/dist → dist}/chadbuffer.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/chadbuffer.js +0 -0
- /package/{packages/sdk/dist → dist}/claim-link.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/claim-link.js +0 -0
- /package/{packages/sdk/dist → dist}/commitment-tree.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/commitment-tree.js +0 -0
- /package/{packages/sdk/dist → dist}/config.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/config.js +0 -0
- /package/{packages/sdk/dist → dist}/core/esplora.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/core/esplora.js +0 -0
- /package/{packages/sdk/dist → dist}/core/mempool.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/core/mempool.js +0 -0
- /package/{packages/sdk/dist → dist}/crypto-babyjub.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/crypto-babyjub.js +0 -0
- /package/{packages/sdk/dist → dist}/crypto.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/crypto.js +0 -0
- /package/{packages/sdk/dist → dist}/event-client.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/event-client.js +0 -0
- /package/{packages/sdk/dist → dist}/events.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/events.js +0 -0
- /package/{packages/sdk/dist → dist}/explorer.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/explorer.js +0 -0
- /package/{packages/sdk/dist → dist}/keys.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/keys.js +0 -0
- /package/{packages/sdk/dist → dist}/logger.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/logger.js +0 -0
- /package/{packages/sdk/dist → dist}/magicblock.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/magicblock.js +0 -0
- /package/{packages/sdk/dist → dist}/merkle.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/merkle.js +0 -0
- /package/{packages/sdk/dist → dist}/note.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/note.js +0 -0
- /package/{packages/sdk/dist → dist}/pda.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/pda.js +0 -0
- /package/{packages/sdk/dist → dist}/pool-state.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/pool-state.js +0 -0
- /package/{packages/sdk/dist → dist}/poseidon.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/poseidon.js +0 -0
- /package/{packages/sdk/dist → dist}/prover/index.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/prover/index.js +0 -0
- /package/{packages/sdk/dist → dist}/prover/mobile.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/prover/mobile.js +0 -0
- /package/{packages/sdk/dist → dist}/prover/web.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/prover/web.js +0 -0
- /package/{packages/sdk/dist → dist}/psbt.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/psbt.js +0 -0
- /package/{packages/sdk/dist → dist}/selective-disclosure.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/selective-disclosure.js +0 -0
- /package/{packages/sdk/dist → dist}/sender-memo.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/sender-memo.js +0 -0
- /package/{packages/sdk/dist → dist}/sns-resolver.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/sns-resolver.js +0 -0
- /package/{packages/sdk/dist → dist}/solana/connection.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/solana/connection.js +0 -0
- /package/{packages/sdk/dist → dist}/solana/priority-fee.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/solana/priority-fee.js +0 -0
- /package/{packages/sdk/dist → dist}/spend-doc.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/spend-doc.js +0 -0
- /package/{packages/sdk/dist → dist}/token-registry.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/token-registry.js +0 -0
- /package/{packages/sdk/dist → dist}/utils/encoding.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/utils/encoding.js +0 -0
- /package/{packages/sdk/dist → dist}/vk-registry.d.ts +0 -0
- /package/{packages/sdk/dist → dist}/vk-registry.js +0 -0
package/src/poseidon.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
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
|
+
|
|
13
|
+
import {
|
|
14
|
+
poseidon1,
|
|
15
|
+
poseidon2,
|
|
16
|
+
poseidon3,
|
|
17
|
+
poseidon4,
|
|
18
|
+
poseidon5,
|
|
19
|
+
poseidon6,
|
|
20
|
+
poseidon7,
|
|
21
|
+
poseidon8,
|
|
22
|
+
poseidon9,
|
|
23
|
+
poseidon10,
|
|
24
|
+
poseidon11,
|
|
25
|
+
poseidon12,
|
|
26
|
+
poseidon13,
|
|
27
|
+
poseidon14,
|
|
28
|
+
poseidon15,
|
|
29
|
+
poseidon16,
|
|
30
|
+
} from "poseidon-lite";
|
|
31
|
+
|
|
32
|
+
// Lookup table for poseidon hash by input count. JoinSplit currently needs at
|
|
33
|
+
// most 12 (2 fixed fields + N+M<=10); the larger arities remain available to
|
|
34
|
+
// other SDK callers.
|
|
35
|
+
const poseidonFns = [
|
|
36
|
+
undefined, // 0 — unused
|
|
37
|
+
poseidon1, // 1 input
|
|
38
|
+
poseidon2, // 2 inputs
|
|
39
|
+
poseidon3, // 3 inputs
|
|
40
|
+
poseidon4, // 4 inputs
|
|
41
|
+
poseidon5, // 5 inputs
|
|
42
|
+
poseidon6, // 6 inputs
|
|
43
|
+
poseidon7, // 7 inputs
|
|
44
|
+
poseidon8, // 8 inputs
|
|
45
|
+
poseidon9, // 9 inputs
|
|
46
|
+
poseidon10, // 10 inputs
|
|
47
|
+
poseidon11, // 11 inputs
|
|
48
|
+
poseidon12, // 12 inputs
|
|
49
|
+
poseidon13, // 13 inputs
|
|
50
|
+
poseidon14, // 14 inputs
|
|
51
|
+
poseidon15, // 15 inputs
|
|
52
|
+
poseidon16, // 16 inputs
|
|
53
|
+
] as const;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Initialize poseidon (no-op — poseidon-lite needs no initialization)
|
|
57
|
+
* Kept for backward API compatibility.
|
|
58
|
+
*/
|
|
59
|
+
export async function initPoseidon(): Promise<void> {
|
|
60
|
+
// poseidon-lite is synchronous and ready immediately
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Whether Poseidon can hash right now.
|
|
65
|
+
*
|
|
66
|
+
* Always true: poseidon-lite is pure synchronous JS with nothing to load, so `initPoseidon()`
|
|
67
|
+
* exists only so callers written against a WASM-backed hasher keep working. Kept honest rather
|
|
68
|
+
* than tracking a flag — the previous version lived in note.ts over its own state and reported
|
|
69
|
+
* false no matter what you called.
|
|
70
|
+
*/
|
|
71
|
+
export function isPoseidonReady(): boolean {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/*
|
|
76
|
+
* The async forms of these hashes are gone: poseidonHash, computeUnifiedCommitment,
|
|
77
|
+
* computeNullifier, hashNullifier, computeMPK, computeNPK, computeJoinSplitCommitment and
|
|
78
|
+
* computeJoinSplitNullifier were each `return xSync(...)`. poseidon-lite is pure synchronous JS,
|
|
79
|
+
* so awaiting them promised work that never happened and pushed async up through every caller
|
|
80
|
+
* for nothing — which is why all eight were unused outside this file while the *Sync forms are
|
|
81
|
+
* called 130-odd times. Use the *Sync functions.
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Synchronous Poseidon hash (no initialization required with poseidon-lite)
|
|
86
|
+
*/
|
|
87
|
+
export function poseidonHashSync(inputs: bigint[]): bigint {
|
|
88
|
+
const n = inputs.length;
|
|
89
|
+
if (n < 1 || n > 16) {
|
|
90
|
+
throw new Error(`Poseidon: unsupported input count ${n} (expected 1-16)`);
|
|
91
|
+
}
|
|
92
|
+
return poseidonFns[n]!(inputs as any);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// BN254 scalar field prime
|
|
96
|
+
export const BN254_SCALAR_FIELD =
|
|
97
|
+
21888242871839275222246405745257275088548364400416034343698204186575808495617n;
|
|
98
|
+
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// Unified Model Functions (Primary API) - Async versions
|
|
101
|
+
// ============================================================================
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// Synchronous versions (internal use only - require prior initPoseidon call)
|
|
105
|
+
// These are used by prover.ts which needs sync computation for circuit inputs
|
|
106
|
+
// ============================================================================
|
|
107
|
+
|
|
108
|
+
export function computeUnifiedCommitmentSync(pubKeyX: bigint, amount: bigint): bigint {
|
|
109
|
+
return poseidonHashSync([pubKeyX, amount]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function computeNullifierSync(privKey: bigint, leafIndex: bigint): bigint {
|
|
113
|
+
return poseidonHashSync([privKey, leafIndex]);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function hashNullifierSync(nullifier: bigint): bigint {
|
|
117
|
+
return poseidonHashSync([nullifier]);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ============================================================================
|
|
121
|
+
// JoinSplit Primitives (Railgun-aligned 3-key model)
|
|
122
|
+
// ============================================================================
|
|
123
|
+
|
|
124
|
+
export function computeMPKSync(pkX: bigint, pkY: bigint, nullifyingKey: bigint): bigint {
|
|
125
|
+
return poseidonHashSync([pkX, pkY, nullifyingKey]);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function computeNPKSync(mpk: bigint, random: bigint): bigint {
|
|
129
|
+
return poseidonHashSync([mpk, random]);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function computeJoinSplitCommitmentSync(npk: bigint, token: bigint, amount: bigint): bigint {
|
|
133
|
+
return poseidonHashSync([npk, token, amount]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function computeJoinSplitNullifierSync(nullifyingKey: bigint, leafIndex: bigint): bigint {
|
|
137
|
+
return poseidonHashSync([nullifyingKey, leafIndex]);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// Multi-Token Utilities
|
|
142
|
+
// ============================================================================
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Reduce a 32-byte big-endian value to BN254 scalar field (mod p).
|
|
146
|
+
* Must match on-chain reduce_to_field_exact in crypto.rs.
|
|
147
|
+
*/
|
|
148
|
+
export function reduceToField(bytes: Uint8Array): bigint {
|
|
149
|
+
// Convert big-endian bytes to bigint
|
|
150
|
+
let value = 0n;
|
|
151
|
+
for (const b of bytes) {
|
|
152
|
+
value = (value << 8n) | BigInt(b);
|
|
153
|
+
}
|
|
154
|
+
return value % BN254_SCALAR_FIELD;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Compute token_id from mint address: Poseidon(reduce_to_field(mint), 0)
|
|
159
|
+
*
|
|
160
|
+
* Must match on-chain compute_token_id in crypto.rs which uses
|
|
161
|
+
* poseidon2_hash(reduce_to_field_exact(mint), [0u8; 32]).
|
|
162
|
+
*/
|
|
163
|
+
export function computeTokenId(mintBytes: Uint8Array): bigint {
|
|
164
|
+
const reduced = reduceToField(mintBytes);
|
|
165
|
+
return poseidonHashSync([reduced, 0n]);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Convenience: compute token_id from a Solana address string
|
|
170
|
+
*/
|
|
171
|
+
export function computeTokenIdFromAddress(mintAddress: string): bigint {
|
|
172
|
+
// Base58 decode — import from @solana/kit if available, otherwise use raw bytes
|
|
173
|
+
// For now, caller should pass raw bytes via computeTokenId
|
|
174
|
+
throw new Error("Use computeTokenId(mintBytes) with raw pubkey bytes");
|
|
175
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
|
|
10
|
+
// Re-export everything from the web prover (default for browser/Node.js)
|
|
11
|
+
export * from "./web";
|
|
12
|
+
|
|
13
|
+
// Re-export types
|
|
14
|
+
export type {
|
|
15
|
+
MerkleProofInput,
|
|
16
|
+
ProofData,
|
|
17
|
+
CircuitType,
|
|
18
|
+
JoinSplitProofInputs,
|
|
19
|
+
} from "./web";
|
|
@@ -0,0 +1,303 @@
|
|
|
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
|
+
|
|
9
|
+
// Re-export types from web for API compatibility
|
|
10
|
+
export type {
|
|
11
|
+
MerkleProofInput,
|
|
12
|
+
ProofData,
|
|
13
|
+
CircuitType,
|
|
14
|
+
JoinSplitProofInputs,
|
|
15
|
+
} from "./web";
|
|
16
|
+
|
|
17
|
+
import type { ProofData, CircuitType, JoinSplitProofInputs } from "./web";
|
|
18
|
+
import {
|
|
19
|
+
computeJoinSplitNullifierSync,
|
|
20
|
+
computeJoinSplitCommitmentSync,
|
|
21
|
+
} from "../poseidon";
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Module state
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
type CircuitResolver = (
|
|
28
|
+
circuitName: string,
|
|
29
|
+
onProgress?: (progress: number) => void,
|
|
30
|
+
) => Promise<string>;
|
|
31
|
+
|
|
32
|
+
let moproModule: any = null;
|
|
33
|
+
let circuitResolver: CircuitResolver | null = null;
|
|
34
|
+
let proverReady = false;
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Configuration
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Inject the circuit file resolver (called from the mobile app layer).
|
|
42
|
+
* The resolver maps a circuit name like "joinsplit_2x2" to the local
|
|
43
|
+
* file-system path of the corresponding .zkey file.
|
|
44
|
+
*/
|
|
45
|
+
export function setCircuitResolver(resolver: CircuitResolver): void {
|
|
46
|
+
circuitResolver = resolver;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Initialisation
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Initialise the native mopro-ffi prover.
|
|
55
|
+
* Safe to call multiple times — subsequent calls are no-ops.
|
|
56
|
+
*/
|
|
57
|
+
export async function initProver(): Promise<void> {
|
|
58
|
+
if (proverReady) return;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// Dynamic require so bundlers don't resolve this at compile time
|
|
62
|
+
// when the native module isn't installed yet.
|
|
63
|
+
moproModule = require("mopro-ffi");
|
|
64
|
+
} catch {
|
|
65
|
+
throw new Error(
|
|
66
|
+
"mopro-ffi native module not found. " +
|
|
67
|
+
"Run the ubrn build step first (see MoproBindings/package.json).",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
proverReady = true;
|
|
72
|
+
console.log("[MobileProver] mopro-ffi native prover initialised");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check whether the native prover is available in the current runtime.
|
|
77
|
+
*/
|
|
78
|
+
export async function isProverAvailable(): Promise<boolean> {
|
|
79
|
+
try {
|
|
80
|
+
if (!moproModule) {
|
|
81
|
+
moproModule = require("mopro-ffi");
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Proof generation
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Generate a JoinSplit Groth16 proof using the native mopro-ffi prover.
|
|
95
|
+
*
|
|
96
|
+
* @param inputs - JoinSplit proof inputs (typed) OR pre-formatted inputs object
|
|
97
|
+
* @param circuitType - Override circuit variant (default: derived from inputs)
|
|
98
|
+
* @param onProgress - Optional progress callback (0..1)
|
|
99
|
+
*/
|
|
100
|
+
export async function generateJoinSplitProof(
|
|
101
|
+
inputs: JoinSplitProofInputs | any,
|
|
102
|
+
circuitType?: string,
|
|
103
|
+
onProgress?: (progress: number) => void,
|
|
104
|
+
): Promise<ProofData> {
|
|
105
|
+
if (!proverReady) await initProver();
|
|
106
|
+
if (!circuitResolver) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
"Circuit resolver not set. Call setCircuitResolver() before generating proofs.",
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Determine circuit variant
|
|
113
|
+
const nIn: number = inputs.nInputs ?? inputs.inputs?.length ?? 1;
|
|
114
|
+
const nOut: number = inputs.nOutputs ?? inputs.outputs?.length ?? 2;
|
|
115
|
+
const variant: CircuitType =
|
|
116
|
+
(circuitType as CircuitType) ?? `joinsplit_${nIn}x${nOut}`;
|
|
117
|
+
|
|
118
|
+
console.log(`[MobileProver] Generating ${variant} Groth16 proof...`);
|
|
119
|
+
const startTime = Date.now();
|
|
120
|
+
|
|
121
|
+
// Resolve the .zkey path (may trigger a download)
|
|
122
|
+
onProgress?.(0);
|
|
123
|
+
const zkeyPath = await circuitResolver(variant, onProgress);
|
|
124
|
+
onProgress?.(0.1);
|
|
125
|
+
|
|
126
|
+
// Format inputs for mopro's flat Record<string, string[]> format
|
|
127
|
+
const flatInputs = formatCircuitInputs(inputs, nIn, nOut);
|
|
128
|
+
|
|
129
|
+
// Call the native prover
|
|
130
|
+
onProgress?.(0.2);
|
|
131
|
+
const result = await moproModule.generateCircomProof(zkeyPath, flatInputs);
|
|
132
|
+
onProgress?.(0.9);
|
|
133
|
+
|
|
134
|
+
const elapsed = Date.now() - startTime;
|
|
135
|
+
console.log(`[MobileProver] Proof generated in ${elapsed}ms`);
|
|
136
|
+
|
|
137
|
+
// Convert mopro result to our ProofData format
|
|
138
|
+
const proofData = convertMoproResult(result);
|
|
139
|
+
onProgress?.(1);
|
|
140
|
+
|
|
141
|
+
return proofData;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// Input formatting
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Convert JoinSplitProofInputs to mopro's flat Record<string, string[]>.
|
|
150
|
+
*
|
|
151
|
+
* mopro expects every value as a string array — scalars become single-element
|
|
152
|
+
* arrays, vectors become multi-element arrays.
|
|
153
|
+
*/
|
|
154
|
+
function formatCircuitInputs(
|
|
155
|
+
inputs: JoinSplitProofInputs | any,
|
|
156
|
+
nIn: number,
|
|
157
|
+
nOut: number,
|
|
158
|
+
): Record<string, string[]> {
|
|
159
|
+
const flat: Record<string, string[]> = {};
|
|
160
|
+
|
|
161
|
+
// If inputs are already in flat format, pass through
|
|
162
|
+
if (!inputs.merkleRoot && !inputs.inputs) {
|
|
163
|
+
const result: Record<string, string[]> = {};
|
|
164
|
+
for (const [key, value] of Object.entries(inputs)) {
|
|
165
|
+
if (Array.isArray(value)) {
|
|
166
|
+
result[key] = (value as any[]).map((v) => String(v));
|
|
167
|
+
} else {
|
|
168
|
+
result[key] = [String(value)];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Typed JoinSplitProofInputs — convert to circuit signal names
|
|
175
|
+
const typed = inputs as JoinSplitProofInputs;
|
|
176
|
+
|
|
177
|
+
flat["merkleRoot"] = [typed.merkleRoot.toString()];
|
|
178
|
+
flat["boundParamsHash"] = [typed.boundParamsHash.toString()];
|
|
179
|
+
flat["token"] = [typed.token.toString()];
|
|
180
|
+
flat["publicKey"] = typed.publicKey.map((p) => p.toString());
|
|
181
|
+
flat["signature"] = typed.signature.map((s) => s.toString());
|
|
182
|
+
flat["nullifyingKey"] = [typed.nullifyingKey.toString()];
|
|
183
|
+
|
|
184
|
+
// Input arrays
|
|
185
|
+
flat["randomIn"] = typed.inputs.map((i) => i.random.toString());
|
|
186
|
+
flat["valueIn"] = typed.inputs.map((i) => i.value.toString());
|
|
187
|
+
flat["leavesIndices"] = typed.inputs.map((i) => i.leafIndex.toString());
|
|
188
|
+
|
|
189
|
+
// Output arrays
|
|
190
|
+
flat["npkOut"] = typed.outputs.map((o) => o.npk.toString());
|
|
191
|
+
flat["valueOut"] = typed.outputs.map((o) => o.value.toString());
|
|
192
|
+
|
|
193
|
+
// Compute nullifiers: Poseidon(nullifyingKey, leafIndex)
|
|
194
|
+
const nullifiers: bigint[] = [];
|
|
195
|
+
for (const inp of typed.inputs) {
|
|
196
|
+
nullifiers.push(computeJoinSplitNullifierSync(typed.nullifyingKey, inp.leafIndex));
|
|
197
|
+
}
|
|
198
|
+
flat["nullifiers"] = nullifiers.map((n) => n.toString());
|
|
199
|
+
|
|
200
|
+
// Compute output commitments: Poseidon(npk, token, value)
|
|
201
|
+
const commitmentsOut: bigint[] = [];
|
|
202
|
+
for (const out of typed.outputs) {
|
|
203
|
+
commitmentsOut.push(computeJoinSplitCommitmentSync(out.npk, typed.token, out.value));
|
|
204
|
+
}
|
|
205
|
+
flat["commitmentsOut"] = commitmentsOut.map((c) => c.toString());
|
|
206
|
+
|
|
207
|
+
// Flattened siblings pathElements[i][j]; circuit derives path bits from leavesIndices.
|
|
208
|
+
const pathElements: string[] = [];
|
|
209
|
+
for (const inp of typed.inputs) {
|
|
210
|
+
for (const sibling of inp.merkleProof.siblings) {
|
|
211
|
+
pathElements.push(sibling.toString());
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
flat["pathElements"] = pathElements;
|
|
215
|
+
|
|
216
|
+
return flat;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// Result conversion
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Convert mopro's CircomProofResult to our ProofData format.
|
|
225
|
+
*
|
|
226
|
+
* mopro returns: { proof: { a: {x, y}, b: {x: [x0,x1], y: [y0,y1]}, c: {x, y} }, inputs: string[] }
|
|
227
|
+
* We need: { proof: Uint8Array(256), publicInputs: string[] }
|
|
228
|
+
*/
|
|
229
|
+
function convertMoproResult(result: any): ProofData {
|
|
230
|
+
const { proof, inputs: publicInputs } = result;
|
|
231
|
+
|
|
232
|
+
const bytes = new Uint8Array(256);
|
|
233
|
+
|
|
234
|
+
// G1 point A (64 bytes): x, y
|
|
235
|
+
writeBigIntBE(bytes, 0, BigInt(proof.a.x), 32);
|
|
236
|
+
writeBigIntBE(bytes, 32, BigInt(proof.a.y), 32);
|
|
237
|
+
|
|
238
|
+
// G2 point B (128 bytes): [x_imag, x_real, y_imag, y_real]
|
|
239
|
+
// mopro b.x = [x0, x1], b.y = [y0, y1]
|
|
240
|
+
// On-chain layout: x_imag(x1), x_real(x0), y_imag(y1), y_real(y0)
|
|
241
|
+
writeBigIntBE(bytes, 64, BigInt(proof.b.x[1]), 32);
|
|
242
|
+
writeBigIntBE(bytes, 96, BigInt(proof.b.x[0]), 32);
|
|
243
|
+
writeBigIntBE(bytes, 128, BigInt(proof.b.y[1]), 32);
|
|
244
|
+
writeBigIntBE(bytes, 160, BigInt(proof.b.y[0]), 32);
|
|
245
|
+
|
|
246
|
+
// G1 point C (64 bytes): x, y
|
|
247
|
+
writeBigIntBE(bytes, 192, BigInt(proof.c.x), 32);
|
|
248
|
+
writeBigIntBE(bytes, 224, BigInt(proof.c.y), 32);
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
proof: bytes,
|
|
252
|
+
publicInputs: publicInputs ?? [],
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function writeBigIntBE(
|
|
257
|
+
buf: Uint8Array,
|
|
258
|
+
offset: number,
|
|
259
|
+
value: bigint,
|
|
260
|
+
length: number,
|
|
261
|
+
): void {
|
|
262
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
263
|
+
buf[offset + i] = Number(value & 0xffn);
|
|
264
|
+
value >>= 8n;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
// Utilities
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Convert a ProofData to raw 256-byte proof for on-chain submission.
|
|
274
|
+
*/
|
|
275
|
+
export function proofToBytes(proof: ProofData): Uint8Array {
|
|
276
|
+
return proof.proof;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Check if a circuit variant is available (resolver can find the .zkey).
|
|
281
|
+
*/
|
|
282
|
+
export async function circuitExists(
|
|
283
|
+
circuitType?: CircuitType | string,
|
|
284
|
+
): Promise<boolean> {
|
|
285
|
+
if (!circuitResolver) return false;
|
|
286
|
+
const name = circuitType ?? "joinsplit_2x2";
|
|
287
|
+
try {
|
|
288
|
+
await circuitResolver(name);
|
|
289
|
+
return true;
|
|
290
|
+
} catch {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Reset module state (for testing or hot-reload scenarios).
|
|
297
|
+
*/
|
|
298
|
+
export async function cleanup(): Promise<void> {
|
|
299
|
+
moproModule = null;
|
|
300
|
+
circuitResolver = null;
|
|
301
|
+
proverReady = false;
|
|
302
|
+
console.log("[MobileProver] Cleaned up");
|
|
303
|
+
}
|