@veil-cash/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/types.ts ADDED
@@ -0,0 +1,306 @@
1
+ /**
2
+ * Type definitions for Veil SDK
3
+ */
4
+
5
+ /**
6
+ * Supported tokens
7
+ */
8
+ export type Token = 'ETH' | 'USDC';
9
+
10
+ /**
11
+ * Encrypted message format (x25519-xsalsa20-poly1305)
12
+ */
13
+ export interface EncryptedMessage {
14
+ version: string;
15
+ nonce: string;
16
+ ephemPublicKey: string;
17
+ ciphertext: string;
18
+ }
19
+
20
+ /**
21
+ * Contract addresses for a network
22
+ */
23
+ export interface NetworkAddresses {
24
+ entry: `0x${string}`;
25
+ ethPool: `0x${string}`;
26
+ ethQueue: `0x${string}`;
27
+ usdcPool: `0x${string}`;
28
+ usdcQueue: `0x${string}`;
29
+ usdcToken: `0x${string}`;
30
+ chainId: number;
31
+ relayUrl: string;
32
+ }
33
+
34
+ /**
35
+ * Options for building a register transaction
36
+ */
37
+ export interface RegisterTxOptions {
38
+ depositKey: string;
39
+ }
40
+
41
+ /**
42
+ * Options for building a deposit transaction
43
+ */
44
+ export interface DepositTxOptions {
45
+ depositKey: string;
46
+ fallbackReceiver: `0x${string}`;
47
+ amount: string;
48
+ token?: Token;
49
+ }
50
+
51
+ /**
52
+ * Transaction data returned by build functions
53
+ */
54
+ export interface TransactionData {
55
+ to: `0x${string}`;
56
+ data: `0x${string}`;
57
+ value?: bigint;
58
+ }
59
+
60
+ /**
61
+ * Pool configuration
62
+ */
63
+ export interface PoolConfig {
64
+ decimals: number;
65
+ displayDecimals: number;
66
+ symbol: string;
67
+ name: string;
68
+ }
69
+
70
+ /**
71
+ * Pending deposit from queue contract
72
+ */
73
+ export interface PendingDeposit {
74
+ nonce: string;
75
+ status: 'pending' | 'accepted' | 'rejected' | 'refunded';
76
+ amount: string;
77
+ amountWei: string;
78
+ timestamp: string;
79
+ }
80
+
81
+ /**
82
+ * Result from getQueueBalance
83
+ */
84
+ export interface QueueBalanceResult {
85
+ address: string;
86
+ queueBalance: string;
87
+ queueBalanceWei: string;
88
+ pendingDeposits: PendingDeposit[];
89
+ pendingCount: number;
90
+ }
91
+
92
+ /**
93
+ * UTXO info for private balance
94
+ */
95
+ export interface UtxoInfo {
96
+ index: number;
97
+ amount: string;
98
+ amountWei: string;
99
+ isSpent: boolean;
100
+ }
101
+
102
+ /**
103
+ * Result from getPrivateBalance
104
+ */
105
+ export interface PrivateBalanceResult {
106
+ privateBalance: string;
107
+ privateBalanceWei: string;
108
+ utxoCount: number;
109
+ spentCount: number;
110
+ unspentCount: number;
111
+ utxos: UtxoInfo[];
112
+ }
113
+
114
+ // =============================================================================
115
+ // Relay Types
116
+ // =============================================================================
117
+
118
+ /**
119
+ * Pool type for relay operations
120
+ */
121
+ export type RelayPool = 'eth' | 'usdc';
122
+
123
+ /**
124
+ * Type of relay transaction
125
+ */
126
+ export type RelayType = 'withdraw' | 'transfer';
127
+
128
+ /**
129
+ * Proof arguments for relay transaction
130
+ */
131
+ export interface RelayProofArgs {
132
+ proof: string;
133
+ root: string;
134
+ inputNullifiers: string[];
135
+ outputCommitments: [string, string];
136
+ publicAmount: string;
137
+ extDataHash: string;
138
+ }
139
+
140
+ /**
141
+ * External data for relay transaction
142
+ */
143
+ export interface RelayExtData {
144
+ recipient: string;
145
+ extAmount: string;
146
+ relayer: string;
147
+ fee: string;
148
+ encryptedOutput1: string;
149
+ encryptedOutput2: string;
150
+ }
151
+
152
+ /**
153
+ * Metadata for relay transaction (optional)
154
+ */
155
+ export interface RelayMetadata {
156
+ amount?: string;
157
+ recipient?: string;
158
+ inputUtxoCount?: number;
159
+ outputUtxoCount?: number;
160
+ }
161
+
162
+ /**
163
+ * Request body for relay service
164
+ */
165
+ export interface RelayRequest {
166
+ type: RelayType;
167
+ proofArgs: RelayProofArgs;
168
+ extData: RelayExtData;
169
+ metadata?: RelayMetadata;
170
+ }
171
+
172
+ /**
173
+ * Response from relay service
174
+ */
175
+ export interface RelayResponse {
176
+ success: boolean;
177
+ transactionHash: string;
178
+ blockNumber: string;
179
+ gasUsed: string;
180
+ status: string;
181
+ network: string;
182
+ }
183
+
184
+ /**
185
+ * Error response from relay service
186
+ */
187
+ export interface RelayErrorResponse {
188
+ error: string;
189
+ message?: string;
190
+ retryAfter?: number;
191
+ network?: string;
192
+ }
193
+
194
+ /**
195
+ * Options for submitRelay function
196
+ */
197
+ export interface SubmitRelayOptions {
198
+ /** Type of transaction: 'withdraw' or 'transfer' */
199
+ type: RelayType;
200
+ /** Pool to use: 'eth' or 'usdc' */
201
+ pool?: RelayPool;
202
+ /** Proof arguments generated by ZK prover */
203
+ proofArgs: RelayProofArgs;
204
+ /** External data for the transaction */
205
+ extData: RelayExtData;
206
+ /** Optional metadata */
207
+ metadata?: RelayMetadata;
208
+ /** Custom relay URL (overrides default) */
209
+ relayUrl?: string;
210
+ }
211
+
212
+ // =============================================================================
213
+ // Withdraw/Transfer Types
214
+ // =============================================================================
215
+
216
+ /**
217
+ * Options for building a withdrawal proof
218
+ */
219
+ export interface BuildWithdrawProofOptions {
220
+ /** Amount to withdraw (human readable, e.g., "0.1") */
221
+ amount: string;
222
+ /** Recipient address for withdrawal */
223
+ recipient: `0x${string}`;
224
+ /** User's keypair for signing */
225
+ keypair: import('./keypair.js').Keypair;
226
+ /** Optional RPC URL */
227
+ rpcUrl?: string;
228
+ /** Progress callback */
229
+ onProgress?: (stage: string, detail?: string) => void;
230
+ }
231
+
232
+ /**
233
+ * Options for building a transfer proof
234
+ */
235
+ export interface BuildTransferProofOptions {
236
+ /** Amount to transfer (human readable, e.g., "0.1") */
237
+ amount: string;
238
+ /** Recipient's address (must be registered) */
239
+ recipientAddress: `0x${string}`;
240
+ /** Sender's keypair */
241
+ senderKeypair: import('./keypair.js').Keypair;
242
+ /** Optional RPC URL */
243
+ rpcUrl?: string;
244
+ /** Progress callback */
245
+ onProgress?: (stage: string, detail?: string) => void;
246
+ }
247
+
248
+ /**
249
+ * Result from building a withdrawal or transfer proof
250
+ */
251
+ export interface ProofBuildResult {
252
+ /** Proof arguments for on-chain verification */
253
+ proofArgs: RelayProofArgs;
254
+ /** External data for transaction */
255
+ extData: RelayExtData;
256
+ /** Number of input UTXOs used */
257
+ inputCount: number;
258
+ /** Number of output UTXOs created */
259
+ outputCount: number;
260
+ /** Amount being withdrawn/transferred */
261
+ amount: string;
262
+ }
263
+
264
+ /**
265
+ * Result from executing a withdrawal
266
+ */
267
+ export interface WithdrawResult {
268
+ /** Whether the withdrawal was successful */
269
+ success: boolean;
270
+ /** Transaction hash */
271
+ transactionHash: string;
272
+ /** Block number of the transaction */
273
+ blockNumber: string;
274
+ /** Amount withdrawn */
275
+ amount: string;
276
+ /** Recipient address */
277
+ recipient: string;
278
+ }
279
+
280
+ /**
281
+ * Result from executing a transfer
282
+ */
283
+ export interface TransferResult {
284
+ /** Whether the transfer was successful */
285
+ success: boolean;
286
+ /** Transaction hash */
287
+ transactionHash: string;
288
+ /** Block number of the transaction */
289
+ blockNumber: string;
290
+ /** Amount transferred */
291
+ amount: string;
292
+ /** Recipient address */
293
+ recipient: string;
294
+ }
295
+
296
+ /**
297
+ * UTXO selection result
298
+ */
299
+ export interface UtxoSelectionResult {
300
+ /** Selected UTXOs */
301
+ selectedUtxos: import('./utxo.js').Utxo[];
302
+ /** Total amount of selected UTXOs (wei) */
303
+ totalSelected: bigint;
304
+ /** Change amount to return to sender (wei) */
305
+ changeAmount: bigint;
306
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Crypto utilities for Veil SDK
3
+ * Poseidon hash, hex conversion, and random number generation
4
+ */
5
+
6
+ import * as crypto from 'crypto';
7
+
8
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
9
+ const circomlib = require('circomlib');
10
+ const poseidon = circomlib.poseidon;
11
+
12
+ /**
13
+ * SNARK scalar field size
14
+ */
15
+ export const FIELD_SIZE = BigInt(
16
+ '21888242871839275222246405745257275088548364400416034343698204186575808495617'
17
+ );
18
+
19
+ /**
20
+ * Compute Poseidon hash of items
21
+ * @param items - Array of values to hash (bigint, string, or number)
22
+ * @returns Poseidon hash as bigint
23
+ */
24
+ export const poseidonHash = (items: (bigint | string | number)[]): bigint =>
25
+ BigInt(poseidon(items).toString());
26
+
27
+ /**
28
+ * Compute Poseidon hash of two items
29
+ * @param a - First value
30
+ * @param b - Second value
31
+ * @returns Poseidon hash as bigint
32
+ */
33
+ export const poseidonHash2 = (a: bigint | string | number, b: bigint | string | number): bigint =>
34
+ poseidonHash([a, b]);
35
+
36
+ /**
37
+ * Generate random bigint of specified byte length
38
+ * @param nbytes - Number of bytes (default: 31)
39
+ * @returns Random bigint
40
+ */
41
+ export const randomBN = (nbytes: number = 31): bigint => {
42
+ const bytes = crypto.randomBytes(nbytes);
43
+ let hex = '0x';
44
+ for (let i = 0; i < bytes.length; i++) {
45
+ hex += bytes[i].toString(16).padStart(2, '0');
46
+ }
47
+ return BigInt(hex);
48
+ };
49
+
50
+ /**
51
+ * Convert bigint/string/number/Buffer to fixed-length hex string
52
+ * @param number - Value to convert
53
+ * @param length - Output byte length (default: 32)
54
+ * @returns Hex string with 0x prefix
55
+ */
56
+ export function toFixedHex(number: bigint | string | number | Buffer, length: number = 32): string {
57
+ let hexValue: string;
58
+
59
+ if (number instanceof Buffer) {
60
+ hexValue = number.toString('hex');
61
+ } else {
62
+ let bigIntValue = BigInt(number as bigint | string | number);
63
+
64
+ if (bigIntValue < 0n) {
65
+ // For negative numbers, use two's complement
66
+ const maxValue = 1n << BigInt(length * 8);
67
+ bigIntValue = maxValue + bigIntValue;
68
+ }
69
+
70
+ hexValue = bigIntValue.toString(16);
71
+ }
72
+
73
+ return '0x' + hexValue.padStart(length * 2, '0');
74
+ }
75
+
76
+ /**
77
+ * Convert value to Buffer of specified byte length
78
+ * @param value - Value to convert
79
+ * @param length - Output byte length
80
+ * @returns Buffer
81
+ */
82
+ export const toBuffer = (value: bigint | string | number, length: number): Buffer => {
83
+ const bigIntValue = BigInt(value);
84
+ const hex = bigIntValue.toString(16).padStart(length * 2, '0');
85
+ return Buffer.from(hex, 'hex');
86
+ };
87
+
88
+ /**
89
+ * External data input for hash calculation
90
+ */
91
+ export interface ExtDataInput {
92
+ recipient: string | bigint;
93
+ extAmount: bigint;
94
+ relayer: string | bigint;
95
+ fee: bigint;
96
+ encryptedOutput1: string;
97
+ encryptedOutput2: string;
98
+ }
99
+
100
+ /**
101
+ * Calculate hash of external data for ZK proof
102
+ * Uses Solidity-compatible ABI encoding and keccak256 hash
103
+ *
104
+ * @param extData - External data to hash
105
+ * @returns Hash as bigint (mod FIELD_SIZE)
106
+ */
107
+ export function getExtDataHash(extData: ExtDataInput): bigint {
108
+ // Use ethers ABI encoder for Solidity-compatible encoding
109
+ const { ethers } = require('ethers');
110
+ const abi = ethers.AbiCoder.defaultAbiCoder();
111
+
112
+ // Encode the struct exactly as Solidity would
113
+ const encodedData = abi.encode(
114
+ ['tuple(address,int256,address,uint256,bytes,bytes)'],
115
+ [[
116
+ extData.recipient,
117
+ extData.extAmount,
118
+ extData.relayer,
119
+ extData.fee,
120
+ extData.encryptedOutput1,
121
+ extData.encryptedOutput2,
122
+ ]]
123
+ );
124
+
125
+ const hash = ethers.keccak256(encodedData);
126
+ return BigInt(hash) % FIELD_SIZE;
127
+ }
128
+
129
+ /**
130
+ * Shuffle an array using Fisher-Yates algorithm
131
+ * Used to randomize input/output order for privacy
132
+ *
133
+ * @param array - Array to shuffle
134
+ * @returns Shuffled array (mutates and returns same array)
135
+ */
136
+ export function shuffle<T>(array: T[]): T[] {
137
+ let currentIndex = array.length;
138
+ let randomIndex: number;
139
+
140
+ // While there remain elements to shuffle...
141
+ while (currentIndex !== 0) {
142
+ // Pick a remaining element...
143
+ randomIndex = Math.floor(Math.random() * currentIndex);
144
+ currentIndex--;
145
+
146
+ // And swap it with the current element
147
+ [array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
148
+ }
149
+
150
+ return array;
151
+ }
package/src/utxo.ts ADDED
@@ -0,0 +1,119 @@
1
+ /**
2
+ * UTXO (Unspent Transaction Output) class for Veil SDK
3
+ * Represents a private balance entry that can be spent
4
+ */
5
+
6
+ import { Keypair } from './keypair.js';
7
+ import { poseidonHash, toBuffer, randomBN } from './utils.js';
8
+
9
+ export interface UtxoParams {
10
+ amount?: bigint | number | string;
11
+ keypair?: Keypair;
12
+ blinding?: bigint;
13
+ index?: number;
14
+ }
15
+
16
+ /**
17
+ * UTXO class - represents a private balance entry
18
+ *
19
+ * A UTXO contains:
20
+ * - amount: The value in wei
21
+ * - blinding: Random value for privacy
22
+ * - keypair: The owner's keypair
23
+ * - index: Position in the merkle tree (needed for nullifier calculation)
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * // Decrypt an encrypted output
28
+ * const utxo = Utxo.decrypt(encryptedOutput, keypair);
29
+ * utxo.index = 5; // Set the merkle tree index
30
+ *
31
+ * // Check if spent
32
+ * const nullifier = utxo.getNullifier();
33
+ * const isSpent = await pool.isSpent(nullifier);
34
+ * ```
35
+ */
36
+ export class Utxo {
37
+ public amount: bigint;
38
+ public blinding: bigint;
39
+ public keypair: Keypair;
40
+ public index?: number;
41
+ private _commitment?: bigint;
42
+ private _nullifier?: bigint;
43
+
44
+ /**
45
+ * Create a new UTXO
46
+ * @param params - UTXO parameters
47
+ */
48
+ constructor(params: UtxoParams = {}) {
49
+ const { amount = 0, keypair = new Keypair(), blinding = randomBN(), index } = params;
50
+ this.amount = BigInt(amount);
51
+ this.blinding = BigInt(blinding);
52
+ this.keypair = keypair;
53
+ this.index = index;
54
+ }
55
+
56
+ /**
57
+ * Get the commitment for this UTXO
58
+ * commitment = poseidonHash([amount, pubkey, blinding])
59
+ * @returns Commitment as bigint
60
+ */
61
+ getCommitment(): bigint {
62
+ if (!this._commitment) {
63
+ this._commitment = poseidonHash([this.amount, this.keypair.pubkey, this.blinding]);
64
+ }
65
+ return this._commitment;
66
+ }
67
+
68
+ /**
69
+ * Get the nullifier for this UTXO
70
+ * Requires index and private key to be set
71
+ * nullifier = poseidonHash([commitment, index, signature])
72
+ * @returns Nullifier as bigint
73
+ */
74
+ getNullifier(): bigint {
75
+ if (!this._nullifier) {
76
+ if (
77
+ this.amount > 0n &&
78
+ (this.index === undefined || !this.keypair.privkey)
79
+ ) {
80
+ throw new Error('Cannot compute nullifier without UTXO index or private key');
81
+ }
82
+ const signature = this.keypair.privkey
83
+ ? this.keypair.sign(this.getCommitment(), this.index || 0)
84
+ : 0n;
85
+ this._nullifier = poseidonHash([this.getCommitment(), this.index || 0, signature]);
86
+ }
87
+ return this._nullifier;
88
+ }
89
+
90
+ /**
91
+ * Encrypt UTXO data using the keypair
92
+ * @returns Encrypted data as 0x-prefixed hex string
93
+ */
94
+ encrypt(): string {
95
+ const bytes = Buffer.concat([
96
+ toBuffer(this.amount, 31),
97
+ toBuffer(this.blinding, 31),
98
+ ]);
99
+ return this.keypair.encrypt(bytes);
100
+ }
101
+
102
+ /**
103
+ * Decrypt an encrypted output to create a UTXO
104
+ * Only succeeds if the keypair owns this UTXO
105
+ *
106
+ * @param data - Encrypted output as hex string
107
+ * @param keypair - Keypair to decrypt with
108
+ * @returns Decrypted UTXO
109
+ * @throws If decryption fails (wrong keypair)
110
+ */
111
+ static decrypt(data: string, keypair: Keypair): Utxo {
112
+ const buf = keypair.decrypt(data);
113
+ return new Utxo({
114
+ amount: BigInt('0x' + buf.slice(0, 31).toString('hex')),
115
+ blinding: BigInt('0x' + buf.slice(31, 62).toString('hex')),
116
+ keypair,
117
+ });
118
+ }
119
+ }