@hula-privacy/mixer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +290 -0
- package/package.json +52 -0
- package/src/api.ts +276 -0
- package/src/constants.ts +108 -0
- package/src/crypto.ts +293 -0
- package/src/index.ts +185 -0
- package/src/merkle.ts +220 -0
- package/src/proof.ts +251 -0
- package/src/transaction.ts +464 -0
- package/src/types.ts +331 -0
- package/src/utxo.ts +358 -0
- package/src/wallet.ts +475 -0
package/src/merkle.ts
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merkle Tree utilities for Hula Privacy Protocol
|
|
3
|
+
*
|
|
4
|
+
* Provides functions for building merkle paths from relayer data
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { MERKLE_TREE_DEPTH } from "./constants";
|
|
8
|
+
import { poseidonHash, getPoseidon, isPoseidonInitialized } from "./crypto";
|
|
9
|
+
import { getRelayerClient } from "./api";
|
|
10
|
+
import type { MerklePath, LeafData } from "./types";
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Zero Values
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
let ZEROS: bigint[] = [];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Compute zero hashes for the merkle tree using Poseidon
|
|
20
|
+
* ZEROS[0] = 0 (empty leaf)
|
|
21
|
+
* ZEROS[i+1] = Poseidon(ZEROS[i], ZEROS[i])
|
|
22
|
+
*/
|
|
23
|
+
export function computeZeros(): bigint[] {
|
|
24
|
+
if (!isPoseidonInitialized()) {
|
|
25
|
+
throw new Error("Poseidon not initialized. Call initPoseidon() first.");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (ZEROS.length > 0) {
|
|
29
|
+
return ZEROS;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const zeros: bigint[] = [0n]; // ZEROS[0] = 0 (empty leaf)
|
|
33
|
+
const poseidon = getPoseidon();
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < MERKLE_TREE_DEPTH; i++) {
|
|
36
|
+
const hash = poseidon([zeros[i], zeros[i]]);
|
|
37
|
+
zeros.push(poseidon.F.toObject(hash));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
ZEROS = zeros;
|
|
41
|
+
return zeros;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get zero value at a specific level
|
|
46
|
+
*/
|
|
47
|
+
export function getZeroAtLevel(level: number): bigint {
|
|
48
|
+
if (ZEROS.length === 0) {
|
|
49
|
+
computeZeros();
|
|
50
|
+
}
|
|
51
|
+
return ZEROS[level];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get the root of an empty merkle tree
|
|
56
|
+
*/
|
|
57
|
+
export function getEmptyTreeRoot(): bigint {
|
|
58
|
+
if (ZEROS.length === 0) {
|
|
59
|
+
computeZeros();
|
|
60
|
+
}
|
|
61
|
+
return ZEROS[MERKLE_TREE_DEPTH];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// Merkle Path Computation
|
|
66
|
+
// ============================================================================
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Compute merkle path from a list of leaves
|
|
70
|
+
*
|
|
71
|
+
* This builds the full tree locally and extracts the path for the given leaf.
|
|
72
|
+
* Use this when you have fetched all leaves from the relayer.
|
|
73
|
+
*/
|
|
74
|
+
export function computeMerklePathFromLeaves(
|
|
75
|
+
leafIndex: number,
|
|
76
|
+
leaves: bigint[]
|
|
77
|
+
): MerklePath {
|
|
78
|
+
if (!isPoseidonInitialized()) {
|
|
79
|
+
throw new Error("Poseidon not initialized. Call initPoseidon() first.");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const zeros = computeZeros();
|
|
83
|
+
const pathElements: bigint[] = [];
|
|
84
|
+
const pathIndices: number[] = [];
|
|
85
|
+
const poseidon = getPoseidon();
|
|
86
|
+
|
|
87
|
+
// Build tree level by level
|
|
88
|
+
let currentLevel = [...leaves];
|
|
89
|
+
|
|
90
|
+
// Pad to tree size with zeros
|
|
91
|
+
const treeSize = 1 << MERKLE_TREE_DEPTH;
|
|
92
|
+
while (currentLevel.length < treeSize) {
|
|
93
|
+
currentLevel.push(zeros[0]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let targetIndex = leafIndex;
|
|
97
|
+
|
|
98
|
+
for (let level = 0; level < MERKLE_TREE_DEPTH; level++) {
|
|
99
|
+
const isLeft = (targetIndex & 1) === 0;
|
|
100
|
+
pathIndices.push(isLeft ? 0 : 1);
|
|
101
|
+
|
|
102
|
+
// Get sibling
|
|
103
|
+
const siblingIndex = isLeft ? targetIndex + 1 : targetIndex - 1;
|
|
104
|
+
pathElements.push(currentLevel[siblingIndex]);
|
|
105
|
+
|
|
106
|
+
// Compute next level
|
|
107
|
+
const nextLevel: bigint[] = [];
|
|
108
|
+
for (let i = 0; i < currentLevel.length; i += 2) {
|
|
109
|
+
const left = currentLevel[i];
|
|
110
|
+
const right = currentLevel[i + 1] ?? zeros[level];
|
|
111
|
+
const hash = poseidon([left, right]);
|
|
112
|
+
nextLevel.push(poseidon.F.toObject(hash));
|
|
113
|
+
}
|
|
114
|
+
currentLevel = nextLevel;
|
|
115
|
+
targetIndex = Math.floor(targetIndex / 2);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { pathElements, pathIndices, root: currentLevel[0] };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Compute merkle root from leaves
|
|
123
|
+
*/
|
|
124
|
+
export function computeMerkleRoot(leaves: bigint[]): bigint {
|
|
125
|
+
if (leaves.length === 0) {
|
|
126
|
+
return getEmptyTreeRoot();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const { root } = computeMerklePathFromLeaves(0, leaves);
|
|
130
|
+
return root;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Verify a merkle path is valid
|
|
135
|
+
*/
|
|
136
|
+
export function verifyMerklePath(
|
|
137
|
+
leafValue: bigint,
|
|
138
|
+
path: MerklePath
|
|
139
|
+
): boolean {
|
|
140
|
+
if (!isPoseidonInitialized()) {
|
|
141
|
+
throw new Error("Poseidon not initialized. Call initPoseidon() first.");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let current = leafValue;
|
|
145
|
+
const poseidon = getPoseidon();
|
|
146
|
+
|
|
147
|
+
for (let i = 0; i < path.pathElements.length; i++) {
|
|
148
|
+
const sibling = path.pathElements[i];
|
|
149
|
+
const isLeft = path.pathIndices[i] === 0;
|
|
150
|
+
|
|
151
|
+
const left = isLeft ? current : sibling;
|
|
152
|
+
const right = isLeft ? sibling : current;
|
|
153
|
+
|
|
154
|
+
const hash = poseidon([left, right]);
|
|
155
|
+
current = poseidon.F.toObject(hash);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return current === path.root;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ============================================================================
|
|
162
|
+
// Relayer Integration
|
|
163
|
+
// ============================================================================
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Fetch merkle path for a UTXO from the relayer
|
|
167
|
+
*
|
|
168
|
+
* This fetches all leaves for the tree and computes the path locally.
|
|
169
|
+
*/
|
|
170
|
+
export async function fetchMerklePath(
|
|
171
|
+
treeIndex: number,
|
|
172
|
+
leafIndex: number,
|
|
173
|
+
relayerUrl?: string
|
|
174
|
+
): Promise<MerklePath> {
|
|
175
|
+
const client = getRelayerClient(relayerUrl);
|
|
176
|
+
|
|
177
|
+
// Get all leaves for this tree
|
|
178
|
+
const leaves = await client.getCommitmentsForTree(treeIndex);
|
|
179
|
+
|
|
180
|
+
if (leafIndex >= leaves.length) {
|
|
181
|
+
throw new Error(`Leaf index ${leafIndex} not found in tree ${treeIndex} (has ${leaves.length} leaves)`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return computeMerklePathFromLeaves(leafIndex, leaves);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Fetch current merkle root for a tree from the relayer
|
|
189
|
+
*/
|
|
190
|
+
export async function fetchMerkleRoot(
|
|
191
|
+
treeIndex: number,
|
|
192
|
+
relayerUrl?: string
|
|
193
|
+
): Promise<bigint> {
|
|
194
|
+
const client = getRelayerClient(relayerUrl);
|
|
195
|
+
const tree = await client.getTree(treeIndex);
|
|
196
|
+
return BigInt(tree.root);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Get next leaf index for a tree
|
|
201
|
+
*/
|
|
202
|
+
export async function getNextLeafIndex(
|
|
203
|
+
treeIndex: number,
|
|
204
|
+
relayerUrl?: string
|
|
205
|
+
): Promise<number> {
|
|
206
|
+
const client = getRelayerClient(relayerUrl);
|
|
207
|
+
const tree = await client.getTree(treeIndex);
|
|
208
|
+
return tree.nextIndex;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get current active tree index
|
|
213
|
+
*/
|
|
214
|
+
export async function getCurrentTreeIndex(relayerUrl?: string): Promise<number> {
|
|
215
|
+
const client = getRelayerClient(relayerUrl);
|
|
216
|
+
const pool = await client.getPool();
|
|
217
|
+
return pool.currentTreeIndex;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
package/src/proof.ts
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZK Proof generation utilities
|
|
3
|
+
*
|
|
4
|
+
* Uses snarkjs to generate Groth16 proofs for the transaction circuit
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
8
|
+
import { execSync } from "child_process";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { PROOF_SIZE } from "./constants";
|
|
11
|
+
import type { CircuitInputs } from "./types";
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Circuit Paths
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
let circuitWasmPath: string | null = null;
|
|
18
|
+
let circuitZkeyPath: string | null = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Set circuit file paths
|
|
22
|
+
*/
|
|
23
|
+
export function setCircuitPaths(wasmPath: string, zkeyPath: string): void {
|
|
24
|
+
circuitWasmPath = wasmPath;
|
|
25
|
+
circuitZkeyPath = zkeyPath;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get circuit file paths (with fallback to default locations)
|
|
30
|
+
*/
|
|
31
|
+
export function getCircuitPaths(): { wasmPath: string; zkeyPath: string } {
|
|
32
|
+
if (circuitWasmPath && circuitZkeyPath) {
|
|
33
|
+
return { wasmPath: circuitWasmPath, zkeyPath: circuitZkeyPath };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Try to find circuits in common locations
|
|
37
|
+
const possibleBasePaths = [
|
|
38
|
+
path.join(process.cwd(), "circuits", "build"),
|
|
39
|
+
path.join(process.cwd(), "..", "circuits", "build"),
|
|
40
|
+
path.join(process.cwd(), "assets"),
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
for (const basePath of possibleBasePaths) {
|
|
44
|
+
const wasmPath = path.join(basePath, "transaction_js", "transaction.wasm");
|
|
45
|
+
const zkeyPath = path.join(basePath, "keys", "transaction_final.zkey");
|
|
46
|
+
|
|
47
|
+
if (existsSync(wasmPath) && existsSync(zkeyPath)) {
|
|
48
|
+
return { wasmPath, zkeyPath };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Also check if files are directly in assets folder
|
|
52
|
+
const altWasmPath = path.join(basePath, "transaction.wasm");
|
|
53
|
+
const altZkeyPath = path.join(basePath, "transaction_final.zkey");
|
|
54
|
+
|
|
55
|
+
if (existsSync(altWasmPath) && existsSync(altZkeyPath)) {
|
|
56
|
+
return { wasmPath: altWasmPath, zkeyPath: altZkeyPath };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
throw new Error(
|
|
61
|
+
"Circuit files not found. Either:\n" +
|
|
62
|
+
"1. Run 'make' in circuits/ directory to build them, or\n" +
|
|
63
|
+
"2. Call setCircuitPaths() with the correct paths"
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Verify circuit files exist
|
|
69
|
+
*/
|
|
70
|
+
export function verifyCircuitFiles(): void {
|
|
71
|
+
const { wasmPath, zkeyPath } = getCircuitPaths();
|
|
72
|
+
|
|
73
|
+
if (!existsSync(wasmPath)) {
|
|
74
|
+
throw new Error(`Circuit WASM not found: ${wasmPath}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!existsSync(zkeyPath)) {
|
|
78
|
+
throw new Error(`Circuit zkey not found: ${zkeyPath}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// Proof Parsing
|
|
84
|
+
// ============================================================================
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Convert a decimal string to 32-byte big-endian Uint8Array
|
|
88
|
+
*/
|
|
89
|
+
function toBigEndianBytes(decimalStr: string): Uint8Array {
|
|
90
|
+
let hex = BigInt(decimalStr).toString(16);
|
|
91
|
+
hex = hex.padStart(64, "0");
|
|
92
|
+
const bytes = new Uint8Array(32);
|
|
93
|
+
for (let i = 0; i < 32; i++) {
|
|
94
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
95
|
+
}
|
|
96
|
+
return bytes;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Parse snarkjs proof JSON into 256-byte proof array
|
|
101
|
+
*
|
|
102
|
+
* Format: proof_a (64 bytes) + proof_b (128 bytes) + proof_c (64 bytes)
|
|
103
|
+
*/
|
|
104
|
+
export function parseProof(proofJson: {
|
|
105
|
+
pi_a: string[];
|
|
106
|
+
pi_b: string[][];
|
|
107
|
+
pi_c: string[];
|
|
108
|
+
}): Uint8Array {
|
|
109
|
+
const proof = new Uint8Array(PROOF_SIZE);
|
|
110
|
+
|
|
111
|
+
// proof_a: G1 point (x, y) - 64 bytes
|
|
112
|
+
const pi_a_x = toBigEndianBytes(proofJson.pi_a[0]);
|
|
113
|
+
const pi_a_y = toBigEndianBytes(proofJson.pi_a[1]);
|
|
114
|
+
proof.set(pi_a_x, 0);
|
|
115
|
+
proof.set(pi_a_y, 32);
|
|
116
|
+
|
|
117
|
+
// proof_b: G2 point - 128 bytes
|
|
118
|
+
// G2 points have coordinates in extension field: [[x0, x1], [y0, y1]]
|
|
119
|
+
// Order for Solana/Solidity verifier: x1 || x0 || y1 || y0
|
|
120
|
+
const pi_b_x0 = toBigEndianBytes(proofJson.pi_b[0][1]);
|
|
121
|
+
const pi_b_x1 = toBigEndianBytes(proofJson.pi_b[0][0]);
|
|
122
|
+
const pi_b_y0 = toBigEndianBytes(proofJson.pi_b[1][1]);
|
|
123
|
+
const pi_b_y1 = toBigEndianBytes(proofJson.pi_b[1][0]);
|
|
124
|
+
proof.set(pi_b_x0, 64);
|
|
125
|
+
proof.set(pi_b_x1, 96);
|
|
126
|
+
proof.set(pi_b_y0, 128);
|
|
127
|
+
proof.set(pi_b_y1, 160);
|
|
128
|
+
|
|
129
|
+
// proof_c: G1 point (x, y) - 64 bytes
|
|
130
|
+
const pi_c_x = toBigEndianBytes(proofJson.pi_c[0]);
|
|
131
|
+
const pi_c_y = toBigEndianBytes(proofJson.pi_c[1]);
|
|
132
|
+
proof.set(pi_c_x, 192);
|
|
133
|
+
proof.set(pi_c_y, 224);
|
|
134
|
+
|
|
135
|
+
return proof;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ============================================================================
|
|
139
|
+
// Proof Generation
|
|
140
|
+
// ============================================================================
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Generate ZK proof using snarkjs CLI (subprocess for Bun compatibility)
|
|
144
|
+
*
|
|
145
|
+
* This spawns snarkjs as a subprocess to avoid web-worker issues with Bun.
|
|
146
|
+
* Works in both Node.js and Bun environments.
|
|
147
|
+
*/
|
|
148
|
+
export async function generateProof(
|
|
149
|
+
circuitInputs: CircuitInputs
|
|
150
|
+
): Promise<{ proof: Uint8Array; publicSignals: string[] }> {
|
|
151
|
+
verifyCircuitFiles();
|
|
152
|
+
const { wasmPath, zkeyPath } = getCircuitPaths();
|
|
153
|
+
|
|
154
|
+
// Get temp directory
|
|
155
|
+
const tempDir = path.dirname(zkeyPath);
|
|
156
|
+
|
|
157
|
+
// Create unique temp file names
|
|
158
|
+
const timestamp = Date.now();
|
|
159
|
+
const inputPath = path.join(tempDir, `temp_input_${timestamp}.json`);
|
|
160
|
+
const witnessPath = path.join(tempDir, `temp_witness_${timestamp}.wtns`);
|
|
161
|
+
const proofPath = path.join(tempDir, `temp_proof_${timestamp}.json`);
|
|
162
|
+
const publicPath = path.join(tempDir, `temp_public_${timestamp}.json`);
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
// Write input to temp file
|
|
166
|
+
writeFileSync(inputPath, JSON.stringify(circuitInputs));
|
|
167
|
+
|
|
168
|
+
// Find witness generation script
|
|
169
|
+
const witnessGenScript = path.join(
|
|
170
|
+
path.dirname(wasmPath),
|
|
171
|
+
"generate_witness.cjs"
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
if (!existsSync(witnessGenScript)) {
|
|
175
|
+
throw new Error(`Witness generation script not found: ${witnessGenScript}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Generate witness using node (not bun) to avoid web-worker issues
|
|
179
|
+
execSync(`node ${witnessGenScript} ${wasmPath} ${inputPath} ${witnessPath}`, {
|
|
180
|
+
stdio: "pipe",
|
|
181
|
+
cwd: tempDir,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Generate proof using snarkjs CLI
|
|
185
|
+
execSync(
|
|
186
|
+
`npx snarkjs groth16 prove ${zkeyPath} ${witnessPath} ${proofPath} ${publicPath}`,
|
|
187
|
+
{
|
|
188
|
+
stdio: "pipe",
|
|
189
|
+
cwd: tempDir,
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Read proof and public signals
|
|
194
|
+
const proofJson = JSON.parse(readFileSync(proofPath, "utf-8"));
|
|
195
|
+
const publicSignals = JSON.parse(readFileSync(publicPath, "utf-8"));
|
|
196
|
+
|
|
197
|
+
const proof = parseProof(proofJson);
|
|
198
|
+
|
|
199
|
+
return { proof, publicSignals };
|
|
200
|
+
} finally {
|
|
201
|
+
// Cleanup temp files
|
|
202
|
+
const cleanup = (filePath: string) => {
|
|
203
|
+
try {
|
|
204
|
+
if (existsSync(filePath)) unlinkSync(filePath);
|
|
205
|
+
} catch {}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
cleanup(inputPath);
|
|
209
|
+
cleanup(witnessPath);
|
|
210
|
+
cleanup(proofPath);
|
|
211
|
+
cleanup(publicPath);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Generate proof using snarkjs in-memory (for Node.js environments)
|
|
217
|
+
*
|
|
218
|
+
* This is more efficient but may not work in all environments.
|
|
219
|
+
*/
|
|
220
|
+
export async function generateProofInMemory(
|
|
221
|
+
circuitInputs: CircuitInputs
|
|
222
|
+
): Promise<{ proof: Uint8Array; publicSignals: string[] }> {
|
|
223
|
+
verifyCircuitFiles();
|
|
224
|
+
const { wasmPath, zkeyPath } = getCircuitPaths();
|
|
225
|
+
|
|
226
|
+
// Dynamic import to handle environments where snarkjs isn't available
|
|
227
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
228
|
+
const snarkjs = await import("snarkjs" as string) as {
|
|
229
|
+
groth16: {
|
|
230
|
+
fullProve: (
|
|
231
|
+
inputs: CircuitInputs,
|
|
232
|
+
wasmPath: string,
|
|
233
|
+
zkeyPath: string
|
|
234
|
+
) => Promise<{
|
|
235
|
+
proof: { pi_a: string[]; pi_b: string[][]; pi_c: string[] };
|
|
236
|
+
publicSignals: string[];
|
|
237
|
+
}>;
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const { proof: proofData, publicSignals } = await snarkjs.groth16.fullProve(
|
|
242
|
+
circuitInputs,
|
|
243
|
+
wasmPath,
|
|
244
|
+
zkeyPath
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const proof = parseProof(proofData);
|
|
248
|
+
|
|
249
|
+
return { proof, publicSignals };
|
|
250
|
+
}
|
|
251
|
+
|