privacycash 1.0.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.
Files changed (56) hide show
  1. package/.github/workflows/npm-publish.yml +67 -0
  2. package/README.md +22 -0
  3. package/__tests__/e2e.test.ts +52 -0
  4. package/__tests__/encryption.test.ts +1635 -0
  5. package/circuit2/transaction2.wasm +0 -0
  6. package/circuit2/transaction2.zkey +0 -0
  7. package/dist/config.d.ts +7 -0
  8. package/dist/config.js +16 -0
  9. package/dist/deposit.d.ts +18 -0
  10. package/dist/deposit.js +402 -0
  11. package/dist/exportUtils.d.ts +6 -0
  12. package/dist/exportUtils.js +6 -0
  13. package/dist/getUtxos.d.ts +27 -0
  14. package/dist/getUtxos.js +352 -0
  15. package/dist/index.d.ts +61 -0
  16. package/dist/index.js +169 -0
  17. package/dist/models/keypair.d.ts +26 -0
  18. package/dist/models/keypair.js +43 -0
  19. package/dist/models/utxo.d.ts +49 -0
  20. package/dist/models/utxo.js +76 -0
  21. package/dist/utils/address_lookup_table.d.ts +8 -0
  22. package/dist/utils/address_lookup_table.js +21 -0
  23. package/dist/utils/constants.d.ts +14 -0
  24. package/dist/utils/constants.js +15 -0
  25. package/dist/utils/encryption.d.ts +107 -0
  26. package/dist/utils/encryption.js +374 -0
  27. package/dist/utils/logger.d.ts +9 -0
  28. package/dist/utils/logger.js +35 -0
  29. package/dist/utils/merkle_tree.d.ts +92 -0
  30. package/dist/utils/merkle_tree.js +186 -0
  31. package/dist/utils/node-shim.d.ts +5 -0
  32. package/dist/utils/node-shim.js +5 -0
  33. package/dist/utils/prover.d.ts +33 -0
  34. package/dist/utils/prover.js +123 -0
  35. package/dist/utils/utils.d.ts +67 -0
  36. package/dist/utils/utils.js +151 -0
  37. package/dist/withdraw.d.ts +21 -0
  38. package/dist/withdraw.js +270 -0
  39. package/package.json +48 -0
  40. package/src/config.ts +28 -0
  41. package/src/deposit.ts +496 -0
  42. package/src/exportUtils.ts +6 -0
  43. package/src/getUtxos.ts +466 -0
  44. package/src/index.ts +191 -0
  45. package/src/models/keypair.ts +52 -0
  46. package/src/models/utxo.ts +97 -0
  47. package/src/utils/address_lookup_table.ts +29 -0
  48. package/src/utils/constants.ts +26 -0
  49. package/src/utils/encryption.ts +461 -0
  50. package/src/utils/logger.ts +42 -0
  51. package/src/utils/merkle_tree.ts +207 -0
  52. package/src/utils/node-shim.ts +6 -0
  53. package/src/utils/prover.ts +189 -0
  54. package/src/utils/utils.ts +213 -0
  55. package/src/withdraw.ts +334 -0
  56. package/tsconfig.json +28 -0
@@ -0,0 +1,49 @@
1
+ /**
2
+ * UTXO (Unspent Transaction Output) module for ZK Cash
3
+ *
4
+ * Provides UTXO functionality for the ZK Cash system
5
+ * Based on: https://github.com/tornadocash/tornado-nova
6
+ */
7
+ import BN from 'bn.js';
8
+ import { Keypair } from './keypair.js';
9
+ import * as hasher from '@lightprotocol/hasher.rs';
10
+ /**
11
+ * Simplified Utxo class inspired by Tornado Cash Nova
12
+ * Based on: https://github.com/tornadocash/tornado-nova/blob/f9264eeffe48bf5e04e19d8086ee6ec58cdf0d9e/src/utxo.js
13
+ */
14
+ export declare class Utxo {
15
+ amount: BN;
16
+ blinding: BN;
17
+ keypair: Keypair;
18
+ index: number;
19
+ mintAddress: string;
20
+ version: 'v1' | 'v2';
21
+ private lightWasm;
22
+ constructor({ lightWasm, amount,
23
+ /**
24
+ * Tornado nova doesn't use solana eddsa with curve 25519 but their own "keypair"
25
+ * which is:
26
+ * - private key: random [31;u8]
27
+ * - public key: PoseidonHash(privateKey)
28
+ *
29
+ * Generate a new keypair for each UTXO
30
+ */
31
+ keypair, blinding, // Use fixed value for consistency instead of randomBN()
32
+ index, mintAddress, // Default to Solana native SOL mint address,
33
+ version }: {
34
+ lightWasm: hasher.LightWasm;
35
+ amount?: BN | number | string;
36
+ keypair?: Keypair;
37
+ blinding?: BN | number | string;
38
+ index?: number;
39
+ mintAddress?: string;
40
+ version?: 'v1' | 'v2';
41
+ });
42
+ getCommitment(): Promise<string>;
43
+ getNullifier(): Promise<string>;
44
+ /**
45
+ * Log all the UTXO's public properties and derived values in JSON format
46
+ * @returns Promise that resolves once all logging is complete
47
+ */
48
+ log(): Promise<void>;
49
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * UTXO (Unspent Transaction Output) module for ZK Cash
3
+ *
4
+ * Provides UTXO functionality for the ZK Cash system
5
+ * Based on: https://github.com/tornadocash/tornado-nova
6
+ */
7
+ import BN from 'bn.js';
8
+ import { Keypair } from './keypair.js';
9
+ import { ethers } from 'ethers';
10
+ /**
11
+ * Simplified Utxo class inspired by Tornado Cash Nova
12
+ * Based on: https://github.com/tornadocash/tornado-nova/blob/f9264eeffe48bf5e04e19d8086ee6ec58cdf0d9e/src/utxo.js
13
+ */
14
+ export class Utxo {
15
+ amount;
16
+ blinding;
17
+ keypair;
18
+ index;
19
+ mintAddress;
20
+ version;
21
+ lightWasm;
22
+ constructor({ lightWasm, amount = new BN(0),
23
+ /**
24
+ * Tornado nova doesn't use solana eddsa with curve 25519 but their own "keypair"
25
+ * which is:
26
+ * - private key: random [31;u8]
27
+ * - public key: PoseidonHash(privateKey)
28
+ *
29
+ * Generate a new keypair for each UTXO
30
+ */
31
+ keypair, blinding = new BN(Math.floor(Math.random() * 1000000000)), // Use fixed value for consistency instead of randomBN()
32
+ index = 0, mintAddress = '11111111111111111111111111111112', // Default to Solana native SOL mint address,
33
+ version = 'v2' }) {
34
+ this.amount = new BN(amount.toString());
35
+ this.blinding = new BN(blinding.toString());
36
+ this.lightWasm = lightWasm;
37
+ this.keypair = keypair || new Keypair(ethers.Wallet.createRandom().privateKey, lightWasm);
38
+ this.index = index;
39
+ this.mintAddress = mintAddress;
40
+ this.version = version;
41
+ }
42
+ async getCommitment() {
43
+ return this.lightWasm.poseidonHashString([this.amount.toString(), this.keypair.pubkey.toString(), this.blinding.toString(), this.mintAddress]);
44
+ }
45
+ async getNullifier() {
46
+ const commitmentValue = await this.getCommitment();
47
+ const signature = this.keypair.sign(commitmentValue, new BN(this.index).toString());
48
+ return this.lightWasm.poseidonHashString([commitmentValue, new BN(this.index).toString(), signature]);
49
+ }
50
+ /**
51
+ * Log all the UTXO's public properties and derived values in JSON format
52
+ * @returns Promise that resolves once all logging is complete
53
+ */
54
+ async log() {
55
+ // Prepare the UTXO data object
56
+ const utxoData = {
57
+ amount: this.amount.toString(),
58
+ blinding: this.blinding.toString(),
59
+ index: this.index,
60
+ mintAddress: this.mintAddress,
61
+ keypair: {
62
+ pubkey: this.keypair.pubkey.toString()
63
+ }
64
+ };
65
+ // Add derived values
66
+ try {
67
+ utxoData.commitment = await this.getCommitment();
68
+ utxoData.nullifier = await this.getNullifier();
69
+ }
70
+ catch (error) {
71
+ utxoData.error = error.message;
72
+ }
73
+ // Output as formatted JSON
74
+ console.log(JSON.stringify(utxoData, null, 2));
75
+ }
76
+ }
@@ -0,0 +1,8 @@
1
+ import { Connection, PublicKey } from '@solana/web3.js';
2
+ /**
3
+ * Helper function to use an existing ALT (recommended for production)
4
+ * Use create_alt.ts to create the ALT once, then hardcode the address and use this function
5
+ */
6
+ export declare function useExistingALT(connection: Connection, altAddress: PublicKey): Promise<{
7
+ value: any;
8
+ } | null>;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Helper function to use an existing ALT (recommended for production)
3
+ * Use create_alt.ts to create the ALT once, then hardcode the address and use this function
4
+ */
5
+ export async function useExistingALT(connection, altAddress) {
6
+ try {
7
+ console.log(`Using existing ALT: ${altAddress.toString()}`);
8
+ const altAccount = await connection.getAddressLookupTable(altAddress);
9
+ if (altAccount.value) {
10
+ console.log(`✅ ALT found with ${altAccount.value.state.addresses.length} addresses`);
11
+ }
12
+ else {
13
+ console.log('❌ ALT not found');
14
+ }
15
+ return altAccount;
16
+ }
17
+ catch (error) {
18
+ console.error('Error getting existing ALT:', error);
19
+ return null;
20
+ }
21
+ }
@@ -0,0 +1,14 @@
1
+ import { PublicKey } from '@solana/web3.js';
2
+ import BN from 'bn.js';
3
+ export declare const FIELD_SIZE: BN;
4
+ export declare const PROGRAM_ID: PublicKey;
5
+ export declare const DEPLOYER_ID: PublicKey;
6
+ export declare const FEE_RECIPIENT: PublicKey;
7
+ export declare const FETCH_UTXOS_GROUP_SIZE = 2000;
8
+ export declare const TRANSACT_IX_DISCRIMINATOR: Buffer<ArrayBuffer>;
9
+ export declare const MERKLE_TREE_DEPTH = 26;
10
+ export declare const ALT_ADDRESS: PublicKey;
11
+ export declare const INDEXER_API_URL: string;
12
+ export declare const SIGN_MESSAGE = "Privacy Money account sign in";
13
+ export declare const LSK_FETCH_OFFSET = "fetch_offset";
14
+ export declare const LSK_ENCRYPTED_OUTPUTS = "encrypted_outputs";
@@ -0,0 +1,15 @@
1
+ import { PublicKey } from '@solana/web3.js';
2
+ import BN from 'bn.js';
3
+ export const FIELD_SIZE = new BN('21888242871839275222246405745257275088548364400416034343698204186575808495617');
4
+ export const PROGRAM_ID = new PublicKey('9fhQBbumKEFuXtMBDw8AaQyAjCorLGJQiS3skWZdQyQD');
5
+ export const DEPLOYER_ID = new PublicKey('AWexibGxNFKTa1b5R5MN4PJr9HWnWRwf8EW9g8cLx3dM');
6
+ export const FEE_RECIPIENT = new PublicKey('AWexibGxNFKTa1b5R5MN4PJr9HWnWRwf8EW9g8cLx3dM');
7
+ export const FETCH_UTXOS_GROUP_SIZE = 2000;
8
+ export const TRANSACT_IX_DISCRIMINATOR = Buffer.from([217, 149, 130, 143, 221, 52, 252, 119]);
9
+ export const MERKLE_TREE_DEPTH = 26;
10
+ export const ALT_ADDRESS = new PublicKey('72bpRay17JKp4k8H87p7ieU9C6aRDy5yCqwvtpTN2wuU');
11
+ export const INDEXER_API_URL = process.env.NEXT_PUBLIC_INDEXER_API_URL ?? 'https://api3.privacycash.org';
12
+ export const SIGN_MESSAGE = `Privacy Money account sign in`;
13
+ // localStorage cache keys
14
+ export const LSK_FETCH_OFFSET = 'fetch_offset';
15
+ export const LSK_ENCRYPTED_OUTPUTS = 'encrypted_outputs';
@@ -0,0 +1,107 @@
1
+ import { Keypair } from '@solana/web3.js';
2
+ import { Utxo } from '../models/utxo.js';
3
+ /**
4
+ * Represents a UTXO with minimal required fields
5
+ */
6
+ export interface UtxoData {
7
+ amount: string;
8
+ blinding: string;
9
+ index: number | string;
10
+ [key: string]: any;
11
+ }
12
+ export interface EncryptionKey {
13
+ v1: Uint8Array;
14
+ v2: Uint8Array;
15
+ }
16
+ /**
17
+ * Service for handling encryption and decryption of UTXO data
18
+ */
19
+ export declare class EncryptionService {
20
+ static readonly ENCRYPTION_VERSION_V2: Buffer<ArrayBuffer>;
21
+ private encryptionKeyV1;
22
+ private encryptionKeyV2;
23
+ private utxoPrivateKeyV1;
24
+ private utxoPrivateKeyV2;
25
+ /**
26
+ * Generate an encryption key from a signature
27
+ * @param signature The user's signature
28
+ * @returns The generated encryption key
29
+ */
30
+ deriveEncryptionKeyFromSignature(signature: Uint8Array): EncryptionKey;
31
+ /**
32
+ * Generate an encryption key from a wallet keypair (V2 format)
33
+ * @param keypair The Solana keypair to derive the encryption key from
34
+ * @returns The generated encryption key
35
+ */
36
+ deriveEncryptionKeyFromWallet(keypair: Keypair): EncryptionKey;
37
+ /**
38
+ * Encrypt data with the stored encryption key
39
+ * @param data The data to encrypt
40
+ * @returns The encrypted data as a Buffer
41
+ * @throws Error if the encryption key has not been generated
42
+ */
43
+ encrypt(data: Buffer | string): Buffer;
44
+ encryptDecryptedDoNotUse(data: Buffer | string): Buffer;
45
+ /**
46
+ * Decrypt data with the stored encryption key
47
+ * @param encryptedData The encrypted data to decrypt
48
+ * @returns The decrypted data as a Buffer
49
+ * @throws Error if the encryption key has not been generated or if the wrong key is used
50
+ */
51
+ decrypt(encryptedData: Buffer): Buffer;
52
+ /**
53
+ * Decrypt data using the old V1 format (120-bit HMAC with SHA256)
54
+ * @param encryptedData The encrypted data to decrypt
55
+ * @param keypair Optional keypair to derive V1 key for backward compatibility
56
+ * @returns The decrypted data as a Buffer
57
+ */
58
+ private decryptV1;
59
+ private timingSafeEqual;
60
+ /**
61
+ * Decrypt data using the new V2 format (256-bit Keccak HMAC)
62
+ * @param encryptedData The encrypted data to decrypt
63
+ * @returns The decrypted data as a Buffer
64
+ */
65
+ private decryptV2;
66
+ /**
67
+ * Reset the encryption keys (mainly for testing purposes)
68
+ */
69
+ resetEncryptionKey(): void;
70
+ /**
71
+ * Encrypt a UTXO using a compact pipe-delimited format
72
+ * Always uses V2 encryption format. The UTXO's version property is used only for key derivation.
73
+ * @param utxo The UTXO to encrypt (includes version property)
74
+ * @returns The encrypted UTXO data as a Buffer
75
+ * @throws Error if the V2 encryption key has not been set
76
+ */
77
+ encryptUtxo(utxo: Utxo): Buffer;
78
+ encryptUtxoDecryptedDoNotUse(utxo: Utxo): Buffer;
79
+ getEncryptionKeyVersion(encryptedData: Buffer | string): 'v1' | 'v2';
80
+ /**
81
+ * Decrypt an encrypted UTXO and parse it to a Utxo instance
82
+ * Automatically detects the UTXO version based on the encryption format
83
+ * @param encryptedData The encrypted UTXO data
84
+ * @param keypair The UTXO keypair to use for the decrypted UTXO
85
+ * @param lightWasm Optional LightWasm instance. If not provided, a new one will be created
86
+ * @param walletKeypair Optional wallet keypair for V1 backward compatibility
87
+ * @returns Promise resolving to the decrypted Utxo instance
88
+ * @throws Error if the encryption key has not been set or if decryption fails
89
+ */
90
+ decryptUtxo(encryptedData: Buffer | string, lightWasm?: any): Promise<Utxo>;
91
+ getUtxoPrivateKeyWithVersion(version: 'v1' | 'v2'): string;
92
+ deriveUtxoPrivateKey(encryptedData?: Buffer | string): string;
93
+ hasUtxoPrivateKeyWithVersion(version: 'v1' | 'v2'): boolean;
94
+ /**
95
+ * Get the cached V1 UTXO private key
96
+ * @returns A private key in hex format that can be used to create a UTXO keypair
97
+ * @throws Error if V1 encryption key has not been set
98
+ */
99
+ getUtxoPrivateKeyV1(): string;
100
+ /**
101
+ * Get the cached V2 UTXO private key
102
+ * @returns A private key in hex format that can be used to create a UTXO keypair
103
+ * @throws Error if V2 encryption key has not been set
104
+ */
105
+ getUtxoPrivateKeyV2(): string;
106
+ }
107
+ export declare function serializeProofAndExtData(proof: any, extData: any): Buffer<ArrayBuffer>;
@@ -0,0 +1,374 @@
1
+ import nacl from 'tweetnacl';
2
+ import * as crypto from 'crypto';
3
+ import { Utxo } from '../models/utxo.js';
4
+ import { WasmFactory } from '@lightprotocol/hasher.rs';
5
+ import { Keypair as UtxoKeypair } from '../models/keypair.js';
6
+ import { keccak256 } from '@ethersproject/keccak256';
7
+ import { TRANSACT_IX_DISCRIMINATOR } from './constants.js';
8
+ import BN from 'bn.js';
9
+ /**
10
+ * Service for handling encryption and decryption of UTXO data
11
+ */
12
+ export class EncryptionService {
13
+ static ENCRYPTION_VERSION_V2 = Buffer.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02]); // Version 2
14
+ encryptionKeyV1 = null;
15
+ encryptionKeyV2 = null;
16
+ utxoPrivateKeyV1 = null;
17
+ utxoPrivateKeyV2 = null;
18
+ /**
19
+ * Generate an encryption key from a signature
20
+ * @param signature The user's signature
21
+ * @returns The generated encryption key
22
+ */
23
+ deriveEncryptionKeyFromSignature(signature) {
24
+ // Extract the first 31 bytes of the signature to create a deterministic key (legacy method)
25
+ const encryptionKeyV1 = signature.slice(0, 31);
26
+ // Store the V1 key in the service
27
+ this.encryptionKeyV1 = encryptionKeyV1;
28
+ // Precompute and cache the UTXO private key
29
+ const hashedSeedV1 = crypto.createHash('sha256').update(encryptionKeyV1).digest();
30
+ this.utxoPrivateKeyV1 = '0x' + hashedSeedV1.toString('hex');
31
+ // Use Keccak256 to derive a full 32-byte encryption key from the signature
32
+ const encryptionKeyV2 = Buffer.from(keccak256(signature).slice(2), 'hex');
33
+ // Store the V2 key in the service
34
+ this.encryptionKeyV2 = encryptionKeyV2;
35
+ // Precompute and cache the UTXO private key
36
+ const hashedSeedV2 = Buffer.from(keccak256(encryptionKeyV2).slice(2), 'hex');
37
+ this.utxoPrivateKeyV2 = '0x' + hashedSeedV2.toString('hex');
38
+ return {
39
+ v1: this.encryptionKeyV1,
40
+ v2: this.encryptionKeyV2
41
+ };
42
+ }
43
+ /**
44
+ * Generate an encryption key from a wallet keypair (V2 format)
45
+ * @param keypair The Solana keypair to derive the encryption key from
46
+ * @returns The generated encryption key
47
+ */
48
+ deriveEncryptionKeyFromWallet(keypair) {
49
+ // Sign a constant message with the keypair
50
+ const message = Buffer.from('Privacy Money account sign in');
51
+ const signature = nacl.sign.detached(message, keypair.secretKey);
52
+ return this.deriveEncryptionKeyFromSignature(signature);
53
+ }
54
+ /**
55
+ * Encrypt data with the stored encryption key
56
+ * @param data The data to encrypt
57
+ * @returns The encrypted data as a Buffer
58
+ * @throws Error if the encryption key has not been generated
59
+ */
60
+ encrypt(data) {
61
+ if (!this.encryptionKeyV2) {
62
+ throw new Error('Encryption key not set. Call setEncryptionKey or deriveEncryptionKeyFromWallet first.');
63
+ }
64
+ // Convert string to Buffer if needed
65
+ const dataBuffer = typeof data === 'string' ? Buffer.from(data) : data;
66
+ // Generate a standard initialization vector (12 bytes for GCM)
67
+ const iv = crypto.randomBytes(12);
68
+ // Use the full 32-byte V2 encryption key for AES-256
69
+ const key = Buffer.from(this.encryptionKeyV2);
70
+ // Use AES-256-GCM for authenticated encryption
71
+ const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
72
+ const encryptedData = Buffer.concat([
73
+ cipher.update(dataBuffer),
74
+ cipher.final()
75
+ ]);
76
+ // Get the authentication tag from GCM (16 bytes)
77
+ const authTag = cipher.getAuthTag();
78
+ // Version 2 format: [version(8)] + [IV(12)] + [authTag(16)] + [encryptedData]
79
+ return Buffer.concat([
80
+ EncryptionService.ENCRYPTION_VERSION_V2,
81
+ iv,
82
+ authTag,
83
+ encryptedData
84
+ ]);
85
+ }
86
+ // v1 encryption, only used for testing now
87
+ encryptDecryptedDoNotUse(data) {
88
+ if (!this.encryptionKeyV1) {
89
+ throw new Error('Encryption key not set. Call setEncryptionKey or deriveEncryptionKeyFromWallet first.');
90
+ }
91
+ // Convert string to Buffer if needed
92
+ const dataBuffer = typeof data === 'string' ? Buffer.from(data) : data;
93
+ // Generate a standard initialization vector (16 bytes)
94
+ const iv = crypto.randomBytes(16);
95
+ // Create a key from our encryption key (using only first 16 bytes for AES-128)
96
+ const key = Buffer.from(this.encryptionKeyV1).slice(0, 16);
97
+ // Use a more compact encryption algorithm (aes-128-ctr)
98
+ const cipher = crypto.createCipheriv('aes-128-ctr', key, iv);
99
+ const encryptedData = Buffer.concat([
100
+ cipher.update(dataBuffer),
101
+ cipher.final()
102
+ ]);
103
+ // Create an authentication tag (HMAC) to verify decryption with correct key
104
+ const hmacKey = Buffer.from(this.encryptionKeyV1).slice(16, 31);
105
+ const hmac = crypto.createHmac('sha256', hmacKey);
106
+ hmac.update(iv);
107
+ hmac.update(encryptedData);
108
+ const authTag = hmac.digest().slice(0, 16); // Use first 16 bytes of HMAC as auth tag
109
+ // Combine IV, auth tag and encrypted data
110
+ return Buffer.concat([iv, authTag, encryptedData]);
111
+ }
112
+ /**
113
+ * Decrypt data with the stored encryption key
114
+ * @param encryptedData The encrypted data to decrypt
115
+ * @returns The decrypted data as a Buffer
116
+ * @throws Error if the encryption key has not been generated or if the wrong key is used
117
+ */
118
+ decrypt(encryptedData) {
119
+ // Check if this is the new version format (starts with 8-byte version identifier)
120
+ if (encryptedData.length >= 8 && encryptedData.subarray(0, 8).equals(EncryptionService.ENCRYPTION_VERSION_V2)) {
121
+ if (!this.encryptionKeyV2) {
122
+ throw new Error('Encryption key not set. Call setEncryptionKey or deriveEncryptionKeyFromWallet first.');
123
+ }
124
+ return this.decryptV2(encryptedData);
125
+ }
126
+ else {
127
+ // V1 format - need V1 key or keypair to derive it
128
+ if (!this.encryptionKeyV1) {
129
+ throw new Error('Encryption key not set. Call setEncryptionKey or deriveEncryptionKeyFromWallet first.');
130
+ }
131
+ return this.decryptV1(encryptedData);
132
+ }
133
+ }
134
+ /**
135
+ * Decrypt data using the old V1 format (120-bit HMAC with SHA256)
136
+ * @param encryptedData The encrypted data to decrypt
137
+ * @param keypair Optional keypair to derive V1 key for backward compatibility
138
+ * @returns The decrypted data as a Buffer
139
+ */
140
+ decryptV1(encryptedData) {
141
+ if (!this.encryptionKeyV1) {
142
+ throw new Error('Encryption key not set. Call setEncryptionKey or deriveEncryptionKeyFromWallet first.');
143
+ }
144
+ // Extract the IV from the first 16 bytes
145
+ const iv = encryptedData.slice(0, 16);
146
+ // Extract the auth tag from the next 16 bytes
147
+ const authTag = encryptedData.slice(16, 32);
148
+ // The rest is the actual encrypted data
149
+ const data = encryptedData.slice(32);
150
+ // Verify the authentication tag
151
+ const hmacKey = Buffer.from(this.encryptionKeyV1).slice(16, 31);
152
+ const hmac = crypto.createHmac('sha256', hmacKey);
153
+ hmac.update(iv);
154
+ hmac.update(data);
155
+ const calculatedTag = hmac.digest().slice(0, 16);
156
+ // Compare tags - if they don't match, the key is wrong
157
+ if (!this.timingSafeEqual(authTag, calculatedTag)) {
158
+ throw new Error('Failed to decrypt data. Invalid encryption key or corrupted data.');
159
+ }
160
+ // Create a key from our encryption key (using only first 16 bytes for AES-128)
161
+ const key = Buffer.from(this.encryptionKeyV1).slice(0, 16);
162
+ // Use the same algorithm as in encrypt
163
+ const decipher = crypto.createDecipheriv('aes-128-ctr', key, iv);
164
+ try {
165
+ return Buffer.concat([
166
+ decipher.update(data),
167
+ decipher.final()
168
+ ]);
169
+ }
170
+ catch (error) {
171
+ throw new Error('Failed to decrypt data. Invalid encryption key or corrupted data.');
172
+ }
173
+ }
174
+ // Custom timingSafeEqual for browser compatibility
175
+ timingSafeEqual(a, b) {
176
+ if (a.length !== b.length) {
177
+ return false;
178
+ }
179
+ let diff = 0;
180
+ for (let i = 0; i < a.length; i++) {
181
+ diff |= a[i] ^ b[i];
182
+ }
183
+ return diff === 0;
184
+ }
185
+ /**
186
+ * Decrypt data using the new V2 format (256-bit Keccak HMAC)
187
+ * @param encryptedData The encrypted data to decrypt
188
+ * @returns The decrypted data as a Buffer
189
+ */
190
+ decryptV2(encryptedData) {
191
+ if (!this.encryptionKeyV2) {
192
+ throw new Error('encryptionKeyV2 not set. Call setEncryptionKey or deriveEncryptionKeyFromWallet first.');
193
+ }
194
+ // Skip 8-byte version identifier and extract components for GCM format
195
+ const iv = encryptedData.slice(8, 20); // bytes 8-19 (12 bytes for GCM)
196
+ const authTag = encryptedData.slice(20, 36); // bytes 20-35 (16 bytes for GCM)
197
+ const data = encryptedData.slice(36); // remaining bytes
198
+ // Use the full 32-byte V2 encryption key for AES-256
199
+ const key = Buffer.from(this.encryptionKeyV2);
200
+ // Use AES-256-GCM for authenticated decryption
201
+ const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
202
+ decipher.setAuthTag(authTag);
203
+ try {
204
+ return Buffer.concat([
205
+ decipher.update(data),
206
+ decipher.final()
207
+ ]);
208
+ }
209
+ catch (error) {
210
+ throw new Error('Failed to decrypt data. Invalid encryption key or corrupted data.');
211
+ }
212
+ }
213
+ /**
214
+ * Reset the encryption keys (mainly for testing purposes)
215
+ */
216
+ resetEncryptionKey() {
217
+ this.encryptionKeyV1 = null;
218
+ this.encryptionKeyV2 = null;
219
+ this.utxoPrivateKeyV1 = null;
220
+ this.utxoPrivateKeyV2 = null;
221
+ }
222
+ /**
223
+ * Encrypt a UTXO using a compact pipe-delimited format
224
+ * Always uses V2 encryption format. The UTXO's version property is used only for key derivation.
225
+ * @param utxo The UTXO to encrypt (includes version property)
226
+ * @returns The encrypted UTXO data as a Buffer
227
+ * @throws Error if the V2 encryption key has not been set
228
+ */
229
+ encryptUtxo(utxo) {
230
+ if (!this.encryptionKeyV2) {
231
+ throw new Error('Encryption key not set. Call setEncryptionKey or deriveEncryptionKeyFromWallet first.');
232
+ }
233
+ // Create a compact string representation using pipe delimiter
234
+ // Version is stored in the UTXO model, not in the encrypted content
235
+ const utxoString = `${utxo.amount.toString()}|${utxo.blinding.toString()}|${utxo.index}|${utxo.mintAddress}`;
236
+ // Always use V2 encryption format (which adds version byte 0x02 at the beginning)
237
+ return this.encrypt(utxoString);
238
+ }
239
+ // Deprecated, only used for testing now
240
+ encryptUtxoDecryptedDoNotUse(utxo) {
241
+ if (!this.encryptionKeyV2) {
242
+ throw new Error('Encryption key not set. Call setEncryptionKey or deriveEncryptionKeyFromWallet first.');
243
+ }
244
+ const utxoString = `${utxo.amount.toString()}|${utxo.blinding.toString()}|${utxo.index}|${utxo.mintAddress}`;
245
+ return this.encryptDecryptedDoNotUse(utxoString);
246
+ }
247
+ getEncryptionKeyVersion(encryptedData) {
248
+ const buffer = typeof encryptedData === 'string' ? Buffer.from(encryptedData, 'hex') : encryptedData;
249
+ if (buffer.length >= 8 && buffer.subarray(0, 8).equals(EncryptionService.ENCRYPTION_VERSION_V2)) {
250
+ // V2 encryption format → V2 UTXO
251
+ return 'v2';
252
+ }
253
+ else {
254
+ // V1 encryption format → UTXO
255
+ return 'v1';
256
+ }
257
+ }
258
+ /**
259
+ * Decrypt an encrypted UTXO and parse it to a Utxo instance
260
+ * Automatically detects the UTXO version based on the encryption format
261
+ * @param encryptedData The encrypted UTXO data
262
+ * @param keypair The UTXO keypair to use for the decrypted UTXO
263
+ * @param lightWasm Optional LightWasm instance. If not provided, a new one will be created
264
+ * @param walletKeypair Optional wallet keypair for V1 backward compatibility
265
+ * @returns Promise resolving to the decrypted Utxo instance
266
+ * @throws Error if the encryption key has not been set or if decryption fails
267
+ */
268
+ async decryptUtxo(encryptedData, lightWasm) {
269
+ // Convert hex string to Buffer if needed
270
+ const encryptedBuffer = typeof encryptedData === 'string'
271
+ ? Buffer.from(encryptedData, 'hex')
272
+ : encryptedData;
273
+ // Detect UTXO version based on encryption format
274
+ let utxoVersion = this.getEncryptionKeyVersion(encryptedBuffer);
275
+ // The decrypt() method already handles encryption format version detection (V1 vs V2)
276
+ // It checks the first byte to determine whether to use decryptV1() or decryptV2()
277
+ const decrypted = this.decrypt(encryptedBuffer);
278
+ // Parse the pipe-delimited format: amount|blinding|index|mintAddress
279
+ const decryptedStr = decrypted.toString();
280
+ const parts = decryptedStr.split('|');
281
+ if (parts.length !== 4) {
282
+ throw new Error('Invalid UTXO format after decryption');
283
+ }
284
+ const [amount, blinding, index, mintAddress] = parts;
285
+ if (!amount || !blinding || index === undefined || mintAddress === undefined) {
286
+ throw new Error('Invalid UTXO format after decryption');
287
+ }
288
+ // Get or create a LightWasm instance
289
+ const wasmInstance = lightWasm || await WasmFactory.getInstance();
290
+ const privateKey = this.getUtxoPrivateKeyWithVersion(utxoVersion);
291
+ // Create a Utxo instance with the detected version
292
+ const utxo = new Utxo({
293
+ lightWasm: wasmInstance,
294
+ amount: amount,
295
+ blinding: blinding,
296
+ keypair: new UtxoKeypair(privateKey, wasmInstance),
297
+ index: Number(index),
298
+ mintAddress: mintAddress,
299
+ version: utxoVersion
300
+ });
301
+ return utxo;
302
+ }
303
+ getUtxoPrivateKeyWithVersion(version) {
304
+ if (version === 'v1') {
305
+ return this.getUtxoPrivateKeyV1();
306
+ }
307
+ return this.getUtxoPrivateKeyV2();
308
+ }
309
+ deriveUtxoPrivateKey(encryptedData) {
310
+ if (encryptedData && this.getEncryptionKeyVersion(encryptedData) === 'v2') {
311
+ return this.getUtxoPrivateKeyWithVersion('v2');
312
+ }
313
+ return this.getUtxoPrivateKeyWithVersion('v1');
314
+ }
315
+ hasUtxoPrivateKeyWithVersion(version) {
316
+ if (version === 'v1') {
317
+ return !!this.utxoPrivateKeyV1;
318
+ }
319
+ return !!this.utxoPrivateKeyV2;
320
+ }
321
+ /**
322
+ * Get the cached V1 UTXO private key
323
+ * @returns A private key in hex format that can be used to create a UTXO keypair
324
+ * @throws Error if V1 encryption key has not been set
325
+ */
326
+ getUtxoPrivateKeyV1() {
327
+ if (!this.utxoPrivateKeyV1) {
328
+ throw new Error('Encryption key not set. Call setEncryptionKey or deriveEncryptionKeyFromWallet first.');
329
+ }
330
+ return this.utxoPrivateKeyV1;
331
+ }
332
+ /**
333
+ * Get the cached V2 UTXO private key
334
+ * @returns A private key in hex format that can be used to create a UTXO keypair
335
+ * @throws Error if V2 encryption key has not been set
336
+ */
337
+ getUtxoPrivateKeyV2() {
338
+ if (!this.utxoPrivateKeyV2) {
339
+ throw new Error('Encryption key not set. Call setEncryptionKey or deriveEncryptionKeyFromWallet first.');
340
+ }
341
+ return this.utxoPrivateKeyV2;
342
+ }
343
+ }
344
+ export function serializeProofAndExtData(proof, extData) {
345
+ // Create the ExtDataMinified object for the program call (only extAmount and fee)
346
+ const extDataMinified = {
347
+ extAmount: extData.extAmount,
348
+ fee: extData.fee
349
+ };
350
+ // Use the same serialization approach as deposit script
351
+ const instructionData = Buffer.concat([
352
+ TRANSACT_IX_DISCRIMINATOR,
353
+ // Serialize proof
354
+ Buffer.from(proof.proofA),
355
+ Buffer.from(proof.proofB),
356
+ Buffer.from(proof.proofC),
357
+ Buffer.from(proof.root),
358
+ Buffer.from(proof.publicAmount),
359
+ Buffer.from(proof.extDataHash),
360
+ Buffer.from(proof.inputNullifiers[0]),
361
+ Buffer.from(proof.inputNullifiers[1]),
362
+ Buffer.from(proof.outputCommitments[0]),
363
+ Buffer.from(proof.outputCommitments[1]),
364
+ // Serialize ExtDataMinified (only extAmount and fee)
365
+ Buffer.from(new BN(extDataMinified.extAmount).toTwos(64).toArray('le', 8)),
366
+ Buffer.from(new BN(extDataMinified.fee).toArray('le', 8)),
367
+ // Serialize encrypted outputs as separate parameters
368
+ Buffer.from(new BN(extData.encryptedOutput1.length).toArray('le', 4)),
369
+ extData.encryptedOutput1,
370
+ Buffer.from(new BN(extData.encryptedOutput2.length).toArray('le', 4)),
371
+ extData.encryptedOutput2,
372
+ ]);
373
+ return instructionData;
374
+ }
@@ -0,0 +1,9 @@
1
+ export type LogLevel = "debug" | "info" | "warn" | "error";
2
+ export type LoggerFn = (level: LogLevel, message: string) => void;
3
+ export declare function setLogger(logger: LoggerFn): void;
4
+ export declare const logger: {
5
+ debug: (...args: unknown[]) => void;
6
+ info: (...args: unknown[]) => void;
7
+ warn: (...args: unknown[]) => void;
8
+ error: (...args: unknown[]) => void;
9
+ };