@veilo/sdk-core 0.3.3 → 0.4.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 +42 -3
- package/SHIELD_INTEGRATION.md +143 -0
- package/config.d.ts +2 -0
- package/config.js +4 -0
- package/dist/cjs/client.d.ts +5 -5
- package/dist/cjs/client.js +79 -42
- package/dist/cjs/compactNote.d.ts +107 -0
- package/dist/cjs/compactNote.js +167 -0
- package/dist/cjs/events.d.ts +1 -1
- package/dist/cjs/events.js +2 -2
- package/dist/cjs/idl/privacy_pool.d.ts +5 -0
- package/dist/cjs/idl/privacy_pool.js +15218 -0
- package/dist/cjs/index.d.ts +16 -13
- package/dist/cjs/index.js +40 -30
- package/dist/cjs/merkle.d.ts +13 -0
- package/dist/cjs/merkle.js +31 -8
- package/dist/cjs/program.d.ts +11 -0
- package/dist/cjs/program.js +26 -3
- package/dist/cjs/proof.d.ts +1 -1
- package/dist/cjs/proof.js +37 -37
- package/dist/cjs/prover.d.ts +1 -1
- package/dist/cjs/prover.js +2 -2
- package/dist/cjs/random.d.ts +16 -0
- package/dist/cjs/random.js +28 -0
- package/dist/cjs/relayer.d.ts +27 -4
- package/dist/cjs/relayer.js +16 -5
- package/dist/cjs/shield/alt.d.ts +87 -0
- package/dist/cjs/shield/alt.js +194 -0
- package/dist/cjs/shield/computeBudget.d.ts +61 -0
- package/dist/cjs/shield/computeBudget.js +64 -0
- package/dist/cjs/shield/errors.d.ts +58 -0
- package/dist/cjs/shield/errors.js +121 -0
- package/dist/cjs/shield/finalize.d.ts +45 -0
- package/dist/cjs/shield/finalize.js +119 -0
- package/dist/cjs/shield/index.d.ts +35 -0
- package/dist/cjs/shield/index.js +68 -0
- package/dist/cjs/shield/ix.d.ts +54 -0
- package/dist/cjs/shield/ix.js +119 -0
- package/dist/cjs/shield/owner.d.ts +36 -0
- package/dist/cjs/shield/owner.js +126 -0
- package/dist/cjs/shield/ports.d.ts +43 -0
- package/dist/cjs/shield/ports.js +153 -0
- package/dist/cjs/shield/preflight.d.ts +30 -0
- package/dist/cjs/shield/preflight.js +154 -0
- package/dist/cjs/shield/shield.d.ts +68 -0
- package/dist/cjs/shield/shield.js +499 -0
- package/dist/cjs/shield/types.d.ts +202 -0
- package/dist/cjs/shield/types.js +2 -0
- package/dist/cjs/utxo.d.ts +20 -0
- package/dist/cjs/utxo.js +41 -28
- package/dist/esm/client.d.ts +407 -0
- package/dist/esm/client.js +9 -5
- package/dist/esm/compactNote.d.ts +107 -0
- package/dist/esm/compactNote.js +156 -0
- package/dist/esm/config.d.ts +82 -0
- package/dist/esm/events.d.ts +77 -0
- package/dist/esm/events.js +1 -1
- package/dist/esm/idl/privacy_pool.d.ts +5 -0
- package/dist/esm/idl/privacy_pool.js +15216 -0
- package/dist/esm/index.d.ts +16 -0
- package/dist/esm/index.js +18 -11
- package/dist/esm/merkle.d.ts +77 -0
- package/dist/esm/merkle.js +23 -1
- package/dist/esm/package.json +1 -0
- package/dist/esm/poseidon.d.ts +29 -0
- package/dist/esm/program.d.ts +37 -0
- package/dist/esm/program.js +23 -1
- package/dist/esm/proof.d.ts +183 -0
- package/dist/esm/proof.js +2 -2
- package/dist/esm/prover.d.ts +54 -0
- package/dist/esm/prover.js +1 -1
- package/dist/esm/random.d.ts +16 -0
- package/dist/esm/random.js +21 -0
- package/dist/esm/relayer.d.ts +318 -0
- package/dist/esm/relayer.js +16 -5
- package/dist/esm/retry.d.ts +32 -0
- package/dist/esm/shield/alt.d.ts +87 -0
- package/dist/esm/shield/alt.js +186 -0
- package/dist/esm/shield/computeBudget.d.ts +61 -0
- package/dist/esm/shield/computeBudget.js +61 -0
- package/dist/esm/shield/errors.d.ts +58 -0
- package/dist/esm/shield/errors.js +115 -0
- package/dist/esm/shield/finalize.d.ts +45 -0
- package/dist/esm/shield/finalize.js +83 -0
- package/dist/esm/shield/index.d.ts +35 -0
- package/dist/esm/shield/index.js +32 -0
- package/dist/esm/shield/ix.d.ts +54 -0
- package/dist/esm/shield/ix.js +82 -0
- package/dist/esm/shield/owner.d.ts +36 -0
- package/dist/esm/shield/owner.js +122 -0
- package/dist/esm/shield/ports.d.ts +43 -0
- package/dist/esm/shield/ports.js +147 -0
- package/dist/esm/shield/preflight.d.ts +30 -0
- package/dist/esm/shield/preflight.js +151 -0
- package/dist/esm/shield/shield.d.ts +68 -0
- package/dist/esm/shield/shield.js +492 -0
- package/dist/esm/shield/types.d.ts +202 -0
- package/dist/esm/shield/types.js +1 -0
- package/dist/esm/utxo.d.ts +235 -0
- package/dist/esm/utxo.js +18 -8
- package/dist/src/client.d.ts +407 -0
- package/dist/src/client.js +951 -0
- package/dist/src/compactNote.d.ts +107 -0
- package/dist/src/compactNote.js +167 -0
- package/dist/src/config.d.ts +82 -0
- package/dist/src/config.js +57 -0
- package/dist/src/events.d.ts +77 -0
- package/dist/src/events.js +167 -0
- package/dist/src/idl/privacy_pool.d.ts +5 -0
- package/dist/src/idl/privacy_pool.js +15218 -0
- package/dist/src/index.d.ts +16 -0
- package/dist/src/index.js +67 -0
- package/dist/src/merkle.d.ts +77 -0
- package/dist/src/merkle.js +156 -0
- package/dist/src/poseidon.d.ts +29 -0
- package/dist/src/poseidon.js +100 -0
- package/dist/src/program.d.ts +37 -0
- package/dist/src/program.js +61 -0
- package/dist/src/proof.d.ts +183 -0
- package/dist/src/proof.js +292 -0
- package/dist/src/prover.d.ts +54 -0
- package/dist/src/prover.js +112 -0
- package/dist/src/random.d.ts +16 -0
- package/dist/src/random.js +28 -0
- package/dist/src/relayer.d.ts +318 -0
- package/dist/src/relayer.js +257 -0
- package/dist/src/retry.d.ts +32 -0
- package/dist/src/retry.js +75 -0
- package/dist/src/shield/alt.d.ts +87 -0
- package/dist/src/shield/alt.js +194 -0
- package/dist/src/shield/computeBudget.d.ts +61 -0
- package/dist/src/shield/computeBudget.js +64 -0
- package/dist/src/shield/errors.d.ts +58 -0
- package/dist/src/shield/errors.js +121 -0
- package/dist/src/shield/finalize.d.ts +45 -0
- package/dist/src/shield/finalize.js +119 -0
- package/dist/src/shield/index.d.ts +35 -0
- package/dist/src/shield/index.js +68 -0
- package/dist/src/shield/ix.d.ts +54 -0
- package/dist/src/shield/ix.js +119 -0
- package/dist/src/shield/owner.d.ts +36 -0
- package/dist/src/shield/owner.js +126 -0
- package/dist/src/shield/ports.d.ts +43 -0
- package/dist/src/shield/ports.js +153 -0
- package/dist/src/shield/preflight.d.ts +30 -0
- package/dist/src/shield/preflight.js +154 -0
- package/dist/src/shield/shield.d.ts +68 -0
- package/dist/src/shield/shield.js +499 -0
- package/dist/src/shield/types.d.ts +202 -0
- package/dist/src/shield/types.js +2 -0
- package/dist/src/utxo.d.ts +235 -0
- package/dist/src/utxo.js +407 -0
- package/dist/tests/compact-note.test.d.ts +1 -0
- package/dist/tests/compact-note.test.js +173 -0
- package/dist/tests/config.test.d.ts +1 -0
- package/dist/tests/config.test.js +102 -0
- package/dist/tests/edge-cases.test.d.ts +1 -0
- package/dist/tests/edge-cases.test.js +220 -0
- package/dist/tests/encryption.test.d.ts +1 -0
- package/dist/tests/encryption.test.js +215 -0
- package/dist/tests/events.test.d.ts +1 -0
- package/dist/tests/events.test.js +78 -0
- package/dist/tests/multi-tree.test.d.ts +1 -0
- package/dist/tests/multi-tree.test.js +405 -0
- package/dist/tests/pda.test.d.ts +1 -0
- package/dist/tests/pda.test.js +229 -0
- package/dist/tests/poseidon-builder-parity.test.d.ts +1 -0
- package/dist/tests/poseidon-builder-parity.test.js +72 -0
- package/dist/tests/poseidon.test.d.ts +1 -0
- package/dist/tests/poseidon.test.js +142 -0
- package/dist/tests/proof.test.d.ts +1 -0
- package/dist/tests/proof.test.js +296 -0
- package/dist/tests/relayer.test.d.ts +1 -0
- package/dist/tests/relayer.test.js +271 -0
- package/dist/tests/sdk.integration.test.d.ts +1 -0
- package/dist/tests/sdk.integration.test.js +330 -0
- package/dist/tests/shield-owner.test.d.ts +1 -0
- package/dist/tests/shield-owner.test.js +89 -0
- package/dist/tests/shield-preflight.test.d.ts +1 -0
- package/dist/tests/shield-preflight.test.js +87 -0
- package/dist/tests/shield-realproof.test.d.ts +1 -0
- package/dist/tests/shield-realproof.test.js +272 -0
- package/dist/tests/shield.test.d.ts +1 -0
- package/dist/tests/shield.test.js +403 -0
- package/dist/tests/utxo.test.d.ts +1 -0
- package/dist/tests/utxo.test.js +140 -0
- package/package.json +69 -11
- package/poseidon.d.ts +2 -0
- package/poseidon.js +4 -0
- package/proof.d.ts +2 -0
- package/proof.js +4 -0
- package/prover.d.ts +2 -0
- package/prover.js +4 -0
- package/shield.d.ts +2 -0
- package/shield.js +4 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import * as anchor from "@coral-xyz/anchor";
|
|
2
|
+
import type { Program, Idl } from "@coral-xyz/anchor";
|
|
3
|
+
import { PublicKey, Transaction, Keypair } from "@solana/web3.js";
|
|
4
|
+
import { TransactionProofStruct, TransactionProofBuilder, ExtData } from "./proof.js";
|
|
5
|
+
import { SerializedUTXO, InputUTXO } from "./utxo.js";
|
|
6
|
+
import { MerkleTree } from "./merkle.js";
|
|
7
|
+
import { PrivacyConfigAccount } from "./config.js";
|
|
8
|
+
/**
|
|
9
|
+
* Get pool PDAs for a given mint address.
|
|
10
|
+
* Note: All seeds use "v3" suffix.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getPoolPdas(programId: PublicKey, mintAddress: PublicKey): {
|
|
13
|
+
config: anchor.web3.PublicKey;
|
|
14
|
+
vault: anchor.web3.PublicKey;
|
|
15
|
+
nullifiers: anchor.web3.PublicKey;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Get the note tree PDA for a specific tree ID.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getNoteTreePda(programId: PublicKey, mintAddress: PublicKey, treeId: number): PublicKey;
|
|
21
|
+
/**
|
|
22
|
+
* Get the global config PDA.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getGlobalConfigPda(programId: PublicKey): PublicKey;
|
|
25
|
+
/**
|
|
26
|
+
* Get the nullifier marker PDA for a specific nullifier.
|
|
27
|
+
* NOTE: Nullifier markers are global (no tree_id) to prevent cross-tree double-spend.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getNullifierMarkerPda(programId: PublicKey, mintAddress: PublicKey, nullifier: Uint8Array): PublicKey;
|
|
30
|
+
/**
|
|
31
|
+
* Fetch and decode the on-chain PrivacyConfig account for a pool.
|
|
32
|
+
*/
|
|
33
|
+
export declare function fetchPoolConfig<T extends Idl>(program: Program<T>, mintAddress: PublicKey): Promise<PrivacyConfigAccount>;
|
|
34
|
+
/**
|
|
35
|
+
* Check whether a nullifier has been spent on-chain.
|
|
36
|
+
* Returns true if the nullifier marker PDA exists (funds already spent).
|
|
37
|
+
*/
|
|
38
|
+
export declare function checkNullifierSpent<T extends Idl>(program: Program<T>, mintAddress: PublicKey, nullifier: Uint8Array): Promise<boolean>;
|
|
39
|
+
/** On-chain tree utilization info */
|
|
40
|
+
export type TreeInfo = {
|
|
41
|
+
treeId: number;
|
|
42
|
+
leafCount: number;
|
|
43
|
+
capacity: number;
|
|
44
|
+
utilizationPercent: number;
|
|
45
|
+
remainingCapacity: number;
|
|
46
|
+
isFull: boolean;
|
|
47
|
+
treePDA: PublicKey;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Fetch utilization info for a specific tree.
|
|
51
|
+
* Returns null if the tree does not exist yet.
|
|
52
|
+
*/
|
|
53
|
+
export declare function getTreeInfo<T extends Idl>(program: Program<T>, mintAddress: PublicKey, treeId: number): Promise<TreeInfo | null>;
|
|
54
|
+
/**
|
|
55
|
+
* Fetch utilization info for all trees in a pool.
|
|
56
|
+
*/
|
|
57
|
+
export declare function getAllTreeInfo<T extends Idl>(program: Program<T>, mintAddress: PublicKey): Promise<TreeInfo[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Find the best tree to deposit into.
|
|
60
|
+
*
|
|
61
|
+
* Strategy (matches veilo-dapp):
|
|
62
|
+
* 1. Discard full trees.
|
|
63
|
+
* 2. Prefer the LOWEST tree ID with remaining capacity ≥ minCapacity (default 0.01%).
|
|
64
|
+
* 3. Ensures trees fill sequentially: 0 → 1 → 2 → …
|
|
65
|
+
* 4. Fallback to the tree with the most remaining capacity.
|
|
66
|
+
*/
|
|
67
|
+
export declare function getBestTreeForDeposit<T extends Idl>(program: Program<T>, mintAddress: PublicKey, minCapacity?: number): Promise<TreeInfo | null>;
|
|
68
|
+
/**
|
|
69
|
+
* Convert a Solana/Anchor transaction error into a user-friendly message.
|
|
70
|
+
* Maps privacy_pool program error codes (6000–6060) to readable strings.
|
|
71
|
+
* Mirrors relayer-server/src/controllers/transaction.helpers.ts `parseOnChainError`.
|
|
72
|
+
*/
|
|
73
|
+
export declare function parseOnChainError(error: any): string;
|
|
74
|
+
/**
|
|
75
|
+
* Initialize the global config (one-time setup).
|
|
76
|
+
*/
|
|
77
|
+
export declare function initializeGlobalConfig<T extends Idl>(params: {
|
|
78
|
+
program: Program<T>;
|
|
79
|
+
admin: anchor.Wallet;
|
|
80
|
+
payer: Keypair;
|
|
81
|
+
}): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Initialize a privacy pool with the new parameters.
|
|
84
|
+
* Creates the first merkle tree (tree ID 0).
|
|
85
|
+
*/
|
|
86
|
+
export declare function initializePool<T extends Idl>(params: {
|
|
87
|
+
program: Program<T>;
|
|
88
|
+
admin: anchor.Wallet;
|
|
89
|
+
payer: Keypair;
|
|
90
|
+
feeBps: number;
|
|
91
|
+
mintAddress: PublicKey;
|
|
92
|
+
minDepositAmount?: bigint;
|
|
93
|
+
maxDepositAmount?: bigint;
|
|
94
|
+
minWithdrawAmount?: bigint;
|
|
95
|
+
maxWithdrawAmount?: bigint;
|
|
96
|
+
}): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Update pool configuration.
|
|
99
|
+
*/
|
|
100
|
+
export declare function updatePoolConfig<T extends Idl>(params: {
|
|
101
|
+
program: Program<T>;
|
|
102
|
+
admin: anchor.Wallet;
|
|
103
|
+
mintAddress: PublicKey;
|
|
104
|
+
minDepositAmount?: bigint;
|
|
105
|
+
maxDepositAmount?: bigint;
|
|
106
|
+
minWithdrawAmount?: bigint;
|
|
107
|
+
maxWithdrawAmount?: bigint;
|
|
108
|
+
feeBps?: number;
|
|
109
|
+
feeErrorMarginBps?: number;
|
|
110
|
+
minWithdrawalFee?: bigint;
|
|
111
|
+
minSwapFee?: bigint;
|
|
112
|
+
swapFeeBps?: number;
|
|
113
|
+
}): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Add a new Merkle tree to the pool.
|
|
116
|
+
* The treeId must equal the current num_trees value in the pool config.
|
|
117
|
+
*/
|
|
118
|
+
export declare function addMerkleTree<T extends Idl>(params: {
|
|
119
|
+
program: Program<T>;
|
|
120
|
+
relayer: Keypair;
|
|
121
|
+
mintAddress: PublicKey;
|
|
122
|
+
treeId: number;
|
|
123
|
+
}): Promise<void>;
|
|
124
|
+
/**
|
|
125
|
+
* Get the pool configuration account.
|
|
126
|
+
*/
|
|
127
|
+
export declare function getPoolConfig<T extends Idl>(program: Program<T>, mintAddress: PublicKey): Promise<PrivacyConfigAccount>;
|
|
128
|
+
/**
|
|
129
|
+
* Add a relayer to the pool.
|
|
130
|
+
*/
|
|
131
|
+
export declare function addRelayer<T extends Idl>(params: {
|
|
132
|
+
program: Program<T>;
|
|
133
|
+
admin: anchor.Wallet;
|
|
134
|
+
mintAddress: PublicKey;
|
|
135
|
+
newRelayer: PublicKey;
|
|
136
|
+
}): Promise<void>;
|
|
137
|
+
/**
|
|
138
|
+
* Set the paused state of a pool.
|
|
139
|
+
*/
|
|
140
|
+
export declare function setPaused<T extends Idl>(params: {
|
|
141
|
+
program: Program<T>;
|
|
142
|
+
admin: anchor.Wallet;
|
|
143
|
+
mintAddress: PublicKey;
|
|
144
|
+
paused: boolean;
|
|
145
|
+
}): Promise<void>;
|
|
146
|
+
/**
|
|
147
|
+
* Execute a transaction on the privacy pool.
|
|
148
|
+
* This is the low-level function that calls the on-chain `transact` instruction.
|
|
149
|
+
*
|
|
150
|
+
* @param publicAmount - Positive for deposits, negative for withdrawals, 0 for transfers
|
|
151
|
+
* @param inputTreeId - The tree ID where input UTXOs are located
|
|
152
|
+
* @param outputTreeId - The tree ID where output commitments will be inserted
|
|
153
|
+
*/
|
|
154
|
+
export declare function transact<T extends Idl>(params: {
|
|
155
|
+
program: Program<T>;
|
|
156
|
+
relayer: Keypair;
|
|
157
|
+
recipient: PublicKey;
|
|
158
|
+
mintAddress: PublicKey;
|
|
159
|
+
root: Uint8Array;
|
|
160
|
+
inputTreeId: number;
|
|
161
|
+
outputTreeId: number;
|
|
162
|
+
publicAmount: bigint;
|
|
163
|
+
inputNullifiers: [Uint8Array, Uint8Array];
|
|
164
|
+
outputCommitments: [Uint8Array, Uint8Array];
|
|
165
|
+
extData: ExtData;
|
|
166
|
+
proof: TransactionProofStruct;
|
|
167
|
+
/** Unix timestamp after which the tx is invalid. Defaults to 0 (no deadline). */
|
|
168
|
+
deadline?: bigint;
|
|
169
|
+
vaultTokenAccount?: PublicKey;
|
|
170
|
+
userTokenAccount?: PublicKey;
|
|
171
|
+
recipientTokenAccount?: PublicKey;
|
|
172
|
+
relayerTokenAccount?: PublicKey;
|
|
173
|
+
}): Promise<string>;
|
|
174
|
+
/**
|
|
175
|
+
* Result of a deposit operation, produced by `DepositBuild.commit()` after the
|
|
176
|
+
* caller has successfully submitted the deposit transaction on-chain.
|
|
177
|
+
*/
|
|
178
|
+
export type DepositResult = {
|
|
179
|
+
/** The output UTXOs created by the deposit */
|
|
180
|
+
outputUTXOs: [SerializedUTXO, SerializedUTXO];
|
|
181
|
+
/** The leaf indices where the UTXOs were inserted */
|
|
182
|
+
leafIndices: [number, number];
|
|
183
|
+
/** The new Merkle root after insertion */
|
|
184
|
+
root: Uint8Array;
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* The return of `deposit()`. Instead of submitting the transaction itself,
|
|
188
|
+
* `deposit()` hands back an unsigned {@link Transaction} so the caller can
|
|
189
|
+
* sign it with whatever wallet they control (Solana wallet-adapter, Ledger,
|
|
190
|
+
* anchor NodeWallet, etc.) and submit it on their own terms.
|
|
191
|
+
*
|
|
192
|
+
* Usage pattern (browser / wallet-adapter):
|
|
193
|
+
*
|
|
194
|
+
* const { transaction, commit } = await deposit({ ... });
|
|
195
|
+
* const signed = await wallet.signTransaction(transaction);
|
|
196
|
+
* const sig = await connection.sendRawTransaction(signed.serialize());
|
|
197
|
+
* await connection.confirmTransaction(sig, "confirmed");
|
|
198
|
+
* const result = commit(); // updates the local tree, returns DepositResult
|
|
199
|
+
*/
|
|
200
|
+
export type DepositBuild = {
|
|
201
|
+
/**
|
|
202
|
+
* Unsigned {@link Transaction} for the deposit. `feePayer` and
|
|
203
|
+
* `recentBlockhash` are already populated, so the caller only needs to sign
|
|
204
|
+
* and send.
|
|
205
|
+
*/
|
|
206
|
+
transaction: Transaction;
|
|
207
|
+
/**
|
|
208
|
+
* Output UTXOs created by this deposit. Safe to read before submission —
|
|
209
|
+
* the commitment values are deterministic from the circuit inputs and do
|
|
210
|
+
* not depend on the on-chain result.
|
|
211
|
+
*/
|
|
212
|
+
outputUTXOs: [SerializedUTXO, SerializedUTXO];
|
|
213
|
+
/**
|
|
214
|
+
* Inserts the two output commitments into the local {@link MerkleTree} and
|
|
215
|
+
* returns the receipt. Call this **only after** the on-chain transaction
|
|
216
|
+
* has been confirmed — deferring the insert keeps the local tree
|
|
217
|
+
* consistent with chain state if the submission fails.
|
|
218
|
+
*
|
|
219
|
+
* Calling `commit()` more than once is unsupported and will corrupt the
|
|
220
|
+
* local tree.
|
|
221
|
+
*/
|
|
222
|
+
commit: () => DepositResult;
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* Build an unsigned deposit {@link Transaction} for the privacy pool.
|
|
226
|
+
*
|
|
227
|
+
* For deposits, we use zero input UTXOs (amount=0) and create output UTXOs
|
|
228
|
+
* with the deposit amount. `publicAmount` is positive. The caller is
|
|
229
|
+
* responsible for signing and submitting the returned transaction, and then
|
|
230
|
+
* calling `commit()` once it confirms.
|
|
231
|
+
*
|
|
232
|
+
* @param amount - Amount to deposit in lamports/token units
|
|
233
|
+
* @param recipientPubkey - UTXO public key that will own the deposited funds
|
|
234
|
+
* @param treeId - The tree ID to deposit into (defaults to 0)
|
|
235
|
+
*/
|
|
236
|
+
export declare function deposit<T extends Idl>(params: {
|
|
237
|
+
program: Program<T>;
|
|
238
|
+
/**
|
|
239
|
+
* Account that will pay for and sign the deposit transaction. Only
|
|
240
|
+
* `publicKey` is read — `deposit()` never touches a secret key. A browser
|
|
241
|
+
* wallet-adapter wallet, a Node `anchor.Wallet`, or a raw `Keypair` are all
|
|
242
|
+
* structurally acceptable.
|
|
243
|
+
*/
|
|
244
|
+
depositor: {
|
|
245
|
+
publicKey: PublicKey;
|
|
246
|
+
};
|
|
247
|
+
amount: bigint;
|
|
248
|
+
mintAddress: PublicKey;
|
|
249
|
+
recipientPubkey: bigint;
|
|
250
|
+
tree: MerkleTree;
|
|
251
|
+
proofBuilder: TransactionProofBuilder;
|
|
252
|
+
treeId?: number;
|
|
253
|
+
extData?: Partial<ExtData>;
|
|
254
|
+
}): Promise<DepositBuild>;
|
|
255
|
+
/**
|
|
256
|
+
* Result of a withdrawal operation.
|
|
257
|
+
*/
|
|
258
|
+
export type WithdrawResult = {
|
|
259
|
+
/** Change UTXO (remaining balance) */
|
|
260
|
+
changeUTXO: SerializedUTXO;
|
|
261
|
+
/** Leaf index of the change UTXO */
|
|
262
|
+
leafIndex: number;
|
|
263
|
+
/** New Merkle root after insertion */
|
|
264
|
+
root: Uint8Array;
|
|
265
|
+
};
|
|
266
|
+
/**
|
|
267
|
+
* Withdraw funds from the privacy pool.
|
|
268
|
+
*
|
|
269
|
+
* For withdrawals, we spend input UTXOs and create change output UTXOs.
|
|
270
|
+
* The publicAmount is negative (funds flowing out).
|
|
271
|
+
*
|
|
272
|
+
* @param withdrawAmount - Amount to withdraw in lamports/token units
|
|
273
|
+
* @param inputUTXOs - The UTXOs being spent
|
|
274
|
+
* @param changePubkey - UTXO public key for the change output
|
|
275
|
+
* @param inputTreeId - The tree ID where input UTXOs are located
|
|
276
|
+
* @param outputTreeId - The tree ID where change outputs will be inserted
|
|
277
|
+
*/
|
|
278
|
+
export declare function withdraw<T extends Idl>(params: {
|
|
279
|
+
program: Program<T>;
|
|
280
|
+
relayer: Keypair;
|
|
281
|
+
recipient: PublicKey;
|
|
282
|
+
mintAddress: PublicKey;
|
|
283
|
+
inputUTXOs: [InputUTXO, InputUTXO];
|
|
284
|
+
changePubkey: bigint;
|
|
285
|
+
withdrawAmount: bigint;
|
|
286
|
+
fee: bigint;
|
|
287
|
+
refund: bigint;
|
|
288
|
+
root: Uint8Array;
|
|
289
|
+
inputTreeId: number;
|
|
290
|
+
outputTreeId: number;
|
|
291
|
+
tree: MerkleTree;
|
|
292
|
+
proofBuilder: TransactionProofBuilder;
|
|
293
|
+
}): Promise<WithdrawResult>;
|
|
294
|
+
/**
|
|
295
|
+
* Result of a private transfer operation.
|
|
296
|
+
*/
|
|
297
|
+
export type TransferResult = {
|
|
298
|
+
/** Output UTXOs created by the transfer */
|
|
299
|
+
outputUTXOs: [SerializedUTXO, SerializedUTXO];
|
|
300
|
+
/** Leaf indices where the UTXOs were inserted */
|
|
301
|
+
leafIndices: [number, number];
|
|
302
|
+
/** New Merkle root after insertion */
|
|
303
|
+
root: Uint8Array;
|
|
304
|
+
};
|
|
305
|
+
/**
|
|
306
|
+
* Execute a private transfer within the pool.
|
|
307
|
+
*
|
|
308
|
+
* For transfers, the publicAmount is 0 (no funds enter or leave the pool).
|
|
309
|
+
* Input amounts must equal output amounts.
|
|
310
|
+
*
|
|
311
|
+
* @param inputUTXOs - The UTXOs being spent
|
|
312
|
+
* @param outputPubkeys - UTXO public keys for the recipients
|
|
313
|
+
* @param outputAmounts - Amounts for each output (must sum to input total)
|
|
314
|
+
* @param inputTreeId - The tree ID where input UTXOs are located
|
|
315
|
+
* @param outputTreeId - The tree ID where output commitments will be inserted
|
|
316
|
+
*/
|
|
317
|
+
export declare function privateTransfer<T extends Idl>(params: {
|
|
318
|
+
program: Program<T>;
|
|
319
|
+
relayer: Keypair;
|
|
320
|
+
mintAddress: PublicKey;
|
|
321
|
+
inputUTXOs: [InputUTXO, InputUTXO];
|
|
322
|
+
outputPubkeys: [bigint, bigint];
|
|
323
|
+
outputAmounts: [bigint, bigint];
|
|
324
|
+
root: Uint8Array;
|
|
325
|
+
inputTreeId: number;
|
|
326
|
+
outputTreeId: number;
|
|
327
|
+
tree: MerkleTree;
|
|
328
|
+
proofBuilder: TransactionProofBuilder;
|
|
329
|
+
fee?: bigint;
|
|
330
|
+
}): Promise<TransferResult>;
|
|
331
|
+
/**
|
|
332
|
+
* Update the global config.
|
|
333
|
+
* Currently takes no extra args — only admin verification.
|
|
334
|
+
*/
|
|
335
|
+
export declare function updateGlobalConfig<T extends Idl>(params: {
|
|
336
|
+
program: Program<T>;
|
|
337
|
+
admin: anchor.Wallet;
|
|
338
|
+
}): Promise<void>;
|
|
339
|
+
/**
|
|
340
|
+
* Get the swap executor PDA.
|
|
341
|
+
* Seeds: ["swap_executor", source_mint, dest_mint, input_nullifier_0, relayer]
|
|
342
|
+
*/
|
|
343
|
+
export declare function getSwapExecutorPda(programId: PublicKey, sourceMint: PublicKey, destMint: PublicKey, inputNullifier0: Uint8Array, relayer: PublicKey): PublicKey;
|
|
344
|
+
/**
|
|
345
|
+
* Build the `fund_native_source` instruction that pre-funds the swap executor
|
|
346
|
+
* with native SOL from the source vault. Only for native SOL source pools.
|
|
347
|
+
*
|
|
348
|
+
* This instruction MUST be the first instruction in the same atomic transaction
|
|
349
|
+
* as `transactSwap`. Call `transactSwap` directly — it handles this internally.
|
|
350
|
+
*/
|
|
351
|
+
export declare function fundNativeSource<T extends Idl>(params: {
|
|
352
|
+
program: Program<T>;
|
|
353
|
+
relayer: Keypair;
|
|
354
|
+
sourceMint: PublicKey;
|
|
355
|
+
destMint: PublicKey;
|
|
356
|
+
inputNullifier0: Uint8Array;
|
|
357
|
+
swapAmount: bigint;
|
|
358
|
+
}): Promise<anchor.web3.TransactionInstruction>;
|
|
359
|
+
/**
|
|
360
|
+
* Swap proof struct (same encoding as TransactionProofStruct).
|
|
361
|
+
*/
|
|
362
|
+
export type SwapProofStruct = TransactionProofStruct;
|
|
363
|
+
/**
|
|
364
|
+
* Swap params committed to in the ZK proof.
|
|
365
|
+
*/
|
|
366
|
+
export type SwapParams = {
|
|
367
|
+
minAmountOut: bigint;
|
|
368
|
+
deadline: bigint;
|
|
369
|
+
sourceMint: PublicKey;
|
|
370
|
+
destMint: PublicKey;
|
|
371
|
+
destAmount: bigint;
|
|
372
|
+
/** SHA-256 of raw swap instruction bytes; use all-zeros for CPMM/AMM swaps. */
|
|
373
|
+
swapDataHash: Uint8Array;
|
|
374
|
+
};
|
|
375
|
+
/**
|
|
376
|
+
* Execute an atomic cross-pool private swap.
|
|
377
|
+
*
|
|
378
|
+
* For native SOL source pools (`sourceMint === NATIVE_SOL_MINT`), this function
|
|
379
|
+
* automatically prepends the `fund_native_source` instruction in the same
|
|
380
|
+
* transaction so the on-chain atomicity requirement is always satisfied.
|
|
381
|
+
*
|
|
382
|
+
* @param swapProgram - Jupiter/Raydium CPMM/AMM program ID
|
|
383
|
+
* @param swapData - Raw DEX instruction bytes
|
|
384
|
+
*/
|
|
385
|
+
export declare function transactSwap<T extends Idl>(params: {
|
|
386
|
+
program: Program<T>;
|
|
387
|
+
relayer: Keypair;
|
|
388
|
+
sourceMint: PublicKey;
|
|
389
|
+
destMint: PublicKey;
|
|
390
|
+
sourceRoot: Uint8Array;
|
|
391
|
+
sourceTreeId: number;
|
|
392
|
+
destTreeId: number;
|
|
393
|
+
inputNullifiers: [Uint8Array, Uint8Array];
|
|
394
|
+
outputCommitments: [Uint8Array, Uint8Array];
|
|
395
|
+
proof: SwapProofStruct;
|
|
396
|
+
swapParams: SwapParams;
|
|
397
|
+
swapAmount: bigint;
|
|
398
|
+
swapData: Buffer;
|
|
399
|
+
extData: ExtData;
|
|
400
|
+
sourceVaultTokenAccount: PublicKey;
|
|
401
|
+
sourceMintAccount: PublicKey;
|
|
402
|
+
destVaultTokenAccount: PublicKey;
|
|
403
|
+
destMintAccount: PublicKey;
|
|
404
|
+
relayerTokenAccount: PublicKey;
|
|
405
|
+
swapProgram: PublicKey;
|
|
406
|
+
jupiterEventAuthority: PublicKey;
|
|
407
|
+
}): Promise<string>;
|
package/dist/esm/client.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as anchor from "@coral-xyz/anchor";
|
|
2
|
+
// Pulled off the namespace rather than named-imported. @coral-xyz/anchor is CJS
|
|
3
|
+
// with no `exports` map, and Node ESM's named-export detection cannot see `BN`
|
|
4
|
+
// (it is re-exported from bn.js), so `import { BN }` throws at runtime under ESM.
|
|
5
|
+
const { BN } = anchor;
|
|
2
6
|
import { PublicKey, SystemProgram, Transaction, SYSVAR_INSTRUCTIONS_PUBKEY, ComputeBudgetProgram, } from "@solana/web3.js";
|
|
3
7
|
import { TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, NATIVE_MINT, getAssociatedTokenAddress, } from "@solana/spl-token";
|
|
4
|
-
import { computeExtDataHash, prepareTransactionInputs, encodeSnarkjsProofToTransactionProof, } from "./proof";
|
|
5
|
-
import { createUTXO, createOwnedZeroUTXO, generateKeypair, } from "./utxo";
|
|
6
|
-
import { NATIVE_SOL_MINT } from "./config";
|
|
7
|
-
import { withRpcRetry } from "./retry";
|
|
8
|
+
import { computeExtDataHash, prepareTransactionInputs, encodeSnarkjsProofToTransactionProof, } from "./proof.js";
|
|
9
|
+
import { createUTXO, createOwnedZeroUTXO, generateKeypair, } from "./utxo.js";
|
|
10
|
+
import { NATIVE_SOL_MINT } from "./config.js";
|
|
11
|
+
import { withRpcRetry } from "./retry.js";
|
|
8
12
|
// -----------------------------------------------------------------------------
|
|
9
13
|
// PDA Helpers
|
|
10
14
|
// -----------------------------------------------------------------------------
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compact note ciphers — the 80-byte payload carried inside the on-chain
|
|
3
|
+
* `transact` instruction (`note_ciphers: Option<NoteCiphers>`) and re-emitted in
|
|
4
|
+
* `CommitmentEvent`.
|
|
5
|
+
*
|
|
6
|
+
* This is what makes a note recoverable from chain data alone: a recipient who
|
|
7
|
+
* was never told about a deposit can still find it by scanning `view_tag` and
|
|
8
|
+
* decrypting the blob. Without it, note delivery depends on someone calling the
|
|
9
|
+
* relayer, which a third-party integrator cannot be trusted to do.
|
|
10
|
+
*
|
|
11
|
+
* ## Byte-compatibility is load-bearing
|
|
12
|
+
*
|
|
13
|
+
* Four sibling implementations already exist (relayer-server, extension ×2,
|
|
14
|
+
* wallet-app). This is the canonical one, and it MUST agree with them byte for
|
|
15
|
+
* byte in both directions:
|
|
16
|
+
*
|
|
17
|
+
* - disagree on encrypt → new notes are invisible to every existing client
|
|
18
|
+
* - disagree on decrypt → the entire historical note corpus becomes unreadable
|
|
19
|
+
*
|
|
20
|
+
* Three details are easy to get wrong and are individually fatal:
|
|
21
|
+
*
|
|
22
|
+
* 1. The KDF is `nacl.hash(x).slice(0, 32)` — **truncated SHA-512**, despite the
|
|
23
|
+
* sibling helper being named `sha256ForNotes`. See `kdfSHA512_256` in utxo.ts.
|
|
24
|
+
* 2. The ephemeral key published on-chain is **Ed25519**; it is converted to
|
|
25
|
+
* X25519 only for the Diffie-Hellman. Scanners convert it back.
|
|
26
|
+
* 3. `viewTag` is the first byte of the hash of the **raw shared secret**, not of
|
|
27
|
+
* the derived key. A wrong view tag makes every note invisible to the scan
|
|
28
|
+
* loop, which rejects ~255/256 candidates on this byte alone.
|
|
29
|
+
*
|
|
30
|
+
* The ECDH primitives are imported from `utxo.ts` rather than reimplemented — a
|
|
31
|
+
* fifth independent Edwards→Montgomery implementation is exactly how the current
|
|
32
|
+
* drift risk between the four siblings arose.
|
|
33
|
+
*
|
|
34
|
+
* Verified against the siblings by `tests/compact-note.test.ts` (golden vectors)
|
|
35
|
+
* and `scripts/verify-compact-cipher-parity.mjs` (differential).
|
|
36
|
+
*/
|
|
37
|
+
/** Size of the on-chain compact blob: nonce[24] || secretbox(40-byte plaintext)[56]. */
|
|
38
|
+
export declare const COMPACT_BLOB_LENGTH = 80;
|
|
39
|
+
export type CompactNoteCipher = {
|
|
40
|
+
/** Ed25519 ephemeral public key, published on-chain. */
|
|
41
|
+
ephemeralPublicKey: Uint8Array;
|
|
42
|
+
/** 80 bytes: nonce[24] || ciphertext[56]. */
|
|
43
|
+
compactBlob: Uint8Array;
|
|
44
|
+
/** 1-byte scan filter. */
|
|
45
|
+
viewTag: number;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Test-only determinism hook. NOT part of the public contract — it exists so
|
|
49
|
+
* golden vectors can pin exact bytes against the sibling implementations, which
|
|
50
|
+
* is the only way to prove byte-compatibility rather than assume it.
|
|
51
|
+
*
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
export type CompactNoteDeterminism = {
|
|
55
|
+
ephemeralSeed: Uint8Array;
|
|
56
|
+
nonce: Uint8Array;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Encrypt a note's secret material to a recipient's Solana wallet key.
|
|
60
|
+
*
|
|
61
|
+
* The recipient does not need to be the signer of the transaction, or to have
|
|
62
|
+
* any prior relationship with the sender — this is what makes shielding to an
|
|
63
|
+
* arbitrary owner possible.
|
|
64
|
+
*
|
|
65
|
+
* @param recipientWalletPubkey 32-byte Ed25519 Solana wallet public key
|
|
66
|
+
* @param blinding 32-byte big-endian blinding factor
|
|
67
|
+
* @param amount note amount in base units
|
|
68
|
+
*/
|
|
69
|
+
export declare function createCompactNoteCipher(recipientWalletPubkey: Uint8Array, blinding: Uint8Array, amount: bigint,
|
|
70
|
+
/** @internal */ __det?: CompactNoteDeterminism): CompactNoteCipher;
|
|
71
|
+
/**
|
|
72
|
+
* Recover a note's blinding and amount from an on-chain compact cipher.
|
|
73
|
+
*
|
|
74
|
+
* Returns `null` when the blob was not encrypted to this key — the expected
|
|
75
|
+
* outcome for the overwhelming majority of candidates during a scan, so callers
|
|
76
|
+
* should filter on `viewTag` first (see {@link matchesViewTag}) and only attempt
|
|
77
|
+
* decryption on the ~1/256 that survive.
|
|
78
|
+
*
|
|
79
|
+
* @param walletSecretKey 32-byte Ed25519 seed or 64-byte secret key
|
|
80
|
+
*/
|
|
81
|
+
export declare function decryptCompactNoteCipher(walletSecretKey: Uint8Array, ephemeralPublicKey: Uint8Array, compactBlob: Uint8Array, precomputed?: {
|
|
82
|
+
sharedSecret?: Uint8Array;
|
|
83
|
+
x25519Private?: Uint8Array;
|
|
84
|
+
}): {
|
|
85
|
+
blinding: Uint8Array;
|
|
86
|
+
amount: bigint;
|
|
87
|
+
} | null;
|
|
88
|
+
/**
|
|
89
|
+
* Cheap scan filter: does this on-chain cipher plausibly belong to us?
|
|
90
|
+
*
|
|
91
|
+
* Rejects ~255/256 candidates for the cost of one scalar multiplication and one
|
|
92
|
+
* hash, versus a full secretbox open. Hoist `x25519Private` across a scan loop
|
|
93
|
+
* with {@link toX25519Private}.
|
|
94
|
+
*/
|
|
95
|
+
export declare function matchesViewTag(walletSecretKey: Uint8Array, ephemeralPublicKey: Uint8Array, viewTag: number, precomputed?: {
|
|
96
|
+
x25519Private?: Uint8Array;
|
|
97
|
+
}): {
|
|
98
|
+
matches: boolean;
|
|
99
|
+
sharedSecret: Uint8Array;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Convert an Ed25519 wallet secret key to its X25519 scalar once, for reuse
|
|
103
|
+
* across a scan loop.
|
|
104
|
+
*/
|
|
105
|
+
export declare function toX25519Private(walletSecretKey: Uint8Array): Uint8Array;
|
|
106
|
+
/** The all-zero cipher the program treats as "no note here". */
|
|
107
|
+
export declare function emptyNoteCipher(): CompactNoteCipher;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import nacl from "tweetnacl";
|
|
2
|
+
import { ed25519PublicKeyToX25519, ed25519SeedToX25519Private, kdfSHA512_256, } from "./utxo.js";
|
|
3
|
+
import { randomBytes } from "./random.js";
|
|
4
|
+
/**
|
|
5
|
+
* Compact note ciphers — the 80-byte payload carried inside the on-chain
|
|
6
|
+
* `transact` instruction (`note_ciphers: Option<NoteCiphers>`) and re-emitted in
|
|
7
|
+
* `CommitmentEvent`.
|
|
8
|
+
*
|
|
9
|
+
* This is what makes a note recoverable from chain data alone: a recipient who
|
|
10
|
+
* was never told about a deposit can still find it by scanning `view_tag` and
|
|
11
|
+
* decrypting the blob. Without it, note delivery depends on someone calling the
|
|
12
|
+
* relayer, which a third-party integrator cannot be trusted to do.
|
|
13
|
+
*
|
|
14
|
+
* ## Byte-compatibility is load-bearing
|
|
15
|
+
*
|
|
16
|
+
* Four sibling implementations already exist (relayer-server, extension ×2,
|
|
17
|
+
* wallet-app). This is the canonical one, and it MUST agree with them byte for
|
|
18
|
+
* byte in both directions:
|
|
19
|
+
*
|
|
20
|
+
* - disagree on encrypt → new notes are invisible to every existing client
|
|
21
|
+
* - disagree on decrypt → the entire historical note corpus becomes unreadable
|
|
22
|
+
*
|
|
23
|
+
* Three details are easy to get wrong and are individually fatal:
|
|
24
|
+
*
|
|
25
|
+
* 1. The KDF is `nacl.hash(x).slice(0, 32)` — **truncated SHA-512**, despite the
|
|
26
|
+
* sibling helper being named `sha256ForNotes`. See `kdfSHA512_256` in utxo.ts.
|
|
27
|
+
* 2. The ephemeral key published on-chain is **Ed25519**; it is converted to
|
|
28
|
+
* X25519 only for the Diffie-Hellman. Scanners convert it back.
|
|
29
|
+
* 3. `viewTag` is the first byte of the hash of the **raw shared secret**, not of
|
|
30
|
+
* the derived key. A wrong view tag makes every note invisible to the scan
|
|
31
|
+
* loop, which rejects ~255/256 candidates on this byte alone.
|
|
32
|
+
*
|
|
33
|
+
* The ECDH primitives are imported from `utxo.ts` rather than reimplemented — a
|
|
34
|
+
* fifth independent Edwards→Montgomery implementation is exactly how the current
|
|
35
|
+
* drift risk between the four siblings arose.
|
|
36
|
+
*
|
|
37
|
+
* Verified against the siblings by `tests/compact-note.test.ts` (golden vectors)
|
|
38
|
+
* and `scripts/verify-compact-cipher-parity.mjs` (differential).
|
|
39
|
+
*/
|
|
40
|
+
/** Size of the on-chain compact blob: nonce[24] || secretbox(40-byte plaintext)[56]. */
|
|
41
|
+
export const COMPACT_BLOB_LENGTH = 80;
|
|
42
|
+
/** Plaintext layout: blinding[32] || amount as little-endian u64[8]. */
|
|
43
|
+
const COMPACT_PLAINTEXT_LENGTH = 40;
|
|
44
|
+
const NONCE_LENGTH = 24;
|
|
45
|
+
/**
|
|
46
|
+
* Encrypt a note's secret material to a recipient's Solana wallet key.
|
|
47
|
+
*
|
|
48
|
+
* The recipient does not need to be the signer of the transaction, or to have
|
|
49
|
+
* any prior relationship with the sender — this is what makes shielding to an
|
|
50
|
+
* arbitrary owner possible.
|
|
51
|
+
*
|
|
52
|
+
* @param recipientWalletPubkey 32-byte Ed25519 Solana wallet public key
|
|
53
|
+
* @param blinding 32-byte big-endian blinding factor
|
|
54
|
+
* @param amount note amount in base units
|
|
55
|
+
*/
|
|
56
|
+
export function createCompactNoteCipher(recipientWalletPubkey, blinding, amount,
|
|
57
|
+
/** @internal */ __det) {
|
|
58
|
+
if (recipientWalletPubkey.length !== 32) {
|
|
59
|
+
throw new Error(`recipientWalletPubkey must be 32 bytes, got ${recipientWalletPubkey.length}`);
|
|
60
|
+
}
|
|
61
|
+
if (blinding.length < 32) {
|
|
62
|
+
throw new Error(`blinding must be at least 32 bytes, got ${blinding.length}`);
|
|
63
|
+
}
|
|
64
|
+
if (amount < 0n || amount > 0xffffffffffffffffn) {
|
|
65
|
+
throw new Error(`amount ${amount} does not fit in a u64`);
|
|
66
|
+
}
|
|
67
|
+
// Ed25519 ephemeral keypair. `nacl.sign.keyPair.fromSeed(seed)` produces the
|
|
68
|
+
// same public key as web3.js `Keypair.generate()` for the same seed, which is
|
|
69
|
+
// what the sibling implementations use — but without pulling @solana/web3.js
|
|
70
|
+
// into the crypto path.
|
|
71
|
+
const ephemeralSeed = __det?.ephemeralSeed ?? randomBytes(32);
|
|
72
|
+
const ephemeralPublicKey = nacl.sign.keyPair.fromSeed(ephemeralSeed).publicKey;
|
|
73
|
+
const sharedSecret = deriveSharedSecret(ephemeralSeed, recipientWalletPubkey);
|
|
74
|
+
const key = kdfSHA512_256(sharedSecret);
|
|
75
|
+
const plaintext = new Uint8Array(COMPACT_PLAINTEXT_LENGTH);
|
|
76
|
+
plaintext.set(blinding.slice(0, 32), 0);
|
|
77
|
+
let amt = amount;
|
|
78
|
+
for (let i = 0; i < 8; i++) {
|
|
79
|
+
plaintext[32 + i] = Number(amt & 0xffn);
|
|
80
|
+
amt >>= 8n;
|
|
81
|
+
}
|
|
82
|
+
const nonce = __det?.nonce ?? nacl.randomBytes(NONCE_LENGTH);
|
|
83
|
+
const encrypted = nacl.secretbox(plaintext, nonce, key);
|
|
84
|
+
const compactBlob = new Uint8Array(COMPACT_BLOB_LENGTH);
|
|
85
|
+
compactBlob.set(nonce, 0);
|
|
86
|
+
compactBlob.set(encrypted, NONCE_LENGTH);
|
|
87
|
+
// First byte of the hash of the RAW shared secret — not of `key`.
|
|
88
|
+
const viewTag = nacl.hash(sharedSecret)[0];
|
|
89
|
+
return { ephemeralPublicKey, compactBlob, viewTag };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Recover a note's blinding and amount from an on-chain compact cipher.
|
|
93
|
+
*
|
|
94
|
+
* Returns `null` when the blob was not encrypted to this key — the expected
|
|
95
|
+
* outcome for the overwhelming majority of candidates during a scan, so callers
|
|
96
|
+
* should filter on `viewTag` first (see {@link matchesViewTag}) and only attempt
|
|
97
|
+
* decryption on the ~1/256 that survive.
|
|
98
|
+
*
|
|
99
|
+
* @param walletSecretKey 32-byte Ed25519 seed or 64-byte secret key
|
|
100
|
+
*/
|
|
101
|
+
export function decryptCompactNoteCipher(walletSecretKey, ephemeralPublicKey, compactBlob, precomputed) {
|
|
102
|
+
if (compactBlob.length !== COMPACT_BLOB_LENGTH)
|
|
103
|
+
return null;
|
|
104
|
+
const sharedSecret = precomputed?.sharedSecret ??
|
|
105
|
+
(precomputed?.x25519Private
|
|
106
|
+
? nacl.scalarMult(precomputed.x25519Private, ed25519PublicKeyToX25519(ephemeralPublicKey))
|
|
107
|
+
: deriveSharedSecret(walletSecretKey, ephemeralPublicKey));
|
|
108
|
+
const key = kdfSHA512_256(sharedSecret);
|
|
109
|
+
const nonce = compactBlob.slice(0, NONCE_LENGTH);
|
|
110
|
+
const encrypted = compactBlob.slice(NONCE_LENGTH);
|
|
111
|
+
const plaintext = nacl.secretbox.open(encrypted, nonce, key);
|
|
112
|
+
if (!plaintext)
|
|
113
|
+
return null;
|
|
114
|
+
const blinding = new Uint8Array(plaintext.slice(0, 32));
|
|
115
|
+
let amount = 0n;
|
|
116
|
+
for (let i = 7; i >= 0; i--) {
|
|
117
|
+
amount = (amount << 8n) | BigInt(plaintext[32 + i]);
|
|
118
|
+
}
|
|
119
|
+
return { blinding, amount };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Cheap scan filter: does this on-chain cipher plausibly belong to us?
|
|
123
|
+
*
|
|
124
|
+
* Rejects ~255/256 candidates for the cost of one scalar multiplication and one
|
|
125
|
+
* hash, versus a full secretbox open. Hoist `x25519Private` across a scan loop
|
|
126
|
+
* with {@link toX25519Private}.
|
|
127
|
+
*/
|
|
128
|
+
export function matchesViewTag(walletSecretKey, ephemeralPublicKey, viewTag, precomputed) {
|
|
129
|
+
const sharedSecret = precomputed?.x25519Private
|
|
130
|
+
? nacl.scalarMult(precomputed.x25519Private, ed25519PublicKeyToX25519(ephemeralPublicKey))
|
|
131
|
+
: deriveSharedSecret(walletSecretKey, ephemeralPublicKey);
|
|
132
|
+
return { matches: nacl.hash(sharedSecret)[0] === viewTag, sharedSecret };
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Convert an Ed25519 wallet secret key to its X25519 scalar once, for reuse
|
|
136
|
+
* across a scan loop.
|
|
137
|
+
*/
|
|
138
|
+
export function toX25519Private(walletSecretKey) {
|
|
139
|
+
return ed25519SeedToX25519Private(seedOf(walletSecretKey));
|
|
140
|
+
}
|
|
141
|
+
/** The all-zero cipher the program treats as "no note here". */
|
|
142
|
+
export function emptyNoteCipher() {
|
|
143
|
+
return {
|
|
144
|
+
ephemeralPublicKey: new Uint8Array(32),
|
|
145
|
+
compactBlob: new Uint8Array(COMPACT_BLOB_LENGTH),
|
|
146
|
+
viewTag: 0,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// -- internal -----------------------------------------------------------------
|
|
150
|
+
/** Accept either a 32-byte Ed25519 seed or a 64-byte secret key, as the siblings do. */
|
|
151
|
+
function seedOf(secretKey) {
|
|
152
|
+
return secretKey.length === 64 ? secretKey.slice(0, 32) : secretKey;
|
|
153
|
+
}
|
|
154
|
+
function deriveSharedSecret(mySecretKey, theirEd25519PublicKey) {
|
|
155
|
+
return nacl.scalarMult(ed25519SeedToX25519Private(seedOf(mySecretKey)), ed25519PublicKeyToX25519(theirEd25519PublicKey));
|
|
156
|
+
}
|