privacycash 1.1.18 → 1.1.20

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/deposit.js CHANGED
@@ -84,201 +84,185 @@ export async function deposit({ lightWasm, storage, keyBasePath, publicKey, conn
84
84
  let inputs;
85
85
  let inputMerklePathIndices;
86
86
  let inputMerklePathElements;
87
- let getProveRetryCount = 0;
88
- let extData;
89
- let encryptedOutput1;
90
- let getProve = async () => {
91
- // Initialize root and nextIndex variables
92
- const { root, nextIndex: currentNextIndex } = await queryRemoteTreeState();
93
- logger.debug(`Using tree root: ${root}`);
94
- logger.debug(`New UTXOs will be inserted at indices: ${currentNextIndex} and ${currentNextIndex + 1}`);
95
- if (existingUnspentUtxos.length === 0) {
96
- // Scenario 1: Fresh deposit with dummy inputs - add new funds to the system
97
- extAmount = amount_in_lamports;
98
- outputAmount = new BN(amount_in_lamports).sub(new BN(fee_amount_in_lamports)).toString();
99
- logger.debug(`Fresh deposit scenario (no existing UTXOs):`);
100
- logger.debug(`External amount (deposit): ${extAmount}`);
101
- logger.debug(`Fee amount: ${fee_amount_in_lamports}`);
102
- logger.debug(`Output amount: ${outputAmount}`);
103
- // Use two dummy UTXOs as inputs
104
- inputs = [
105
- new Utxo({
106
- lightWasm,
107
- keypair: utxoKeypair
108
- }),
109
- new Utxo({
110
- lightWasm,
111
- keypair: utxoKeypair
112
- })
113
- ];
114
- // Both inputs are dummy, so use mock indices and zero-filled Merkle paths
115
- inputMerklePathIndices = inputs.map((input) => input.index || 0);
116
- inputMerklePathElements = inputs.map(() => {
117
- return [...new Array(tree.levels).fill("0")];
118
- });
119
- }
120
- else {
121
- // Scenario 2: Deposit that consolidates with existing UTXO(s)
122
- const firstUtxo = existingUnspentUtxos[0];
123
- const firstUtxoAmount = firstUtxo.amount;
124
- const secondUtxoAmount = existingUnspentUtxos.length > 1 ? existingUnspentUtxos[1].amount : new BN(0);
125
- extAmount = amount_in_lamports; // Still depositing new funds
126
- // Output combines existing UTXO amounts + new deposit amount - fee
127
- outputAmount = firstUtxoAmount.add(secondUtxoAmount).add(new BN(amount_in_lamports)).sub(new BN(fee_amount_in_lamports)).toString();
128
- logger.debug(`Deposit with consolidation scenario:`);
129
- logger.debug(`First existing UTXO amount: ${firstUtxoAmount.toString()}`);
130
- if (secondUtxoAmount.gt(new BN(0))) {
131
- logger.debug(`Second existing UTXO amount: ${secondUtxoAmount.toString()}`);
132
- }
133
- logger.debug(`New deposit amount: ${amount_in_lamports}`);
134
- logger.debug(`Fee amount: ${fee_amount_in_lamports}`);
135
- logger.debug(`Output amount (existing UTXOs + deposit - fee): ${outputAmount}`);
136
- logger.debug(`External amount (deposit): ${extAmount}`);
137
- logger.debug('\nFirst UTXO to be consolidated:');
138
- await firstUtxo.log();
139
- // Use first existing UTXO as first input, and either second UTXO or dummy UTXO as second input
140
- const secondUtxo = existingUnspentUtxos.length > 1 ? existingUnspentUtxos[1] : new Utxo({
141
- lightWasm,
142
- keypair: utxoKeypair,
143
- amount: '0'
144
- });
145
- inputs = [
146
- firstUtxo, // Use the first existing UTXO
147
- secondUtxo // Use second UTXO if available, otherwise dummy
148
- ];
149
- // Fetch Merkle proofs for real UTXOs
150
- const firstUtxoCommitment = await firstUtxo.getCommitment();
151
- const firstUtxoMerkleProof = await fetchMerkleProof(firstUtxoCommitment);
152
- let secondUtxoMerkleProof;
153
- if (secondUtxo.amount.gt(new BN(0))) {
154
- // Second UTXO is real, fetch its proof
155
- const secondUtxoCommitment = await secondUtxo.getCommitment();
156
- secondUtxoMerkleProof = await fetchMerkleProof(secondUtxoCommitment);
157
- logger.debug('\nSecond UTXO to be consolidated:');
158
- await secondUtxo.log();
159
- }
160
- // Use the real pathIndices from API for real inputs, mock index for dummy input
161
- inputMerklePathIndices = [
162
- firstUtxo.index || 0, // Use the real UTXO's index
163
- secondUtxo.amount.gt(new BN(0)) ? (secondUtxo.index || 0) : 0 // Real UTXO index or dummy
164
- ];
165
- // Create Merkle path elements: real proof for real inputs, zeros for dummy input
166
- inputMerklePathElements = [
167
- firstUtxoMerkleProof.pathElements, // Real Merkle proof for first existing UTXO
168
- secondUtxo.amount.gt(new BN(0)) ? secondUtxoMerkleProof.pathElements : [...new Array(tree.levels).fill("0")] // Real proof or zero-filled for dummy
169
- ];
170
- logger.debug(`Using first UTXO with amount: ${firstUtxo.amount.toString()} and index: ${firstUtxo.index}`);
171
- logger.debug(`Using second ${secondUtxo.amount.gt(new BN(0)) ? 'UTXO' : 'dummy UTXO'} with amount: ${secondUtxo.amount.toString()}${secondUtxo.amount.gt(new BN(0)) ? ` and index: ${secondUtxo.index}` : ''}`);
172
- logger.debug(`First UTXO Merkle proof path indices from API: [${firstUtxoMerkleProof.pathIndices.join(', ')}]`);
173
- if (secondUtxo.amount.gt(new BN(0))) {
174
- logger.debug(`Second UTXO Merkle proof path indices from API: [${secondUtxoMerkleProof.pathIndices.join(', ')}]`);
175
- }
176
- }
177
- const publicAmountForCircuit = new BN(extAmount).sub(new BN(fee_amount_in_lamports)).add(FIELD_SIZE).mod(FIELD_SIZE);
178
- logger.debug(`Public amount calculation: (${extAmount} - ${fee_amount_in_lamports} + FIELD_SIZE) % FIELD_SIZE = ${publicAmountForCircuit.toString()}`);
179
- // Create outputs for the transaction with the same shared keypair
180
- const outputs = [
87
+ let root;
88
+ let nextIndex;
89
+ if (existingUnspentUtxos.length === 0) {
90
+ const treeState = await queryRemoteTreeState();
91
+ root = treeState.root;
92
+ nextIndex = treeState.nextIndex;
93
+ // Scenario 1: Fresh deposit with dummy inputs - add new funds to the system
94
+ extAmount = amount_in_lamports;
95
+ outputAmount = new BN(amount_in_lamports).sub(new BN(fee_amount_in_lamports)).toString();
96
+ logger.debug(`Fresh deposit scenario (no existing UTXOs):`);
97
+ logger.debug(`External amount (deposit): ${extAmount}`);
98
+ logger.debug(`Fee amount: ${fee_amount_in_lamports}`);
99
+ logger.debug(`Output amount: ${outputAmount}`);
100
+ // Use two dummy UTXOs as inputs
101
+ inputs = [
181
102
  new Utxo({
182
103
  lightWasm,
183
- amount: outputAmount,
184
- keypair: utxoKeypair,
185
- index: currentNextIndex // This UTXO will be inserted at currentNextIndex
186
- }), // Output with value (either deposit amount minus fee, or input amount minus fee)
104
+ keypair: utxoKeypair
105
+ }),
187
106
  new Utxo({
188
107
  lightWasm,
189
- amount: '0',
190
- keypair: utxoKeypair,
191
- index: currentNextIndex + 1 // This UTXO will be inserted at currentNextIndex + 1
192
- }) // Empty UTXO
108
+ keypair: utxoKeypair
109
+ })
193
110
  ];
194
- // Verify this matches the circuit balance equation: sumIns + publicAmount = sumOuts
195
- const sumIns = inputs.reduce((sum, input) => sum.add(input.amount), new BN(0));
196
- const sumOuts = outputs.reduce((sum, output) => sum.add(output.amount), new BN(0));
197
- logger.debug(`Circuit balance check: sumIns(${sumIns.toString()}) + publicAmount(${publicAmountForCircuit.toString()}) should equal sumOuts(${sumOuts.toString()})`);
198
- // Convert to circuit-compatible format
199
- const publicAmountCircuitResult = sumIns.add(publicAmountForCircuit).mod(FIELD_SIZE);
200
- logger.debug(`Balance verification: ${sumIns.toString()} + ${publicAmountForCircuit.toString()} (mod FIELD_SIZE) = ${publicAmountCircuitResult.toString()}`);
201
- logger.debug(`Expected sum of outputs: ${sumOuts.toString()}`);
202
- logger.debug(`Balance equation satisfied: ${publicAmountCircuitResult.eq(sumOuts)}`);
203
- // Generate nullifiers and commitments
204
- const inputNullifiers = await Promise.all(inputs.map(x => x.getNullifier()));
205
- const outputCommitments = await Promise.all(outputs.map(x => x.getCommitment()));
206
- // Save original commitment and nullifier values for verification
207
- logger.debug('\n=== UTXO VALIDATION ===');
208
- logger.debug('Output 0 Commitment:', outputCommitments[0]);
209
- logger.debug('Output 1 Commitment:', outputCommitments[1]);
210
- // Encrypt the UTXO data using a compact format that includes the keypair
211
- logger.debug('\nEncrypting UTXOs with keypair data...');
212
- encryptedOutput1 = encryptionService.encryptUtxo(outputs[0]);
213
- const encryptedOutput2 = encryptionService.encryptUtxo(outputs[1]);
214
- logger.debug(`\nOutput[0] (with value):`);
215
- await outputs[0].log();
216
- logger.debug(`\nOutput[1] (empty):`);
217
- await outputs[1].log();
218
- logger.debug(`\nEncrypted output 1 size: ${encryptedOutput1.length} bytes`);
219
- logger.debug(`Encrypted output 2 size: ${encryptedOutput2.length} bytes`);
220
- logger.debug(`Total encrypted outputs size: ${encryptedOutput1.length + encryptedOutput2.length} bytes`);
221
- // Test decryption to verify commitment values match
222
- logger.debug('\n=== TESTING DECRYPTION ===');
223
- logger.debug('Decrypting output 1 to verify commitment matches...');
224
- const decryptedUtxo1 = await encryptionService.decryptUtxo(encryptedOutput1, lightWasm);
225
- const decryptedCommitment1 = await decryptedUtxo1.getCommitment();
226
- logger.debug('Original commitment:', outputCommitments[0]);
227
- logger.debug('Decrypted commitment:', decryptedCommitment1);
228
- logger.debug('Commitment matches:', outputCommitments[0] === decryptedCommitment1);
229
- // Create the deposit ExtData with real encrypted outputs
230
- extData = {
231
- // recipient - just a placeholder, not actually used for deposits.
232
- recipient: new PublicKey('AWexibGxNFKTa1b5R5MN4PJr9HWnWRwf8EW9g8cLx3dM'),
233
- extAmount: new BN(extAmount),
234
- encryptedOutput1: encryptedOutput1,
235
- encryptedOutput2: encryptedOutput2,
236
- fee: new BN(fee_amount_in_lamports),
237
- feeRecipient: FEE_RECIPIENT,
238
- mintAddress: inputs[0].mintAddress
239
- };
240
- // Calculate the extDataHash with the encrypted outputs (now includes mintAddress for security)
241
- const calculatedExtDataHash = getExtDataHash(extData);
242
- // Create the input for the proof generation (must match circuit input order exactly)
243
- const input = {
244
- // Common transaction data
245
- root: root,
246
- inputNullifier: inputNullifiers, // Use resolved values instead of Promise objects
247
- outputCommitment: outputCommitments, // Use resolved values instead of Promise objects
248
- publicAmount: publicAmountForCircuit.toString(), // Use proper field arithmetic result
249
- extDataHash: calculatedExtDataHash,
250
- // Input UTXO data (UTXOs being spent) - ensure all values are in decimal format
251
- inAmount: inputs.map(x => x.amount.toString(10)),
252
- inPrivateKey: inputs.map(x => x.keypair.privkey),
253
- inBlinding: inputs.map(x => x.blinding.toString(10)),
254
- inPathIndices: inputMerklePathIndices,
255
- inPathElements: inputMerklePathElements,
256
- // Output UTXO data (UTXOs being created) - ensure all values are in decimal format
257
- outAmount: outputs.map(x => x.amount.toString(10)),
258
- outBlinding: outputs.map(x => x.blinding.toString(10)),
259
- outPubkey: outputs.map(x => x.keypair.pubkey),
260
- // new mint address
261
- mintAddress: inputs[0].mintAddress
262
- };
263
- logger.info('generating ZK proof...');
264
- // Generate the zero-knowledge proof
265
- return await prove(input, keyBasePath);
266
- };
267
- let getProveResult = null;
268
- while (!getProveResult) {
269
- try {
270
- getProveResult = await getProve();
111
+ // Both inputs are dummy, so use mock indices and zero-filled Merkle paths
112
+ inputMerklePathIndices = inputs.map((input) => input.index || 0);
113
+ inputMerklePathElements = inputs.map(() => {
114
+ return [...new Array(tree.levels).fill("0")];
115
+ });
116
+ }
117
+ else {
118
+ // Scenario 2: Deposit that consolidates with existing UTXO(s)
119
+ const firstUtxo = existingUnspentUtxos[0];
120
+ const firstUtxoAmount = firstUtxo.amount;
121
+ const secondUtxoAmount = existingUnspentUtxos.length > 1 ? existingUnspentUtxos[1].amount : new BN(0);
122
+ extAmount = amount_in_lamports; // Still depositing new funds
123
+ // Output combines existing UTXO amounts + new deposit amount - fee
124
+ outputAmount = firstUtxoAmount.add(secondUtxoAmount).add(new BN(amount_in_lamports)).sub(new BN(fee_amount_in_lamports)).toString();
125
+ logger.debug(`Deposit with consolidation scenario:`);
126
+ logger.debug(`First existing UTXO amount: ${firstUtxoAmount.toString()}`);
127
+ if (secondUtxoAmount.gt(new BN(0))) {
128
+ logger.debug(`Second existing UTXO amount: ${secondUtxoAmount.toString()}`);
129
+ }
130
+ logger.debug(`New deposit amount: ${amount_in_lamports}`);
131
+ logger.debug(`Fee amount: ${fee_amount_in_lamports}`);
132
+ logger.debug(`Output amount (existing UTXOs + deposit - fee): ${outputAmount}`);
133
+ logger.debug(`External amount (deposit): ${extAmount}`);
134
+ logger.debug('\nFirst UTXO to be consolidated:');
135
+ await firstUtxo.log();
136
+ // Use first existing UTXO as first input, and either second UTXO or dummy UTXO as second input
137
+ const secondUtxo = existingUnspentUtxos.length > 1 ? existingUnspentUtxos[1] : new Utxo({
138
+ lightWasm,
139
+ keypair: utxoKeypair,
140
+ amount: '0'
141
+ });
142
+ inputs = [
143
+ firstUtxo, // Use the first existing UTXO
144
+ secondUtxo // Use second UTXO if available, otherwise dummy
145
+ ];
146
+ // Fetch Merkle proofs for real UTXOs
147
+ const firstUtxoCommitment = await firstUtxo.getCommitment();
148
+ let commitmentsToFetch = [firstUtxoCommitment];
149
+ if (secondUtxo.amount.gt(new BN(0))) {
150
+ // Second UTXO is real, fetch its proof
151
+ const secondUtxoCommitment = await secondUtxo.getCommitment();
152
+ commitmentsToFetch.push(secondUtxoCommitment);
153
+ logger.debug('\nSecond UTXO to be consolidated:');
154
+ await secondUtxo.log();
271
155
  }
272
- catch (error) {
273
- logger.error('Proof generation failed, retrying...', error);
274
- getProveRetryCount++;
275
- if (getProveRetryCount >= 2) {
276
- throw new Error('Proof generation failed after 2 attempts. Please try again later.');
277
- }
278
- await new Promise(resolve => setTimeout(resolve, 500));
156
+ let data = await fetchMerkleProof(commitmentsToFetch);
157
+ root = data.root;
158
+ nextIndex = data.nextIndex;
159
+ let [firstUtxoMerkleProof, secondUtxoMerkleProof] = data.proofs;
160
+ // Use the real pathIndices from API for real inputs, mock index for dummy input
161
+ inputMerklePathIndices = [
162
+ firstUtxo.index || 0, // Use the real UTXO's index
163
+ secondUtxo.amount.gt(new BN(0)) ? (secondUtxo.index || 0) : 0 // Real UTXO index or dummy
164
+ ];
165
+ // Create Merkle path elements: real proof for real inputs, zeros for dummy input
166
+ inputMerklePathElements = [
167
+ firstUtxoMerkleProof.pathElements, // Real Merkle proof for first existing UTXO
168
+ secondUtxo.amount.gt(new BN(0)) ? secondUtxoMerkleProof.pathElements : [...new Array(tree.levels).fill("0")] // Real proof or zero-filled for dummy
169
+ ];
170
+ logger.debug(`Using first UTXO with amount: ${firstUtxo.amount.toString()} and index: ${firstUtxo.index}`);
171
+ logger.debug(`Using second ${secondUtxo.amount.gt(new BN(0)) ? 'UTXO' : 'dummy UTXO'} with amount: ${secondUtxo.amount.toString()}${secondUtxo.amount.gt(new BN(0)) ? ` and index: ${secondUtxo.index}` : ''}`);
172
+ logger.debug(`First UTXO Merkle proof path indices from API: [${firstUtxoMerkleProof.pathIndices.join(', ')}]`);
173
+ if (secondUtxo.amount.gt(new BN(0))) {
174
+ logger.debug(`Second UTXO Merkle proof path indices from API: [${secondUtxoMerkleProof.pathIndices.join(', ')}]`);
279
175
  }
280
176
  }
281
- const { proof, publicSignals } = getProveResult;
177
+ const publicAmountForCircuit = new BN(extAmount).sub(new BN(fee_amount_in_lamports)).add(FIELD_SIZE).mod(FIELD_SIZE);
178
+ logger.debug(`Public amount calculation: (${extAmount} - ${fee_amount_in_lamports} + FIELD_SIZE) % FIELD_SIZE = ${publicAmountForCircuit.toString()}`);
179
+ // Create outputs for the transaction with the same shared keypair
180
+ const outputs = [
181
+ new Utxo({
182
+ lightWasm,
183
+ amount: outputAmount,
184
+ keypair: utxoKeypair,
185
+ index: nextIndex // This UTXO will be inserted at currentNextIndex
186
+ }), // Output with value (either deposit amount minus fee, or input amount minus fee)
187
+ new Utxo({
188
+ lightWasm,
189
+ amount: '0',
190
+ keypair: utxoKeypair,
191
+ index: nextIndex + 1 // This UTXO will be inserted at currentNextIndex + 1
192
+ }) // Empty UTXO
193
+ ];
194
+ // Verify this matches the circuit balance equation: sumIns + publicAmount = sumOuts
195
+ const sumIns = inputs.reduce((sum, input) => sum.add(input.amount), new BN(0));
196
+ const sumOuts = outputs.reduce((sum, output) => sum.add(output.amount), new BN(0));
197
+ logger.debug(`Circuit balance check: sumIns(${sumIns.toString()}) + publicAmount(${publicAmountForCircuit.toString()}) should equal sumOuts(${sumOuts.toString()})`);
198
+ // Convert to circuit-compatible format
199
+ const publicAmountCircuitResult = sumIns.add(publicAmountForCircuit).mod(FIELD_SIZE);
200
+ logger.debug(`Balance verification: ${sumIns.toString()} + ${publicAmountForCircuit.toString()} (mod FIELD_SIZE) = ${publicAmountCircuitResult.toString()}`);
201
+ logger.debug(`Expected sum of outputs: ${sumOuts.toString()}`);
202
+ logger.debug(`Balance equation satisfied: ${publicAmountCircuitResult.eq(sumOuts)}`);
203
+ // Generate nullifiers and commitments
204
+ const inputNullifiers = await Promise.all(inputs.map(x => x.getNullifier()));
205
+ const outputCommitments = await Promise.all(outputs.map(x => x.getCommitment()));
206
+ // Save original commitment and nullifier values for verification
207
+ logger.debug('\n=== UTXO VALIDATION ===');
208
+ logger.debug('Output 0 Commitment:', outputCommitments[0]);
209
+ logger.debug('Output 1 Commitment:', outputCommitments[1]);
210
+ // Encrypt the UTXO data using a compact format that includes the keypair
211
+ logger.debug('\nEncrypting UTXOs with keypair data...');
212
+ const encryptedOutput1 = encryptionService.encryptUtxo(outputs[0]);
213
+ const encryptedOutput2 = encryptionService.encryptUtxo(outputs[1]);
214
+ logger.debug(`\nOutput[0] (with value):`);
215
+ await outputs[0].log();
216
+ logger.debug(`\nOutput[1] (empty):`);
217
+ await outputs[1].log();
218
+ logger.debug(`\nEncrypted output 1 size: ${encryptedOutput1.length} bytes`);
219
+ logger.debug(`Encrypted output 2 size: ${encryptedOutput2.length} bytes`);
220
+ logger.debug(`Total encrypted outputs size: ${encryptedOutput1.length + encryptedOutput2.length} bytes`);
221
+ // Test decryption to verify commitment values match
222
+ logger.debug('\n=== TESTING DECRYPTION ===');
223
+ logger.debug('Decrypting output 1 to verify commitment matches...');
224
+ const decryptedUtxo1 = await encryptionService.decryptUtxo(encryptedOutput1, lightWasm);
225
+ const decryptedCommitment1 = await decryptedUtxo1.getCommitment();
226
+ logger.debug('Original commitment:', outputCommitments[0]);
227
+ logger.debug('Decrypted commitment:', decryptedCommitment1);
228
+ logger.debug('Commitment matches:', outputCommitments[0] === decryptedCommitment1);
229
+ // Create the deposit ExtData with real encrypted outputs
230
+ const extData = {
231
+ // recipient - just a placeholder, not actually used for deposits.
232
+ recipient: new PublicKey('AWexibGxNFKTa1b5R5MN4PJr9HWnWRwf8EW9g8cLx3dM'),
233
+ extAmount: new BN(extAmount),
234
+ encryptedOutput1: encryptedOutput1,
235
+ encryptedOutput2: encryptedOutput2,
236
+ fee: new BN(fee_amount_in_lamports),
237
+ feeRecipient: FEE_RECIPIENT,
238
+ mintAddress: inputs[0].mintAddress
239
+ };
240
+ // Calculate the extDataHash with the encrypted outputs (now includes mintAddress for security)
241
+ const calculatedExtDataHash = getExtDataHash(extData);
242
+ // Create the input for the proof generation (must match circuit input order exactly)
243
+ const input = {
244
+ // Common transaction data
245
+ root: root,
246
+ inputNullifier: inputNullifiers, // Use resolved values instead of Promise objects
247
+ outputCommitment: outputCommitments, // Use resolved values instead of Promise objects
248
+ publicAmount: publicAmountForCircuit.toString(), // Use proper field arithmetic result
249
+ extDataHash: calculatedExtDataHash,
250
+ // Input UTXO data (UTXOs being spent) - ensure all values are in decimal format
251
+ inAmount: inputs.map(x => x.amount.toString(10)),
252
+ inPrivateKey: inputs.map(x => x.keypair.privkey),
253
+ inBlinding: inputs.map(x => x.blinding.toString(10)),
254
+ inPathIndices: inputMerklePathIndices,
255
+ inPathElements: inputMerklePathElements,
256
+ // Output UTXO data (UTXOs being created) - ensure all values are in decimal format
257
+ outAmount: outputs.map(x => x.amount.toString(10)),
258
+ outBlinding: outputs.map(x => x.blinding.toString(10)),
259
+ outPubkey: outputs.map(x => x.keypair.pubkey),
260
+ // new mint address
261
+ mintAddress: inputs[0].mintAddress
262
+ };
263
+ logger.info('generating ZK proof...');
264
+ // Generate the zero-knowledge proof
265
+ const { proof, publicSignals } = await prove(input, keyBasePath);
282
266
  // Parse the proof and public signals into byte arrays
283
267
  const proofInBytes = parseProofToBytesArray(proof);
284
268
  const inputsInBytes = parseToBytesArray(publicSignals);