@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/README.md +446 -0
- package/dist/cli/index.cjs +6431 -0
- package/dist/index.cjs +1912 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2099 -0
- package/dist/index.d.ts +2099 -0
- package/dist/index.js +1840 -0
- package/dist/index.js.map +1 -0
- package/keys/transaction16.wasm +0 -0
- package/keys/transaction16.zkey +0 -0
- package/keys/transaction2.wasm +0 -0
- package/keys/transaction2.zkey +0 -0
- package/package.json +70 -0
- package/src/abi.ts +631 -0
- package/src/addresses.ts +53 -0
- package/src/balance.ts +266 -0
- package/src/cli/commands/balance.ts +118 -0
- package/src/cli/commands/deposit.ts +115 -0
- package/src/cli/commands/init.ts +147 -0
- package/src/cli/commands/keypair.ts +31 -0
- package/src/cli/commands/private-balance.ts +68 -0
- package/src/cli/commands/queue-balance.ts +58 -0
- package/src/cli/commands/register.ts +119 -0
- package/src/cli/commands/transfer.ts +137 -0
- package/src/cli/commands/withdraw.ts +79 -0
- package/src/cli/config.ts +58 -0
- package/src/cli/errors.ts +114 -0
- package/src/cli/index.ts +52 -0
- package/src/cli/wallet.ts +228 -0
- package/src/deposit.ts +183 -0
- package/src/index.ts +160 -0
- package/src/keypair.ts +170 -0
- package/src/merkle.ts +71 -0
- package/src/prover.ts +176 -0
- package/src/relay.ts +216 -0
- package/src/transaction.ts +260 -0
- package/src/transfer.ts +462 -0
- package/src/types.ts +306 -0
- package/src/utils.ts +151 -0
- package/src/utxo.ts +119 -0
- package/src/withdraw.ts +299 -0
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
|
+
}
|