@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.
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/package.json +108 -0
- package/packages/btc-client/src/esplora-client.ts +153 -0
- package/packages/btc-client/src/index.ts +3 -0
- package/packages/btc-client/src/op-return.ts +93 -0
- package/packages/btc-client/src/types.ts +112 -0
- package/packages/sdk/README.md +277 -0
- package/packages/sdk/dist/announcement-client.d.ts +64 -0
- package/packages/sdk/dist/announcement-client.js +337 -0
- package/packages/sdk/dist/auditor-ciphertext.d.ts +72 -0
- package/packages/sdk/dist/auditor-ciphertext.js +135 -0
- package/packages/sdk/dist/auditor.d.ts +115 -0
- package/packages/sdk/dist/auditor.js +292 -0
- package/packages/sdk/dist/bitcoin/ika.d.ts +47 -0
- package/packages/sdk/dist/bitcoin/ika.js +74 -0
- package/packages/sdk/dist/bitcoin/index.d.ts +2 -0
- package/packages/sdk/dist/bitcoin/index.js +1 -0
- package/packages/sdk/dist/bound-params.d.ts +96 -0
- package/packages/sdk/dist/bound-params.js +249 -0
- package/packages/sdk/dist/chadbuffer.d.ts +124 -0
- package/packages/sdk/dist/chadbuffer.js +405 -0
- package/packages/sdk/dist/claim-link.d.ts +30 -0
- package/packages/sdk/dist/claim-link.js +50 -0
- package/packages/sdk/dist/client.d.ts +233 -0
- package/packages/sdk/dist/client.js +416 -0
- package/packages/sdk/dist/commitment-tree.d.ts +268 -0
- package/packages/sdk/dist/commitment-tree.js +564 -0
- package/packages/sdk/dist/config.d.ts +177 -0
- package/packages/sdk/dist/config.js +426 -0
- package/packages/sdk/dist/core/esplora.d.ts +110 -0
- package/packages/sdk/dist/core/esplora.js +187 -0
- package/packages/sdk/dist/core/mempool.d.ts +60 -0
- package/packages/sdk/dist/core/mempool.js +107 -0
- package/packages/sdk/dist/crypto-babyjub.d.ts +105 -0
- package/packages/sdk/dist/crypto-babyjub.js +312 -0
- package/packages/sdk/dist/crypto-ed25519.d.ts +116 -0
- package/packages/sdk/dist/crypto-ed25519.js +235 -0
- package/packages/sdk/dist/crypto.d.ts +59 -0
- package/packages/sdk/dist/crypto.js +140 -0
- package/packages/sdk/dist/event-client.d.ts +64 -0
- package/packages/sdk/dist/event-client.js +165 -0
- package/packages/sdk/dist/events.d.ts +127 -0
- package/packages/sdk/dist/events.js +305 -0
- package/packages/sdk/dist/explorer.d.ts +86 -0
- package/packages/sdk/dist/explorer.js +197 -0
- package/packages/sdk/dist/index.d.ts +61 -0
- package/packages/sdk/dist/index.js +216 -0
- package/packages/sdk/dist/instructions.d.ts +1125 -0
- package/packages/sdk/dist/instructions.js +1760 -0
- package/packages/sdk/dist/keys.d.ts +467 -0
- package/packages/sdk/dist/keys.js +799 -0
- package/packages/sdk/dist/logger.d.ts +10 -0
- package/packages/sdk/dist/logger.js +40 -0
- package/packages/sdk/dist/magicblock.d.ts +90 -0
- package/packages/sdk/dist/magicblock.js +164 -0
- package/packages/sdk/dist/merkle.d.ts +82 -0
- package/packages/sdk/dist/merkle.js +141 -0
- package/packages/sdk/dist/note.d.ts +348 -0
- package/packages/sdk/dist/note.js +483 -0
- package/packages/sdk/dist/pda.d.ts +160 -0
- package/packages/sdk/dist/pda.js +384 -0
- package/packages/sdk/dist/pool-state.d.ts +100 -0
- package/packages/sdk/dist/pool-state.js +126 -0
- package/packages/sdk/dist/poseidon.d.ts +53 -0
- package/packages/sdk/dist/poseidon.js +136 -0
- package/packages/sdk/dist/prover/index.d.ts +10 -0
- package/packages/sdk/dist/prover/index.js +10 -0
- package/packages/sdk/dist/prover/mobile.d.ts +45 -0
- package/packages/sdk/dist/prover/mobile.js +227 -0
- package/packages/sdk/dist/prover/web.d.ts +136 -0
- package/packages/sdk/dist/prover/web.js +597 -0
- package/packages/sdk/dist/psbt.d.ts +73 -0
- package/packages/sdk/dist/psbt.js +202 -0
- package/packages/sdk/dist/selective-disclosure.d.ts +181 -0
- package/packages/sdk/dist/selective-disclosure.js +172 -0
- package/packages/sdk/dist/sender-memo.d.ts +149 -0
- package/packages/sdk/dist/sender-memo.js +250 -0
- package/packages/sdk/dist/sns-resolver.d.ts +121 -0
- package/packages/sdk/dist/sns-resolver.js +229 -0
- package/packages/sdk/dist/solana/connection.d.ts +77 -0
- package/packages/sdk/dist/solana/connection.js +133 -0
- package/packages/sdk/dist/solana/priority-fee.d.ts +78 -0
- package/packages/sdk/dist/solana/priority-fee.js +137 -0
- package/packages/sdk/dist/stealth.d.ts +429 -0
- package/packages/sdk/dist/stealth.js +703 -0
- package/packages/sdk/dist/taproot.d.ts +172 -0
- package/packages/sdk/dist/taproot.js +494 -0
- package/packages/sdk/dist/token-registry.d.ts +96 -0
- package/packages/sdk/dist/token-registry.js +122 -0
- package/packages/sdk/dist/utils/encoding.d.ts +19 -0
- package/packages/sdk/dist/utils/encoding.js +29 -0
- package/packages/sdk/dist/vk-registry.d.ts +100 -0
- 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;
|