dop-wallet-v6 1.3.39 → 1.3.41
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/dist/services/dop/core/prover.d.ts +7 -0
- package/dist/services/dop/core/prover.js +6 -48
- package/dist/services/dop/core/prover.js.map +1 -1
- package/dist/services/dop/crypto/index.d.ts +3 -1
- package/dist/services/dop/crypto/index.js +43 -15
- package/dist/services/dop/crypto/index.js.map +1 -1
- package/dist/services/dop/crypto/rapidsnark-groth16.d.ts +211 -0
- package/dist/services/dop/crypto/rapidsnark-groth16.js +418 -0
- package/dist/services/dop/crypto/rapidsnark-groth16.js.map +1 -0
- package/dist/services/dop/crypto/react-native-prover.d.ts +338 -0
- package/dist/services/dop/crypto/react-native-prover.js +814 -0
- package/dist/services/dop/crypto/react-native-prover.js.map +1 -0
- package/dist/services/dop/crypto/react-native-rapidsnark-prover.js +27 -26
- package/dist/services/dop/crypto/react-native-rapidsnark-prover.js.map +1 -1
- package/dist/services/dop/crypto/wcd-prover.d.ts +242 -0
- package/dist/services/dop/crypto/wcd-prover.js +499 -0
- package/dist/services/dop/crypto/wcd-prover.js.map +1 -0
- package/package.json +1 -1
- package/dist/services/dop/crypto/custom-prover.d.ts +0 -78
- package/dist/services/dop/crypto/custom-prover.js +0 -78
- package/dist/services/dop/crypto/custom-prover.js.map +0 -1
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable no-bitwise */
|
|
3
|
+
/* eslint-disable no-plusplus */
|
|
4
|
+
/* eslint-disable max-classes-per-file */
|
|
5
|
+
/* eslint-disable camelcase */
|
|
6
|
+
/* eslint-disable import/no-extraneous-dependencies */
|
|
7
|
+
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
|
|
8
|
+
/**
|
|
9
|
+
* React Native Prover - Complete ZK Proof Solution
|
|
10
|
+
*
|
|
11
|
+
* This module provides a complete proof generation solution for React Native
|
|
12
|
+
* that works exactly like snarkjs in Node.js:
|
|
13
|
+
*
|
|
14
|
+
* Node.js snarkjs flow:
|
|
15
|
+
* 1. groth16.fullProve(inputs, wasmPath, zkeyPath)
|
|
16
|
+
* 2. Internally: Load WASM -> Create WitnessCalculator -> Calculate witness -> Generate proof
|
|
17
|
+
*
|
|
18
|
+
* React Native rapidsnark flow (this module):
|
|
19
|
+
* 1. setReactNativeProver(config) - Initialize with rapidsnark + file system
|
|
20
|
+
* 2. DOP Engine calls groth16.fullProveDop(inputs, wasm, zkey)
|
|
21
|
+
* 3. Internally: Calculate witness using WASM -> Save files -> Call rapidsnark -> Return proof
|
|
22
|
+
*
|
|
23
|
+
* Key components:
|
|
24
|
+
* - CircuitArtifactManager: Downloads and stores circuit files (WASM, zkey) locally
|
|
25
|
+
* - WitnessGenerator: Calculates witness from inputs using WASM (like circom_runtime)
|
|
26
|
+
* - RapidsnarkProver: Generates proof using witness and zkey
|
|
27
|
+
*/
|
|
28
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
29
|
+
if (k2 === undefined) k2 = k;
|
|
30
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
31
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
32
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
33
|
+
}
|
|
34
|
+
Object.defineProperty(o, k2, desc);
|
|
35
|
+
}) : (function(o, m, k, k2) {
|
|
36
|
+
if (k2 === undefined) k2 = k;
|
|
37
|
+
o[k2] = m[k];
|
|
38
|
+
}));
|
|
39
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
40
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
41
|
+
}) : function(o, v) {
|
|
42
|
+
o["default"] = v;
|
|
43
|
+
});
|
|
44
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
45
|
+
if (mod && mod.__esModule) return mod;
|
|
46
|
+
var result = {};
|
|
47
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
48
|
+
__setModuleDefault(result, mod);
|
|
49
|
+
return result;
|
|
50
|
+
};
|
|
51
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
+
exports.hasRNArtifacts = exports.getRNArtifactPath = exports.createRNArtifactStore = exports.createExpoFSInterface = exports.createRNFSInterface = exports.getReactNativeProver = exports.setReactNativeProver = exports.CircuitArtifactManager = exports.calculateWitnessFromWASM = exports.base64ToUint8Array = exports.uint8ArrayToBase64 = void 0;
|
|
53
|
+
const engine_1 = require("../core/engine");
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// Utility Functions
|
|
56
|
+
// ============================================================================
|
|
57
|
+
/**
|
|
58
|
+
* Convert Uint8Array to base64 string (React Native compatible)
|
|
59
|
+
*/
|
|
60
|
+
function uint8ArrayToBase64(data) {
|
|
61
|
+
// Use Buffer if available (Node.js/React Native with buffer polyfill)
|
|
62
|
+
if (typeof Buffer !== 'undefined') {
|
|
63
|
+
return Buffer.from(data).toString('base64');
|
|
64
|
+
}
|
|
65
|
+
// Fallback for pure JS environments
|
|
66
|
+
let binary = '';
|
|
67
|
+
const len = data.byteLength;
|
|
68
|
+
for (let i = 0; i < len; i++) {
|
|
69
|
+
binary += String.fromCharCode(data[i]);
|
|
70
|
+
}
|
|
71
|
+
return btoa(binary);
|
|
72
|
+
}
|
|
73
|
+
exports.uint8ArrayToBase64 = uint8ArrayToBase64;
|
|
74
|
+
/**
|
|
75
|
+
* Convert base64 string to Uint8Array (React Native compatible)
|
|
76
|
+
*/
|
|
77
|
+
function base64ToUint8Array(base64) {
|
|
78
|
+
// Use Buffer if available
|
|
79
|
+
if (typeof Buffer !== 'undefined') {
|
|
80
|
+
return new Uint8Array(Buffer.from(base64, 'base64'));
|
|
81
|
+
}
|
|
82
|
+
// Fallback for pure JS environments
|
|
83
|
+
const binary = atob(base64);
|
|
84
|
+
const bytes = new Uint8Array(binary.length);
|
|
85
|
+
for (let i = 0; i < binary.length; i++) {
|
|
86
|
+
bytes[i] = binary.charCodeAt(i);
|
|
87
|
+
}
|
|
88
|
+
return bytes;
|
|
89
|
+
}
|
|
90
|
+
exports.base64ToUint8Array = base64ToUint8Array;
|
|
91
|
+
/**
|
|
92
|
+
* Get circuit ID from formatted inputs
|
|
93
|
+
* DOP circuits are named like "3x2" (3 nullifiers, 2 commitments)
|
|
94
|
+
*/
|
|
95
|
+
function getCircuitIdFromInputs(inputs) {
|
|
96
|
+
const nullifierCount = inputs.nullifiers?.filter((n) => n !== undefined && n !== null && BigInt(n) !== BigInt(0)).length || 0;
|
|
97
|
+
const commitmentCount = inputs.commitmentsOut?.filter((c) => c !== undefined && c !== null && BigInt(c) !== BigInt(0)).length || 0;
|
|
98
|
+
return `${nullifierCount}x${commitmentCount}`;
|
|
99
|
+
}
|
|
100
|
+
// ============================================================================
|
|
101
|
+
// Witness Calculator (WASM-based, like circom_runtime)
|
|
102
|
+
// ============================================================================
|
|
103
|
+
/**
|
|
104
|
+
* The BN128 prime field used in Groth16
|
|
105
|
+
*/
|
|
106
|
+
const BN128_PRIME = BigInt('21888242871839275222246405745257275088548364400416034343698204186575808495617');
|
|
107
|
+
/**
|
|
108
|
+
* FNV-1a hash function for signal names (same as circom_runtime)
|
|
109
|
+
*/
|
|
110
|
+
function fnvHash(str) {
|
|
111
|
+
const FNV_OFFSET = BigInt('0xCBF29CE484222325');
|
|
112
|
+
const FNV_PRIME = BigInt('0x100000001B3');
|
|
113
|
+
let hash = FNV_OFFSET;
|
|
114
|
+
for (let i = 0; i < str.length; i++) {
|
|
115
|
+
hash ^= BigInt(str.charCodeAt(i));
|
|
116
|
+
hash = (hash * FNV_PRIME) % (BigInt(1) << BigInt(64));
|
|
117
|
+
}
|
|
118
|
+
return hash;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Convert bigint to 32-bit array (little-endian, like circom_runtime)
|
|
122
|
+
*/
|
|
123
|
+
function toArray32(num, size) {
|
|
124
|
+
const result = new Uint32Array(size);
|
|
125
|
+
let temp = num;
|
|
126
|
+
for (let i = 0; i < size; i++) {
|
|
127
|
+
result[i] = Number(temp & BigInt(0xFFFFFFFF));
|
|
128
|
+
temp >>= BigInt(32);
|
|
129
|
+
}
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Normalize value to prime field (like circom_runtime)
|
|
134
|
+
*/
|
|
135
|
+
function normalizeToField(value) {
|
|
136
|
+
let num;
|
|
137
|
+
if (typeof value === 'bigint') {
|
|
138
|
+
num = value;
|
|
139
|
+
}
|
|
140
|
+
else if (typeof value === 'string') {
|
|
141
|
+
// Handle hex strings
|
|
142
|
+
if (value.startsWith('0x')) {
|
|
143
|
+
num = BigInt(value);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
num = BigInt(value);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
num = BigInt(value);
|
|
151
|
+
}
|
|
152
|
+
// Normalize to prime field
|
|
153
|
+
num %= BN128_PRIME;
|
|
154
|
+
if (num < BigInt(0)) {
|
|
155
|
+
num += BN128_PRIME;
|
|
156
|
+
}
|
|
157
|
+
return num;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Flatten nested arrays (like circom_runtime)
|
|
161
|
+
*/
|
|
162
|
+
function flatArray(arr) {
|
|
163
|
+
const result = [];
|
|
164
|
+
function flatten(item) {
|
|
165
|
+
if (Array.isArray(item)) {
|
|
166
|
+
for (const subItem of item) {
|
|
167
|
+
flatten(subItem);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else if (typeof item === 'bigint' || typeof item === 'string' || typeof item === 'number') {
|
|
171
|
+
result.push(normalizeToField(item));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
flatten(arr);
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Create WTNS (witness) binary format
|
|
179
|
+
* This matches the format expected by rapidsnark
|
|
180
|
+
*
|
|
181
|
+
* WTNS format:
|
|
182
|
+
* - 4 bytes: "wtns" magic
|
|
183
|
+
* - 4 bytes: version (2)
|
|
184
|
+
* - 4 bytes: number of sections
|
|
185
|
+
* - Section 1 (header): field info
|
|
186
|
+
* - Section 2 (data): witness values
|
|
187
|
+
*/
|
|
188
|
+
function createWTNSBin(witness) {
|
|
189
|
+
const n8 = 32; // 32 bytes per field element (256 bits)
|
|
190
|
+
const witnessSize = witness.length;
|
|
191
|
+
// Calculate total size
|
|
192
|
+
// Header: magic(4) + version(4) + numSections(4) = 12
|
|
193
|
+
// Section 1 header: sectionType(4) + sectionSize(8) = 12
|
|
194
|
+
// Section 1 data: n8(4) + prime(n8) + witnessSize(4) = 4 + 32 + 4 = 40
|
|
195
|
+
// Section 2 header: sectionType(4) + sectionSize(8) = 12
|
|
196
|
+
// Section 2 data: witness values = witnessSize * n8
|
|
197
|
+
const section1Size = 4 + n8 + 4; // n8 size + prime + witness count
|
|
198
|
+
const section2Size = witnessSize * n8;
|
|
199
|
+
const totalSize = 12 + 12 + section1Size + 12 + section2Size;
|
|
200
|
+
const buffer = new ArrayBuffer(totalSize);
|
|
201
|
+
const view = new DataView(buffer);
|
|
202
|
+
const uint8 = new Uint8Array(buffer);
|
|
203
|
+
let offset = 0;
|
|
204
|
+
// Magic "wtns"
|
|
205
|
+
uint8[offset++] = 0x77; // 'w'
|
|
206
|
+
uint8[offset++] = 0x74; // 't'
|
|
207
|
+
uint8[offset++] = 0x6e; // 'n'
|
|
208
|
+
uint8[offset++] = 0x73; // 's'
|
|
209
|
+
// Version (2)
|
|
210
|
+
view.setUint32(offset, 2, true);
|
|
211
|
+
offset += 4;
|
|
212
|
+
// Number of sections (2)
|
|
213
|
+
view.setUint32(offset, 2, true);
|
|
214
|
+
offset += 4;
|
|
215
|
+
// Section 1: Header
|
|
216
|
+
view.setUint32(offset, 1, true); // section type
|
|
217
|
+
offset += 4;
|
|
218
|
+
// Section size as 64-bit
|
|
219
|
+
view.setUint32(offset, section1Size, true);
|
|
220
|
+
offset += 4;
|
|
221
|
+
view.setUint32(offset, 0, true);
|
|
222
|
+
offset += 4;
|
|
223
|
+
// n8 (field element size in bytes)
|
|
224
|
+
view.setUint32(offset, n8, true);
|
|
225
|
+
offset += 4;
|
|
226
|
+
// Prime (BN128 prime, little-endian)
|
|
227
|
+
const primeArray = toArray32(BN128_PRIME, 8);
|
|
228
|
+
for (let i = 0; i < 8; i++) {
|
|
229
|
+
view.setUint32(offset, primeArray[i], true);
|
|
230
|
+
offset += 4;
|
|
231
|
+
}
|
|
232
|
+
// Witness size
|
|
233
|
+
view.setUint32(offset, witnessSize, true);
|
|
234
|
+
offset += 4;
|
|
235
|
+
// Section 2: Data (witness values)
|
|
236
|
+
view.setUint32(offset, 2, true); // section type
|
|
237
|
+
offset += 4;
|
|
238
|
+
// Section size as 64-bit
|
|
239
|
+
view.setUint32(offset, section2Size, true);
|
|
240
|
+
offset += 4;
|
|
241
|
+
view.setUint32(offset, 0, true);
|
|
242
|
+
offset += 4;
|
|
243
|
+
// Write witness values (each as 32-byte little-endian)
|
|
244
|
+
for (const w of witness) {
|
|
245
|
+
const wArray = toArray32(w, 8);
|
|
246
|
+
for (let i = 0; i < 8; i++) {
|
|
247
|
+
view.setUint32(offset, wArray[i], true);
|
|
248
|
+
offset += 4;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return uint8;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Calculate witness using WASM
|
|
255
|
+
* This is the core function that mimics circom_runtime's WitnessCalculator
|
|
256
|
+
*
|
|
257
|
+
* For React Native, the WASM execution needs special handling.
|
|
258
|
+
* This function provides the interface - the actual WASM execution
|
|
259
|
+
* should be done by a native module or WebAssembly polyfill.
|
|
260
|
+
*/
|
|
261
|
+
async function calculateWitnessFromWASM(wasmBuffer, inputs, wasmExecutor) {
|
|
262
|
+
let witness;
|
|
263
|
+
if (wasmExecutor) {
|
|
264
|
+
// Use provided WASM executor
|
|
265
|
+
witness = await wasmExecutor(wasmBuffer, inputs);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
// Try to use circom_runtime if available
|
|
269
|
+
try {
|
|
270
|
+
const circomRuntime = await Promise.resolve().then(() => __importStar(require('circom_runtime')));
|
|
271
|
+
const witnessCalculator = await circomRuntime.WitnessCalculatorBuilder(wasmBuffer);
|
|
272
|
+
const witnessBin = await witnessCalculator.calculateWTNSBin(inputs);
|
|
273
|
+
// Return directly as it's already in WTNS format
|
|
274
|
+
return {
|
|
275
|
+
witnessBin: new Uint8Array(witnessBin),
|
|
276
|
+
witnessBase64: uint8ArrayToBase64(new Uint8Array(witnessBin)),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
throw new Error('WASM execution not available. Please provide a wasmExecutor or ensure circom_runtime is available.');
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// Create WTNS binary from witness array
|
|
284
|
+
const witnessBin = createWTNSBin(witness);
|
|
285
|
+
return {
|
|
286
|
+
witnessBin,
|
|
287
|
+
witnessBase64: uint8ArrayToBase64(witnessBin),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
exports.calculateWitnessFromWASM = calculateWitnessFromWASM;
|
|
291
|
+
// ============================================================================
|
|
292
|
+
// Circuit Artifact Manager
|
|
293
|
+
// ============================================================================
|
|
294
|
+
/**
|
|
295
|
+
* Manages circuit artifacts (WASM, zkey, vkey) on the device
|
|
296
|
+
*/
|
|
297
|
+
class CircuitArtifactManager {
|
|
298
|
+
fs;
|
|
299
|
+
artifactDir;
|
|
300
|
+
debug;
|
|
301
|
+
constructor(fs, artifactDir, debug = false) {
|
|
302
|
+
this.fs = fs;
|
|
303
|
+
this.artifactDir = artifactDir || `${fs.documentDirectory}/dop-circuits`;
|
|
304
|
+
this.debug = debug;
|
|
305
|
+
}
|
|
306
|
+
log(message) {
|
|
307
|
+
if (this.debug) {
|
|
308
|
+
console.log(`[CircuitArtifactManager] ${message}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Ensure artifact directory exists
|
|
313
|
+
*/
|
|
314
|
+
async ensureDir(dir) {
|
|
315
|
+
try {
|
|
316
|
+
if (!(await this.fs.exists(dir))) {
|
|
317
|
+
await this.fs.mkdir(dir);
|
|
318
|
+
this.log(`Created directory: ${dir}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
// Directory might already exist
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Get paths for circuit artifacts
|
|
327
|
+
*/
|
|
328
|
+
getArtifactPaths(circuitId) {
|
|
329
|
+
const circuitDir = `${this.artifactDir}/${circuitId}`;
|
|
330
|
+
return {
|
|
331
|
+
wasmPath: `${circuitDir}/circuit.wasm`,
|
|
332
|
+
zkeyPath: `${circuitDir}/circuit.zkey`,
|
|
333
|
+
vkeyPath: `${circuitDir}/vkey.json`,
|
|
334
|
+
circuitId,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Check if circuit artifacts are already stored
|
|
339
|
+
*/
|
|
340
|
+
async hasArtifacts(circuitId) {
|
|
341
|
+
const paths = this.getArtifactPaths(circuitId);
|
|
342
|
+
const [hasWasm, hasZkey] = await Promise.all([
|
|
343
|
+
this.fs.exists(paths.wasmPath),
|
|
344
|
+
this.fs.exists(paths.zkeyPath),
|
|
345
|
+
]);
|
|
346
|
+
return hasWasm && hasZkey;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Store circuit artifacts from buffers
|
|
350
|
+
* Call this after downloading artifacts
|
|
351
|
+
*/
|
|
352
|
+
async storeArtifacts(circuitId, wasmBuffer, zkeyBuffer, vkeyJson) {
|
|
353
|
+
const paths = this.getArtifactPaths(circuitId);
|
|
354
|
+
const circuitDir = `${this.artifactDir}/${circuitId}`;
|
|
355
|
+
await this.ensureDir(this.artifactDir);
|
|
356
|
+
await this.ensureDir(circuitDir);
|
|
357
|
+
this.log(`Storing artifacts for circuit ${circuitId}`);
|
|
358
|
+
// Store WASM
|
|
359
|
+
await this.fs.writeFile(paths.wasmPath, uint8ArrayToBase64(wasmBuffer), 'base64');
|
|
360
|
+
this.log(`Stored WASM: ${paths.wasmPath} (${wasmBuffer.byteLength} bytes)`);
|
|
361
|
+
// Store zkey
|
|
362
|
+
await this.fs.writeFile(paths.zkeyPath, uint8ArrayToBase64(zkeyBuffer), 'base64');
|
|
363
|
+
this.log(`Stored zkey: ${paths.zkeyPath} (${zkeyBuffer.byteLength} bytes)`);
|
|
364
|
+
// Store vkey if provided
|
|
365
|
+
if (vkeyJson) {
|
|
366
|
+
await this.fs.writeFile(paths.vkeyPath, JSON.stringify(vkeyJson), 'utf8');
|
|
367
|
+
this.log(`Stored vkey: ${paths.vkeyPath}`);
|
|
368
|
+
}
|
|
369
|
+
return paths;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Store artifacts from the DOP Engine's artifact getter
|
|
373
|
+
* This is called during proof generation when we receive buffers from the engine
|
|
374
|
+
*/
|
|
375
|
+
async storeArtifactsFromBuffers(circuitId, wasmOrDat, zkey) {
|
|
376
|
+
const wasmBuffer = wasmOrDat instanceof Uint8Array
|
|
377
|
+
? wasmOrDat
|
|
378
|
+
: new Uint8Array(wasmOrDat);
|
|
379
|
+
const zkeyBuffer = new Uint8Array(zkey);
|
|
380
|
+
return this.storeArtifacts(circuitId, wasmBuffer, zkeyBuffer);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Load WASM buffer from stored file
|
|
384
|
+
*/
|
|
385
|
+
async loadWASM(circuitId) {
|
|
386
|
+
const paths = this.getArtifactPaths(circuitId);
|
|
387
|
+
const base64 = await this.fs.readFile(paths.wasmPath, 'base64');
|
|
388
|
+
return base64ToUint8Array(base64);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Get zkey path for rapidsnark
|
|
392
|
+
*/
|
|
393
|
+
getZkeyPath(circuitId) {
|
|
394
|
+
return this.getArtifactPaths(circuitId).zkeyPath;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Clean up artifacts for a circuit
|
|
398
|
+
*/
|
|
399
|
+
async removeArtifacts(circuitId) {
|
|
400
|
+
const paths = this.getArtifactPaths(circuitId);
|
|
401
|
+
try {
|
|
402
|
+
await this.fs.unlink(paths.wasmPath);
|
|
403
|
+
}
|
|
404
|
+
catch { /* ignore */ }
|
|
405
|
+
try {
|
|
406
|
+
await this.fs.unlink(paths.zkeyPath);
|
|
407
|
+
}
|
|
408
|
+
catch { /* ignore */ }
|
|
409
|
+
try {
|
|
410
|
+
await this.fs.unlink(paths.vkeyPath);
|
|
411
|
+
}
|
|
412
|
+
catch { /* ignore */ }
|
|
413
|
+
this.log(`Removed artifacts for circuit ${circuitId}`);
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Get all stored circuit IDs
|
|
417
|
+
*/
|
|
418
|
+
getArtifactDirectory() {
|
|
419
|
+
return this.artifactDir;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
exports.CircuitArtifactManager = CircuitArtifactManager;
|
|
423
|
+
// ============================================================================
|
|
424
|
+
// React Native Prover
|
|
425
|
+
// ============================================================================
|
|
426
|
+
/**
|
|
427
|
+
* React Native Prover Instance
|
|
428
|
+
* Manages the complete proof generation flow
|
|
429
|
+
*/
|
|
430
|
+
class ReactNativeProverInstance {
|
|
431
|
+
config;
|
|
432
|
+
artifactManager;
|
|
433
|
+
initialized = false;
|
|
434
|
+
constructor(config) {
|
|
435
|
+
this.config = config;
|
|
436
|
+
this.artifactManager = new CircuitArtifactManager(config.fs, config.artifactDir, config.debug);
|
|
437
|
+
}
|
|
438
|
+
log(message) {
|
|
439
|
+
if (this.config.debug) {
|
|
440
|
+
console.log(`[ReactNativeProver] ${message}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Initialize the prover (ensure directories exist)
|
|
445
|
+
*/
|
|
446
|
+
async initialize() {
|
|
447
|
+
if (this.initialized)
|
|
448
|
+
return;
|
|
449
|
+
await this.artifactManager.ensureDir(this.config.artifactDir || `${this.config.fs.documentDirectory}/dop-circuits`);
|
|
450
|
+
this.initialized = true;
|
|
451
|
+
this.log('Initialized');
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Generate proof using rapidsnark
|
|
455
|
+
* This is the main function that mimics snarkjs.groth16.fullProve
|
|
456
|
+
*/
|
|
457
|
+
async generateProof(inputs, wasmBuffer, zkeyBuffer, logger, progressCallback) {
|
|
458
|
+
await this.initialize();
|
|
459
|
+
const circuitId = getCircuitIdFromInputs(inputs);
|
|
460
|
+
this.log(`Generating proof for circuit ${circuitId}`);
|
|
461
|
+
progressCallback?.(5);
|
|
462
|
+
// Step 1: Store artifacts on device (if not already stored)
|
|
463
|
+
logger.debug(`[RN Prover] Step 1: Preparing artifacts for circuit ${circuitId}...`);
|
|
464
|
+
const wasm = wasmBuffer instanceof Uint8Array
|
|
465
|
+
? wasmBuffer
|
|
466
|
+
: new Uint8Array(wasmBuffer);
|
|
467
|
+
const artifacts = await this.artifactManager.storeArtifactsFromBuffers(circuitId, wasm, zkeyBuffer);
|
|
468
|
+
this.log(`Artifacts stored at: ${artifacts.zkeyPath}`);
|
|
469
|
+
progressCallback?.(20);
|
|
470
|
+
// Step 2: Calculate witness from inputs
|
|
471
|
+
logger.debug('[RN Prover] Step 2: Calculating witness...');
|
|
472
|
+
let witnessResult;
|
|
473
|
+
if (this.config.witnessCalculator) {
|
|
474
|
+
// Use custom witness calculator
|
|
475
|
+
const witnessBin = await this.config.witnessCalculator(wasm, inputs);
|
|
476
|
+
witnessResult = {
|
|
477
|
+
witnessBin,
|
|
478
|
+
witnessBase64: uint8ArrayToBase64(witnessBin),
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
// Use built-in WASM-based calculator
|
|
483
|
+
witnessResult = await calculateWitnessFromWASM(wasm, inputs);
|
|
484
|
+
}
|
|
485
|
+
this.log(`Witness calculated: ${witnessResult.witnessBin.byteLength} bytes`);
|
|
486
|
+
progressCallback?.(50);
|
|
487
|
+
// Step 3: Generate proof with rapidsnark
|
|
488
|
+
logger.debug('[RN Prover] Step 3: Generating proof with rapidsnark...');
|
|
489
|
+
const rapidsnarkResult = await this.config.groth16Prove(artifacts.zkeyPath, witnessResult.witnessBase64);
|
|
490
|
+
this.log('Proof generated successfully');
|
|
491
|
+
progressCallback?.(90);
|
|
492
|
+
// Step 4: Convert to DOP Engine format
|
|
493
|
+
const proof = {
|
|
494
|
+
pi_a: [rapidsnarkResult.proof.a[0], rapidsnarkResult.proof.a[1]],
|
|
495
|
+
pi_b: [
|
|
496
|
+
[rapidsnarkResult.proof.b[0][0], rapidsnarkResult.proof.b[0][1]],
|
|
497
|
+
[rapidsnarkResult.proof.b[1][0], rapidsnarkResult.proof.b[1][1]],
|
|
498
|
+
],
|
|
499
|
+
pi_c: [rapidsnarkResult.proof.c[0], rapidsnarkResult.proof.c[1]],
|
|
500
|
+
};
|
|
501
|
+
progressCallback?.(100);
|
|
502
|
+
return {
|
|
503
|
+
proof,
|
|
504
|
+
publicSignals: rapidsnarkResult.pub_signals,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Get the artifact manager for manual artifact management
|
|
509
|
+
*/
|
|
510
|
+
getArtifactManager() {
|
|
511
|
+
return this.artifactManager;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
// ============================================================================
|
|
515
|
+
// Main Export: setReactNativeProver
|
|
516
|
+
// ============================================================================
|
|
517
|
+
let globalReactNativeProver = null;
|
|
518
|
+
/**
|
|
519
|
+
* Set up React Native prover for DOP Engine
|
|
520
|
+
*
|
|
521
|
+
* This function configures the DOP Engine to use rapidsnark for proof generation
|
|
522
|
+
* on React Native. It works exactly like setSnarkJSGroth16 but uses rapidsnark
|
|
523
|
+
* under the hood.
|
|
524
|
+
*
|
|
525
|
+
* @example
|
|
526
|
+
* ```typescript
|
|
527
|
+
* import { groth16Prove } from 'react-native-rapidsnark';
|
|
528
|
+
* import RNFS from 'react-native-fs';
|
|
529
|
+
* import { setReactNativeProver } from 'new-dop-wallet-v3';
|
|
530
|
+
*
|
|
531
|
+
* // Create file system interface for react-native-fs
|
|
532
|
+
* const fs: RNFileSystem = {
|
|
533
|
+
* writeFile: (path, data, encoding) => RNFS.writeFile(path, data, encoding),
|
|
534
|
+
* readFile: (path, encoding) => RNFS.readFile(path, encoding),
|
|
535
|
+
* exists: RNFS.exists,
|
|
536
|
+
* unlink: RNFS.unlink,
|
|
537
|
+
* mkdir: RNFS.mkdir,
|
|
538
|
+
* documentDirectory: RNFS.DocumentDirectoryPath,
|
|
539
|
+
* cacheDirectory: RNFS.CachesDirectoryPath,
|
|
540
|
+
* };
|
|
541
|
+
*
|
|
542
|
+
* // Initialize React Native prover (call after initDOP)
|
|
543
|
+
* await setReactNativeProver({
|
|
544
|
+
* groth16Prove,
|
|
545
|
+
* fs,
|
|
546
|
+
* debug: true,
|
|
547
|
+
* });
|
|
548
|
+
*
|
|
549
|
+
* // Now all DOP SDK transactions will use rapidsnark automatically!
|
|
550
|
+
* ```
|
|
551
|
+
*/
|
|
552
|
+
async function setReactNativeProver(config) {
|
|
553
|
+
const engine = (0, engine_1.getEngine)();
|
|
554
|
+
if (!engine) {
|
|
555
|
+
throw new Error('DOP Engine not initialized. Call initDOP() first.');
|
|
556
|
+
}
|
|
557
|
+
// Create prover instance
|
|
558
|
+
globalReactNativeProver = new ReactNativeProverInstance(config);
|
|
559
|
+
await globalReactNativeProver.initialize();
|
|
560
|
+
console.log('[RN Prover] Setting up React Native prover with rapidsnark');
|
|
561
|
+
// Create groth16 implementation
|
|
562
|
+
const reactNativeGroth16 = {
|
|
563
|
+
fullProve: async (inputs, wasm, zkey, logger) => {
|
|
564
|
+
if (!globalReactNativeProver) {
|
|
565
|
+
throw new Error('React Native prover not initialized');
|
|
566
|
+
}
|
|
567
|
+
if (!wasm) {
|
|
568
|
+
throw new Error('WASM buffer is required for proof generation');
|
|
569
|
+
}
|
|
570
|
+
return globalReactNativeProver.generateProof(inputs, wasm, zkey, logger, undefined);
|
|
571
|
+
},
|
|
572
|
+
/**
|
|
573
|
+
* Verify a proof using rapidsnark's groth16Verify
|
|
574
|
+
* This works like snarkjs.groth16.verify in Node.js
|
|
575
|
+
*/
|
|
576
|
+
verify: async (vkey, publicSignals, proof) => {
|
|
577
|
+
// Check if groth16Verify was provided in config
|
|
578
|
+
if (!config.groth16Verify) {
|
|
579
|
+
console.warn('[RN Prover] groth16Verify not provided in config. Cannot verify proof locally.');
|
|
580
|
+
throw new Error('Proof verification requires groth16Verify function. ' +
|
|
581
|
+
'Pass groth16Verify from react-native-rapidsnark in setReactNativeProver config.');
|
|
582
|
+
}
|
|
583
|
+
try {
|
|
584
|
+
// Convert proof to rapidsnark format
|
|
585
|
+
// rapidsnark expects: proof as JSON string, inputs as JSON string, vkey as JSON string
|
|
586
|
+
const rapidsnarkProof = {
|
|
587
|
+
pi_a: proof.pi_a,
|
|
588
|
+
pi_b: proof.pi_b,
|
|
589
|
+
pi_c: proof.pi_c,
|
|
590
|
+
protocol: 'groth16',
|
|
591
|
+
curve: 'bn128',
|
|
592
|
+
};
|
|
593
|
+
const proofJson = JSON.stringify(rapidsnarkProof);
|
|
594
|
+
const inputsJson = JSON.stringify(publicSignals);
|
|
595
|
+
const vkeyJson = JSON.stringify(vkey);
|
|
596
|
+
const isValid = await config.groth16Verify(proofJson, inputsJson, vkeyJson);
|
|
597
|
+
console.log('[RN Prover] Proof verification result:', isValid);
|
|
598
|
+
return isValid;
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
console.error('[RN Prover] Proof verification failed:', error);
|
|
602
|
+
throw error;
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
};
|
|
606
|
+
// Set on engine
|
|
607
|
+
engine.prover.setSnarkJSGroth16(reactNativeGroth16);
|
|
608
|
+
console.log('[RN Prover] React Native prover configured successfully');
|
|
609
|
+
}
|
|
610
|
+
exports.setReactNativeProver = setReactNativeProver;
|
|
611
|
+
/**
|
|
612
|
+
* Get the global React Native prover instance
|
|
613
|
+
* Useful for accessing the artifact manager directly
|
|
614
|
+
*/
|
|
615
|
+
function getReactNativeProver() {
|
|
616
|
+
return globalReactNativeProver;
|
|
617
|
+
}
|
|
618
|
+
exports.getReactNativeProver = getReactNativeProver;
|
|
619
|
+
// ============================================================================
|
|
620
|
+
// Helper: Create file system interfaces
|
|
621
|
+
// ============================================================================
|
|
622
|
+
/**
|
|
623
|
+
* Create RNFileSystem from react-native-fs (RNFS)
|
|
624
|
+
*
|
|
625
|
+
* @example
|
|
626
|
+
* ```typescript
|
|
627
|
+
* import RNFS from 'react-native-fs';
|
|
628
|
+
* const fs = createRNFSInterface(RNFS);
|
|
629
|
+
* ```
|
|
630
|
+
*/
|
|
631
|
+
function createRNFSInterface(RNFS) {
|
|
632
|
+
return {
|
|
633
|
+
writeFile: (path, data, encoding) => RNFS.writeFile(path, data, encoding),
|
|
634
|
+
readFile: (path, encoding) => RNFS.readFile(path, encoding),
|
|
635
|
+
exists: RNFS.exists,
|
|
636
|
+
unlink: RNFS.unlink,
|
|
637
|
+
mkdir: (path) => RNFS.mkdir(path, { NSURLIsExcludedFromBackupKey: true }),
|
|
638
|
+
documentDirectory: RNFS.DocumentDirectoryPath,
|
|
639
|
+
cacheDirectory: RNFS.CachesDirectoryPath,
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
exports.createRNFSInterface = createRNFSInterface;
|
|
643
|
+
/**
|
|
644
|
+
* Create RNFileSystem from expo-file-system
|
|
645
|
+
*
|
|
646
|
+
* @example
|
|
647
|
+
* ```typescript
|
|
648
|
+
* import * as FileSystem from 'expo-file-system';
|
|
649
|
+
* const fs = createExpoFSInterface(FileSystem);
|
|
650
|
+
* ```
|
|
651
|
+
*/
|
|
652
|
+
function createExpoFSInterface(ExpoFS) {
|
|
653
|
+
const docDir = ExpoFS.documentDirectory || '';
|
|
654
|
+
const cacheDir = ExpoFS.cacheDirectory || '';
|
|
655
|
+
return {
|
|
656
|
+
writeFile: async (path, data, encoding) => {
|
|
657
|
+
const options = encoding === 'base64'
|
|
658
|
+
? { encoding: 'base64' }
|
|
659
|
+
: { encoding: 'utf8' };
|
|
660
|
+
await ExpoFS.writeAsStringAsync(path, data, options);
|
|
661
|
+
},
|
|
662
|
+
readFile: async (path, encoding) => {
|
|
663
|
+
const options = encoding === 'base64'
|
|
664
|
+
? { encoding: 'base64' }
|
|
665
|
+
: { encoding: 'utf8' };
|
|
666
|
+
return ExpoFS.readAsStringAsync(path, options);
|
|
667
|
+
},
|
|
668
|
+
exists: async (path) => {
|
|
669
|
+
const info = await ExpoFS.getInfoAsync(path);
|
|
670
|
+
return info.exists;
|
|
671
|
+
},
|
|
672
|
+
unlink: (path) => ExpoFS.deleteAsync(path, { idempotent: true }),
|
|
673
|
+
mkdir: (path) => ExpoFS.makeDirectoryAsync(path, { intermediates: true }),
|
|
674
|
+
documentDirectory: docDir,
|
|
675
|
+
cacheDirectory: cacheDir,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
exports.createExpoFSInterface = createExpoFSInterface;
|
|
679
|
+
/**
|
|
680
|
+
* Create an ArtifactStore for React Native that stores files to device storage
|
|
681
|
+
*
|
|
682
|
+
* This allows the SDK's ArtifactDownloader to automatically save artifacts
|
|
683
|
+
* to a location that rapidsnark can access by file path.
|
|
684
|
+
*
|
|
685
|
+
* Use this when initializing the DOP Engine to make artifacts available for rapidsnark.
|
|
686
|
+
*
|
|
687
|
+
* @example
|
|
688
|
+
* ```typescript
|
|
689
|
+
* import RNFS from 'react-native-fs';
|
|
690
|
+
* import { createRNArtifactStore, createRNFSInterface } from 'new-dop-wallet-v3';
|
|
691
|
+
*
|
|
692
|
+
* const fs = createRNFSInterface(RNFS);
|
|
693
|
+
* const artifactStore = createRNArtifactStore(fs);
|
|
694
|
+
*
|
|
695
|
+
* // Pass to ArtifactDownloader when initializing DOP Engine
|
|
696
|
+
* const artifactDownloader = new ArtifactDownloader(artifactStore, false);
|
|
697
|
+
* ```
|
|
698
|
+
*/
|
|
699
|
+
function createRNArtifactStore(fs, baseDir) {
|
|
700
|
+
const artifactBaseDir = baseDir || `${fs.documentDirectory}/dop-artifacts`;
|
|
701
|
+
// Ensure base directory exists
|
|
702
|
+
const ensureBaseDir = async () => {
|
|
703
|
+
try {
|
|
704
|
+
if (!(await fs.exists(artifactBaseDir))) {
|
|
705
|
+
await fs.mkdir(artifactBaseDir);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
catch {
|
|
709
|
+
// Directory might already exist
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
return {
|
|
713
|
+
/**
|
|
714
|
+
* Get artifact content from device storage
|
|
715
|
+
*/
|
|
716
|
+
get: async (path) => {
|
|
717
|
+
const fullPath = `${artifactBaseDir}/${path}`;
|
|
718
|
+
try {
|
|
719
|
+
if (!(await fs.exists(fullPath))) {
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
// Check if it's a JSON file (vkey)
|
|
723
|
+
if (path.endsWith('.json')) {
|
|
724
|
+
const content = await fs.readFile(fullPath, 'utf8');
|
|
725
|
+
return content;
|
|
726
|
+
}
|
|
727
|
+
// Binary file (zkey, wasm, dat)
|
|
728
|
+
const base64 = await fs.readFile(fullPath, 'base64');
|
|
729
|
+
return Buffer.from(base64, 'base64');
|
|
730
|
+
}
|
|
731
|
+
catch {
|
|
732
|
+
return null;
|
|
733
|
+
}
|
|
734
|
+
},
|
|
735
|
+
/**
|
|
736
|
+
* Store artifact to device storage
|
|
737
|
+
* This is called by SDK's ArtifactDownloader after downloading
|
|
738
|
+
*/
|
|
739
|
+
store: async (dir, path, item) => {
|
|
740
|
+
await ensureBaseDir();
|
|
741
|
+
const fullDir = `${artifactBaseDir}/${dir}`;
|
|
742
|
+
const fullPath = `${artifactBaseDir}/${path}`;
|
|
743
|
+
// Ensure directory exists
|
|
744
|
+
try {
|
|
745
|
+
if (!(await fs.exists(fullDir))) {
|
|
746
|
+
await fs.mkdir(fullDir);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
catch {
|
|
750
|
+
// Directory might already exist
|
|
751
|
+
}
|
|
752
|
+
// Store the artifact
|
|
753
|
+
if (typeof item === 'string') {
|
|
754
|
+
// JSON string (vkey)
|
|
755
|
+
await fs.writeFile(fullPath, item, 'utf8');
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
// Binary data (zkey, wasm, dat)
|
|
759
|
+
const base64 = uint8ArrayToBase64(item);
|
|
760
|
+
await fs.writeFile(fullPath, base64, 'base64');
|
|
761
|
+
}
|
|
762
|
+
console.log(`[RN ArtifactStore] Stored artifact: ${path}`);
|
|
763
|
+
},
|
|
764
|
+
/**
|
|
765
|
+
* Check if artifact exists on device
|
|
766
|
+
*/
|
|
767
|
+
exists: async (path) => {
|
|
768
|
+
const fullPath = `${artifactBaseDir}/${path}`;
|
|
769
|
+
return fs.exists(fullPath);
|
|
770
|
+
},
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
exports.createRNArtifactStore = createRNArtifactStore;
|
|
774
|
+
/**
|
|
775
|
+
* Get the full file path for an artifact stored via RNArtifactStore
|
|
776
|
+
*
|
|
777
|
+
* Use this to get the path for rapidsnark's groth16Prove function
|
|
778
|
+
*
|
|
779
|
+
* @example
|
|
780
|
+
* ```typescript
|
|
781
|
+
* const zkeyPath = getRNArtifactPath(fs, '3x2', 'zkey');
|
|
782
|
+
* const proofResult = await groth16Prove(zkeyPath, witnessBase64);
|
|
783
|
+
* ```
|
|
784
|
+
*/
|
|
785
|
+
function getRNArtifactPath(fs, circuitId, artifactType, baseDir) {
|
|
786
|
+
const artifactBaseDir = baseDir || `${fs.documentDirectory}/dop-artifacts`;
|
|
787
|
+
const artifactDir = `artifacts-v2.1/${circuitId}`;
|
|
788
|
+
switch (artifactType) {
|
|
789
|
+
case 'zkey':
|
|
790
|
+
return `${artifactBaseDir}/${artifactDir}/zkey`;
|
|
791
|
+
case 'wasm':
|
|
792
|
+
return `${artifactBaseDir}/${artifactDir}/wasm`;
|
|
793
|
+
case 'vkey':
|
|
794
|
+
return `${artifactBaseDir}/${artifactDir}/vkey.json`;
|
|
795
|
+
case 'dat':
|
|
796
|
+
return `${artifactBaseDir}/${artifactDir}/dat`;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
exports.getRNArtifactPath = getRNArtifactPath;
|
|
800
|
+
/**
|
|
801
|
+
* Check if all artifacts for a circuit are downloaded
|
|
802
|
+
*/
|
|
803
|
+
async function hasRNArtifacts(fs, circuitId, baseDir, useNativeArtifacts = false) {
|
|
804
|
+
const zkeyPath = getRNArtifactPath(fs, circuitId, 'zkey', baseDir);
|
|
805
|
+
const wasmOrDatPath = getRNArtifactPath(fs, circuitId, useNativeArtifacts ? 'dat' : 'wasm', baseDir);
|
|
806
|
+
const [hasZkey, hasWasmOrDat] = await Promise.all([
|
|
807
|
+
fs.exists(zkeyPath),
|
|
808
|
+
fs.exists(wasmOrDatPath),
|
|
809
|
+
]);
|
|
810
|
+
return hasZkey && hasWasmOrDat;
|
|
811
|
+
}
|
|
812
|
+
exports.hasRNArtifacts = hasRNArtifacts;
|
|
813
|
+
// Types and classes are exported where they are defined
|
|
814
|
+
//# sourceMappingURL=react-native-prover.js.map
|