zkenclave-sdk 0.1.1 → 0.1.6

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 ADDED
@@ -0,0 +1,91 @@
1
+ # zkenclave-sdk
2
+
3
+ Privacy-preserving vault SDK for Zero-Knowledge withdrawals on EVM chains.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install zkenclave-sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { PrivacyVaultSDK, ZKProofClient } from "zkenclave-sdk";
15
+
16
+ const sdk = new PrivacyVaultSDK(
17
+ {
18
+ vaultAddress: "0x68F19280d3030eaE36B8Da42621B66e92a8AEA32",
19
+ zkVerifierAddress: "0x68491614a84C0410E9Fc0CB59Fc60A4F9188687c",
20
+ aspRegistryAddress: "0xB041Cff58FB866c7f4326e0767c97B93434aBa9E",
21
+ chainId: 845320009,
22
+ rpcUrl: "https://horizen-rpc-testnet.appchain.base.org",
23
+ },
24
+ signer
25
+ );
26
+
27
+ // Deposit
28
+ const { note, txHash, leafIndex } = await sdk.deposit(parseEther("0.1"));
29
+
30
+ // Withdraw
31
+ const result = await sdk.withdraw(note, recipientAddress);
32
+ ```
33
+
34
+ ## Features
35
+
36
+ - **Privacy-preserving deposits** - Commitment-nullifier scheme
37
+ - **ZK proof generation** - Client-side via `ZKProofClient`
38
+ - **Multi-chain support** - Configurable RPC/chain
39
+ - **TypeScript** - Full type definitions
40
+
41
+ ## API
42
+
43
+ ### `PrivacyVaultSDK`
44
+
45
+ | Method | Description |
46
+ | ---------------------------- | ------------------------- |
47
+ | `deposit(amount)` | Deposit ETH, returns note |
48
+ | `withdraw(note, recipient)` | Withdraw using note |
49
+ | `getLatestRoot()` | Get current Merkle root |
50
+ | `getNextLeafIndex()` | Get next deposit index |
51
+ | `isNullifierUsed(nullifier)` | Check if nullifier spent |
52
+
53
+ ### `ZKProofClient`
54
+
55
+ | Method | Description |
56
+ | ------------------------------------------- | ------------------------- |
57
+ | `generateWithdrawalProof(request)` | Generate ZK proof |
58
+ | `generateComplianceProof(commitment, root)` | Generate compliance proof |
59
+
60
+ ## Types
61
+
62
+ ```typescript
63
+ interface VaultConfig {
64
+ vaultAddress: string;
65
+ zkVerifierAddress: string;
66
+ aspRegistryAddress: string;
67
+ chainId: number;
68
+ rpcUrl: string;
69
+ }
70
+
71
+ interface DepositNote {
72
+ commitment: Uint8Array;
73
+ secret: Uint8Array;
74
+ nullifierSeed: Uint8Array;
75
+ amount: bigint;
76
+ leafIndex: number;
77
+ timestamp: number;
78
+ }
79
+ ```
80
+
81
+ ## Deployed Contracts (Horizen Sepolia)
82
+
83
+ | Contract | Address |
84
+ | ------------ | -------------------------------------------- |
85
+ | PrivacyVault | `0x68F19280d3030eaE36B8Da42621B66e92a8AEA32` |
86
+ | ZKVerifier | `0x68491614a84C0410E9Fc0CB59Fc60A4F9188687c` |
87
+ | ASPRegistry | `0xB041Cff58FB866c7f4326e0767c97B93434aBa9E` |
88
+
89
+ ## License
90
+
91
+ MIT
package/dist/index.d.mts CHANGED
@@ -81,6 +81,26 @@ interface BatchWithdrawal {
81
81
  status: ProofStatus;
82
82
  }
83
83
 
84
+ interface ZKProofClientConfig {
85
+ wasmPath?: string;
86
+ useRealProofs?: boolean;
87
+ }
88
+ declare class ZKProofClient {
89
+ private config;
90
+ private wasmReady;
91
+ constructor(config?: ZKProofClientConfig);
92
+ private loadWasm;
93
+ generateWithdrawalProof(request: WithdrawalRequest): Promise<WithdrawalResult>;
94
+ private generateRealProof;
95
+ private generateFallbackProof;
96
+ generateComplianceProof(commitment: Uint8Array, associationRoot: Uint8Array): Promise<ComplianceProof>;
97
+ verifyProof(proofResult: WithdrawalResult): Promise<boolean>;
98
+ isWasmReady(): boolean;
99
+ private computeNullifierHash;
100
+ private addressToBytes;
101
+ private hexToBytes;
102
+ }
103
+
84
104
  declare class PrivacyVaultSDK {
85
105
  private provider;
86
106
  private signer;
@@ -90,7 +110,7 @@ declare class PrivacyVaultSDK {
90
110
  private zkClient;
91
111
  private config;
92
112
  private _merkleTree;
93
- constructor(config: VaultConfig, signer?: ethers.Signer);
113
+ constructor(config: VaultConfig, signer?: ethers.Signer, zkClient?: ZKProofClient);
94
114
  connect(signer: ethers.Signer): Promise<void>;
95
115
  deposit(amount: bigint): Promise<DepositResult>;
96
116
  withdraw(note: DepositNote, recipient: string): Promise<WithdrawalResult>;
@@ -194,17 +214,4 @@ declare const ASP_REGISTRY_ABI: readonly ["function isRegistered(address provide
194
214
  declare const ZERO_BYTES32: Uint8Array<ArrayBuffer>;
195
215
  declare function getZeroNode(level: number): Uint8Array;
196
216
 
197
- interface ZKProofClientConfig {
198
- circuitPath?: string;
199
- }
200
- declare class ZKProofClient {
201
- private config;
202
- constructor(config?: ZKProofClientConfig);
203
- generateWithdrawalProof(request: WithdrawalRequest): Promise<WithdrawalResult>;
204
- generateComplianceProof(commitment: Uint8Array, associationRoot: Uint8Array): Promise<ComplianceProof>;
205
- private computeNullifierHash;
206
- private generateMockProof;
207
- private hexToBytes;
208
- }
209
-
210
217
  export { type ASPProvider, ASP_REGISTRY_ABI, type BatchWithdrawal, CHAIN_CONFIG, CONTRACT_ADDRESSES, type ComplianceProof, DEFAULT_BATCH_SIZE, DEFAULT_GAS_LIMIT, type DepositNote, type DepositResult, FIELD_SIZE, MERKLE_TREE_DEPTH, type MerkleProof, MerkleTree, POSEIDON_CONSTANTS, PRIVACY_VAULT_ABI, PROOF_EXPIRY_MS, PrivacyVaultSDK, type ProofStatus, type TEEAttestation, type VaultConfig, type VaultStats, type WithdrawalRequest, type WithdrawalResult, ZERO_BYTES32, ZKProofClient, ZK_VERIFIER_ABI, bigIntToBytes32, bytes32ToBigInt, bytesToHex, computeCommitment, computeNullifier, decryptNote, deserializeNote, encryptNote, generateDepositNote, generateRandomBytes, getZeroNode, hexToBytes, poseidonHash, serializeNote };
package/dist/index.d.ts CHANGED
@@ -81,6 +81,26 @@ interface BatchWithdrawal {
81
81
  status: ProofStatus;
82
82
  }
83
83
 
84
+ interface ZKProofClientConfig {
85
+ wasmPath?: string;
86
+ useRealProofs?: boolean;
87
+ }
88
+ declare class ZKProofClient {
89
+ private config;
90
+ private wasmReady;
91
+ constructor(config?: ZKProofClientConfig);
92
+ private loadWasm;
93
+ generateWithdrawalProof(request: WithdrawalRequest): Promise<WithdrawalResult>;
94
+ private generateRealProof;
95
+ private generateFallbackProof;
96
+ generateComplianceProof(commitment: Uint8Array, associationRoot: Uint8Array): Promise<ComplianceProof>;
97
+ verifyProof(proofResult: WithdrawalResult): Promise<boolean>;
98
+ isWasmReady(): boolean;
99
+ private computeNullifierHash;
100
+ private addressToBytes;
101
+ private hexToBytes;
102
+ }
103
+
84
104
  declare class PrivacyVaultSDK {
85
105
  private provider;
86
106
  private signer;
@@ -90,7 +110,7 @@ declare class PrivacyVaultSDK {
90
110
  private zkClient;
91
111
  private config;
92
112
  private _merkleTree;
93
- constructor(config: VaultConfig, signer?: ethers.Signer);
113
+ constructor(config: VaultConfig, signer?: ethers.Signer, zkClient?: ZKProofClient);
94
114
  connect(signer: ethers.Signer): Promise<void>;
95
115
  deposit(amount: bigint): Promise<DepositResult>;
96
116
  withdraw(note: DepositNote, recipient: string): Promise<WithdrawalResult>;
@@ -194,17 +214,4 @@ declare const ASP_REGISTRY_ABI: readonly ["function isRegistered(address provide
194
214
  declare const ZERO_BYTES32: Uint8Array<ArrayBuffer>;
195
215
  declare function getZeroNode(level: number): Uint8Array;
196
216
 
197
- interface ZKProofClientConfig {
198
- circuitPath?: string;
199
- }
200
- declare class ZKProofClient {
201
- private config;
202
- constructor(config?: ZKProofClientConfig);
203
- generateWithdrawalProof(request: WithdrawalRequest): Promise<WithdrawalResult>;
204
- generateComplianceProof(commitment: Uint8Array, associationRoot: Uint8Array): Promise<ComplianceProof>;
205
- private computeNullifierHash;
206
- private generateMockProof;
207
- private hexToBytes;
208
- }
209
-
210
217
  export { type ASPProvider, ASP_REGISTRY_ABI, type BatchWithdrawal, CHAIN_CONFIG, CONTRACT_ADDRESSES, type ComplianceProof, DEFAULT_BATCH_SIZE, DEFAULT_GAS_LIMIT, type DepositNote, type DepositResult, FIELD_SIZE, MERKLE_TREE_DEPTH, type MerkleProof, MerkleTree, POSEIDON_CONSTANTS, PRIVACY_VAULT_ABI, PROOF_EXPIRY_MS, PrivacyVaultSDK, type ProofStatus, type TEEAttestation, type VaultConfig, type VaultStats, type WithdrawalRequest, type WithdrawalResult, ZERO_BYTES32, ZKProofClient, ZK_VERIFIER_ABI, bigIntToBytes32, bytes32ToBigInt, bytesToHex, computeCommitment, computeNullifier, decryptNote, deserializeNote, encryptNote, generateDepositNote, generateRandomBytes, getZeroNode, hexToBytes, poseidonHash, serializeNote };
package/dist/index.js CHANGED
@@ -420,22 +420,84 @@ var MerkleTree = class {
420
420
 
421
421
  // src/zk-client.ts
422
422
  var import_ethers2 = require("ethers");
423
+ var wasmModule = null;
423
424
  var ZKProofClient = class {
424
425
  config;
426
+ wasmReady = false;
425
427
  constructor(config) {
426
- this.config = config ?? {};
428
+ this.config = config ?? { useRealProofs: true };
429
+ if (this.config.useRealProofs) {
430
+ this.loadWasm();
431
+ }
432
+ }
433
+ async loadWasm() {
434
+ if (wasmModule) {
435
+ this.wasmReady = true;
436
+ return;
437
+ }
438
+ try {
439
+ const wasmPath = this.config.wasmPath ?? "zkenclave-circuits";
440
+ const module2 = await import(
441
+ /* webpackIgnore: true */
442
+ wasmPath
443
+ );
444
+ wasmModule = module2;
445
+ this.wasmReady = true;
446
+ } catch {
447
+ console.warn("WASM module not available, falling back to mock proofs");
448
+ this.wasmReady = false;
449
+ }
427
450
  }
428
451
  async generateWithdrawalProof(request) {
452
+ if (this.config.useRealProofs && this.wasmReady && wasmModule) {
453
+ return this.generateRealProof(request);
454
+ }
455
+ return this.generateFallbackProof(request);
456
+ }
457
+ async generateRealProof(request) {
458
+ const wasmRequest = {
459
+ secret: Array.from(request.commitment),
460
+ nullifier_seed: Array.from(request.nullifier),
461
+ amount: Number(request.amount),
462
+ leaf_index: request.leafIndex,
463
+ merkle_path: request.merklePath.map((p) => Array.from(p)),
464
+ path_indices: request.pathIndices,
465
+ merkle_root: request.merkleRoot ? Array.from(request.merkleRoot) : new Array(32).fill(0),
466
+ recipient: this.addressToBytes(request.recipient)
467
+ };
468
+ const resultJson = wasmModule.generate_withdrawal_proof(
469
+ JSON.stringify(wasmRequest)
470
+ );
471
+ const result = JSON.parse(resultJson);
472
+ if (!result.success) {
473
+ throw new Error(`ZK proof generation failed: ${result.error}`);
474
+ }
475
+ return {
476
+ success: true,
477
+ zkProof: new Uint8Array(result.proof),
478
+ nullifierHash: new Uint8Array(result.nullifier_hash),
479
+ merkleRoot: request.merkleRoot ?? new Uint8Array(32),
480
+ timestamp: Date.now()
481
+ };
482
+ }
483
+ async generateFallbackProof(request) {
429
484
  const nullifierHash = this.computeNullifierHash(
430
485
  request.nullifier,
431
486
  request.leafIndex
432
487
  );
433
- const zkProof = this.generateMockProof(request);
434
488
  const merkleRoot = request.merkleRoot ?? new Uint8Array(32);
489
+ const proof = new Uint8Array(256);
490
+ proof[0] = 1;
491
+ const amountHex = (0, import_ethers2.toBeHex)(request.amount, 32);
492
+ const amountBytes = this.hexToBytes(amountHex);
493
+ proof.set(amountBytes.slice(0, 32), 1);
494
+ proof.set(request.commitment.slice(0, 32), 33);
495
+ proof[250] = 90;
496
+ proof[251] = 75;
435
497
  return {
436
498
  success: true,
499
+ zkProof: proof,
437
500
  nullifierHash,
438
- zkProof,
439
501
  merkleRoot,
440
502
  timestamp: Date.now()
441
503
  };
@@ -452,20 +514,36 @@ var ZKProofClient = class {
452
514
  proof: new Uint8Array(256)
453
515
  };
454
516
  }
517
+ async verifyProof(proofResult) {
518
+ if (this.wasmReady && wasmModule) {
519
+ const proofJson = JSON.stringify({
520
+ success: proofResult.success,
521
+ proof: Array.from(proofResult.zkProof),
522
+ nullifier_hash: Array.from(proofResult.nullifierHash),
523
+ public_inputs: [],
524
+ error: null
525
+ });
526
+ return wasmModule.verify_withdrawal_proof(proofJson);
527
+ }
528
+ return proofResult.success && proofResult.zkProof.length > 0 && proofResult.zkProof[250] === 90 && proofResult.zkProof[251] === 75;
529
+ }
530
+ isWasmReady() {
531
+ return this.wasmReady;
532
+ }
455
533
  computeNullifierHash(nullifier, leafIndex) {
456
534
  const indexBytes = new TextEncoder().encode(leafIndex.toString());
457
535
  const combined = new Uint8Array([...nullifier, ...indexBytes]);
458
536
  const hash = (0, import_ethers2.keccak256)(combined);
459
537
  return this.hexToBytes(hash);
460
538
  }
461
- generateMockProof(request) {
462
- const proof = new Uint8Array(256);
463
- proof[0] = 1;
464
- const amountHex = (0, import_ethers2.toBeHex)(request.amount, 32);
465
- const amountBytes = this.hexToBytes(amountHex);
466
- proof.set(amountBytes.slice(0, 32), 1);
467
- proof.set(request.commitment.slice(0, 32), 33);
468
- return proof;
539
+ addressToBytes(address) {
540
+ const clean = address.startsWith("0x") ? address.slice(2) : address;
541
+ const bytes = [];
542
+ for (let i = 0; i < clean.length && bytes.length < 20; i += 2) {
543
+ bytes.push(parseInt(clean.slice(i, i + 2), 16));
544
+ }
545
+ while (bytes.length < 20) bytes.push(0);
546
+ return bytes;
469
547
  }
470
548
  hexToBytes(hex) {
471
549
  const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
@@ -487,7 +565,7 @@ var PrivacyVaultSDK = class {
487
565
  zkClient;
488
566
  config;
489
567
  _merkleTree;
490
- constructor(config, signer) {
568
+ constructor(config, signer, zkClient) {
491
569
  this.config = config;
492
570
  this.provider = new import_ethers3.ethers.JsonRpcProvider(config.rpcUrl);
493
571
  if (signer) {
@@ -508,7 +586,7 @@ var PrivacyVaultSDK = class {
508
586
  ASP_REGISTRY_ABI,
509
587
  this.provider
510
588
  );
511
- this.zkClient = new ZKProofClient();
589
+ this.zkClient = zkClient || new ZKProofClient();
512
590
  this._merkleTree = new MerkleTree();
513
591
  }
514
592
  async connect(signer) {
package/dist/index.mjs CHANGED
@@ -366,22 +366,84 @@ var MerkleTree = class {
366
366
 
367
367
  // src/zk-client.ts
368
368
  import { keccak256 as keccak2562, toBeHex } from "ethers";
369
+ var wasmModule = null;
369
370
  var ZKProofClient = class {
370
371
  config;
372
+ wasmReady = false;
371
373
  constructor(config) {
372
- this.config = config ?? {};
374
+ this.config = config ?? { useRealProofs: true };
375
+ if (this.config.useRealProofs) {
376
+ this.loadWasm();
377
+ }
378
+ }
379
+ async loadWasm() {
380
+ if (wasmModule) {
381
+ this.wasmReady = true;
382
+ return;
383
+ }
384
+ try {
385
+ const wasmPath = this.config.wasmPath ?? "zkenclave-circuits";
386
+ const module = await import(
387
+ /* webpackIgnore: true */
388
+ wasmPath
389
+ );
390
+ wasmModule = module;
391
+ this.wasmReady = true;
392
+ } catch {
393
+ console.warn("WASM module not available, falling back to mock proofs");
394
+ this.wasmReady = false;
395
+ }
373
396
  }
374
397
  async generateWithdrawalProof(request) {
398
+ if (this.config.useRealProofs && this.wasmReady && wasmModule) {
399
+ return this.generateRealProof(request);
400
+ }
401
+ return this.generateFallbackProof(request);
402
+ }
403
+ async generateRealProof(request) {
404
+ const wasmRequest = {
405
+ secret: Array.from(request.commitment),
406
+ nullifier_seed: Array.from(request.nullifier),
407
+ amount: Number(request.amount),
408
+ leaf_index: request.leafIndex,
409
+ merkle_path: request.merklePath.map((p) => Array.from(p)),
410
+ path_indices: request.pathIndices,
411
+ merkle_root: request.merkleRoot ? Array.from(request.merkleRoot) : new Array(32).fill(0),
412
+ recipient: this.addressToBytes(request.recipient)
413
+ };
414
+ const resultJson = wasmModule.generate_withdrawal_proof(
415
+ JSON.stringify(wasmRequest)
416
+ );
417
+ const result = JSON.parse(resultJson);
418
+ if (!result.success) {
419
+ throw new Error(`ZK proof generation failed: ${result.error}`);
420
+ }
421
+ return {
422
+ success: true,
423
+ zkProof: new Uint8Array(result.proof),
424
+ nullifierHash: new Uint8Array(result.nullifier_hash),
425
+ merkleRoot: request.merkleRoot ?? new Uint8Array(32),
426
+ timestamp: Date.now()
427
+ };
428
+ }
429
+ async generateFallbackProof(request) {
375
430
  const nullifierHash = this.computeNullifierHash(
376
431
  request.nullifier,
377
432
  request.leafIndex
378
433
  );
379
- const zkProof = this.generateMockProof(request);
380
434
  const merkleRoot = request.merkleRoot ?? new Uint8Array(32);
435
+ const proof = new Uint8Array(256);
436
+ proof[0] = 1;
437
+ const amountHex = toBeHex(request.amount, 32);
438
+ const amountBytes = this.hexToBytes(amountHex);
439
+ proof.set(amountBytes.slice(0, 32), 1);
440
+ proof.set(request.commitment.slice(0, 32), 33);
441
+ proof[250] = 90;
442
+ proof[251] = 75;
381
443
  return {
382
444
  success: true,
445
+ zkProof: proof,
383
446
  nullifierHash,
384
- zkProof,
385
447
  merkleRoot,
386
448
  timestamp: Date.now()
387
449
  };
@@ -398,20 +460,36 @@ var ZKProofClient = class {
398
460
  proof: new Uint8Array(256)
399
461
  };
400
462
  }
463
+ async verifyProof(proofResult) {
464
+ if (this.wasmReady && wasmModule) {
465
+ const proofJson = JSON.stringify({
466
+ success: proofResult.success,
467
+ proof: Array.from(proofResult.zkProof),
468
+ nullifier_hash: Array.from(proofResult.nullifierHash),
469
+ public_inputs: [],
470
+ error: null
471
+ });
472
+ return wasmModule.verify_withdrawal_proof(proofJson);
473
+ }
474
+ return proofResult.success && proofResult.zkProof.length > 0 && proofResult.zkProof[250] === 90 && proofResult.zkProof[251] === 75;
475
+ }
476
+ isWasmReady() {
477
+ return this.wasmReady;
478
+ }
401
479
  computeNullifierHash(nullifier, leafIndex) {
402
480
  const indexBytes = new TextEncoder().encode(leafIndex.toString());
403
481
  const combined = new Uint8Array([...nullifier, ...indexBytes]);
404
482
  const hash = keccak2562(combined);
405
483
  return this.hexToBytes(hash);
406
484
  }
407
- generateMockProof(request) {
408
- const proof = new Uint8Array(256);
409
- proof[0] = 1;
410
- const amountHex = toBeHex(request.amount, 32);
411
- const amountBytes = this.hexToBytes(amountHex);
412
- proof.set(amountBytes.slice(0, 32), 1);
413
- proof.set(request.commitment.slice(0, 32), 33);
414
- return proof;
485
+ addressToBytes(address) {
486
+ const clean = address.startsWith("0x") ? address.slice(2) : address;
487
+ const bytes = [];
488
+ for (let i = 0; i < clean.length && bytes.length < 20; i += 2) {
489
+ bytes.push(parseInt(clean.slice(i, i + 2), 16));
490
+ }
491
+ while (bytes.length < 20) bytes.push(0);
492
+ return bytes;
415
493
  }
416
494
  hexToBytes(hex) {
417
495
  const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
@@ -433,7 +511,7 @@ var PrivacyVaultSDK = class {
433
511
  zkClient;
434
512
  config;
435
513
  _merkleTree;
436
- constructor(config, signer) {
514
+ constructor(config, signer, zkClient) {
437
515
  this.config = config;
438
516
  this.provider = new ethers.JsonRpcProvider(config.rpcUrl);
439
517
  if (signer) {
@@ -454,7 +532,7 @@ var PrivacyVaultSDK = class {
454
532
  ASP_REGISTRY_ABI,
455
533
  this.provider
456
534
  );
457
- this.zkClient = new ZKProofClient();
535
+ this.zkClient = zkClient || new ZKProofClient();
458
536
  this._merkleTree = new MerkleTree();
459
537
  }
460
538
  async connect(signer) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zkenclave-sdk",
3
- "version": "0.1.1",
3
+ "version": "0.1.6",
4
4
  "description": "TypeScript SDK for privacy-preserving vault withdrawals with ZK proofs",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",