@auditable/privacy-pool-zk-sdk 0.7.0 → 0.8.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/assets/main.wasm +0 -0
- package/assets/main_final.zkey +0 -0
- package/dist/cli.js +740 -569
- package/dist/cli.js.map +1 -1
- package/dist/derived-escrow-key.d.ts +10 -0
- package/dist/domain-separators.d.ts +10 -0
- package/dist/index.d.ts +10 -7
- package/dist/index.mjs +598 -375
- package/dist/index.mjs.map +1 -1
- package/dist/kyt-flow.d.ts +0 -3
- package/dist/kyt-passage.d.ts +8 -11
- package/dist/output-note-encryption.d.ts +1 -0
- package/dist/sdk.d.ts +23 -17
- package/dist/stealth-sign-message.d.ts +15 -2
- package/dist/stealth-signature.d.ts +13 -5
- package/dist/transaction-audit.d.ts +5 -0
- package/dist/types.d.ts +2 -0
- package/dist/withdrawal-transaction-input.d.ts +34 -10
- package/package.json +2 -2
- package/pkg/client_sdk_wasm.d.ts +23 -15
- package/pkg/client_sdk_wasm.js +90 -48
- package/pkg/client_sdk_wasm_bg.wasm +0 -0
- package/pkg/client_sdk_wasm_bg.wasm.d.ts +6 -5
- package/dist/kyt-onboarding.d.ts +0 -36
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as snarkjs from 'snarkjs';
|
|
2
|
-
import { StrKey,
|
|
2
|
+
import { StrKey, Address, Keypair, rpc, TransactionBuilder, Operation, xdr, nativeToScVal, authorizeEntry } from '@stellar/stellar-sdk';
|
|
3
3
|
import { buildBabyjub, buildMimc7, buildPoseidon } from 'circomlibjs';
|
|
4
4
|
|
|
5
5
|
/* @ts-self-types="./client_sdk_wasm.d.ts" */
|
|
@@ -40,35 +40,69 @@ function buildWithdrawMerkleWitness(coin_json, state_json) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* Calculate nullifier hash from nullifier decimal
|
|
43
|
+
* Calculate owner-bound nullifier hash from nullifier and spend-scalar decimal strings.
|
|
44
44
|
* Returns hex string (0x...)
|
|
45
45
|
* @param {string} nullifier_decimal
|
|
46
|
+
* @param {string} priv_key_scalar_decimal
|
|
46
47
|
* @returns {string}
|
|
47
48
|
*/
|
|
48
|
-
function calculateNullifierHash(nullifier_decimal) {
|
|
49
|
-
let
|
|
50
|
-
let
|
|
49
|
+
function calculateNullifierHash(nullifier_decimal, priv_key_scalar_decimal) {
|
|
50
|
+
let deferred4_0;
|
|
51
|
+
let deferred4_1;
|
|
51
52
|
try {
|
|
52
53
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
53
54
|
const ptr0 = passStringToWasm0(nullifier_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
54
55
|
const len0 = WASM_VECTOR_LEN;
|
|
55
|
-
|
|
56
|
+
const ptr1 = passStringToWasm0(priv_key_scalar_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
57
|
+
const len1 = WASM_VECTOR_LEN;
|
|
58
|
+
wasm.calculateNullifierHash(retptr, ptr0, len0, ptr1, len1);
|
|
56
59
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
57
60
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
58
61
|
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
59
62
|
var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
|
|
60
|
-
var
|
|
61
|
-
var
|
|
63
|
+
var ptr3 = r0;
|
|
64
|
+
var len3 = r1;
|
|
62
65
|
if (r3) {
|
|
63
|
-
|
|
66
|
+
ptr3 = 0; len3 = 0;
|
|
64
67
|
throw takeObject(r2);
|
|
65
68
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return getStringFromWasm0(
|
|
69
|
+
deferred4_0 = ptr3;
|
|
70
|
+
deferred4_1 = len3;
|
|
71
|
+
return getStringFromWasm0(ptr3, len3);
|
|
72
|
+
} finally {
|
|
73
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
74
|
+
wasm.__wbindgen_export4(deferred4_0, deferred4_1, 1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* `R = Poseidon255(2)(hi, lo)`; `scalar` is the raw Poseidon hash when it is
|
|
80
|
+
* already in `(0, BabyJub subgroup order)`. Out-of-range hashes fail so the
|
|
81
|
+
* caller can rejection-sample a new nonce.
|
|
82
|
+
* @param {string} nonce_decimal
|
|
83
|
+
* @param {string} recipient_hi_decimal
|
|
84
|
+
* @param {string} recipient_lo_decimal
|
|
85
|
+
* @returns {any}
|
|
86
|
+
*/
|
|
87
|
+
function derivedEscrowKey$1(nonce_decimal, recipient_hi_decimal, recipient_lo_decimal) {
|
|
88
|
+
try {
|
|
89
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
90
|
+
const ptr0 = passStringToWasm0(nonce_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
91
|
+
const len0 = WASM_VECTOR_LEN;
|
|
92
|
+
const ptr1 = passStringToWasm0(recipient_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
93
|
+
const len1 = WASM_VECTOR_LEN;
|
|
94
|
+
const ptr2 = passStringToWasm0(recipient_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
95
|
+
const len2 = WASM_VECTOR_LEN;
|
|
96
|
+
wasm.derivedEscrowKey(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
|
|
97
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
98
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
99
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
100
|
+
if (r2) {
|
|
101
|
+
throw takeObject(r1);
|
|
102
|
+
}
|
|
103
|
+
return takeObject(r0);
|
|
69
104
|
} finally {
|
|
70
105
|
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
71
|
-
wasm.__wbindgen_export4(deferred3_0, deferred3_1, 1);
|
|
72
106
|
}
|
|
73
107
|
}
|
|
74
108
|
|
|
@@ -149,26 +183,28 @@ function ecdhSharedKey(priv_hex, pub_x_hex, pub_y_hex) {
|
|
|
149
183
|
}
|
|
150
184
|
|
|
151
185
|
/**
|
|
152
|
-
* Generate a new coin with random nullifier, secret, and
|
|
153
|
-
* `
|
|
186
|
+
* Generate a new coin with random nullifier, secret, and owner public-key field elements.
|
|
187
|
+
* `amount_decimal` is an arbitrary-precision decimal field element (never JS `number`).
|
|
154
188
|
* `asset_hi_decimal` / `asset_lo_decimal` are decimal Fr strings for the Stellar asset contract id (two limbs).
|
|
155
189
|
* Returns JSON: { coin: { value, nullifier, secret, commitment, asset_hi, asset_lo }, commitment_hex, precommitement_hex }
|
|
156
|
-
* @param {
|
|
190
|
+
* @param {string} amount_decimal
|
|
157
191
|
* @param {string} asset_hi_decimal
|
|
158
192
|
* @param {string} asset_lo_decimal
|
|
159
193
|
* @param {string} application_id_decimal
|
|
160
194
|
* @returns {any}
|
|
161
195
|
*/
|
|
162
|
-
function generateCoin(
|
|
196
|
+
function generateCoin(amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
|
|
163
197
|
try {
|
|
164
198
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
165
|
-
const ptr0 = passStringToWasm0(
|
|
199
|
+
const ptr0 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
166
200
|
const len0 = WASM_VECTOR_LEN;
|
|
167
|
-
const ptr1 = passStringToWasm0(
|
|
201
|
+
const ptr1 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
168
202
|
const len1 = WASM_VECTOR_LEN;
|
|
169
|
-
const ptr2 = passStringToWasm0(
|
|
203
|
+
const ptr2 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
170
204
|
const len2 = WASM_VECTOR_LEN;
|
|
171
|
-
|
|
205
|
+
const ptr3 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
206
|
+
const len3 = WASM_VECTOR_LEN;
|
|
207
|
+
wasm.generateCoin(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3);
|
|
172
208
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
173
209
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
174
210
|
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
@@ -182,32 +218,34 @@ function generateCoin(amount, asset_hi_decimal, asset_lo_decimal, application_id
|
|
|
182
218
|
}
|
|
183
219
|
|
|
184
220
|
/**
|
|
185
|
-
* `Poseidon₁(scalar)` secret +
|
|
221
|
+
* `Poseidon₁(scalar)` secret + recipient owner public key (hex), matching an aligned deposit witness.
|
|
186
222
|
* @param {string} scalar_hex
|
|
187
|
-
* @param {string}
|
|
188
|
-
* @param {string}
|
|
189
|
-
* @param {
|
|
223
|
+
* @param {string} owner_x_hex
|
|
224
|
+
* @param {string} owner_y_hex
|
|
225
|
+
* @param {string} amount_decimal
|
|
190
226
|
* @param {string} asset_hi_decimal
|
|
191
227
|
* @param {string} asset_lo_decimal
|
|
192
228
|
* @param {string} application_id_decimal
|
|
193
229
|
* @returns {any}
|
|
194
230
|
*/
|
|
195
|
-
function
|
|
231
|
+
function generateCoinForDepositWithOwnerPubHex(scalar_hex, owner_x_hex, owner_y_hex, amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
|
|
196
232
|
try {
|
|
197
233
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
198
234
|
const ptr0 = passStringToWasm0(scalar_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
199
235
|
const len0 = WASM_VECTOR_LEN;
|
|
200
|
-
const ptr1 = passStringToWasm0(
|
|
236
|
+
const ptr1 = passStringToWasm0(owner_x_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
201
237
|
const len1 = WASM_VECTOR_LEN;
|
|
202
|
-
const ptr2 = passStringToWasm0(
|
|
238
|
+
const ptr2 = passStringToWasm0(owner_y_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
203
239
|
const len2 = WASM_VECTOR_LEN;
|
|
204
|
-
const ptr3 = passStringToWasm0(
|
|
240
|
+
const ptr3 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
205
241
|
const len3 = WASM_VECTOR_LEN;
|
|
206
|
-
const ptr4 = passStringToWasm0(
|
|
242
|
+
const ptr4 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
207
243
|
const len4 = WASM_VECTOR_LEN;
|
|
208
|
-
const ptr5 = passStringToWasm0(
|
|
244
|
+
const ptr5 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
209
245
|
const len5 = WASM_VECTOR_LEN;
|
|
210
|
-
|
|
246
|
+
const ptr6 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
247
|
+
const len6 = WASM_VECTOR_LEN;
|
|
248
|
+
wasm.generateCoinForDepositWithOwnerPubHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5, ptr6, len6);
|
|
211
249
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
212
250
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
213
251
|
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
@@ -223,24 +261,26 @@ function generateCoinForDepositWithSharedHex(scalar_hex, shared_x_hex, shared_y_
|
|
|
223
261
|
/**
|
|
224
262
|
* `secret` in coin = `Poseidon255(1)(scalar)` per `deposit.circom`; scalar is 32-byte hex (64 chars, optional `0x`).
|
|
225
263
|
* @param {string} scalar_hex
|
|
226
|
-
* @param {
|
|
264
|
+
* @param {string} amount_decimal
|
|
227
265
|
* @param {string} asset_hi_decimal
|
|
228
266
|
* @param {string} asset_lo_decimal
|
|
229
267
|
* @param {string} application_id_decimal
|
|
230
268
|
* @returns {any}
|
|
231
269
|
*/
|
|
232
|
-
function generateCoinFromDepositEphemeralScalarHex(scalar_hex,
|
|
270
|
+
function generateCoinFromDepositEphemeralScalarHex(scalar_hex, amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
|
|
233
271
|
try {
|
|
234
272
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
235
273
|
const ptr0 = passStringToWasm0(scalar_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
236
274
|
const len0 = WASM_VECTOR_LEN;
|
|
237
|
-
const ptr1 = passStringToWasm0(
|
|
275
|
+
const ptr1 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
238
276
|
const len1 = WASM_VECTOR_LEN;
|
|
239
|
-
const ptr2 = passStringToWasm0(
|
|
277
|
+
const ptr2 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
240
278
|
const len2 = WASM_VECTOR_LEN;
|
|
241
|
-
const ptr3 = passStringToWasm0(
|
|
279
|
+
const ptr3 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
242
280
|
const len3 = WASM_VECTOR_LEN;
|
|
243
|
-
|
|
281
|
+
const ptr4 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
282
|
+
const len4 = WASM_VECTOR_LEN;
|
|
283
|
+
wasm.generateCoinFromDepositEphemeralScalarHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4);
|
|
244
284
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
245
285
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
246
286
|
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
@@ -254,29 +294,31 @@ function generateCoinFromDepositEphemeralScalarHex(scalar_hex, amount, asset_hi_
|
|
|
254
294
|
}
|
|
255
295
|
|
|
256
296
|
/**
|
|
257
|
-
* Same as `generateCoin`, but commitment uses the given
|
|
258
|
-
* @param {string}
|
|
259
|
-
* @param {string}
|
|
260
|
-
* @param {
|
|
297
|
+
* Same as `generateCoin`, but commitment uses the given owner public key (64-char hex coords).
|
|
298
|
+
* @param {string} owner_x_hex
|
|
299
|
+
* @param {string} owner_y_hex
|
|
300
|
+
* @param {string} amount_decimal
|
|
261
301
|
* @param {string} asset_hi_decimal
|
|
262
302
|
* @param {string} asset_lo_decimal
|
|
263
303
|
* @param {string} application_id_decimal
|
|
264
304
|
* @returns {any}
|
|
265
305
|
*/
|
|
266
|
-
function
|
|
306
|
+
function generateCoinWithOwnerPubHex(owner_x_hex, owner_y_hex, amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
|
|
267
307
|
try {
|
|
268
308
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
269
|
-
const ptr0 = passStringToWasm0(
|
|
309
|
+
const ptr0 = passStringToWasm0(owner_x_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
270
310
|
const len0 = WASM_VECTOR_LEN;
|
|
271
|
-
const ptr1 = passStringToWasm0(
|
|
311
|
+
const ptr1 = passStringToWasm0(owner_y_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
272
312
|
const len1 = WASM_VECTOR_LEN;
|
|
273
|
-
const ptr2 = passStringToWasm0(
|
|
313
|
+
const ptr2 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
274
314
|
const len2 = WASM_VECTOR_LEN;
|
|
275
|
-
const ptr3 = passStringToWasm0(
|
|
315
|
+
const ptr3 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
276
316
|
const len3 = WASM_VECTOR_LEN;
|
|
277
|
-
const ptr4 = passStringToWasm0(
|
|
317
|
+
const ptr4 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
278
318
|
const len4 = WASM_VECTOR_LEN;
|
|
279
|
-
|
|
319
|
+
const ptr5 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
320
|
+
const len5 = WASM_VECTOR_LEN;
|
|
321
|
+
wasm.generateCoinWithOwnerPubHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5);
|
|
280
322
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
281
323
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
282
324
|
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
@@ -689,13 +731,14 @@ var wasmBindings = /*#__PURE__*/Object.freeze({
|
|
|
689
731
|
buildWithdrawMerkleWitness: buildWithdrawMerkleWitness,
|
|
690
732
|
calculateNullifierHash: calculateNullifierHash,
|
|
691
733
|
default: __wbg_init,
|
|
734
|
+
derivedEscrowKey: derivedEscrowKey$1,
|
|
692
735
|
ecdhEphemeralPublicKey: ecdhEphemeralPublicKey,
|
|
693
736
|
ecdhEphemeralPublicKeyFromScalarHex: ecdhEphemeralPublicKeyFromScalarHex,
|
|
694
737
|
ecdhSharedKey: ecdhSharedKey,
|
|
695
738
|
generateCoin: generateCoin,
|
|
696
|
-
|
|
739
|
+
generateCoinForDepositWithOwnerPubHex: generateCoinForDepositWithOwnerPubHex,
|
|
697
740
|
generateCoinFromDepositEphemeralScalarHex: generateCoinFromDepositEphemeralScalarHex,
|
|
698
|
-
|
|
741
|
+
generateCoinWithOwnerPubHex: generateCoinWithOwnerPubHex,
|
|
699
742
|
initSync: initSync,
|
|
700
743
|
proofToHex: proofToHex,
|
|
701
744
|
publicToHex: publicToHex
|
|
@@ -1531,7 +1574,7 @@ function bytesToHex$1(bytes) {
|
|
|
1531
1574
|
}
|
|
1532
1575
|
return s;
|
|
1533
1576
|
}
|
|
1534
|
-
function concatBytes(a, b) {
|
|
1577
|
+
function concatBytes$1(a, b) {
|
|
1535
1578
|
const out = new Uint8Array(a.length + b.length);
|
|
1536
1579
|
out.set(a, 0);
|
|
1537
1580
|
out.set(b, a.length);
|
|
@@ -1547,7 +1590,7 @@ function encodeStealthAddress(decoded) {
|
|
|
1547
1590
|
if (xb.length !== yb.length) {
|
|
1548
1591
|
throw new Error('stealth-address: x and y must encode the same number of bytes for a reversible address');
|
|
1549
1592
|
}
|
|
1550
|
-
const payload = concatBytes(xb, yb);
|
|
1593
|
+
const payload = concatBytes$1(xb, yb);
|
|
1551
1594
|
const words = bech32.toWords(payload);
|
|
1552
1595
|
return bech32.encode(STEALTH_ADDRESS_HRP, words, BECH32_STEALTH_LIMIT);
|
|
1553
1596
|
}
|
|
@@ -1572,12 +1615,165 @@ function decodeStealthAddress(address) {
|
|
|
1572
1615
|
|
|
1573
1616
|
/** Default nonce when deriving the wallet sign-in message. */
|
|
1574
1617
|
const DEFAULT_STEALTH_SIGN_NONCE = 'main address';
|
|
1618
|
+
/** Domain tag mixed into the spend-scalar digest (H6). */
|
|
1619
|
+
const SPEND_SCALAR_DOMAIN_TAG = 'privacy-pool-spend-scalar-v1';
|
|
1620
|
+
/** Schema version bound into the spend-scalar message and digest. */
|
|
1621
|
+
const OWNER_BOUND_NOTE_SCHEMA_VERSION = 1;
|
|
1622
|
+
function resolveSpendScalarSchemaVersion(domain) {
|
|
1623
|
+
return domain.schemaVersion ?? OWNER_BOUND_NOTE_SCHEMA_VERSION;
|
|
1624
|
+
}
|
|
1575
1625
|
/**
|
|
1576
|
-
* Plaintext for Stellar wallet message signing (
|
|
1626
|
+
* Plaintext for Stellar wallet message signing (spend-key derivation).
|
|
1577
1627
|
* Sign the exact bytes of this string (UTF-8) with the stellar account key.
|
|
1628
|
+
*
|
|
1629
|
+
* The derived scalar is the owner-bound spend key for notes in this environment.
|
|
1630
|
+
*/
|
|
1631
|
+
function buildStealthAddressSignMessage(address, domain, nonce = DEFAULT_STEALTH_SIGN_NONCE) {
|
|
1632
|
+
const schemaVersion = resolveSpendScalarSchemaVersion(domain);
|
|
1633
|
+
return [
|
|
1634
|
+
'Arcane privacy layer: derive your private-note spend key.',
|
|
1635
|
+
'',
|
|
1636
|
+
`Wallet: ${address}`,
|
|
1637
|
+
`Network: ${domain.networkPassphrase}`,
|
|
1638
|
+
`Pool: ${domain.poolContract}`,
|
|
1639
|
+
`Registry: ${domain.registryContract}`,
|
|
1640
|
+
`Schema: owner-bound-note-v${schemaVersion}`,
|
|
1641
|
+
`Nonce: ${nonce}`,
|
|
1642
|
+
'',
|
|
1643
|
+
'This signature derives the key that authorizes spending your private notes in this environment.',
|
|
1644
|
+
].join('\n');
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
const DOM_PADDING = 1347436868n;
|
|
1648
|
+
const DOM_NULLIFIER = 1346917708n;
|
|
1649
|
+
const DOM_ESCROW = 1346720579n;
|
|
1650
|
+
const DOM_ENC = 1346719299n;
|
|
1651
|
+
const BABYJUB_SUBGROUP_ORDER = 2736030358979909402780800718157159386076813972158567259200215660948447373041n;
|
|
1652
|
+
/**
|
|
1653
|
+
* Map any integer into `(0, l)` so it satisfies circom `LessThan(252)` vs
|
|
1654
|
+
* `BABYJUB_SUBGROUP_ORDER` and `BabyPbk`. Values already in range are unchanged.
|
|
1655
|
+
*/
|
|
1656
|
+
function canonicalBabyJubScalarFromInteger(value) {
|
|
1657
|
+
if (value > 0n && value < BABYJUB_SUBGROUP_ORDER) {
|
|
1658
|
+
return value;
|
|
1659
|
+
}
|
|
1660
|
+
return (value % (BABYJUB_SUBGROUP_ORDER - 1n)) + 1n;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
function hexToBytes(hex) {
|
|
1664
|
+
const out = new Uint8Array(hex.length / 2);
|
|
1665
|
+
for (let i = 0; i < out.length; i++) {
|
|
1666
|
+
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
1667
|
+
}
|
|
1668
|
+
return out;
|
|
1669
|
+
}
|
|
1670
|
+
/** Base64 → bytes via `atob` (browsers; Node 16+). No Node `Buffer`. */
|
|
1671
|
+
function decodeBase64ToBytes(s) {
|
|
1672
|
+
const t = s.replace(/\s/g, '');
|
|
1673
|
+
if (typeof atob !== 'function') {
|
|
1674
|
+
throw new Error('Base64 decoding requires atob (browser or Node 16+)');
|
|
1675
|
+
}
|
|
1676
|
+
try {
|
|
1677
|
+
const binary = atob(t);
|
|
1678
|
+
const out = new Uint8Array(binary.length);
|
|
1679
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1680
|
+
out[i] = binary.charCodeAt(i);
|
|
1681
|
+
}
|
|
1682
|
+
return out;
|
|
1683
|
+
}
|
|
1684
|
+
catch {
|
|
1685
|
+
throw new Error('Invalid base64 signature');
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Ed25519 signature as **128 hex chars** (optional `0x`) or **base64** (typically 88 chars for 64 bytes, whitespace ignored).
|
|
1690
|
+
*/
|
|
1691
|
+
function parseStellarEd25519SignatureRaw(input) {
|
|
1692
|
+
const trimmed = input.trim();
|
|
1693
|
+
const no0x = trimmed.replace(/^0x/i, '');
|
|
1694
|
+
if (/^[0-9a-fA-F]+$/.test(no0x) && no0x.length === 128) {
|
|
1695
|
+
return hexToBytes(no0x.toLowerCase());
|
|
1696
|
+
}
|
|
1697
|
+
const fromB64 = decodeBase64ToBytes(trimmed);
|
|
1698
|
+
if (fromB64.length !== 64) {
|
|
1699
|
+
throw new Error(`Decoded signature must be 64 bytes (Ed25519); got ${fromB64.length} from base64`);
|
|
1700
|
+
}
|
|
1701
|
+
return fromB64;
|
|
1702
|
+
}
|
|
1703
|
+
/** SHA-256 via Web Crypto only (`crypto.subtle`) — works in browsers and Node 19+ (global `crypto`). */
|
|
1704
|
+
async function sha256$1(data) {
|
|
1705
|
+
const subtle = globalThis.crypto?.subtle;
|
|
1706
|
+
if (!subtle) {
|
|
1707
|
+
throw new Error('SHA-256 requires crypto.subtle (HTTPS or localhost in browsers, or Node.js 19+).');
|
|
1708
|
+
}
|
|
1709
|
+
const copy = new Uint8Array(data.length);
|
|
1710
|
+
copy.set(data);
|
|
1711
|
+
return new Uint8Array(await subtle.digest('SHA-256', copy));
|
|
1712
|
+
}
|
|
1713
|
+
function appendU32BE$1(parts, value) {
|
|
1714
|
+
const buf = new Uint8Array(4);
|
|
1715
|
+
new DataView(buf.buffer).setUint32(0, value >>> 0, false);
|
|
1716
|
+
parts.push(buf);
|
|
1717
|
+
}
|
|
1718
|
+
function appendUtf8Prefixed(parts, text) {
|
|
1719
|
+
const bytes = new TextEncoder().encode(text);
|
|
1720
|
+
appendU32BE$1(parts, bytes.length);
|
|
1721
|
+
parts.push(bytes);
|
|
1722
|
+
}
|
|
1723
|
+
function concatBytes(parts) {
|
|
1724
|
+
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
1725
|
+
const out = new Uint8Array(total);
|
|
1726
|
+
let offset = 0;
|
|
1727
|
+
for (const part of parts) {
|
|
1728
|
+
out.set(part, offset);
|
|
1729
|
+
offset += part.length;
|
|
1730
|
+
}
|
|
1731
|
+
return out;
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Domain-separated spend-scalar digest: SHA-256(
|
|
1735
|
+
* SPEND_SCALAR_DOMAIN_TAG || network || pool || registry || schemaVersion || signature
|
|
1736
|
+
* ).
|
|
1737
|
+
*/
|
|
1738
|
+
async function spendScalarDigestFromStellarSignature(signature, domain) {
|
|
1739
|
+
const raw = parseStellarEd25519SignatureRaw(signature);
|
|
1740
|
+
const parts = [new TextEncoder().encode(SPEND_SCALAR_DOMAIN_TAG)];
|
|
1741
|
+
appendUtf8Prefixed(parts, domain.networkPassphrase);
|
|
1742
|
+
appendUtf8Prefixed(parts, domain.poolContract);
|
|
1743
|
+
appendUtf8Prefixed(parts, domain.registryContract);
|
|
1744
|
+
appendU32BE$1(parts, resolveSpendScalarSchemaVersion(domain));
|
|
1745
|
+
parts.push(raw);
|
|
1746
|
+
return sha256$1(concatBytes(parts));
|
|
1747
|
+
}
|
|
1748
|
+
function digestToCanonicalScalar(digest) {
|
|
1749
|
+
let v = 0n;
|
|
1750
|
+
for (let i = 0; i < digest.length; i++) {
|
|
1751
|
+
v = (v << 8n) + BigInt(digest[i]);
|
|
1752
|
+
}
|
|
1753
|
+
return canonicalBabyJubScalarFromInteger(v);
|
|
1754
|
+
}
|
|
1755
|
+
function canonicalScalarHex(scalar) {
|
|
1756
|
+
return scalar.toString(16).padStart(64, '0');
|
|
1757
|
+
}
|
|
1758
|
+
async function spendScalarHexFromStellarSignature(signature, domain) {
|
|
1759
|
+
const digest = await spendScalarDigestFromStellarSignature(signature, domain);
|
|
1760
|
+
return canonicalScalarHex(digestToCanonicalScalar(digest));
|
|
1761
|
+
}
|
|
1762
|
+
/**
|
|
1763
|
+
* Stellar Ed25519 signature (hex or base64) → domain-separated SHA-256 → scalar → WASM ECDH.
|
|
1764
|
+
*/
|
|
1765
|
+
async function stealthAddressFromStellarSignature(ecdhFromScalarHex, encodeStealth, signature, domain) {
|
|
1766
|
+
const digest = await spendScalarDigestFromStellarSignature(signature, domain);
|
|
1767
|
+
const decoded = ecdhFromScalarHex(canonicalScalarHex(digestToCanonicalScalar(digest)));
|
|
1768
|
+
return encodeStealth(decoded);
|
|
1769
|
+
}
|
|
1770
|
+
/**
|
|
1771
|
+
* Domain-separated SHA-256(signature, network, pool, registry, schema) reduced into
|
|
1772
|
+
* `(0, BabyJub subgroup order)` so `privKeyScalar` matches `BabyPbk` and circom `LessThan(l)`.
|
|
1578
1773
|
*/
|
|
1579
|
-
function
|
|
1580
|
-
|
|
1774
|
+
async function privKeyScalarDecimalFromStellarSignature(signature, domain) {
|
|
1775
|
+
const digest = await spendScalarDigestFromStellarSignature(signature, domain);
|
|
1776
|
+
return digestToCanonicalScalar(digest).toString(10);
|
|
1581
1777
|
}
|
|
1582
1778
|
|
|
1583
1779
|
const TRANSACTION_N_AUDIT_SLOTS = 4;
|
|
@@ -1644,15 +1840,30 @@ function withApplicationIdOnDeposit(deposit, applicationId) {
|
|
|
1644
1840
|
function withApplicationIdOnWithdraw(withdraw, applicationId) {
|
|
1645
1841
|
return { ...withdraw, applicationId };
|
|
1646
1842
|
}
|
|
1843
|
+
/**
|
|
1844
|
+
* Prefer an explicit key, then `NOTE_AUDIT_PUBLIC_KEY_X`/`_Y` env (decimal Fr),
|
|
1845
|
+
* then the built-in demo key. Used by CLI/`demo.sh` for per-app audit pubkeys.
|
|
1846
|
+
*/
|
|
1847
|
+
function resolveAuditPublicKeyFromEnv(auditPublicKey) {
|
|
1848
|
+
if (auditPublicKey) {
|
|
1849
|
+
return auditPublicKey;
|
|
1850
|
+
}
|
|
1851
|
+
const x = process.env.NOTE_AUDIT_PUBLIC_KEY_X?.trim();
|
|
1852
|
+
const y = process.env.NOTE_AUDIT_PUBLIC_KEY_Y?.trim();
|
|
1853
|
+
if (x && y) {
|
|
1854
|
+
return [x, y];
|
|
1855
|
+
}
|
|
1856
|
+
return DEMO_AUDIT_PUBLIC_KEY;
|
|
1857
|
+
}
|
|
1647
1858
|
function resolveTransactionAuditParams(applicationId, auditPublicKey) {
|
|
1648
|
-
return buildUniformAuditParams(applicationId, auditPublicKey
|
|
1859
|
+
return buildUniformAuditParams(applicationId, resolveAuditPublicKeyFromEnv(auditPublicKey));
|
|
1649
1860
|
}
|
|
1650
1861
|
|
|
1651
|
-
/** Matches `Transaction(20, 2, 2, publicNInputs, publicNOutputs, 4,
|
|
1862
|
+
/** Matches `Transaction(20, 2, 2, publicNInputs, publicNOutputs, 4, 12, 6)` in `circuits/main.circom`. */
|
|
1652
1863
|
const TRANSACTION_TREE_DEPTH = 20;
|
|
1653
1864
|
const TRANSACTION_N_INS = 2;
|
|
1654
1865
|
const TRANSACTION_N_OUTS = 2;
|
|
1655
|
-
const NOTE_AUDIT_LEN$1 =
|
|
1866
|
+
const NOTE_AUDIT_LEN$1 = 12;
|
|
1656
1867
|
const NOTE_OUTPUT_LEN$1 = 6;
|
|
1657
1868
|
/** BN254 scalar field modulus (ark `Fr`, circom signals). */
|
|
1658
1869
|
const BN254_SCALAR_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
|
|
@@ -1722,6 +1933,12 @@ function randomFrDecimal253() {
|
|
|
1722
1933
|
const hex = generateRandomScalarHex32();
|
|
1723
1934
|
return BigInt(`0x${normalizeHex$1(hex)}`).toString(10);
|
|
1724
1935
|
}
|
|
1936
|
+
/** Spend-scalar hex in `(0, BabyJub subgroup order)` so dummy withdraws satisfy `LessThan(l)`. */
|
|
1937
|
+
function randomCanonicalBabyJubScalarHex() {
|
|
1938
|
+
const hex = generateRandomScalarHex32();
|
|
1939
|
+
const reduced = canonicalBabyJubScalarFromInteger(BigInt(`0x${normalizeHex$1(hex)}`));
|
|
1940
|
+
return reduced.toString(16).padStart(64, '0');
|
|
1941
|
+
}
|
|
1725
1942
|
function zerosTreeSiblings() {
|
|
1726
1943
|
return Array(TRANSACTION_TREE_DEPTH).fill('0');
|
|
1727
1944
|
}
|
|
@@ -1729,14 +1946,20 @@ function dummyWithdraw(wasm) {
|
|
|
1729
1946
|
const nullifier = randomFrDecimal();
|
|
1730
1947
|
const secretHex = generateRandomScalarHex32();
|
|
1731
1948
|
const secret = (BigInt(`0x${normalizeHex$1(secretHex)}`) % BN254_SCALAR_MOD).toString(10);
|
|
1732
|
-
const
|
|
1949
|
+
const scalarHex = randomCanonicalBabyJubScalarHex();
|
|
1950
|
+
const privKeyScalar = scalarHexToFrDecimal(scalarHex);
|
|
1951
|
+
const pt = wasm.ecdhEphemeralPublicKeyFromScalarHex(scalarHex);
|
|
1733
1952
|
return {
|
|
1734
1953
|
value: '0',
|
|
1735
1954
|
nullifier,
|
|
1736
1955
|
secret,
|
|
1737
1956
|
asset: ['0', '0'],
|
|
1738
1957
|
applicationId: '0',
|
|
1739
|
-
|
|
1958
|
+
ownerPub: [coordHexToDecimal(pt.x), coordHexToDecimal(pt.y)],
|
|
1959
|
+
privKeyScalar,
|
|
1960
|
+
paddingRandom: randomFrDecimal(),
|
|
1961
|
+
escrowNonce: '0',
|
|
1962
|
+
recipientStellar: ['0', '0'],
|
|
1740
1963
|
stateSiblings: zerosTreeSiblings(),
|
|
1741
1964
|
stateIndex: '0',
|
|
1742
1965
|
};
|
|
@@ -1753,6 +1976,8 @@ function dummyDeposit(wasm) {
|
|
|
1753
1976
|
asset: ['0', '0'],
|
|
1754
1977
|
applicationId: '0',
|
|
1755
1978
|
recipientPublicKeys: [coordHexToDecimal(pt.x), coordHexToDecimal(pt.y)],
|
|
1979
|
+
escrowNonce: '0',
|
|
1980
|
+
recipientStellar: ['0', '0'],
|
|
1756
1981
|
};
|
|
1757
1982
|
}
|
|
1758
1983
|
function resolveWithdraw(slot, wasm) {
|
|
@@ -1777,12 +2002,19 @@ function buildTransactionWitnessInput(publicParams, publicLegs, withdrawSlots, d
|
|
|
1777
2002
|
stateRoot: publicParams.stateRoot,
|
|
1778
2003
|
withdrawAddressHi: publicParams.withdrawAddressHi,
|
|
1779
2004
|
withdrawAddressLo: publicParams.withdrawAddressLo,
|
|
1780
|
-
|
|
2005
|
+
escrowRecipientHi: publicParams.escrowRecipientHi ?? '0',
|
|
2006
|
+
escrowRecipientLo: publicParams.escrowRecipientLo ?? '0',
|
|
2007
|
+
sweepOutputOwnerPubX: publicParams.sweepOutputOwnerPubX ?? '0',
|
|
2008
|
+
sweepOutputOwnerPubY: publicParams.sweepOutputOwnerPubY ?? '0',
|
|
2009
|
+
privKeyScalars: [w0.privKeyScalar, w1.privKeyScalar],
|
|
2010
|
+
ownerPubs: [w0.ownerPub, w1.ownerPub],
|
|
2011
|
+
paddingRandoms: [w0.paddingRandom, w1.paddingRandom],
|
|
1781
2012
|
withdrawnValues: [w0.value, w1.value],
|
|
1782
2013
|
withdrawnNullifiers: [w0.nullifier, w1.nullifier],
|
|
1783
2014
|
withdrawnSecrets: [w0.secret, w1.secret],
|
|
1784
2015
|
withdrawnAssets: [w0.asset, w1.asset],
|
|
1785
|
-
|
|
2016
|
+
withdrawnEscrowNonces: [w0.escrowNonce, w1.escrowNonce],
|
|
2017
|
+
inputRecipientStellar: [w0.recipientStellar, w1.recipientStellar],
|
|
1786
2018
|
stateSiblings: [w0.stateSiblings, w1.stateSiblings],
|
|
1787
2019
|
stateIndex: [w0.stateIndex, w1.stateIndex],
|
|
1788
2020
|
depositedValues: [d0.value, d1.value],
|
|
@@ -1790,6 +2022,11 @@ function buildTransactionWitnessInput(publicParams, publicLegs, withdrawSlots, d
|
|
|
1790
2022
|
depositedAssets: [d0.asset, d1.asset],
|
|
1791
2023
|
depositedEphemeralKeyScalars: [d0.ephemeralKeyScalar, d1.ephemeralKeyScalar],
|
|
1792
2024
|
depositedRecipientPublicKeys: [d0.recipientPublicKeys, d1.recipientPublicKeys],
|
|
2025
|
+
depositedEscrowNonces: [d0.escrowNonce ?? '0', d1.escrowNonce ?? '0'],
|
|
2026
|
+
outputRecipientStellar: [
|
|
2027
|
+
d0.recipientStellar ?? ['0', '0'],
|
|
2028
|
+
d1.recipientStellar ?? ['0', '0'],
|
|
2029
|
+
],
|
|
1793
2030
|
inputApplicationIds: appIds.inputApplicationIds,
|
|
1794
2031
|
outputApplicationIds: appIds.outputApplicationIds,
|
|
1795
2032
|
auditEphemeralScalars: audit.auditEphemeralScalars,
|
|
@@ -1805,112 +2042,56 @@ function recipientPublicKeysDecimalFromStealthAddress(stealthAddress) {
|
|
|
1805
2042
|
const { x, y } = decodeStealthAddress(stealthAddress);
|
|
1806
2043
|
return [coordHexToDecimal(x), coordHexToDecimal(y)];
|
|
1807
2044
|
}
|
|
1808
|
-
/** First withdraw leg: Merkle witness +
|
|
1809
|
-
function withdrawObjectFromMerkleWitness(witness,
|
|
2045
|
+
/** First withdraw leg: Merkle witness + owner public key (hex) and matching scalar. */
|
|
2046
|
+
function withdrawObjectFromMerkleWitness(witness, ownerPubHex, applicationId, privKeyScalar) {
|
|
1810
2047
|
return {
|
|
1811
2048
|
value: witness.value,
|
|
1812
2049
|
nullifier: witness.nullifier,
|
|
1813
2050
|
secret: witness.secret,
|
|
1814
2051
|
asset: witness.withdrawnAsset,
|
|
1815
2052
|
applicationId,
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
2053
|
+
ownerPub: [coordHexToDecimal(ownerPubHex.x), coordHexToDecimal(ownerPubHex.y)],
|
|
2054
|
+
privKeyScalar,
|
|
2055
|
+
paddingRandom: randomFrDecimal(),
|
|
2056
|
+
escrowNonce: '0',
|
|
2057
|
+
recipientStellar: ['0', '0'],
|
|
1820
2058
|
stateSiblings: witness.stateSiblings,
|
|
1821
2059
|
stateIndex: witness.stateIndex,
|
|
1822
2060
|
};
|
|
1823
2061
|
}
|
|
1824
2062
|
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
}
|
|
1830
|
-
return out;
|
|
1831
|
-
}
|
|
1832
|
-
function bytesToHexLower(bytes) {
|
|
1833
|
-
let s = '';
|
|
1834
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
1835
|
-
s += bytes[i].toString(16).padStart(2, '0');
|
|
2063
|
+
/** Stroops amount as a decimal field string. Reject JS `number` so 18-decimal values cannot silently narrow. */
|
|
2064
|
+
function coinValueDecimal(amount) {
|
|
2065
|
+
if (typeof amount === 'number') {
|
|
2066
|
+
throw new TypeError('coin value must be bigint or decimal string, not number');
|
|
1836
2067
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
function decodeBase64ToBytes(s) {
|
|
1841
|
-
const t = s.replace(/\s/g, '');
|
|
1842
|
-
if (typeof atob !== 'function') {
|
|
1843
|
-
throw new Error('Base64 decoding requires atob (browser or Node 16+)');
|
|
1844
|
-
}
|
|
1845
|
-
try {
|
|
1846
|
-
const binary = atob(t);
|
|
1847
|
-
const out = new Uint8Array(binary.length);
|
|
1848
|
-
for (let i = 0; i < binary.length; i++) {
|
|
1849
|
-
out[i] = binary.charCodeAt(i);
|
|
2068
|
+
if (typeof amount === 'bigint') {
|
|
2069
|
+
if (amount < 0n) {
|
|
2070
|
+
throw new RangeError('amount must be a non-negative integer');
|
|
1850
2071
|
}
|
|
1851
|
-
return
|
|
1852
|
-
}
|
|
1853
|
-
catch {
|
|
1854
|
-
throw new Error('Invalid base64 signature');
|
|
1855
|
-
}
|
|
1856
|
-
}
|
|
1857
|
-
/**
|
|
1858
|
-
* Ed25519 signature as **128 hex chars** (optional `0x`) or **base64** (typically 88 chars for 64 bytes, whitespace ignored).
|
|
1859
|
-
*/
|
|
1860
|
-
function parseStellarEd25519SignatureRaw(input) {
|
|
1861
|
-
const trimmed = input.trim();
|
|
1862
|
-
const no0x = trimmed.replace(/^0x/i, '');
|
|
1863
|
-
if (/^[0-9a-fA-F]+$/.test(no0x) && no0x.length === 128) {
|
|
1864
|
-
return hexToBytes(no0x.toLowerCase());
|
|
2072
|
+
return amount.toString(10);
|
|
1865
2073
|
}
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
throw new Error(`Decoded signature must be 64 bytes (Ed25519); got ${fromB64.length} from base64`);
|
|
1869
|
-
}
|
|
1870
|
-
return fromB64;
|
|
1871
|
-
}
|
|
1872
|
-
/** SHA-256 via Web Crypto only (`crypto.subtle`) — works in browsers and Node 19+ (global `crypto`). */
|
|
1873
|
-
async function sha256$1(data) {
|
|
1874
|
-
const subtle = globalThis.crypto?.subtle;
|
|
1875
|
-
if (!subtle) {
|
|
1876
|
-
throw new Error('SHA-256 requires crypto.subtle (HTTPS or localhost in browsers, or Node.js 19+).');
|
|
2074
|
+
if (typeof amount !== 'string') {
|
|
2075
|
+
throw new TypeError('coin value must be bigint or decimal string, not number');
|
|
1877
2076
|
}
|
|
1878
|
-
const
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
}
|
|
1882
|
-
/**
|
|
1883
|
-
* Stellar Ed25519 signature (hex or base64) → SHA-256(signature bytes) → scalar → WASM ECDH (no UTF-8 seed hash).
|
|
1884
|
-
*/
|
|
1885
|
-
async function stealthAddressFromStellarSignature(ecdhFromScalarHex, encodeStealth, signature) {
|
|
1886
|
-
const raw = parseStellarEd25519SignatureRaw(signature);
|
|
1887
|
-
const scalar = await sha256$1(raw);
|
|
1888
|
-
const scalarHex = bytesToHexLower(scalar);
|
|
1889
|
-
const decoded = ecdhFromScalarHex(scalarHex);
|
|
1890
|
-
return encodeStealth(decoded);
|
|
1891
|
-
}
|
|
1892
|
-
/**
|
|
1893
|
-
* SHA-256(signature) as 32-byte BE integer, truncated to **253 bits** (same effective scalar as
|
|
1894
|
-
* `libs/cryptography` `scalar_mul_253` / circom `Num2Bits(253)`), decimal for `privKeyScalar`.
|
|
1895
|
-
*/
|
|
1896
|
-
async function privKeyScalarDecimalFromStellarSignature(signature) {
|
|
1897
|
-
const raw = parseStellarEd25519SignatureRaw(signature);
|
|
1898
|
-
const h = await sha256$1(raw);
|
|
1899
|
-
let v = 0n;
|
|
1900
|
-
for (let i = 0; i < h.length; i++) {
|
|
1901
|
-
v = (v << 8n) + BigInt(h[i]);
|
|
2077
|
+
const trimmed = amount.trim();
|
|
2078
|
+
if (!/^[0-9]+$/.test(trimmed)) {
|
|
2079
|
+
throw new TypeError('coin value decimal string must be a non-negative integer');
|
|
1902
2080
|
}
|
|
1903
|
-
|
|
1904
|
-
return (v & mask).toString(10);
|
|
2081
|
+
return trimmed;
|
|
1905
2082
|
}
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
throw new RangeError('amount must be a non-negative u64 (stroops)');
|
|
2083
|
+
function generatedCoinFromWasm(value) {
|
|
2084
|
+
const raw = value;
|
|
2085
|
+
const precommitementHex = raw.precommitementHex ?? raw.precommitement_hex;
|
|
2086
|
+
if (!precommitementHex) {
|
|
2087
|
+
throw new Error('generated coin missing precommitement hex');
|
|
1912
2088
|
}
|
|
1913
|
-
return
|
|
2089
|
+
return {
|
|
2090
|
+
coin: raw.coin,
|
|
2091
|
+
commitment_hex: raw.commitment_hex,
|
|
2092
|
+
precommitement_hex: precommitementHex,
|
|
2093
|
+
precommitementHex,
|
|
2094
|
+
};
|
|
1914
2095
|
}
|
|
1915
2096
|
class PrivacyPoolSDK {
|
|
1916
2097
|
constructor(wasm, options) {
|
|
@@ -1942,39 +2123,40 @@ class PrivacyPoolSDK {
|
|
|
1942
2123
|
return generateRandomScalarHex32();
|
|
1943
2124
|
}
|
|
1944
2125
|
/**
|
|
1945
|
-
* Text to sign with a Stellar wallet for
|
|
2126
|
+
* Text to sign with a Stellar wallet for spend-key derivation (UTF-8). No WASM required.
|
|
2127
|
+
* The message binds network, pool, registry, and schema version (H6).
|
|
1946
2128
|
*/
|
|
1947
|
-
static buildStealthAddressSignMessage(address, nonce = DEFAULT_STEALTH_SIGN_NONCE) {
|
|
1948
|
-
return buildStealthAddressSignMessage(address, nonce);
|
|
2129
|
+
static buildStealthAddressSignMessage(address, domain, nonce = DEFAULT_STEALTH_SIGN_NONCE) {
|
|
2130
|
+
return buildStealthAddressSignMessage(address, domain, nonce);
|
|
1949
2131
|
}
|
|
1950
2132
|
/**
|
|
1951
|
-
* Generate a new coin with random nullifier, secret, and random
|
|
1952
|
-
* @param amount
|
|
2133
|
+
* Generate a new coin with random nullifier, secret, and random owner-pub field elements (dev / self-contained tests).
|
|
2134
|
+
* @param amount Decimal field element as `bigint` or decimal `string` (never JS `number`).
|
|
1953
2135
|
* @param assetHiDecimal / assetLoDecimal Decimal Fr strings for Stellar asset contract id (two limbs).
|
|
1954
2136
|
*/
|
|
1955
2137
|
generateCoin(amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
|
|
1956
|
-
return this.wasm.generateCoin(
|
|
2138
|
+
return generatedCoinFromWasm(this.wasm.generateCoin(coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
|
|
1957
2139
|
}
|
|
1958
2140
|
/**
|
|
1959
|
-
* Generate a coin with the same commitment shape as on-chain deposit: pass
|
|
1960
|
-
* @param amount
|
|
2141
|
+
* Generate a coin with the same commitment shape as on-chain deposit: pass owner public key (hex x, y).
|
|
2142
|
+
* @param amount Decimal field element (`bigint` | `string`).
|
|
1961
2143
|
*/
|
|
1962
|
-
|
|
1963
|
-
return this.wasm.
|
|
2144
|
+
generateCoinWithOwnerPub(ownerPub, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
|
|
2145
|
+
return generatedCoinFromWasm(this.wasm.generateCoinWithOwnerPubHex(ownerPub.x, ownerPub.y, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
|
|
1964
2146
|
}
|
|
1965
2147
|
/**
|
|
1966
2148
|
* Coin for a depositor `ephemeralKeyScalar` (32-byte hex): `coin.secret = Poseidon255(1)(scalar)` as in `deposit.circom`.
|
|
1967
|
-
* @param amount
|
|
2149
|
+
* @param amount Decimal field element (`bigint` | `string`).
|
|
1968
2150
|
*/
|
|
1969
2151
|
generateCoinFromDepositEphemeralScalarHex(scalarHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
|
|
1970
|
-
return this.wasm.generateCoinFromDepositEphemeralScalarHex(scalarHex,
|
|
2152
|
+
return generatedCoinFromWasm(this.wasm.generateCoinFromDepositEphemeralScalarHex(scalarHex, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
|
|
1971
2153
|
}
|
|
1972
2154
|
/**
|
|
1973
|
-
* Aligned deposit coin: `secret = Poseidon₁(scalar)` and
|
|
1974
|
-
* @param amount
|
|
2155
|
+
* Aligned deposit coin: `secret = Poseidon₁(scalar)` and owner pub from recipient hex coords.
|
|
2156
|
+
* @param amount Decimal field element (`bigint` | `string`).
|
|
1975
2157
|
*/
|
|
1976
|
-
|
|
1977
|
-
return this.wasm.
|
|
2158
|
+
generateCoinForDepositWithOwnerPubHex(scalarHex, ownerXHex, ownerYHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = '0') {
|
|
2159
|
+
return generatedCoinFromWasm(this.wasm.generateCoinForDepositWithOwnerPubHex(scalarHex, ownerXHex, ownerYHex, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
|
|
1978
2160
|
}
|
|
1979
2161
|
/**
|
|
1980
2162
|
* Merkle root, path, and coin fields for the first withdraw leg (Rust LeanIMT + Poseidon).
|
|
@@ -1993,10 +2175,8 @@ class PrivacyPoolSDK {
|
|
|
1993
2175
|
*/
|
|
1994
2176
|
async proveWithdrawal(coin, state, params) {
|
|
1995
2177
|
const witness = this.buildWithdrawMerkleWitness(coin, state);
|
|
1996
|
-
const
|
|
1997
|
-
|
|
1998
|
-
y: params.ephemeralYHex,
|
|
1999
|
-
}, params.applicationId ?? coin.application_id ?? '0');
|
|
2178
|
+
const ownerPubHex = this.ecdhEphemeralPublicKeyFromScalarHex(BigInt(params.privKeyScalar).toString(16).padStart(64, '0'));
|
|
2179
|
+
const w0 = withdrawObjectFromMerkleWitness(witness, ownerPubHex, params.applicationId ?? coin.application_id ?? '0', params.privKeyScalar);
|
|
2000
2180
|
const fullV = BigInt(coin.value);
|
|
2001
2181
|
let publicWithdrawals;
|
|
2002
2182
|
let deposits;
|
|
@@ -2082,19 +2262,20 @@ class PrivacyPoolSDK {
|
|
|
2082
2262
|
};
|
|
2083
2263
|
}
|
|
2084
2264
|
/**
|
|
2085
|
-
* Calculate nullifier hash: Poseidon(nullifier)
|
|
2265
|
+
* Calculate owner-bound nullifier hash: Poseidon(DOM_NULLIFIER, nullifier, privKeyScalar)
|
|
2086
2266
|
* @param nullifier Nullifier decimal string from coin data
|
|
2267
|
+
* @param privKeyScalar Spend scalar decimal string
|
|
2087
2268
|
* @returns Hex string (0x...) of the hash bytes
|
|
2088
2269
|
*/
|
|
2089
|
-
calculateNullifierHash(nullifier) {
|
|
2090
|
-
return this.wasm.calculateNullifierHash(nullifier);
|
|
2270
|
+
calculateNullifierHash(nullifier, privKeyScalar) {
|
|
2271
|
+
return this.wasm.calculateNullifierHash(nullifier, privKeyScalar);
|
|
2091
2272
|
}
|
|
2092
2273
|
/**
|
|
2093
2274
|
* Ed25519 signature from signing {@link buildStealthAddressSignMessage}: **128 hex chars** (optional `0x`)
|
|
2094
|
-
* or **base64** (64 raw bytes after decode).
|
|
2275
|
+
* or **base64** (64 raw bytes after decode). Domain-separated SHA-256 → scalar → ECDH → `stpl1` Bech32.
|
|
2095
2276
|
*/
|
|
2096
|
-
async generateStealthAddressFromStellarSignature(signature) {
|
|
2097
|
-
return stealthAddressFromStellarSignature((h) => this.wasm.ecdhEphemeralPublicKeyFromScalarHex(h), encodeStealthAddress, signature);
|
|
2277
|
+
async generateStealthAddressFromStellarSignature(signature, domain) {
|
|
2278
|
+
return stealthAddressFromStellarSignature((h) => this.wasm.ecdhEphemeralPublicKeyFromScalarHex(h), encodeStealthAddress, signature, domain);
|
|
2098
2279
|
}
|
|
2099
2280
|
encodeDecodedEphemeralKey(decoded) {
|
|
2100
2281
|
return encodeDecodedEphemeralKey(decoded);
|
|
@@ -2121,6 +2302,20 @@ const POOL_MERKLE_TREE_DEPTH = 20;
|
|
|
2121
2302
|
/** Default coin value in stroops (1 XLM); matches Rust `coin::COIN_VALUE`. */
|
|
2122
2303
|
const COIN_VALUE_STROOPS = 1000000000;
|
|
2123
2304
|
|
|
2305
|
+
// Split a JS number into u32 halves without a BigInt allocation. Exact only for integers
|
|
2306
|
+
// `0 <= n < 2**53`; callers use it on byte / bit counters, which JS length math caps far below
|
|
2307
|
+
// that (an ArrayBuffer cannot exceed 2**53 - 1 bytes).
|
|
2308
|
+
const fromNumH = (n) => (n / 2 ** 32) | 0;
|
|
2309
|
+
const fromNumL = (n) => n >>> 0;
|
|
2310
|
+
// Drop-in replacement for `view.setBigUint64(byteOffset, BigInt(n), isLE)` without the per-call
|
|
2311
|
+
// BigInt allocation. Same `n < 2**53` precondition as `fromNumH`/`fromNumL`.
|
|
2312
|
+
function setU64FromNum(view, byteOffset, n, isLE) {
|
|
2313
|
+
const h = fromNumH(n);
|
|
2314
|
+
const l = fromNumL(n);
|
|
2315
|
+
view.setUint32(byteOffset, isLE ? l : h, isLE);
|
|
2316
|
+
view.setUint32(byteOffset + 4, isLE ? h : l, isLE);
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2124
2319
|
/**
|
|
2125
2320
|
* Checks if something is Uint8Array. Be careful: nodejs Buffer will return true.
|
|
2126
2321
|
* @param a - value to test
|
|
@@ -2142,6 +2337,9 @@ function isBytes(a) {
|
|
|
2142
2337
|
'BYTES_PER_ELEMENT' in a &&
|
|
2143
2338
|
a.BYTES_PER_ELEMENT === 1));
|
|
2144
2339
|
}
|
|
2340
|
+
// Shared error-message prefix builder. Only called on throw paths, so assert
|
|
2341
|
+
// success paths never pay for the string concatenation.
|
|
2342
|
+
const atitle = (title) => (title ? `"${title}" ` : '');
|
|
2145
2343
|
/**
|
|
2146
2344
|
* Asserts something is Uint8Array.
|
|
2147
2345
|
* @param value - value to validate
|
|
@@ -2157,20 +2355,32 @@ function isBytes(a) {
|
|
|
2157
2355
|
* ```
|
|
2158
2356
|
*/
|
|
2159
2357
|
function abytes(value, length, title = '') {
|
|
2358
|
+
// Success path first: this runs at the start of every update() / digestInto(), and the
|
|
2359
|
+
// common `abytes(data)` form must not pay for length handling it does not use.
|
|
2360
|
+
if (isBytes(value) && (length === undefined))
|
|
2361
|
+
return value;
|
|
2160
2362
|
const bytes = isBytes(value);
|
|
2161
|
-
const
|
|
2162
|
-
const
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
throw new
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2363
|
+
const ofLen = '';
|
|
2364
|
+
const got = bytes ? `length=${value.length}` : `type=${typeof value}`;
|
|
2365
|
+
const message = atitle(title) + 'expected Uint8Array' + ofLen + ', got ' + got;
|
|
2366
|
+
if (!bytes)
|
|
2367
|
+
throw new TypeError(message);
|
|
2368
|
+
throw new RangeError(message);
|
|
2369
|
+
}
|
|
2370
|
+
const aobject = (value, label) => {
|
|
2371
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
2372
|
+
throw new TypeError((label === 'object' ? '' : `"${label}" `) + 'expected object, got type=' + typeof value);
|
|
2373
|
+
};
|
|
2374
|
+
const aopts = (value, label) => {
|
|
2375
|
+
aobject(value, label);
|
|
2376
|
+
const proto = Object.getPrototypeOf(value);
|
|
2377
|
+
if (proto !== Object.prototype && proto !== null)
|
|
2378
|
+
throw new TypeError(`"${label}" expected plain object`);
|
|
2379
|
+
// Object.assign() treats an own "__proto__" source key as a write to the target's legacy
|
|
2380
|
+
// prototype setter. Reject it before merging so inherited option values cannot be injected.
|
|
2381
|
+
if (Object.hasOwn(value, '__proto__'))
|
|
2382
|
+
throw new TypeError(`"${label}.__proto__" is not allowed`);
|
|
2383
|
+
};
|
|
2174
2384
|
/**
|
|
2175
2385
|
* Asserts a hash instance has not been destroyed or finished.
|
|
2176
2386
|
* @param instance - hash instance to validate
|
|
@@ -2186,10 +2396,12 @@ function abytes(value, length, title = '') {
|
|
|
2186
2396
|
* ```
|
|
2187
2397
|
*/
|
|
2188
2398
|
function aexists(instance, checkFinished = true) {
|
|
2399
|
+
// Runs on every update()/digestInto(); the flags are library-owned booleans, so only their
|
|
2400
|
+
// truthiness is checked - re-validating their type per call was pure hot-path overhead.
|
|
2189
2401
|
if (instance.destroyed)
|
|
2190
|
-
throw new Error('
|
|
2402
|
+
throw new Error('hash was destroyed');
|
|
2191
2403
|
if (checkFinished && instance.finished)
|
|
2192
|
-
throw new Error('
|
|
2404
|
+
throw new Error('digest() was already called');
|
|
2193
2405
|
}
|
|
2194
2406
|
/**
|
|
2195
2407
|
* Asserts output is a sufficiently-sized byte array.
|
|
@@ -2208,10 +2420,12 @@ function aexists(instance, checkFinished = true) {
|
|
|
2208
2420
|
* ```
|
|
2209
2421
|
*/
|
|
2210
2422
|
function aoutput(out, instance) {
|
|
2211
|
-
abytes(out, undefined, '
|
|
2423
|
+
abytes(out, undefined, 'output');
|
|
2424
|
+
// `outputLen` is a library-owned readonly number; the negated comparison keeps failing fast
|
|
2425
|
+
// when it is missing/NaN (comparisons with undefined/NaN are false) without an anumber() call.
|
|
2212
2426
|
const min = instance.outputLen;
|
|
2213
|
-
if (out.length
|
|
2214
|
-
throw new RangeError('"
|
|
2427
|
+
if (!(out.length >= min)) {
|
|
2428
|
+
throw new RangeError('"output" expected length >= ' + min);
|
|
2215
2429
|
}
|
|
2216
2430
|
}
|
|
2217
2431
|
/**
|
|
@@ -2286,6 +2500,28 @@ function bytesToHex(bytes) {
|
|
|
2286
2500
|
}
|
|
2287
2501
|
return hex;
|
|
2288
2502
|
}
|
|
2503
|
+
/**
|
|
2504
|
+
* Merges default options and passed options.
|
|
2505
|
+
* @param defaults - base option object
|
|
2506
|
+
* @param opts - user overrides
|
|
2507
|
+
* @param title - label included in thrown override errors
|
|
2508
|
+
* @returns Fresh merged option object with a null prototype.
|
|
2509
|
+
* @throws On wrong argument types. {@link TypeError}
|
|
2510
|
+
* @example
|
|
2511
|
+
* Merge user overrides onto default options.
|
|
2512
|
+
* ```ts
|
|
2513
|
+
* checkOpts({ dkLen: 32 }, { asyncTick: 10 });
|
|
2514
|
+
* ```
|
|
2515
|
+
*/
|
|
2516
|
+
function checkOpts(defaults, opts, title = 'opts') {
|
|
2517
|
+
aopts(defaults, 'defaults');
|
|
2518
|
+
if (opts !== undefined)
|
|
2519
|
+
aopts(opts, title);
|
|
2520
|
+
// Callers read optional fields directly, so omitted values must not fall through to ambient
|
|
2521
|
+
// Object.prototype pollution (for example a forged `dkLen` changing SHAKE's default output).
|
|
2522
|
+
const merged = Object.assign(Object.create(null), defaults, opts);
|
|
2523
|
+
return merged;
|
|
2524
|
+
}
|
|
2289
2525
|
/**
|
|
2290
2526
|
* Creates a callable hash function from a stateful class constructor.
|
|
2291
2527
|
* @param hashCons - hash constructor or factory
|
|
@@ -2294,6 +2530,7 @@ function bytesToHex(bytes) {
|
|
|
2294
2530
|
* Wrapper construction eagerly calls `hashCons(undefined)` once to read
|
|
2295
2531
|
* `outputLen` / `blockLen`, so constructor side effects happen at module
|
|
2296
2532
|
* init time.
|
|
2533
|
+
* @throws On wrong argument types. {@link TypeError}
|
|
2297
2534
|
* @example
|
|
2298
2535
|
* Wrap a stateful hash constructor into a callable helper.
|
|
2299
2536
|
* ```ts
|
|
@@ -2304,6 +2541,9 @@ function bytesToHex(bytes) {
|
|
|
2304
2541
|
* ```
|
|
2305
2542
|
*/
|
|
2306
2543
|
function createHasher(hashCons, info = {}) {
|
|
2544
|
+
if (typeof hashCons !== 'function')
|
|
2545
|
+
throw new TypeError('"hashCons" expected function, got type=' + typeof hashCons);
|
|
2546
|
+
info = checkOpts({}, info, 'info');
|
|
2307
2547
|
const hashC = (msg, opts) => hashCons(opts)
|
|
2308
2548
|
.update(msg)
|
|
2309
2549
|
.digest();
|
|
@@ -2415,6 +2655,7 @@ class HashMD {
|
|
|
2415
2655
|
abytes(data);
|
|
2416
2656
|
const { view, buffer, blockLen } = this;
|
|
2417
2657
|
const len = data.length;
|
|
2658
|
+
let processed = false;
|
|
2418
2659
|
for (let pos = 0; pos < len;) {
|
|
2419
2660
|
const take = Math.min(blockLen - this.pos, len - pos);
|
|
2420
2661
|
// Fast path only when there is no buffered partial block: `take === blockLen` implies
|
|
@@ -2423,18 +2664,25 @@ class HashMD {
|
|
|
2423
2664
|
const dataView = createView(data);
|
|
2424
2665
|
for (; blockLen <= len - pos; pos += blockLen)
|
|
2425
2666
|
this.process(dataView, pos);
|
|
2667
|
+
processed = true;
|
|
2426
2668
|
continue;
|
|
2427
2669
|
}
|
|
2428
|
-
|
|
2670
|
+
// When the whole input is buffered in one go (common for short messages), passing `data`
|
|
2671
|
+
// directly avoids allocating a subarray view.
|
|
2672
|
+
buffer.set(pos === 0 && take === len ? data : data.subarray(pos, pos + take), this.pos);
|
|
2429
2673
|
this.pos += take;
|
|
2430
2674
|
pos += take;
|
|
2431
2675
|
if (this.pos === blockLen) {
|
|
2432
2676
|
this.process(view, 0);
|
|
2433
2677
|
this.pos = 0;
|
|
2678
|
+
processed = true;
|
|
2434
2679
|
}
|
|
2435
2680
|
}
|
|
2436
2681
|
this.length += data.length;
|
|
2437
|
-
|
|
2682
|
+
// Shared schedule buffers only pick up input-derived words inside process(); if everything
|
|
2683
|
+
// was buffered without processing, there is nothing to zero.
|
|
2684
|
+
if (processed)
|
|
2685
|
+
this.roundClean();
|
|
2438
2686
|
return this;
|
|
2439
2687
|
}
|
|
2440
2688
|
digestInto(out) {
|
|
@@ -2446,32 +2694,33 @@ class HashMD {
|
|
|
2446
2694
|
// was previously not allocated here. But it won't change performance.
|
|
2447
2695
|
const { buffer, view, blockLen, isLE } = this;
|
|
2448
2696
|
let { pos } = this;
|
|
2449
|
-
// append the bit '1' to the message
|
|
2697
|
+
// append the bit '1' to the message, then zero-pad the rest of the block
|
|
2450
2698
|
buffer[pos++] = 0b10000000;
|
|
2451
|
-
|
|
2699
|
+
buffer.fill(0, pos);
|
|
2452
2700
|
// we have less than padOffset left in buffer, so we cannot put length in
|
|
2453
2701
|
// current block, need process it and pad again
|
|
2454
2702
|
if (this.padOffset > blockLen - pos) {
|
|
2455
2703
|
this.process(view, 0);
|
|
2456
|
-
|
|
2704
|
+
buffer.fill(0);
|
|
2457
2705
|
}
|
|
2458
|
-
// Pad until full block byte with zeros
|
|
2459
|
-
for (let i = pos; i < blockLen; i++)
|
|
2460
|
-
buffer[i] = 0;
|
|
2461
2706
|
// `padOffset` reserves the whole length field. For SHA-384/512 the high 64 bits stay zero from
|
|
2462
2707
|
// the padding fill above, and JS will overflow before user input can make that half non-zero.
|
|
2463
|
-
// So we only need to write the low 64 bits here
|
|
2464
|
-
|
|
2708
|
+
// So we only need to write the low 64 bits here (`length * 8` only scales the exponent of an
|
|
2709
|
+
// integer below 2**53, so the split inside the helper stays exact).
|
|
2710
|
+
setU64FromNum(view, blockLen - 8, this.length * 8, isLE);
|
|
2465
2711
|
this.process(view, 0);
|
|
2466
|
-
|
|
2712
|
+
// The final block above is processed outside update(), so the shared message-schedule
|
|
2713
|
+
// buffers (e.g. SHA256_W) would otherwise retain input-derived words after digest().
|
|
2714
|
+
this.roundClean();
|
|
2715
|
+
// digest() passes our own `buffer` as `out`; reuse its cached view instead of allocating one.
|
|
2716
|
+
const oview = out === buffer ? view : createView(out);
|
|
2467
2717
|
const len = this.outputLen;
|
|
2468
2718
|
// NOTE: we do division by 4 later, which must be fused in single op with modulo by JIT
|
|
2469
|
-
if (len % 4)
|
|
2470
|
-
throw new Error('_sha2: outputLen must be aligned to 32bit');
|
|
2471
2719
|
const outLen = len / 4;
|
|
2472
2720
|
const state = this.get();
|
|
2473
|
-
|
|
2474
|
-
|
|
2721
|
+
// Subclass-misconfiguration invariant: outputLen must be 32-bit aligned and fit the state.
|
|
2722
|
+
if (len % 4 || outLen > state.length)
|
|
2723
|
+
throw new Error('invalid outputLen');
|
|
2475
2724
|
for (let i = 0; i < outLen; i++)
|
|
2476
2725
|
oview.setUint32(4 * i, state[i], isLE);
|
|
2477
2726
|
}
|
|
@@ -2484,18 +2733,16 @@ class HashMD {
|
|
|
2484
2733
|
this.destroy();
|
|
2485
2734
|
return res;
|
|
2486
2735
|
}
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
to.set(...this.get());
|
|
2490
|
-
const { blockLen, buffer, length, finished, destroyed, pos } = this;
|
|
2736
|
+
_cloneIntoMeta(to) {
|
|
2737
|
+
const { buffer, length, finished, destroyed, pos } = this;
|
|
2491
2738
|
to.destroyed = destroyed;
|
|
2492
2739
|
to.finished = finished;
|
|
2493
2740
|
to.length = length;
|
|
2494
2741
|
to.pos = pos;
|
|
2495
2742
|
// Only partial-block bytes need copying: when `length % blockLen === 0`, `pos === 0` and
|
|
2496
2743
|
// later `update()` / `digestInto()` overwrite `to.buffer` from the start before reading it.
|
|
2497
|
-
if (
|
|
2498
|
-
to.buffer.set(buffer);
|
|
2744
|
+
if (pos)
|
|
2745
|
+
to.buffer.set(buffer); // Avoid a hot modulo guard.
|
|
2499
2746
|
return to;
|
|
2500
2747
|
}
|
|
2501
2748
|
clone() {
|
|
@@ -2539,8 +2786,28 @@ const SHA256_K = /* @__PURE__ */ Uint32Array.from([
|
|
|
2539
2786
|
const SHA256_W = /* @__PURE__ */ new Uint32Array(64);
|
|
2540
2787
|
/** Internal SHA-224 / SHA-256 compression engine from RFC 6234 §6.2. */
|
|
2541
2788
|
class SHA2_32B extends HashMD {
|
|
2542
|
-
|
|
2789
|
+
// We cannot use array here since array allows indexing by variable
|
|
2790
|
+
// which means optimizer/compiler cannot use registers.
|
|
2791
|
+
// Numeric initializers matter: starting the fields as `undefined` changes
|
|
2792
|
+
// V8's field representation and makes sha256 3x slower (measured).
|
|
2793
|
+
A = 0;
|
|
2794
|
+
B = 0;
|
|
2795
|
+
C = 0;
|
|
2796
|
+
D = 0;
|
|
2797
|
+
E = 0;
|
|
2798
|
+
F = 0;
|
|
2799
|
+
G = 0;
|
|
2800
|
+
H = 0;
|
|
2801
|
+
constructor(outputLen, IV) {
|
|
2543
2802
|
super(64, outputLen, 8, false);
|
|
2803
|
+
this.A = IV[0] | 0;
|
|
2804
|
+
this.B = IV[1] | 0;
|
|
2805
|
+
this.C = IV[2] | 0;
|
|
2806
|
+
this.D = IV[3] | 0;
|
|
2807
|
+
this.E = IV[4] | 0;
|
|
2808
|
+
this.F = IV[5] | 0;
|
|
2809
|
+
this.G = IV[6] | 0;
|
|
2810
|
+
this.H = IV[7] | 0;
|
|
2544
2811
|
}
|
|
2545
2812
|
get() {
|
|
2546
2813
|
const { A, B, C, D, E, F, G, H } = this;
|
|
@@ -2557,6 +2824,10 @@ class SHA2_32B extends HashMD {
|
|
|
2557
2824
|
this.G = G | 0;
|
|
2558
2825
|
this.H = H | 0;
|
|
2559
2826
|
}
|
|
2827
|
+
_cloneInto(to) {
|
|
2828
|
+
(to ||= new this.constructor()).set(...this.get());
|
|
2829
|
+
return this._cloneIntoMeta(to);
|
|
2830
|
+
}
|
|
2560
2831
|
process(view, offset) {
|
|
2561
2832
|
// Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array
|
|
2562
2833
|
for (let i = 0; i < 16; i++, offset += 4)
|
|
@@ -2608,18 +2879,8 @@ class SHA2_32B extends HashMD {
|
|
|
2608
2879
|
}
|
|
2609
2880
|
/** Internal SHA-256 hash class grounded in RFC 6234 §6.2. */
|
|
2610
2881
|
class _SHA256 extends SHA2_32B {
|
|
2611
|
-
// We cannot use array here since array allows indexing by variable
|
|
2612
|
-
// which means optimizer/compiler cannot use registers.
|
|
2613
|
-
A = SHA256_IV[0] | 0;
|
|
2614
|
-
B = SHA256_IV[1] | 0;
|
|
2615
|
-
C = SHA256_IV[2] | 0;
|
|
2616
|
-
D = SHA256_IV[3] | 0;
|
|
2617
|
-
E = SHA256_IV[4] | 0;
|
|
2618
|
-
F = SHA256_IV[5] | 0;
|
|
2619
|
-
G = SHA256_IV[6] | 0;
|
|
2620
|
-
H = SHA256_IV[7] | 0;
|
|
2621
2882
|
constructor() {
|
|
2622
|
-
super(32);
|
|
2883
|
+
super(32, SHA256_IV);
|
|
2623
2884
|
}
|
|
2624
2885
|
}
|
|
2625
2886
|
/**
|
|
@@ -2630,6 +2891,7 @@ class _SHA256 extends SHA2_32B {
|
|
|
2630
2891
|
* - Each sha256 hash is executing 2^18 bit operations.
|
|
2631
2892
|
* - Good 2024 ASICs can do 200Th/sec with 3500 watts of power, corresponding to 2^36 hashes/joule.
|
|
2632
2893
|
* @param msg - message bytes to hash
|
|
2894
|
+
* @param opts - Reserved hash options.
|
|
2633
2895
|
* @returns Digest bytes.
|
|
2634
2896
|
* @example
|
|
2635
2897
|
* Hash a message with SHA2-256.
|
|
@@ -2640,121 +2902,16 @@ class _SHA256 extends SHA2_32B {
|
|
|
2640
2902
|
const sha256 = /* @__PURE__ */ createHasher(() => new _SHA256(),
|
|
2641
2903
|
/* @__PURE__ */ oidNist(0x01));
|
|
2642
2904
|
|
|
2643
|
-
const ONBOARDING_ZERO_DOMAIN = 'privacy-pool-onboarding-zero-v1';
|
|
2644
|
-
function sha256Hex$1(data) {
|
|
2645
|
-
return bytesToHex(sha256(data));
|
|
2646
|
-
}
|
|
2647
|
-
function appendU8$1(parts, value) {
|
|
2648
|
-
parts.push(Buffer.from([value & 0xff]));
|
|
2649
|
-
}
|
|
2650
|
-
function appendU32BE$1(parts, value) {
|
|
2651
|
-
const buf = Buffer.alloc(4);
|
|
2652
|
-
buf.writeUInt32BE(value >>> 0);
|
|
2653
|
-
parts.push(buf);
|
|
2654
|
-
}
|
|
2655
|
-
function appendU128BE$1(parts, value) {
|
|
2656
|
-
const buf = Buffer.alloc(16);
|
|
2657
|
-
buf.writeBigUInt64BE(value >> 64n, 0);
|
|
2658
|
-
buf.writeBigUInt64BE(value & ((1n << 64n) - 1n), 8);
|
|
2659
|
-
parts.push(buf);
|
|
2660
|
-
}
|
|
2661
|
-
function appendAddressStrkey$1(parts, address) {
|
|
2662
|
-
const bytes = Buffer.from(address.trim(), 'utf8');
|
|
2663
|
-
appendU32BE$1(parts, bytes.length);
|
|
2664
|
-
parts.push(bytes);
|
|
2665
|
-
}
|
|
2666
|
-
function appendBytes32$1(parts, value) {
|
|
2667
|
-
if (value.length !== 32) {
|
|
2668
|
-
throw new Error(`expected 32-byte field, got ${value.length}`);
|
|
2669
|
-
}
|
|
2670
|
-
parts.push(value);
|
|
2671
|
-
}
|
|
2672
|
-
function appendBytesBlob(parts, data) {
|
|
2673
|
-
appendU32BE$1(parts, data.length);
|
|
2674
|
-
parts.push(data);
|
|
2675
|
-
}
|
|
2676
|
-
function encodePlaintextNote(parts, note) {
|
|
2677
|
-
appendU128BE$1(parts, note.value);
|
|
2678
|
-
appendBytes32$1(parts, note.asset_hi);
|
|
2679
|
-
appendBytes32$1(parts, note.asset_lo);
|
|
2680
|
-
appendBytes32$1(parts, note.nullifier);
|
|
2681
|
-
appendBytes32$1(parts, note.secret);
|
|
2682
|
-
if (note.deposited_ephemeral_scalar !== undefined) {
|
|
2683
|
-
appendU8$1(parts, 1);
|
|
2684
|
-
appendBytes32$1(parts, note.deposited_ephemeral_scalar);
|
|
2685
|
-
}
|
|
2686
|
-
else {
|
|
2687
|
-
appendU8$1(parts, 0);
|
|
2688
|
-
}
|
|
2689
|
-
}
|
|
2690
|
-
function encodeOptionalRegistration(parts, registration) {
|
|
2691
|
-
if (registration.tag === 'None') {
|
|
2692
|
-
appendU8$1(parts, 0);
|
|
2693
|
-
return;
|
|
2694
|
-
}
|
|
2695
|
-
appendU8$1(parts, 1);
|
|
2696
|
-
const reg = registration.values[0];
|
|
2697
|
-
appendAddressStrkey$1(parts, reg.owner);
|
|
2698
|
-
appendBytes32$1(parts, reg.public_key_x);
|
|
2699
|
-
appendBytes32$1(parts, reg.public_key_y);
|
|
2700
|
-
}
|
|
2701
|
-
/** Canonical `onboardingHash` matching `libs/kyt-passage/src/lib.rs::hash_onboarding`. */
|
|
2702
|
-
function hashOnboardingCanonical(onboarding) {
|
|
2703
|
-
if (onboarding === null || onboarding === undefined) {
|
|
2704
|
-
return sha256Hex$1(Buffer.from(ONBOARDING_ZERO_DOMAIN, 'utf8'));
|
|
2705
|
-
}
|
|
2706
|
-
const parts = [];
|
|
2707
|
-
appendAddressStrkey$1(parts, onboarding.owner);
|
|
2708
|
-
appendBytes32$1(parts, onboarding.temp_public_key_x);
|
|
2709
|
-
appendBytes32$1(parts, onboarding.temp_public_key_y);
|
|
2710
|
-
appendBytesBlob(parts, onboarding.encrypted_private_key);
|
|
2711
|
-
appendU32BE$1(parts, onboarding.notes.length);
|
|
2712
|
-
for (const note of onboarding.notes) {
|
|
2713
|
-
encodePlaintextNote(parts, note);
|
|
2714
|
-
}
|
|
2715
|
-
encodeOptionalRegistration(parts, onboarding.private_address_registration);
|
|
2716
|
-
return sha256Hex$1(Buffer.concat(parts));
|
|
2717
|
-
}
|
|
2718
|
-
function onboardingContractValue(onboarding) {
|
|
2719
|
-
return {
|
|
2720
|
-
owner: onboarding.owner,
|
|
2721
|
-
temp_public_key_x: onboarding.temp_public_key_x,
|
|
2722
|
-
temp_public_key_y: onboarding.temp_public_key_y,
|
|
2723
|
-
encrypted_private_key: onboarding.encrypted_private_key,
|
|
2724
|
-
notes: {
|
|
2725
|
-
notes: onboarding.notes.map((note) => ({
|
|
2726
|
-
value: note.value,
|
|
2727
|
-
asset_hi: note.asset_hi,
|
|
2728
|
-
asset_lo: note.asset_lo,
|
|
2729
|
-
nullifier: note.nullifier,
|
|
2730
|
-
secret: note.secret,
|
|
2731
|
-
deposited_ephemeral_scalar: note.deposited_ephemeral_scalar,
|
|
2732
|
-
})),
|
|
2733
|
-
},
|
|
2734
|
-
private_address_registration: onboarding.private_address_registration,
|
|
2735
|
-
};
|
|
2736
|
-
}
|
|
2737
|
-
function onboardingToScVal(onboarding) {
|
|
2738
|
-
return nativeToScVal(onboardingContractValue(onboarding));
|
|
2739
|
-
}
|
|
2740
|
-
function onboardingOptionToScVal(onboarding) {
|
|
2741
|
-
if (onboarding === null || onboarding === undefined) {
|
|
2742
|
-
return xdr.ScVal.scvVoid();
|
|
2743
|
-
}
|
|
2744
|
-
return onboardingToScVal(onboarding);
|
|
2745
|
-
}
|
|
2746
|
-
function addressToScVal(address) {
|
|
2747
|
-
return nativeToScVal(address, { type: 'address' });
|
|
2748
|
-
}
|
|
2749
|
-
|
|
2750
2905
|
const KYT_PASSAGE_AUTH_DOMAIN = 'privacy-pool-kyt-passage-v1';
|
|
2751
2906
|
const PUBLIC_LEG_CONTEXT_DOMAIN = 'privacy-pool-public-leg-context-v1';
|
|
2752
|
-
const PASSAGE_ID_DOMAIN = 'privacy-pool-passage-id-
|
|
2753
|
-
const
|
|
2754
|
-
const
|
|
2907
|
+
const PASSAGE_ID_DOMAIN = 'privacy-pool-passage-id-v2';
|
|
2908
|
+
const PASSAGE_ID_DOMAIN_V1 = 'privacy-pool-passage-id-v1';
|
|
2909
|
+
const NOTE_AUDIT_LEN = 12;
|
|
2910
|
+
const TOTAL_PUBLIC_SIGNALS = 93;
|
|
2911
|
+
const NOTE_AUDIT_PLAINTEXT_LEN = 12;
|
|
2755
2912
|
const FIELD_BYTES = 32;
|
|
2756
2913
|
const LENGTH_PREFIX_BYTES = 4;
|
|
2757
|
-
const STATE_ROOT_SIGNAL_INDEX =
|
|
2914
|
+
const STATE_ROOT_SIGNAL_INDEX = 82;
|
|
2758
2915
|
function sha256Hex(data) {
|
|
2759
2916
|
return bytesToHex(sha256(data));
|
|
2760
2917
|
}
|
|
@@ -2763,7 +2920,18 @@ function publicSignalsBuffer(publicSignalsBytes) {
|
|
|
2763
2920
|
? Buffer.from(publicSignalsBytes.replace(/^0x/, ''), 'hex')
|
|
2764
2921
|
: publicSignalsBytes;
|
|
2765
2922
|
}
|
|
2766
|
-
function
|
|
2923
|
+
function expectedSignalCountForNonce(zkConfigNonce = 0n) {
|
|
2924
|
+
if (zkConfigNonce !== 0n) {
|
|
2925
|
+
throw new Error(`unknown zk config nonce ${zkConfigNonce.toString()}`);
|
|
2926
|
+
}
|
|
2927
|
+
return TOTAL_PUBLIC_SIGNALS;
|
|
2928
|
+
}
|
|
2929
|
+
function stateRootIndexForNonce(zkConfigNonce = 0n) {
|
|
2930
|
+
expectedSignalCountForNonce(zkConfigNonce);
|
|
2931
|
+
return STATE_ROOT_SIGNAL_INDEX;
|
|
2932
|
+
}
|
|
2933
|
+
function parsePublicSignalsBytes(publicSignalsBytes, zkConfigNonce = 0n) {
|
|
2934
|
+
const expectedCount = expectedSignalCountForNonce(zkConfigNonce);
|
|
2767
2935
|
const buf = publicSignalsBuffer(publicSignalsBytes);
|
|
2768
2936
|
let offset = 0;
|
|
2769
2937
|
let hasLengthPrefix = false;
|
|
@@ -2775,11 +2943,11 @@ function parsePublicSignalsBytes(publicSignalsBytes) {
|
|
|
2775
2943
|
}
|
|
2776
2944
|
}
|
|
2777
2945
|
const signalBytes = buf.subarray(offset);
|
|
2778
|
-
if (signalBytes.length !==
|
|
2779
|
-
throw new Error(`expected ${
|
|
2946
|
+
if (signalBytes.length !== expectedCount * FIELD_BYTES) {
|
|
2947
|
+
throw new Error(`expected ${expectedCount} public signals`);
|
|
2780
2948
|
}
|
|
2781
2949
|
return {
|
|
2782
|
-
fields: Array.from({ length:
|
|
2950
|
+
fields: Array.from({ length: expectedCount }, (_, index) => Buffer.from(signalBytes.subarray(index * FIELD_BYTES, (index + 1) * FIELD_BYTES))),
|
|
2783
2951
|
hasLengthPrefix,
|
|
2784
2952
|
};
|
|
2785
2953
|
}
|
|
@@ -2874,9 +3042,9 @@ function encodeLegSlot(parts, kind, slot, isPresent, assetHi, assetLo, amount, a
|
|
|
2874
3042
|
parts.push(zeroAddressBuffer());
|
|
2875
3043
|
}
|
|
2876
3044
|
}
|
|
2877
|
-
function hashPublicSignalBytes(publicSignalsBytes) {
|
|
2878
|
-
const parsed = parsePublicSignalsBytes(publicSignalsBytes);
|
|
2879
|
-
parsed.fields[
|
|
3045
|
+
function hashPublicSignalBytes(publicSignalsBytes, zkConfigNonce = 0n) {
|
|
3046
|
+
const parsed = parsePublicSignalsBytes(publicSignalsBytes, zkConfigNonce);
|
|
3047
|
+
parsed.fields[stateRootIndexForNonce(zkConfigNonce)] = Buffer.alloc(FIELD_BYTES);
|
|
2880
3048
|
return sha256Hex(encodePublicSignalsBytes(parsed.fields, parsed.hasLengthPrefix));
|
|
2881
3049
|
}
|
|
2882
3050
|
function hashPublicLegContext(layout, input) {
|
|
@@ -2898,16 +3066,22 @@ function hashPublicLegContext(layout, input) {
|
|
|
2898
3066
|
}
|
|
2899
3067
|
return sha256Hex(Buffer.concat(parts));
|
|
2900
3068
|
}
|
|
2901
|
-
function
|
|
2902
|
-
return hashOnboardingCanonical(onboarding);
|
|
2903
|
-
}
|
|
2904
|
-
function derivePassageId(owner, nonce, publicSignalHash, publicLegContextHash, onboardingHash) {
|
|
3069
|
+
function derivePassageId(owner, nonce, publicSignalHash, publicLegContextHash) {
|
|
2905
3070
|
const parts = [];
|
|
2906
3071
|
appendBytes(parts, Buffer.from(PASSAGE_ID_DOMAIN, 'utf8'));
|
|
2907
3072
|
appendAddressStrkey(parts, owner);
|
|
2908
3073
|
appendU64BE(parts, nonce);
|
|
2909
3074
|
appendBytes32(parts, publicSignalHash);
|
|
2910
3075
|
appendBytes32(parts, publicLegContextHash);
|
|
3076
|
+
return sha256Hex(Buffer.concat(parts));
|
|
3077
|
+
}
|
|
3078
|
+
function derivePassageIdV1(owner, nonce, publicSignalHash, publicLegContextHash, onboardingHash) {
|
|
3079
|
+
const parts = [];
|
|
3080
|
+
appendBytes(parts, Buffer.from(PASSAGE_ID_DOMAIN_V1, 'utf8'));
|
|
3081
|
+
appendAddressStrkey(parts, owner);
|
|
3082
|
+
appendU64BE(parts, nonce);
|
|
3083
|
+
appendBytes32(parts, publicSignalHash);
|
|
3084
|
+
appendBytes32(parts, publicLegContextHash);
|
|
2911
3085
|
appendBytes32(parts, onboardingHash);
|
|
2912
3086
|
return sha256Hex(Buffer.concat(parts));
|
|
2913
3087
|
}
|
|
@@ -2929,15 +3103,13 @@ function buildKytPassageAuthorization(input) {
|
|
|
2929
3103
|
}
|
|
2930
3104
|
function derivePassageFromTransactContext(input) {
|
|
2931
3105
|
const layout = input.layout ?? { publicNInputs: 1, publicNOutputs: 1 };
|
|
2932
|
-
const nonce = input.nonce ??
|
|
2933
|
-
const publicSignalHash = hashPublicSignalBytes(input.publicSignalsBytes);
|
|
3106
|
+
const nonce = BigInt(input.nonce ?? 0);
|
|
3107
|
+
const publicSignalHash = hashPublicSignalBytes(input.publicSignalsBytes, nonce);
|
|
2934
3108
|
const publicLegContextHash = hashPublicLegContext(layout, input.publicLegContext);
|
|
2935
|
-
const
|
|
2936
|
-
const passageId = derivePassageId(input.owner, nonce, publicSignalHash, publicLegContextHash, onboardingHash);
|
|
3109
|
+
const passageId = derivePassageId(input.owner, nonce, publicSignalHash, publicLegContextHash);
|
|
2937
3110
|
return {
|
|
2938
3111
|
publicSignalHash,
|
|
2939
3112
|
publicLegContextHash,
|
|
2940
|
-
onboardingHash,
|
|
2941
3113
|
passageId,
|
|
2942
3114
|
};
|
|
2943
3115
|
}
|
|
@@ -2968,14 +3140,16 @@ var kytPassage = /*#__PURE__*/Object.freeze({
|
|
|
2968
3140
|
__proto__: null,
|
|
2969
3141
|
KYT_PASSAGE_AUTH_DOMAIN: KYT_PASSAGE_AUTH_DOMAIN,
|
|
2970
3142
|
NOTE_AUDIT_LEN: NOTE_AUDIT_LEN,
|
|
3143
|
+
NOTE_AUDIT_PLAINTEXT_LEN: NOTE_AUDIT_PLAINTEXT_LEN,
|
|
2971
3144
|
PASSAGE_ID_DOMAIN: PASSAGE_ID_DOMAIN,
|
|
3145
|
+
PASSAGE_ID_DOMAIN_V1: PASSAGE_ID_DOMAIN_V1,
|
|
2972
3146
|
PUBLIC_LEG_CONTEXT_DOMAIN: PUBLIC_LEG_CONTEXT_DOMAIN,
|
|
2973
3147
|
TOTAL_PUBLIC_SIGNALS: TOTAL_PUBLIC_SIGNALS,
|
|
2974
3148
|
addressToCanonicalXdr: addressToCanonicalXdr,
|
|
2975
3149
|
buildKytPassageAuthorization: buildKytPassageAuthorization,
|
|
2976
3150
|
derivePassageFromTransactContext: derivePassageFromTransactContext,
|
|
2977
3151
|
derivePassageId: derivePassageId,
|
|
2978
|
-
|
|
3152
|
+
derivePassageIdV1: derivePassageIdV1,
|
|
2979
3153
|
hashPublicLegContext: hashPublicLegContext,
|
|
2980
3154
|
hashPublicSignalBytes: hashPublicSignalBytes,
|
|
2981
3155
|
inspectKytPassage: inspectKytPassage,
|
|
@@ -2983,6 +3157,9 @@ var kytPassage = /*#__PURE__*/Object.freeze({
|
|
|
2983
3157
|
zeroAddressSentinelBuffer: zeroAddressSentinelBuffer
|
|
2984
3158
|
});
|
|
2985
3159
|
|
|
3160
|
+
function addressToScVal(address) {
|
|
3161
|
+
return nativeToScVal(address, { type: 'address' });
|
|
3162
|
+
}
|
|
2986
3163
|
function passageIdHexToScVal(passageIdHex) {
|
|
2987
3164
|
const bytes = Buffer.from(passageIdHex.replace(/^0x/, ''), 'hex');
|
|
2988
3165
|
return xdr.ScVal.scvBytes(bytes);
|
|
@@ -3019,7 +3196,6 @@ function poolTransactOperation(params) {
|
|
|
3019
3196
|
nativeToScVal(params.nonce, { type: 'u64' }),
|
|
3020
3197
|
xdr.ScVal.scvBytes(params.proofBytes),
|
|
3021
3198
|
xdr.ScVal.scvBytes(params.publicSignalsBytes),
|
|
3022
|
-
onboardingOptionToScVal(params.onboarding),
|
|
3023
3199
|
kytAuthorizationOptionToScVal(params.kytAuthorization),
|
|
3024
3200
|
],
|
|
3025
3201
|
});
|
|
@@ -3033,7 +3209,6 @@ function helperSubmitWithPassageOperation(helperContractId, params, passageId, e
|
|
|
3033
3209
|
nativeToScVal(params.nonce, { type: 'u64' }),
|
|
3034
3210
|
xdr.ScVal.scvBytes(params.proofBytes),
|
|
3035
3211
|
xdr.ScVal.scvBytes(params.publicSignalsBytes),
|
|
3036
|
-
onboardingOptionToScVal(params.onboarding),
|
|
3037
3212
|
passageIdHexToScVal(passageId),
|
|
3038
3213
|
xdr.ScVal.scvU32(expiresAtLedger),
|
|
3039
3214
|
signatureToScVal(signatureBase64ToBytes(signature)),
|
|
@@ -3166,7 +3341,6 @@ async function registerKytPassage(params) {
|
|
|
3166
3341
|
nonce: params.nonce,
|
|
3167
3342
|
publicSignalsBytes: params.publicSignalsBytes,
|
|
3168
3343
|
publicLegContext: params.publicLegContext,
|
|
3169
|
-
onboarding: params.onboarding,
|
|
3170
3344
|
});
|
|
3171
3345
|
await submitTransaction({
|
|
3172
3346
|
source: params.source,
|
|
@@ -3216,7 +3390,6 @@ async function submitWithKytPassage(params) {
|
|
|
3216
3390
|
nonce: params.nonce,
|
|
3217
3391
|
publicSignalsBytes: params.publicSignalsBytes,
|
|
3218
3392
|
publicLegContext: params.publicLegContext,
|
|
3219
|
-
onboarding: params.onboarding,
|
|
3220
3393
|
});
|
|
3221
3394
|
if (approved.passageId.replace(/^0x/, '') !== derivation.passageId) {
|
|
3222
3395
|
throw new Error('KYT approval passageId does not match local transaction context');
|
|
@@ -3231,7 +3404,6 @@ async function submitWithKytPassage(params) {
|
|
|
3231
3404
|
nonce: params.nonce,
|
|
3232
3405
|
proofBytes: params.proofBytes,
|
|
3233
3406
|
publicSignalsBytes: params.publicSignalsBytes,
|
|
3234
|
-
onboarding: params.onboarding,
|
|
3235
3407
|
kytSubmitHelperId: params.kytSubmitHelperId,
|
|
3236
3408
|
passageId: derivation.passageId,
|
|
3237
3409
|
expiresAtLedger: approved.expiresAtLedger,
|
|
@@ -3255,6 +3427,44 @@ function localSignKytPassageAuthorization(secretSeed, authorization) {
|
|
|
3255
3427
|
return sig.toString('base64');
|
|
3256
3428
|
}
|
|
3257
3429
|
|
|
3430
|
+
const SCALAR_HEX_LENGTH = 64;
|
|
3431
|
+
const OUT_OF_RANGE_MESSAGE = 'derived escrow hash is outside BabyJub subgroup; retry nonce';
|
|
3432
|
+
function hexFromCanonicalScalar(value) {
|
|
3433
|
+
return value.toString(16).padStart(SCALAR_HEX_LENGTH, '0');
|
|
3434
|
+
}
|
|
3435
|
+
function assertCanonicalBabyJubScalar(scalar) {
|
|
3436
|
+
if (!(scalar > 0n && scalar < BABYJUB_SUBGROUP_ORDER)) {
|
|
3437
|
+
throw new Error(OUT_OF_RANGE_MESSAGE);
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
async function derivedEscrowKey(nonce, recipientHi, recipientLo) {
|
|
3441
|
+
const wasm = await loadWasm();
|
|
3442
|
+
const out = wasm.derivedEscrowKey(nonce.toString(), recipientHi.toString(), recipientLo.toString());
|
|
3443
|
+
const scalar = BigInt(`0x${out.scalarHex}`);
|
|
3444
|
+
assertCanonicalBabyJubScalar(scalar);
|
|
3445
|
+
return {
|
|
3446
|
+
scalarHex: hexFromCanonicalScalar(scalar),
|
|
3447
|
+
pointXHex: out.pointXHex,
|
|
3448
|
+
pointYHex: out.pointYHex,
|
|
3449
|
+
};
|
|
3450
|
+
}
|
|
3451
|
+
async function sampleDerivedEscrowKey(recipientHi, recipientLo, startNonce = 1n, window = 1024n) {
|
|
3452
|
+
for (let offset = 0n; offset < window; offset += 1n) {
|
|
3453
|
+
const nonce = startNonce + offset;
|
|
3454
|
+
try {
|
|
3455
|
+
const key = await derivedEscrowKey(nonce, recipientHi, recipientLo);
|
|
3456
|
+
return { nonce, key };
|
|
3457
|
+
}
|
|
3458
|
+
catch (error) {
|
|
3459
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3460
|
+
if (!message.includes('outside BabyJub subgroup')) {
|
|
3461
|
+
throw error;
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
throw new Error(OUT_OF_RANGE_MESSAGE);
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3258
3468
|
const NOTE_OUTPUT_LEN = 6;
|
|
3259
3469
|
const OUTPUT_NOTE_TAG_DOMAIN = 3n;
|
|
3260
3470
|
const STREAM_DOMAIN = 1n;
|
|
@@ -3312,6 +3522,9 @@ function mimcHash(parameters) {
|
|
|
3312
3522
|
function poseidonHash(poseidon, inputs) {
|
|
3313
3523
|
return fieldBigint(poseidon.F, poseidon(inputs.map((input) => poseidon.F.e(input))));
|
|
3314
3524
|
}
|
|
3525
|
+
function deriveEncCipherKey(poseidon, sharedX) {
|
|
3526
|
+
return poseidonHash(poseidon, [DOM_ENC, sharedX]);
|
|
3527
|
+
}
|
|
3315
3528
|
function sharedKeyFromDepositor(parameters) {
|
|
3316
3529
|
const scalarHex = frDecimalToHex(parameters.ephemeralKeyScalarDecimal);
|
|
3317
3530
|
const shared = parameters.ecdhShared(scalarHex, frDecimalToHex(parameters.recipientPublicKey[0]), frDecimalToHex(parameters.recipientPublicKey[1]));
|
|
@@ -3413,12 +3626,14 @@ async function encryptOutputNoteForDeposit(parameters) {
|
|
|
3413
3626
|
secret,
|
|
3414
3627
|
applicationId: parameters.deposit.applicationId,
|
|
3415
3628
|
};
|
|
3416
|
-
const [
|
|
3629
|
+
const [sharedX, sharedY] = sharedKeyFromDepositor({
|
|
3417
3630
|
babyJub,
|
|
3418
3631
|
ecdhShared: parameters.ecdhShared,
|
|
3419
3632
|
ephemeralKeyScalarDecimal: parameters.deposit.ephemeralKeyScalar,
|
|
3420
3633
|
recipientPublicKey: parameters.deposit.recipientPublicKeys,
|
|
3421
3634
|
});
|
|
3635
|
+
const key = deriveEncCipherKey(poseidon, sharedX);
|
|
3636
|
+
const nonce = sharedY;
|
|
3422
3637
|
const message = plaintextBigints(plaintext);
|
|
3423
3638
|
const ciphertext = encryptFields({ mimc7, poseidon, key, nonce, plaintext: message });
|
|
3424
3639
|
const tag = outputNoteTag({ mimc7, poseidon, key, nonce, plaintext: message });
|
|
@@ -3427,13 +3642,22 @@ async function encryptOutputNoteForDeposit(parameters) {
|
|
|
3427
3642
|
tag: frDecimalFromBigint(tag),
|
|
3428
3643
|
};
|
|
3429
3644
|
}
|
|
3430
|
-
function
|
|
3431
|
-
const
|
|
3432
|
-
|
|
3645
|
+
function ownerPubFromRecipientScalar(babyJub, recipientScalarHex) {
|
|
3646
|
+
const product = babyJub.mulPointEscalar(babyJub.Base8, bigintFromHex(recipientScalarHex) & SCALAR_253_MASK);
|
|
3647
|
+
return [
|
|
3648
|
+
fieldBigint(babyJub.F, product[0]),
|
|
3649
|
+
fieldBigint(babyJub.F, product[1]),
|
|
3650
|
+
];
|
|
3651
|
+
}
|
|
3652
|
+
function commitmentFromPlaintextAndOwnerPub(parameters) {
|
|
3653
|
+
const ownerKeyHash = poseidonHash(parameters.poseidon, [
|
|
3654
|
+
parameters.ownerPub[0],
|
|
3655
|
+
parameters.ownerPub[1],
|
|
3656
|
+
]);
|
|
3433
3657
|
const precommitment = poseidonHash(parameters.poseidon, [
|
|
3434
3658
|
bigintFromDecimal(parameters.plaintext.secret),
|
|
3435
|
-
|
|
3436
|
-
|
|
3659
|
+
parameters.ownerMode,
|
|
3660
|
+
ownerKeyHash,
|
|
3437
3661
|
]);
|
|
3438
3662
|
const balance = poseidonHash(parameters.poseidon, [
|
|
3439
3663
|
bigintFromDecimal(parameters.plaintext.value),
|
|
@@ -3453,11 +3677,13 @@ function commitmentFromPlaintextAndSharedSecret(parameters) {
|
|
|
3453
3677
|
}
|
|
3454
3678
|
async function decryptOutputNoteEvent(input) {
|
|
3455
3679
|
const { babyJub, mimc7, poseidon } = await primitives();
|
|
3456
|
-
const [
|
|
3680
|
+
const [sharedX, sharedY] = sharedKeyFromRecipient({
|
|
3457
3681
|
babyJub,
|
|
3458
3682
|
recipientScalarHex: input.recipientScalarHex,
|
|
3459
3683
|
ephemeralPublicKey: input.createdEphemeralKey,
|
|
3460
3684
|
});
|
|
3685
|
+
const key = deriveEncCipherKey(poseidon, sharedX);
|
|
3686
|
+
const nonce = sharedY;
|
|
3461
3687
|
const decrypted = decryptFields({
|
|
3462
3688
|
mimc7,
|
|
3463
3689
|
poseidon,
|
|
@@ -3476,14 +3702,11 @@ async function decryptOutputNoteEvent(input) {
|
|
|
3476
3702
|
throw new Error('Output note tag verification failed');
|
|
3477
3703
|
}
|
|
3478
3704
|
const plaintext = plaintextFromBigints(decrypted);
|
|
3479
|
-
const
|
|
3480
|
-
x: bigintToFrHex(key),
|
|
3481
|
-
y: bigintToFrHex(nonce),
|
|
3482
|
-
};
|
|
3483
|
-
const commitmentHex = commitmentFromPlaintextAndSharedSecret({
|
|
3705
|
+
const commitmentHex = commitmentFromPlaintextAndOwnerPub({
|
|
3484
3706
|
poseidon,
|
|
3485
3707
|
plaintext,
|
|
3486
|
-
|
|
3708
|
+
ownerPub: ownerPubFromRecipientScalar(babyJub, input.recipientScalarHex),
|
|
3709
|
+
ownerMode: input.ownerMode ?? 0n,
|
|
3487
3710
|
});
|
|
3488
3711
|
const commitmentMatches = normalizeHex(input.commitmentHashHex) === commitmentHex;
|
|
3489
3712
|
if (!commitmentMatches) {
|
|
@@ -3496,5 +3719,5 @@ async function decryptOutputNoteEvent(input) {
|
|
|
3496
3719
|
};
|
|
3497
3720
|
}
|
|
3498
3721
|
|
|
3499
|
-
export { BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE, BN254_SCALAR_MOD, COIN_VALUE_STROOPS, DECODED_EPHEMERAL_HRP, DEFAULT_APPLICATION_ID, DEFAULT_STEALTH_SIGN_NONCE, DEMO_AUDIT_PUBLIC_KEY, DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP, NOTE_AUDIT_LEN as KYT_NOTE_AUDIT_LEN, NOTE_AUDIT_LEN$1 as NOTE_AUDIT_LEN, NOTE_OUTPUT_LEN$1 as NOTE_OUTPUT_LEN, NOTE_OUTPUT_LEN as OUTPUT_NOTE_LEN, POOL_MERKLE_TREE_DEPTH, PrivacyPoolSDK, STEALTH_ADDRESS_HRP, TOTAL_PUBLIC_SIGNALS, TRANSACTION_N_AUDIT_SLOTS, TRANSACTION_N_INS, TRANSACTION_N_OUTS, TRANSACTION_TREE_DEPTH, addressToCanonicalXdr, buildKytPassageAuthorization, buildStealthAddressSignMessage, buildTransactionWitnessInput, buildUniformAuditParams, coordHexToDecimal, decodeDecodedEphemeralKey, decodeDepositorSharedSecretPreimage, decodeStealthAddress, decryptOutputNoteEvent, derivePassageFromTransactContext, derivePassageId, ed25519PubkeyPayloadHexToWithdrawFrDecimals, encodeDecodedEphemeralKey, encodeDepositorSharedSecretPreimage, encodeStealthAddress, encryptOutputNoteForDeposit, generateRandomScalarHex32,
|
|
3722
|
+
export { BABYJUB_SUBGROUP_ORDER, BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE, BN254_SCALAR_MOD, COIN_VALUE_STROOPS, DECODED_EPHEMERAL_HRP, DEFAULT_APPLICATION_ID, DEFAULT_STEALTH_SIGN_NONCE, DEMO_AUDIT_PUBLIC_KEY, DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP, DOM_ENC, DOM_ESCROW, DOM_NULLIFIER, DOM_PADDING, NOTE_AUDIT_LEN as KYT_NOTE_AUDIT_LEN, NOTE_AUDIT_LEN$1 as NOTE_AUDIT_LEN, NOTE_AUDIT_PLAINTEXT_LEN, NOTE_OUTPUT_LEN$1 as NOTE_OUTPUT_LEN, NOTE_OUTPUT_LEN as OUTPUT_NOTE_LEN, OWNER_BOUND_NOTE_SCHEMA_VERSION, POOL_MERKLE_TREE_DEPTH, PrivacyPoolSDK, SPEND_SCALAR_DOMAIN_TAG, STEALTH_ADDRESS_HRP, TOTAL_PUBLIC_SIGNALS, TRANSACTION_N_AUDIT_SLOTS, TRANSACTION_N_INS, TRANSACTION_N_OUTS, TRANSACTION_TREE_DEPTH, addressToCanonicalXdr, buildKytPassageAuthorization, buildStealthAddressSignMessage, buildTransactionWitnessInput, buildUniformAuditParams, canonicalBabyJubScalarFromInteger, coordHexToDecimal, decodeDecodedEphemeralKey, decodeDepositorSharedSecretPreimage, decodeStealthAddress, decryptOutputNoteEvent, derivePassageFromTransactContext, derivePassageId, derivePassageIdV1, derivedEscrowKey, ed25519PubkeyPayloadHexToWithdrawFrDecimals, encodeDecodedEphemeralKey, encodeDepositorSharedSecretPreimage, encodeStealthAddress, encryptOutputNoteForDeposit, generateRandomScalarHex32, generateWitness, hashPublicLegContext, hashPublicSignalBytes, inspectKytPassage, localSignKytPassageAuthorization, planKytSubmit, privKeyScalarDecimalFromStellarSignature, randomFrDecimal, randomFrDecimal253, recipientPublicKeysDecimalFromStealthAddress, registerKytPassage, resolveAuditPublicKeyFromEnv, resolveDepositsForWitness, resolveSlotApplicationIds, resolveSpendScalarSchemaVersion, resolveTransactionAuditParams, sampleDerivedEscrowKey, scalarHexToFrDecimal, secretFromDepositEphemeralScalarDecimal, sharedSecretFromDepositorPreimage, sharedSecretFromRecipientPreimage, spendScalarHexFromStellarSignature, stellarContractAddressToAssetFrDecimals, submitApprovedKytPassage, submitPoolTransact, submitWithKytPassage, withApplicationIdOnDeposit, withApplicationIdOnWithdraw, withdrawObjectFromMerkleWitness, zeroAddressSentinelBuffer };
|
|
3500
3723
|
//# sourceMappingURL=index.mjs.map
|