@arcanetech/stellar-privacy-pool-zk-sdk 0.9.1-rc.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/dist/ciphertext-blob.d.ts +17 -0
- package/dist/circuit-artifacts.d.ts +9 -0
- package/dist/circuit-urls-2x2-COiK1uEc.mjs +6 -0
- package/dist/circuit-urls-2x2-COiK1uEc.mjs.map +1 -0
- package/dist/circuit-urls-2x2-DVpZIkgS.js +12 -0
- package/dist/circuit-urls-2x2-DVpZIkgS.js.map +1 -0
- package/dist/circuit-urls-2x2.d.ts +3 -0
- package/dist/circuit-urls-6x6-CqdzNC4I.js +12 -0
- package/dist/circuit-urls-6x6-CqdzNC4I.js.map +1 -0
- package/dist/circuit-urls-6x6-Dtp8u02o.mjs +6 -0
- package/dist/circuit-urls-6x6-Dtp8u02o.mjs.map +1 -0
- package/dist/circuit-urls-6x6.d.ts +3 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +3114 -0
- package/dist/cli.js.map +1 -0
- package/dist/derived-escrow-key.d.ts +10 -0
- package/dist/domain-separators.d.ts +16 -0
- package/dist/ephemeral-key.d.ts +28 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.mjs +3875 -0
- package/dist/index.mjs.map +1 -0
- package/dist/kyt-flow.d.ts +60 -0
- package/dist/kyt-onboarding.d.ts +36 -0
- package/dist/kyt-passage.d.ts +83 -0
- package/dist/output-note-encryption.d.ts +72 -0
- package/dist/prover.d.ts +5 -0
- package/dist/sdk.d.ts +130 -0
- package/dist/shared-secret.d.ts +24 -0
- package/dist/stealth-address.d.ts +23 -0
- package/dist/stealth-sign-message.d.ts +20 -0
- package/dist/stealth-signature.d.ts +29 -0
- package/dist/transaction-audit.d.ts +26 -0
- package/dist/types.d.ts +64 -0
- package/dist/wasm.d.ts +1 -0
- package/dist/withdrawal-transaction-input.d.ts +162 -0
- package/dist/witness.d.ts +3 -0
- package/dist/zk-artifact-url.d.ts +4 -0
- package/dist/zk-layout.d.ts +84 -0
- package/package.json +57 -0
- package/pkg/client_sdk_wasm.d.ts +120 -0
- package/pkg/client_sdk_wasm.js +765 -0
- package/pkg/client_sdk_wasm_bg.wasm +0 -0
- package/pkg/client_sdk_wasm_bg.wasm.d.ts +21 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,3114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var fs = require('fs');
|
|
5
|
+
var stellarSdk = require('@stellar/stellar-sdk');
|
|
6
|
+
var circomlibjs = require('circomlibjs');
|
|
7
|
+
|
|
8
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
9
|
+
function _interopNamespaceDefault(e) {
|
|
10
|
+
var n = Object.create(null);
|
|
11
|
+
if (e) {
|
|
12
|
+
Object.keys(e).forEach(function (k) {
|
|
13
|
+
if (k !== 'default') {
|
|
14
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
15
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function () { return e[k]; }
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
n.default = e;
|
|
23
|
+
return Object.freeze(n);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
27
|
+
|
|
28
|
+
var dist = {};
|
|
29
|
+
|
|
30
|
+
Object.defineProperty(dist, "__esModule", { value: true });
|
|
31
|
+
dist.bech32m = bech32 = dist.bech32 = void 0;
|
|
32
|
+
const ALPHABET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
|
|
33
|
+
const ALPHABET_MAP = {};
|
|
34
|
+
for (let z = 0; z < ALPHABET.length; z++) {
|
|
35
|
+
const x = ALPHABET.charAt(z);
|
|
36
|
+
ALPHABET_MAP[x] = z;
|
|
37
|
+
}
|
|
38
|
+
function polymodStep(pre) {
|
|
39
|
+
const b = pre >> 25;
|
|
40
|
+
return (((pre & 0x1ffffff) << 5) ^
|
|
41
|
+
(-((b >> 0) & 1) & 0x3b6a57b2) ^
|
|
42
|
+
(-((b >> 1) & 1) & 0x26508e6d) ^
|
|
43
|
+
(-((b >> 2) & 1) & 0x1ea119fa) ^
|
|
44
|
+
(-((b >> 3) & 1) & 0x3d4233dd) ^
|
|
45
|
+
(-((b >> 4) & 1) & 0x2a1462b3));
|
|
46
|
+
}
|
|
47
|
+
function prefixChk(prefix) {
|
|
48
|
+
let chk = 1;
|
|
49
|
+
for (let i = 0; i < prefix.length; ++i) {
|
|
50
|
+
const c = prefix.charCodeAt(i);
|
|
51
|
+
if (c < 33 || c > 126)
|
|
52
|
+
return 'Invalid prefix (' + prefix + ')';
|
|
53
|
+
chk = polymodStep(chk) ^ (c >> 5);
|
|
54
|
+
}
|
|
55
|
+
chk = polymodStep(chk);
|
|
56
|
+
for (let i = 0; i < prefix.length; ++i) {
|
|
57
|
+
const v = prefix.charCodeAt(i);
|
|
58
|
+
chk = polymodStep(chk) ^ (v & 0x1f);
|
|
59
|
+
}
|
|
60
|
+
return chk;
|
|
61
|
+
}
|
|
62
|
+
function convert(data, inBits, outBits, pad) {
|
|
63
|
+
let value = 0;
|
|
64
|
+
let bits = 0;
|
|
65
|
+
const maxV = (1 << outBits) - 1;
|
|
66
|
+
const result = [];
|
|
67
|
+
for (let i = 0; i < data.length; ++i) {
|
|
68
|
+
value = (value << inBits) | data[i];
|
|
69
|
+
bits += inBits;
|
|
70
|
+
while (bits >= outBits) {
|
|
71
|
+
bits -= outBits;
|
|
72
|
+
result.push((value >> bits) & maxV);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (pad) {
|
|
76
|
+
if (bits > 0) {
|
|
77
|
+
result.push((value << (outBits - bits)) & maxV);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
if (bits >= inBits)
|
|
82
|
+
return 'Excess padding';
|
|
83
|
+
if ((value << (outBits - bits)) & maxV)
|
|
84
|
+
return 'Non-zero padding';
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
function toWords(bytes) {
|
|
89
|
+
return convert(bytes, 8, 5, true);
|
|
90
|
+
}
|
|
91
|
+
function fromWordsUnsafe(words) {
|
|
92
|
+
const res = convert(words, 5, 8, false);
|
|
93
|
+
if (Array.isArray(res))
|
|
94
|
+
return res;
|
|
95
|
+
}
|
|
96
|
+
function fromWords(words) {
|
|
97
|
+
const res = convert(words, 5, 8, false);
|
|
98
|
+
if (Array.isArray(res))
|
|
99
|
+
return res;
|
|
100
|
+
throw new Error(res);
|
|
101
|
+
}
|
|
102
|
+
function getLibraryFromEncoding(encoding) {
|
|
103
|
+
let ENCODING_CONST;
|
|
104
|
+
if (encoding === 'bech32') {
|
|
105
|
+
ENCODING_CONST = 1;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
ENCODING_CONST = 0x2bc830a3;
|
|
109
|
+
}
|
|
110
|
+
function encode(prefix, words, LIMIT) {
|
|
111
|
+
LIMIT = LIMIT || 90;
|
|
112
|
+
if (prefix.length + 7 + words.length > LIMIT)
|
|
113
|
+
throw new TypeError('Exceeds length limit');
|
|
114
|
+
prefix = prefix.toLowerCase();
|
|
115
|
+
// determine chk mod
|
|
116
|
+
let chk = prefixChk(prefix);
|
|
117
|
+
if (typeof chk === 'string')
|
|
118
|
+
throw new Error(chk);
|
|
119
|
+
let result = prefix + '1';
|
|
120
|
+
for (let i = 0; i < words.length; ++i) {
|
|
121
|
+
const x = words[i];
|
|
122
|
+
if (x >> 5 !== 0)
|
|
123
|
+
throw new Error('Non 5-bit word');
|
|
124
|
+
chk = polymodStep(chk) ^ x;
|
|
125
|
+
result += ALPHABET.charAt(x);
|
|
126
|
+
}
|
|
127
|
+
for (let i = 0; i < 6; ++i) {
|
|
128
|
+
chk = polymodStep(chk);
|
|
129
|
+
}
|
|
130
|
+
chk ^= ENCODING_CONST;
|
|
131
|
+
for (let i = 0; i < 6; ++i) {
|
|
132
|
+
const v = (chk >> ((5 - i) * 5)) & 0x1f;
|
|
133
|
+
result += ALPHABET.charAt(v);
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
function __decode(str, LIMIT) {
|
|
138
|
+
LIMIT = LIMIT || 90;
|
|
139
|
+
if (str.length < 8)
|
|
140
|
+
return str + ' too short';
|
|
141
|
+
if (str.length > LIMIT)
|
|
142
|
+
return 'Exceeds length limit';
|
|
143
|
+
// don't allow mixed case
|
|
144
|
+
const lowered = str.toLowerCase();
|
|
145
|
+
const uppered = str.toUpperCase();
|
|
146
|
+
if (str !== lowered && str !== uppered)
|
|
147
|
+
return 'Mixed-case string ' + str;
|
|
148
|
+
str = lowered;
|
|
149
|
+
const split = str.lastIndexOf('1');
|
|
150
|
+
if (split === -1)
|
|
151
|
+
return 'No separator character for ' + str;
|
|
152
|
+
if (split === 0)
|
|
153
|
+
return 'Missing prefix for ' + str;
|
|
154
|
+
const prefix = str.slice(0, split);
|
|
155
|
+
const wordChars = str.slice(split + 1);
|
|
156
|
+
if (wordChars.length < 6)
|
|
157
|
+
return 'Data too short';
|
|
158
|
+
let chk = prefixChk(prefix);
|
|
159
|
+
if (typeof chk === 'string')
|
|
160
|
+
return chk;
|
|
161
|
+
const words = [];
|
|
162
|
+
for (let i = 0; i < wordChars.length; ++i) {
|
|
163
|
+
const c = wordChars.charAt(i);
|
|
164
|
+
const v = ALPHABET_MAP[c];
|
|
165
|
+
if (v === undefined)
|
|
166
|
+
return 'Unknown character ' + c;
|
|
167
|
+
chk = polymodStep(chk) ^ v;
|
|
168
|
+
// not in the checksum?
|
|
169
|
+
if (i + 6 >= wordChars.length)
|
|
170
|
+
continue;
|
|
171
|
+
words.push(v);
|
|
172
|
+
}
|
|
173
|
+
if (chk !== ENCODING_CONST)
|
|
174
|
+
return 'Invalid checksum for ' + str;
|
|
175
|
+
return { prefix, words };
|
|
176
|
+
}
|
|
177
|
+
function decodeUnsafe(str, LIMIT) {
|
|
178
|
+
const res = __decode(str, LIMIT);
|
|
179
|
+
if (typeof res === 'object')
|
|
180
|
+
return res;
|
|
181
|
+
}
|
|
182
|
+
function decode(str, LIMIT) {
|
|
183
|
+
const res = __decode(str, LIMIT);
|
|
184
|
+
if (typeof res === 'object')
|
|
185
|
+
return res;
|
|
186
|
+
throw new Error(res);
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
decodeUnsafe,
|
|
190
|
+
decode,
|
|
191
|
+
encode,
|
|
192
|
+
toWords,
|
|
193
|
+
fromWordsUnsafe,
|
|
194
|
+
fromWords,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
var bech32 = dist.bech32 = getLibraryFromEncoding('bech32');
|
|
198
|
+
dist.bech32m = getLibraryFromEncoding('bech32m');
|
|
199
|
+
|
|
200
|
+
const STEALTH_ADDRESS_HRP = 'stpl1';
|
|
201
|
+
/** BIP-173 default 90 is too small for 64-byte payload (32+32 field coords) after 8→5 bit conversion. */
|
|
202
|
+
const BECH32_STEALTH_LIMIT = 1023;
|
|
203
|
+
function normalizeHex$3(hex) {
|
|
204
|
+
const s = hex.trim().replace(/^0x/i, '');
|
|
205
|
+
if (!/^[0-9a-fA-F]*$/.test(s)) {
|
|
206
|
+
throw new Error('stealth-address: hex must contain only 0-9, a-f');
|
|
207
|
+
}
|
|
208
|
+
return s.length % 2 === 0 ? s : `0${s}`;
|
|
209
|
+
}
|
|
210
|
+
function hexToBytes$2(hex) {
|
|
211
|
+
const norm = normalizeHex$3(hex);
|
|
212
|
+
const out = new Uint8Array(norm.length / 2);
|
|
213
|
+
for (let i = 0; i < out.length; i++) {
|
|
214
|
+
out[i] = parseInt(norm.slice(i * 2, i * 2 + 2), 16);
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
function bytesToHex$1(bytes) {
|
|
219
|
+
let s = '';
|
|
220
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
221
|
+
s += bytes[i].toString(16).padStart(2, '0');
|
|
222
|
+
}
|
|
223
|
+
return s;
|
|
224
|
+
}
|
|
225
|
+
function concatBytes$1(a, b) {
|
|
226
|
+
const out = new Uint8Array(a.length + b.length);
|
|
227
|
+
out.set(a, 0);
|
|
228
|
+
out.set(b, a.length);
|
|
229
|
+
return out;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Encodes `x || y` as Bech32 (BIP-173) with human-readable part {@link STEALTH_ADDRESS_HRP} (`stpl1`).
|
|
233
|
+
* `x` and `y` must have the same byte length so decoding can split the payload in half.
|
|
234
|
+
*/
|
|
235
|
+
function encodeStealthAddress(decoded) {
|
|
236
|
+
const xb = hexToBytes$2(decoded.x);
|
|
237
|
+
const yb = hexToBytes$2(decoded.y);
|
|
238
|
+
if (xb.length !== yb.length) {
|
|
239
|
+
throw new Error('stealth-address: x and y must encode the same number of bytes for a reversible address');
|
|
240
|
+
}
|
|
241
|
+
const payload = concatBytes$1(xb, yb);
|
|
242
|
+
const words = bech32.toWords(payload);
|
|
243
|
+
return bech32.encode(STEALTH_ADDRESS_HRP, words, BECH32_STEALTH_LIMIT);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Decodes a `stpl1` Bech32 stealth address into `x` and `y` (each half of the payload, as lowercase hex).
|
|
247
|
+
*/
|
|
248
|
+
function decodeStealthAddress(address) {
|
|
249
|
+
const { prefix, words } = bech32.decode(address, BECH32_STEALTH_LIMIT);
|
|
250
|
+
if (prefix !== STEALTH_ADDRESS_HRP) {
|
|
251
|
+
throw new Error(`stealth-address: expected HRP ${STEALTH_ADDRESS_HRP}, got ${JSON.stringify(prefix)}`);
|
|
252
|
+
}
|
|
253
|
+
const bytes = new Uint8Array(bech32.fromWords(words));
|
|
254
|
+
if (bytes.length === 0 || bytes.length % 2 !== 0) {
|
|
255
|
+
throw new Error('stealth-address: payload length must be positive and even');
|
|
256
|
+
}
|
|
257
|
+
const mid = bytes.length / 2;
|
|
258
|
+
return {
|
|
259
|
+
x: bytesToHex$1(bytes.subarray(0, mid)),
|
|
260
|
+
y: bytesToHex$1(bytes.subarray(mid)),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Default nonce when deriving the wallet sign-in message. */
|
|
265
|
+
const DEFAULT_STEALTH_SIGN_NONCE = 'main address';
|
|
266
|
+
/** Domain tag mixed into the spend-scalar digest (H6). */
|
|
267
|
+
const SPEND_SCALAR_DOMAIN_TAG = 'privacy-pool-spend-scalar-v1';
|
|
268
|
+
/** Schema version bound into the spend-scalar message and digest. */
|
|
269
|
+
const OWNER_BOUND_NOTE_SCHEMA_VERSION = 1;
|
|
270
|
+
function resolveSpendScalarSchemaVersion(domain) {
|
|
271
|
+
return domain.schemaVersion ?? OWNER_BOUND_NOTE_SCHEMA_VERSION;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Plaintext for Stellar wallet message signing (spend-key derivation).
|
|
275
|
+
* Sign the exact bytes of this string (UTF-8) with the stellar account key.
|
|
276
|
+
*
|
|
277
|
+
* The derived scalar is the owner-bound spend key for notes in this environment.
|
|
278
|
+
*/
|
|
279
|
+
function buildStealthAddressSignMessage(address, domain, nonce = DEFAULT_STEALTH_SIGN_NONCE) {
|
|
280
|
+
const schemaVersion = resolveSpendScalarSchemaVersion(domain);
|
|
281
|
+
return [
|
|
282
|
+
'Arcane privacy layer: derive your private-note spend key.',
|
|
283
|
+
'',
|
|
284
|
+
`Wallet: ${address}`,
|
|
285
|
+
`Network: ${domain.networkPassphrase}`,
|
|
286
|
+
`Pool: ${domain.poolContract}`,
|
|
287
|
+
`Registry: ${domain.registryContract}`,
|
|
288
|
+
`Schema: owner-bound-note-v${schemaVersion}`,
|
|
289
|
+
`Nonce: ${nonce}`,
|
|
290
|
+
'',
|
|
291
|
+
'This signature derives the key that authorizes spending your private notes in this environment.',
|
|
292
|
+
].join('\n');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const DOM_ENC = 1346719299n;
|
|
296
|
+
const DOM_AUDIT_TAG = 1346458695n;
|
|
297
|
+
const DOM_OUTPUT_NOTE_TAG = 1347310663n;
|
|
298
|
+
const BABYJUB_SUBGROUP_ORDER = 2736030358979909402780800718157159386076813972158567259200215660948447373041n;
|
|
299
|
+
/**
|
|
300
|
+
* Map any integer into `(0, l)` so it satisfies circom `LessThan(252)` vs
|
|
301
|
+
* `BABYJUB_SUBGROUP_ORDER` and `BabyPbk`. Values already in range are unchanged.
|
|
302
|
+
*/
|
|
303
|
+
function canonicalBabyJubScalarFromInteger(value) {
|
|
304
|
+
if (value > 0n && value < BABYJUB_SUBGROUP_ORDER) {
|
|
305
|
+
return value;
|
|
306
|
+
}
|
|
307
|
+
return (value % (BABYJUB_SUBGROUP_ORDER - 1n)) + 1n;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function hexToBytes$1(hex) {
|
|
311
|
+
const out = new Uint8Array(hex.length / 2);
|
|
312
|
+
for (let i = 0; i < out.length; i++) {
|
|
313
|
+
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
314
|
+
}
|
|
315
|
+
return out;
|
|
316
|
+
}
|
|
317
|
+
/** Base64 → bytes via `atob` (browsers; Node 16+). No Node `Buffer`. */
|
|
318
|
+
function decodeBase64ToBytes(s) {
|
|
319
|
+
const t = s.replace(/\s/g, '');
|
|
320
|
+
if (typeof atob !== 'function') {
|
|
321
|
+
throw new Error('Base64 decoding requires atob (browser or Node 16+)');
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
const binary = atob(t);
|
|
325
|
+
const out = new Uint8Array(binary.length);
|
|
326
|
+
for (let i = 0; i < binary.length; i++) {
|
|
327
|
+
out[i] = binary.charCodeAt(i);
|
|
328
|
+
}
|
|
329
|
+
return out;
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
throw new Error('Invalid base64 signature');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Ed25519 signature as **128 hex chars** (optional `0x`) or **base64** (typically 88 chars for 64 bytes, whitespace ignored).
|
|
337
|
+
*/
|
|
338
|
+
function parseStellarEd25519SignatureRaw(input) {
|
|
339
|
+
const trimmed = input.trim();
|
|
340
|
+
const no0x = trimmed.replace(/^0x/i, '');
|
|
341
|
+
if (/^[0-9a-fA-F]+$/.test(no0x) && no0x.length === 128) {
|
|
342
|
+
return hexToBytes$1(no0x.toLowerCase());
|
|
343
|
+
}
|
|
344
|
+
const fromB64 = decodeBase64ToBytes(trimmed);
|
|
345
|
+
if (fromB64.length !== 64) {
|
|
346
|
+
throw new Error(`Decoded signature must be 64 bytes (Ed25519); got ${fromB64.length} from base64`);
|
|
347
|
+
}
|
|
348
|
+
return fromB64;
|
|
349
|
+
}
|
|
350
|
+
/** SHA-256 via Web Crypto only (`crypto.subtle`) — works in browsers and Node 19+ (global `crypto`). */
|
|
351
|
+
async function sha256(data) {
|
|
352
|
+
const subtle = globalThis.crypto?.subtle;
|
|
353
|
+
if (!subtle) {
|
|
354
|
+
throw new Error('SHA-256 requires crypto.subtle (HTTPS or localhost in browsers, or Node.js 19+).');
|
|
355
|
+
}
|
|
356
|
+
const copy = new Uint8Array(data.length);
|
|
357
|
+
copy.set(data);
|
|
358
|
+
return new Uint8Array(await subtle.digest('SHA-256', copy));
|
|
359
|
+
}
|
|
360
|
+
function appendU32BE(parts, value) {
|
|
361
|
+
const buf = new Uint8Array(4);
|
|
362
|
+
new DataView(buf.buffer).setUint32(0, value >>> 0, false);
|
|
363
|
+
parts.push(buf);
|
|
364
|
+
}
|
|
365
|
+
function appendUtf8Prefixed(parts, text) {
|
|
366
|
+
const bytes = new TextEncoder().encode(text);
|
|
367
|
+
appendU32BE(parts, bytes.length);
|
|
368
|
+
parts.push(bytes);
|
|
369
|
+
}
|
|
370
|
+
function concatBytes(parts) {
|
|
371
|
+
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
372
|
+
const out = new Uint8Array(total);
|
|
373
|
+
let offset = 0;
|
|
374
|
+
for (const part of parts) {
|
|
375
|
+
out.set(part, offset);
|
|
376
|
+
offset += part.length;
|
|
377
|
+
}
|
|
378
|
+
return out;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Domain-separated spend-scalar digest: SHA-256(
|
|
382
|
+
* SPEND_SCALAR_DOMAIN_TAG || network || pool || registry || schemaVersion || signature
|
|
383
|
+
* ).
|
|
384
|
+
*/
|
|
385
|
+
async function spendScalarDigestFromStellarSignature(signature, domain) {
|
|
386
|
+
const raw = parseStellarEd25519SignatureRaw(signature);
|
|
387
|
+
const parts = [new TextEncoder().encode(SPEND_SCALAR_DOMAIN_TAG)];
|
|
388
|
+
appendUtf8Prefixed(parts, domain.networkPassphrase);
|
|
389
|
+
appendUtf8Prefixed(parts, domain.poolContract);
|
|
390
|
+
appendUtf8Prefixed(parts, domain.registryContract);
|
|
391
|
+
appendU32BE(parts, resolveSpendScalarSchemaVersion(domain));
|
|
392
|
+
parts.push(raw);
|
|
393
|
+
return sha256(concatBytes(parts));
|
|
394
|
+
}
|
|
395
|
+
function digestToCanonicalScalar(digest) {
|
|
396
|
+
let v = 0n;
|
|
397
|
+
for (let i = 0; i < digest.length; i++) {
|
|
398
|
+
v = (v << 8n) + BigInt(digest[i]);
|
|
399
|
+
}
|
|
400
|
+
return canonicalBabyJubScalarFromInteger(v);
|
|
401
|
+
}
|
|
402
|
+
function canonicalScalarHex(scalar) {
|
|
403
|
+
return scalar.toString(16).padStart(64, '0');
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Stellar Ed25519 signature (hex or base64) → domain-separated SHA-256 → scalar → WASM ECDH.
|
|
407
|
+
*/
|
|
408
|
+
async function stealthAddressFromStellarSignature(ecdhFromScalarHex, encodeStealth, signature, domain) {
|
|
409
|
+
const digest = await spendScalarDigestFromStellarSignature(signature, domain);
|
|
410
|
+
const decoded = ecdhFromScalarHex(canonicalScalarHex(digestToCanonicalScalar(digest)));
|
|
411
|
+
return encodeStealth(decoded);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Domain-separated SHA-256(signature, network, pool, registry, schema) reduced into
|
|
415
|
+
* `(0, BabyJub subgroup order)` so `privKeyScalar` matches `BabyPbk` and circom `LessThan(l)`.
|
|
416
|
+
*/
|
|
417
|
+
async function privKeyScalarDecimalFromStellarSignature(signature, domain) {
|
|
418
|
+
const digest = await spendScalarDigestFromStellarSignature(signature, domain);
|
|
419
|
+
return digestToCanonicalScalar(digest).toString(10);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/* @ts-self-types="./client_sdk_wasm.d.ts" */
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Merkle root, path, and coin field strings for the first withdraw leg (JSON → JSON).
|
|
426
|
+
* @param {string} coin_json
|
|
427
|
+
* @param {string} state_json
|
|
428
|
+
* @returns {string}
|
|
429
|
+
*/
|
|
430
|
+
function buildWithdrawMerkleWitness(coin_json, state_json) {
|
|
431
|
+
let deferred4_0;
|
|
432
|
+
let deferred4_1;
|
|
433
|
+
try {
|
|
434
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
435
|
+
const ptr0 = passStringToWasm0(coin_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
436
|
+
const len0 = WASM_VECTOR_LEN;
|
|
437
|
+
const ptr1 = passStringToWasm0(state_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
438
|
+
const len1 = WASM_VECTOR_LEN;
|
|
439
|
+
wasm.buildWithdrawMerkleWitness(retptr, ptr0, len0, ptr1, len1);
|
|
440
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
441
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
442
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
443
|
+
var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
|
|
444
|
+
var ptr3 = r0;
|
|
445
|
+
var len3 = r1;
|
|
446
|
+
if (r3) {
|
|
447
|
+
ptr3 = 0; len3 = 0;
|
|
448
|
+
throw takeObject(r2);
|
|
449
|
+
}
|
|
450
|
+
deferred4_0 = ptr3;
|
|
451
|
+
deferred4_1 = len3;
|
|
452
|
+
return getStringFromWasm0(ptr3, len3);
|
|
453
|
+
} finally {
|
|
454
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
455
|
+
wasm.__wbindgen_export4(deferred4_0, deferred4_1, 1);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Calculate owner-bound nullifier hash from nullifier and spend-scalar decimal strings.
|
|
461
|
+
* Returns hex string (0x...)
|
|
462
|
+
* @param {string} nullifier_decimal
|
|
463
|
+
* @param {string} priv_key_scalar_decimal
|
|
464
|
+
* @returns {string}
|
|
465
|
+
*/
|
|
466
|
+
function calculateNullifierHash(nullifier_decimal, priv_key_scalar_decimal) {
|
|
467
|
+
let deferred4_0;
|
|
468
|
+
let deferred4_1;
|
|
469
|
+
try {
|
|
470
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
471
|
+
const ptr0 = passStringToWasm0(nullifier_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
472
|
+
const len0 = WASM_VECTOR_LEN;
|
|
473
|
+
const ptr1 = passStringToWasm0(priv_key_scalar_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
474
|
+
const len1 = WASM_VECTOR_LEN;
|
|
475
|
+
wasm.calculateNullifierHash(retptr, ptr0, len0, ptr1, len1);
|
|
476
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
477
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
478
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
479
|
+
var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
|
|
480
|
+
var ptr3 = r0;
|
|
481
|
+
var len3 = r1;
|
|
482
|
+
if (r3) {
|
|
483
|
+
ptr3 = 0; len3 = 0;
|
|
484
|
+
throw takeObject(r2);
|
|
485
|
+
}
|
|
486
|
+
deferred4_0 = ptr3;
|
|
487
|
+
deferred4_1 = len3;
|
|
488
|
+
return getStringFromWasm0(ptr3, len3);
|
|
489
|
+
} finally {
|
|
490
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
491
|
+
wasm.__wbindgen_export4(deferred4_0, deferred4_1, 1);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* `R = Poseidon255(2)(hi, lo)`; `scalar` is the raw Poseidon hash when it is
|
|
497
|
+
* already in `(0, BabyJub subgroup order)`. Out-of-range hashes fail so the
|
|
498
|
+
* caller can rejection-sample a new nonce.
|
|
499
|
+
* @param {string} nonce_decimal
|
|
500
|
+
* @param {string} recipient_hi_decimal
|
|
501
|
+
* @param {string} recipient_lo_decimal
|
|
502
|
+
* @returns {any}
|
|
503
|
+
*/
|
|
504
|
+
function derivedEscrowKey(nonce_decimal, recipient_hi_decimal, recipient_lo_decimal) {
|
|
505
|
+
try {
|
|
506
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
507
|
+
const ptr0 = passStringToWasm0(nonce_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
508
|
+
const len0 = WASM_VECTOR_LEN;
|
|
509
|
+
const ptr1 = passStringToWasm0(recipient_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
510
|
+
const len1 = WASM_VECTOR_LEN;
|
|
511
|
+
const ptr2 = passStringToWasm0(recipient_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
512
|
+
const len2 = WASM_VECTOR_LEN;
|
|
513
|
+
wasm.derivedEscrowKey(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
|
|
514
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
515
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
516
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
517
|
+
if (r2) {
|
|
518
|
+
throw takeObject(r1);
|
|
519
|
+
}
|
|
520
|
+
return takeObject(r0);
|
|
521
|
+
} finally {
|
|
522
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* UTF-8 seed → `SHA256` → scalar → BabyJubJub `BASE8 * r` (circom `ECDHEphemeralKey`).
|
|
528
|
+
* `x` and `y` are lowercase hex (no `0x`). Bech32 / stealth string: TypeScript `encodeStealthAddress`.
|
|
529
|
+
* @param {string} seed
|
|
530
|
+
* @returns {any}
|
|
531
|
+
*/
|
|
532
|
+
function ecdhEphemeralPublicKey(seed) {
|
|
533
|
+
try {
|
|
534
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
535
|
+
const ptr0 = passStringToWasm0(seed, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
536
|
+
const len0 = WASM_VECTOR_LEN;
|
|
537
|
+
wasm.ecdhEphemeralPublicKey(retptr, ptr0, len0);
|
|
538
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
539
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
540
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
541
|
+
if (r2) {
|
|
542
|
+
throw takeObject(r1);
|
|
543
|
+
}
|
|
544
|
+
return takeObject(r0);
|
|
545
|
+
} finally {
|
|
546
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* 32-byte scalar as 64 hex chars (optional `0x`) → `ecdh_ephemeral_public_key` (no UTF-8 seed hash).
|
|
552
|
+
* @param {string} scalar_hex
|
|
553
|
+
* @returns {any}
|
|
554
|
+
*/
|
|
555
|
+
function ecdhEphemeralPublicKeyFromScalarHex(scalar_hex) {
|
|
556
|
+
try {
|
|
557
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
558
|
+
const ptr0 = passStringToWasm0(scalar_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
559
|
+
const len0 = WASM_VECTOR_LEN;
|
|
560
|
+
wasm.ecdhEphemeralPublicKeyFromScalarHex(retptr, ptr0, len0);
|
|
561
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
562
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
563
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
564
|
+
if (r2) {
|
|
565
|
+
throw takeObject(r1);
|
|
566
|
+
}
|
|
567
|
+
return takeObject(r0);
|
|
568
|
+
} finally {
|
|
569
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Circuit `ECDH`: `priv * (pub_x, pub_y)` → shared key (`key[0], key[1]` hex).
|
|
575
|
+
* @param {string} priv_hex
|
|
576
|
+
* @param {string} pub_x_hex
|
|
577
|
+
* @param {string} pub_y_hex
|
|
578
|
+
* @returns {any}
|
|
579
|
+
*/
|
|
580
|
+
function ecdhSharedKey(priv_hex, pub_x_hex, pub_y_hex) {
|
|
581
|
+
try {
|
|
582
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
583
|
+
const ptr0 = passStringToWasm0(priv_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
584
|
+
const len0 = WASM_VECTOR_LEN;
|
|
585
|
+
const ptr1 = passStringToWasm0(pub_x_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
586
|
+
const len1 = WASM_VECTOR_LEN;
|
|
587
|
+
const ptr2 = passStringToWasm0(pub_y_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
588
|
+
const len2 = WASM_VECTOR_LEN;
|
|
589
|
+
wasm.ecdhSharedKey(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
|
|
590
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
591
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
592
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
593
|
+
if (r2) {
|
|
594
|
+
throw takeObject(r1);
|
|
595
|
+
}
|
|
596
|
+
return takeObject(r0);
|
|
597
|
+
} finally {
|
|
598
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Generate a new coin with random nullifier, secret, and owner public-key field elements.
|
|
604
|
+
* `amount_decimal` is an arbitrary-precision decimal field element (never JS `number`).
|
|
605
|
+
* `asset_hi_decimal` / `asset_lo_decimal` are decimal Fr strings for the Stellar asset contract id (two limbs).
|
|
606
|
+
* Returns JSON: { coin: { value, nullifier, secret, commitment, asset_hi, asset_lo }, commitment_hex, precommitement_hex }
|
|
607
|
+
* @param {string} amount_decimal
|
|
608
|
+
* @param {string} asset_hi_decimal
|
|
609
|
+
* @param {string} asset_lo_decimal
|
|
610
|
+
* @param {string} application_id_decimal
|
|
611
|
+
* @returns {any}
|
|
612
|
+
*/
|
|
613
|
+
function generateCoin(amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
|
|
614
|
+
try {
|
|
615
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
616
|
+
const ptr0 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
617
|
+
const len0 = WASM_VECTOR_LEN;
|
|
618
|
+
const ptr1 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
619
|
+
const len1 = WASM_VECTOR_LEN;
|
|
620
|
+
const ptr2 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
621
|
+
const len2 = WASM_VECTOR_LEN;
|
|
622
|
+
const ptr3 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
623
|
+
const len3 = WASM_VECTOR_LEN;
|
|
624
|
+
wasm.generateCoin(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3);
|
|
625
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
626
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
627
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
628
|
+
if (r2) {
|
|
629
|
+
throw takeObject(r1);
|
|
630
|
+
}
|
|
631
|
+
return takeObject(r0);
|
|
632
|
+
} finally {
|
|
633
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* `Poseidon₁(scalar)` secret + recipient owner public key (hex), matching an aligned deposit witness.
|
|
639
|
+
* @param {string} scalar_hex
|
|
640
|
+
* @param {string} owner_x_hex
|
|
641
|
+
* @param {string} owner_y_hex
|
|
642
|
+
* @param {string} amount_decimal
|
|
643
|
+
* @param {string} asset_hi_decimal
|
|
644
|
+
* @param {string} asset_lo_decimal
|
|
645
|
+
* @param {string} application_id_decimal
|
|
646
|
+
* @returns {any}
|
|
647
|
+
*/
|
|
648
|
+
function generateCoinForDepositWithOwnerPubHex(scalar_hex, owner_x_hex, owner_y_hex, amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
|
|
649
|
+
try {
|
|
650
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
651
|
+
const ptr0 = passStringToWasm0(scalar_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
652
|
+
const len0 = WASM_VECTOR_LEN;
|
|
653
|
+
const ptr1 = passStringToWasm0(owner_x_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
654
|
+
const len1 = WASM_VECTOR_LEN;
|
|
655
|
+
const ptr2 = passStringToWasm0(owner_y_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
656
|
+
const len2 = WASM_VECTOR_LEN;
|
|
657
|
+
const ptr3 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
658
|
+
const len3 = WASM_VECTOR_LEN;
|
|
659
|
+
const ptr4 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
660
|
+
const len4 = WASM_VECTOR_LEN;
|
|
661
|
+
const ptr5 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
662
|
+
const len5 = WASM_VECTOR_LEN;
|
|
663
|
+
const ptr6 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
664
|
+
const len6 = WASM_VECTOR_LEN;
|
|
665
|
+
wasm.generateCoinForDepositWithOwnerPubHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5, ptr6, len6);
|
|
666
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
667
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
668
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
669
|
+
if (r2) {
|
|
670
|
+
throw takeObject(r1);
|
|
671
|
+
}
|
|
672
|
+
return takeObject(r0);
|
|
673
|
+
} finally {
|
|
674
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* `secret` in coin = `Poseidon255(1)(scalar)` per `deposit.circom`; scalar is 32-byte hex (64 chars, optional `0x`).
|
|
680
|
+
* @param {string} scalar_hex
|
|
681
|
+
* @param {string} amount_decimal
|
|
682
|
+
* @param {string} asset_hi_decimal
|
|
683
|
+
* @param {string} asset_lo_decimal
|
|
684
|
+
* @param {string} application_id_decimal
|
|
685
|
+
* @returns {any}
|
|
686
|
+
*/
|
|
687
|
+
function generateCoinFromDepositEphemeralScalarHex(scalar_hex, amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
|
|
688
|
+
try {
|
|
689
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
690
|
+
const ptr0 = passStringToWasm0(scalar_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
691
|
+
const len0 = WASM_VECTOR_LEN;
|
|
692
|
+
const ptr1 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
693
|
+
const len1 = WASM_VECTOR_LEN;
|
|
694
|
+
const ptr2 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
695
|
+
const len2 = WASM_VECTOR_LEN;
|
|
696
|
+
const ptr3 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
697
|
+
const len3 = WASM_VECTOR_LEN;
|
|
698
|
+
const ptr4 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
699
|
+
const len4 = WASM_VECTOR_LEN;
|
|
700
|
+
wasm.generateCoinFromDepositEphemeralScalarHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4);
|
|
701
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
702
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
703
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
704
|
+
if (r2) {
|
|
705
|
+
throw takeObject(r1);
|
|
706
|
+
}
|
|
707
|
+
return takeObject(r0);
|
|
708
|
+
} finally {
|
|
709
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Same as `generateCoin`, but commitment uses the given owner public key (64-char hex coords).
|
|
715
|
+
* @param {string} owner_x_hex
|
|
716
|
+
* @param {string} owner_y_hex
|
|
717
|
+
* @param {string} amount_decimal
|
|
718
|
+
* @param {string} asset_hi_decimal
|
|
719
|
+
* @param {string} asset_lo_decimal
|
|
720
|
+
* @param {string} application_id_decimal
|
|
721
|
+
* @returns {any}
|
|
722
|
+
*/
|
|
723
|
+
function generateCoinWithOwnerPubHex(owner_x_hex, owner_y_hex, amount_decimal, asset_hi_decimal, asset_lo_decimal, application_id_decimal) {
|
|
724
|
+
try {
|
|
725
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
726
|
+
const ptr0 = passStringToWasm0(owner_x_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
727
|
+
const len0 = WASM_VECTOR_LEN;
|
|
728
|
+
const ptr1 = passStringToWasm0(owner_y_hex, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
729
|
+
const len1 = WASM_VECTOR_LEN;
|
|
730
|
+
const ptr2 = passStringToWasm0(amount_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
731
|
+
const len2 = WASM_VECTOR_LEN;
|
|
732
|
+
const ptr3 = passStringToWasm0(asset_hi_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
733
|
+
const len3 = WASM_VECTOR_LEN;
|
|
734
|
+
const ptr4 = passStringToWasm0(asset_lo_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
735
|
+
const len4 = WASM_VECTOR_LEN;
|
|
736
|
+
const ptr5 = passStringToWasm0(application_id_decimal, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
737
|
+
const len5 = WASM_VECTOR_LEN;
|
|
738
|
+
wasm.generateCoinWithOwnerPubHex(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5);
|
|
739
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
740
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
741
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
742
|
+
if (r2) {
|
|
743
|
+
throw takeObject(r1);
|
|
744
|
+
}
|
|
745
|
+
return takeObject(r0);
|
|
746
|
+
} finally {
|
|
747
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Convert snarkjs proof JSON to hex bytes for Soroban contract.
|
|
753
|
+
* @param {string} proof_json
|
|
754
|
+
* @returns {string}
|
|
755
|
+
*/
|
|
756
|
+
function proofToHex(proof_json) {
|
|
757
|
+
let deferred2_0;
|
|
758
|
+
let deferred2_1;
|
|
759
|
+
try {
|
|
760
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
761
|
+
const ptr0 = passStringToWasm0(proof_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
762
|
+
const len0 = WASM_VECTOR_LEN;
|
|
763
|
+
wasm.proofToHex(retptr, ptr0, len0);
|
|
764
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
765
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
766
|
+
deferred2_0 = r0;
|
|
767
|
+
deferred2_1 = r1;
|
|
768
|
+
return getStringFromWasm0(r0, r1);
|
|
769
|
+
} finally {
|
|
770
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
771
|
+
wasm.__wbindgen_export4(deferred2_0, deferred2_1, 1);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Graph witness + arkworks Groth16 prove. Returns `{ proof_hex, public_hex }`.
|
|
777
|
+
* @param {Uint8Array} graph
|
|
778
|
+
* @param {Uint8Array} proving_key
|
|
779
|
+
* @param {Uint8Array} r1cs
|
|
780
|
+
* @param {string} inputs_json
|
|
781
|
+
* @returns {any}
|
|
782
|
+
*/
|
|
783
|
+
function proveGroth16(graph, proving_key, r1cs, inputs_json) {
|
|
784
|
+
try {
|
|
785
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
786
|
+
const ptr0 = passArray8ToWasm0(graph, wasm.__wbindgen_export);
|
|
787
|
+
const len0 = WASM_VECTOR_LEN;
|
|
788
|
+
const ptr1 = passArray8ToWasm0(proving_key, wasm.__wbindgen_export);
|
|
789
|
+
const len1 = WASM_VECTOR_LEN;
|
|
790
|
+
const ptr2 = passArray8ToWasm0(r1cs, wasm.__wbindgen_export);
|
|
791
|
+
const len2 = WASM_VECTOR_LEN;
|
|
792
|
+
const ptr3 = passStringToWasm0(inputs_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
793
|
+
const len3 = WASM_VECTOR_LEN;
|
|
794
|
+
wasm.proveGroth16(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3);
|
|
795
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
796
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
797
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
798
|
+
if (r2) {
|
|
799
|
+
throw takeObject(r1);
|
|
800
|
+
}
|
|
801
|
+
return takeObject(r0);
|
|
802
|
+
} finally {
|
|
803
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Convert snarkjs public signals JSON to hex bytes for Soroban contract.
|
|
809
|
+
* @param {string} public_json
|
|
810
|
+
* @returns {string}
|
|
811
|
+
*/
|
|
812
|
+
function publicToHex(public_json) {
|
|
813
|
+
let deferred2_0;
|
|
814
|
+
let deferred2_1;
|
|
815
|
+
try {
|
|
816
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
817
|
+
const ptr0 = passStringToWasm0(public_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
818
|
+
const len0 = WASM_VECTOR_LEN;
|
|
819
|
+
wasm.publicToHex(retptr, ptr0, len0);
|
|
820
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
821
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
822
|
+
deferred2_0 = r0;
|
|
823
|
+
deferred2_1 = r1;
|
|
824
|
+
return getStringFromWasm0(r0, r1);
|
|
825
|
+
} finally {
|
|
826
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
827
|
+
wasm.__wbindgen_export4(deferred2_0, deferred2_1, 1);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function __wbg_get_imports() {
|
|
832
|
+
const import0 = {
|
|
833
|
+
__proto__: null,
|
|
834
|
+
__wbg_String_8564e559799eccda: function(arg0, arg1) {
|
|
835
|
+
const ret = String(getObject(arg1));
|
|
836
|
+
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
837
|
+
const len1 = WASM_VECTOR_LEN;
|
|
838
|
+
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
|
839
|
+
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
|
840
|
+
},
|
|
841
|
+
__wbg___wbindgen_is_function_3c846841762788c1: function(arg0) {
|
|
842
|
+
const ret = typeof(getObject(arg0)) === 'function';
|
|
843
|
+
return ret;
|
|
844
|
+
},
|
|
845
|
+
__wbg___wbindgen_is_object_781bc9f159099513: function(arg0) {
|
|
846
|
+
const val = getObject(arg0);
|
|
847
|
+
const ret = typeof(val) === 'object' && val !== null;
|
|
848
|
+
return ret;
|
|
849
|
+
},
|
|
850
|
+
__wbg___wbindgen_is_string_7ef6b97b02428fae: function(arg0) {
|
|
851
|
+
const ret = typeof(getObject(arg0)) === 'string';
|
|
852
|
+
return ret;
|
|
853
|
+
},
|
|
854
|
+
__wbg___wbindgen_is_undefined_52709e72fb9f179c: function(arg0) {
|
|
855
|
+
const ret = getObject(arg0) === undefined;
|
|
856
|
+
return ret;
|
|
857
|
+
},
|
|
858
|
+
__wbg___wbindgen_throw_6ddd609b62940d55: function(arg0, arg1) {
|
|
859
|
+
throw new Error(getStringFromWasm0(arg0, arg1));
|
|
860
|
+
},
|
|
861
|
+
__wbg_call_2d781c1f4d5c0ef8: function() { return handleError(function (arg0, arg1, arg2) {
|
|
862
|
+
const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));
|
|
863
|
+
return addHeapObject(ret);
|
|
864
|
+
}, arguments); },
|
|
865
|
+
__wbg_crypto_38df2bab126b63dc: function(arg0) {
|
|
866
|
+
const ret = getObject(arg0).crypto;
|
|
867
|
+
return addHeapObject(ret);
|
|
868
|
+
},
|
|
869
|
+
__wbg_getRandomValues_c44a50d8cfdaebeb: function() { return handleError(function (arg0, arg1) {
|
|
870
|
+
getObject(arg0).getRandomValues(getObject(arg1));
|
|
871
|
+
}, arguments); },
|
|
872
|
+
__wbg_length_ea16607d7b61445b: function(arg0) {
|
|
873
|
+
const ret = getObject(arg0).length;
|
|
874
|
+
return ret;
|
|
875
|
+
},
|
|
876
|
+
__wbg_msCrypto_bd5a034af96bcba6: function(arg0) {
|
|
877
|
+
const ret = getObject(arg0).msCrypto;
|
|
878
|
+
return addHeapObject(ret);
|
|
879
|
+
},
|
|
880
|
+
__wbg_new_ab79df5bd7c26067: function() {
|
|
881
|
+
const ret = new Object();
|
|
882
|
+
return addHeapObject(ret);
|
|
883
|
+
},
|
|
884
|
+
__wbg_new_with_length_825018a1616e9e55: function(arg0) {
|
|
885
|
+
const ret = new Uint8Array(arg0 >>> 0);
|
|
886
|
+
return addHeapObject(ret);
|
|
887
|
+
},
|
|
888
|
+
__wbg_node_84ea875411254db1: function(arg0) {
|
|
889
|
+
const ret = getObject(arg0).node;
|
|
890
|
+
return addHeapObject(ret);
|
|
891
|
+
},
|
|
892
|
+
__wbg_process_44c7a14e11e9f69e: function(arg0) {
|
|
893
|
+
const ret = getObject(arg0).process;
|
|
894
|
+
return addHeapObject(ret);
|
|
895
|
+
},
|
|
896
|
+
__wbg_prototypesetcall_d62e5099504357e6: function(arg0, arg1, arg2) {
|
|
897
|
+
Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), getObject(arg2));
|
|
898
|
+
},
|
|
899
|
+
__wbg_randomFillSync_6c25eac9869eb53c: function() { return handleError(function (arg0, arg1) {
|
|
900
|
+
getObject(arg0).randomFillSync(takeObject(arg1));
|
|
901
|
+
}, arguments); },
|
|
902
|
+
__wbg_require_b4edbdcf3e2a1ef0: function() { return handleError(function () {
|
|
903
|
+
const ret = module.require;
|
|
904
|
+
return addHeapObject(ret);
|
|
905
|
+
}, arguments); },
|
|
906
|
+
__wbg_set_6be42768c690e380: function(arg0, arg1, arg2) {
|
|
907
|
+
getObject(arg0)[takeObject(arg1)] = takeObject(arg2);
|
|
908
|
+
},
|
|
909
|
+
__wbg_static_accessor_GLOBAL_8adb955bd33fac2f: function() {
|
|
910
|
+
const ret = typeof global === 'undefined' ? null : global;
|
|
911
|
+
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
912
|
+
},
|
|
913
|
+
__wbg_static_accessor_GLOBAL_THIS_ad356e0db91c7913: function() {
|
|
914
|
+
const ret = typeof globalThis === 'undefined' ? null : globalThis;
|
|
915
|
+
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
916
|
+
},
|
|
917
|
+
__wbg_static_accessor_SELF_f207c857566db248: function() {
|
|
918
|
+
const ret = typeof self === 'undefined' ? null : self;
|
|
919
|
+
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
920
|
+
},
|
|
921
|
+
__wbg_static_accessor_WINDOW_bb9f1ba69d61b386: function() {
|
|
922
|
+
const ret = typeof window === 'undefined' ? null : window;
|
|
923
|
+
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
924
|
+
},
|
|
925
|
+
__wbg_subarray_a068d24e39478a8a: function(arg0, arg1, arg2) {
|
|
926
|
+
const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);
|
|
927
|
+
return addHeapObject(ret);
|
|
928
|
+
},
|
|
929
|
+
__wbg_versions_276b2795b1c6a219: function(arg0) {
|
|
930
|
+
const ret = getObject(arg0).versions;
|
|
931
|
+
return addHeapObject(ret);
|
|
932
|
+
},
|
|
933
|
+
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
|
|
934
|
+
// Cast intrinsic for `Ref(Slice(U8)) -> NamedExternref("Uint8Array")`.
|
|
935
|
+
const ret = getArrayU8FromWasm0(arg0, arg1);
|
|
936
|
+
return addHeapObject(ret);
|
|
937
|
+
},
|
|
938
|
+
__wbindgen_cast_0000000000000002: function(arg0, arg1) {
|
|
939
|
+
// Cast intrinsic for `Ref(String) -> Externref`.
|
|
940
|
+
const ret = getStringFromWasm0(arg0, arg1);
|
|
941
|
+
return addHeapObject(ret);
|
|
942
|
+
},
|
|
943
|
+
__wbindgen_object_clone_ref: function(arg0) {
|
|
944
|
+
const ret = getObject(arg0);
|
|
945
|
+
return addHeapObject(ret);
|
|
946
|
+
},
|
|
947
|
+
__wbindgen_object_drop_ref: function(arg0) {
|
|
948
|
+
takeObject(arg0);
|
|
949
|
+
},
|
|
950
|
+
};
|
|
951
|
+
return {
|
|
952
|
+
__proto__: null,
|
|
953
|
+
"./client_sdk_wasm_bg.js": import0,
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function addHeapObject(obj) {
|
|
958
|
+
if (heap_next === heap.length) heap.push(heap.length + 1);
|
|
959
|
+
const idx = heap_next;
|
|
960
|
+
heap_next = heap[idx];
|
|
961
|
+
|
|
962
|
+
heap[idx] = obj;
|
|
963
|
+
return idx;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function dropObject(idx) {
|
|
967
|
+
if (idx < 1028) return;
|
|
968
|
+
heap[idx] = heap_next;
|
|
969
|
+
heap_next = idx;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function getArrayU8FromWasm0(ptr, len) {
|
|
973
|
+
ptr = ptr >>> 0;
|
|
974
|
+
return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
let cachedDataViewMemory0 = null;
|
|
978
|
+
function getDataViewMemory0() {
|
|
979
|
+
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
|
|
980
|
+
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
|
|
981
|
+
}
|
|
982
|
+
return cachedDataViewMemory0;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
function getStringFromWasm0(ptr, len) {
|
|
986
|
+
ptr = ptr >>> 0;
|
|
987
|
+
return decodeText(ptr, len);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
let cachedUint8ArrayMemory0 = null;
|
|
991
|
+
function getUint8ArrayMemory0() {
|
|
992
|
+
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
|
|
993
|
+
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
|
|
994
|
+
}
|
|
995
|
+
return cachedUint8ArrayMemory0;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
function getObject(idx) { return heap[idx]; }
|
|
999
|
+
|
|
1000
|
+
function handleError(f, args) {
|
|
1001
|
+
try {
|
|
1002
|
+
return f.apply(this, args);
|
|
1003
|
+
} catch (e) {
|
|
1004
|
+
wasm.__wbindgen_export3(addHeapObject(e));
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
let heap = new Array(1024).fill(undefined);
|
|
1009
|
+
heap.push(undefined, null, true, false);
|
|
1010
|
+
|
|
1011
|
+
let heap_next = heap.length;
|
|
1012
|
+
|
|
1013
|
+
function isLikeNone(x) {
|
|
1014
|
+
return x === undefined || x === null;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
function passArray8ToWasm0(arg, malloc) {
|
|
1018
|
+
const ptr = malloc(arg.length * 1, 1) >>> 0;
|
|
1019
|
+
getUint8ArrayMemory0().set(arg, ptr / 1);
|
|
1020
|
+
WASM_VECTOR_LEN = arg.length;
|
|
1021
|
+
return ptr;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
function passStringToWasm0(arg, malloc, realloc) {
|
|
1025
|
+
if (realloc === undefined) {
|
|
1026
|
+
const buf = cachedTextEncoder.encode(arg);
|
|
1027
|
+
const ptr = malloc(buf.length, 1) >>> 0;
|
|
1028
|
+
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
|
|
1029
|
+
WASM_VECTOR_LEN = buf.length;
|
|
1030
|
+
return ptr;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
let len = arg.length;
|
|
1034
|
+
let ptr = malloc(len, 1) >>> 0;
|
|
1035
|
+
|
|
1036
|
+
const mem = getUint8ArrayMemory0();
|
|
1037
|
+
|
|
1038
|
+
let offset = 0;
|
|
1039
|
+
|
|
1040
|
+
for (; offset < len; offset++) {
|
|
1041
|
+
const code = arg.charCodeAt(offset);
|
|
1042
|
+
if (code > 0x7F) break;
|
|
1043
|
+
mem[ptr + offset] = code;
|
|
1044
|
+
}
|
|
1045
|
+
if (offset !== len) {
|
|
1046
|
+
if (offset !== 0) {
|
|
1047
|
+
arg = arg.slice(offset);
|
|
1048
|
+
}
|
|
1049
|
+
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
|
|
1050
|
+
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
|
|
1051
|
+
const ret = cachedTextEncoder.encodeInto(arg, view);
|
|
1052
|
+
|
|
1053
|
+
offset += ret.written;
|
|
1054
|
+
ptr = realloc(ptr, len, offset, 1) >>> 0;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
WASM_VECTOR_LEN = offset;
|
|
1058
|
+
return ptr;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function takeObject(idx) {
|
|
1062
|
+
const ret = getObject(idx);
|
|
1063
|
+
dropObject(idx);
|
|
1064
|
+
return ret;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
|
1068
|
+
cachedTextDecoder.decode();
|
|
1069
|
+
const MAX_SAFARI_DECODE_BYTES = 2146435072;
|
|
1070
|
+
let numBytesDecoded = 0;
|
|
1071
|
+
function decodeText(ptr, len) {
|
|
1072
|
+
numBytesDecoded += len;
|
|
1073
|
+
if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
|
|
1074
|
+
cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
|
1075
|
+
cachedTextDecoder.decode();
|
|
1076
|
+
numBytesDecoded = len;
|
|
1077
|
+
}
|
|
1078
|
+
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
const cachedTextEncoder = new TextEncoder();
|
|
1082
|
+
|
|
1083
|
+
if (!('encodeInto' in cachedTextEncoder)) {
|
|
1084
|
+
cachedTextEncoder.encodeInto = function (arg, view) {
|
|
1085
|
+
const buf = cachedTextEncoder.encode(arg);
|
|
1086
|
+
view.set(buf);
|
|
1087
|
+
return {
|
|
1088
|
+
read: arg.length,
|
|
1089
|
+
written: buf.length
|
|
1090
|
+
};
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
let WASM_VECTOR_LEN = 0;
|
|
1095
|
+
|
|
1096
|
+
let wasm;
|
|
1097
|
+
function __wbg_finalize_init(instance, module) {
|
|
1098
|
+
wasm = instance.exports;
|
|
1099
|
+
cachedDataViewMemory0 = null;
|
|
1100
|
+
cachedUint8ArrayMemory0 = null;
|
|
1101
|
+
return wasm;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
async function __wbg_load(module, imports) {
|
|
1105
|
+
if (typeof Response === 'function' && module instanceof Response) {
|
|
1106
|
+
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
|
1107
|
+
try {
|
|
1108
|
+
return await WebAssembly.instantiateStreaming(module, imports);
|
|
1109
|
+
} catch (e) {
|
|
1110
|
+
const validResponse = module.ok && expectedResponseType(module.type);
|
|
1111
|
+
|
|
1112
|
+
if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
|
|
1113
|
+
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
|
|
1114
|
+
|
|
1115
|
+
} else { throw e; }
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
const bytes = await module.arrayBuffer();
|
|
1120
|
+
return await WebAssembly.instantiate(bytes, imports);
|
|
1121
|
+
} else {
|
|
1122
|
+
const instance = await WebAssembly.instantiate(module, imports);
|
|
1123
|
+
|
|
1124
|
+
if (instance instanceof WebAssembly.Instance) {
|
|
1125
|
+
return { instance, module };
|
|
1126
|
+
} else {
|
|
1127
|
+
return instance;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
function expectedResponseType(type) {
|
|
1132
|
+
switch (type) {
|
|
1133
|
+
case 'basic': case 'cors': case 'default': return true;
|
|
1134
|
+
}
|
|
1135
|
+
return false;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
function initSync(module) {
|
|
1140
|
+
if (wasm !== undefined) return wasm;
|
|
1141
|
+
|
|
1142
|
+
|
|
1143
|
+
if (module !== undefined) {
|
|
1144
|
+
if (Object.getPrototypeOf(module) === Object.prototype) {
|
|
1145
|
+
({module} = module);
|
|
1146
|
+
} else {
|
|
1147
|
+
console.warn('using deprecated parameters for `initSync()`; pass a single object instead');
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
const imports = __wbg_get_imports();
|
|
1152
|
+
if (!(module instanceof WebAssembly.Module)) {
|
|
1153
|
+
module = new WebAssembly.Module(module);
|
|
1154
|
+
}
|
|
1155
|
+
const instance = new WebAssembly.Instance(module, imports);
|
|
1156
|
+
return __wbg_finalize_init(instance);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
async function __wbg_init(module_or_path) {
|
|
1160
|
+
if (wasm !== undefined) return wasm;
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
if (module_or_path !== undefined) {
|
|
1164
|
+
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
|
|
1165
|
+
({module_or_path} = module_or_path);
|
|
1166
|
+
} else {
|
|
1167
|
+
console.warn('using deprecated parameters for the initialization function; pass a single object instead');
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
if (module_or_path === undefined) {
|
|
1172
|
+
module_or_path = new URL('client_sdk_wasm_bg.wasm', (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href)));
|
|
1173
|
+
}
|
|
1174
|
+
const imports = __wbg_get_imports();
|
|
1175
|
+
|
|
1176
|
+
if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
|
|
1177
|
+
module_or_path = fetch(module_or_path);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
const { instance, module } = await __wbg_load(await module_or_path, imports);
|
|
1181
|
+
|
|
1182
|
+
return __wbg_finalize_init(instance);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
var wasmBindings = /*#__PURE__*/Object.freeze({
|
|
1186
|
+
__proto__: null,
|
|
1187
|
+
buildWithdrawMerkleWitness: buildWithdrawMerkleWitness,
|
|
1188
|
+
calculateNullifierHash: calculateNullifierHash,
|
|
1189
|
+
default: __wbg_init,
|
|
1190
|
+
derivedEscrowKey: derivedEscrowKey,
|
|
1191
|
+
ecdhEphemeralPublicKey: ecdhEphemeralPublicKey,
|
|
1192
|
+
ecdhEphemeralPublicKeyFromScalarHex: ecdhEphemeralPublicKeyFromScalarHex,
|
|
1193
|
+
ecdhSharedKey: ecdhSharedKey,
|
|
1194
|
+
generateCoin: generateCoin,
|
|
1195
|
+
generateCoinForDepositWithOwnerPubHex: generateCoinForDepositWithOwnerPubHex,
|
|
1196
|
+
generateCoinFromDepositEphemeralScalarHex: generateCoinFromDepositEphemeralScalarHex,
|
|
1197
|
+
generateCoinWithOwnerPubHex: generateCoinWithOwnerPubHex,
|
|
1198
|
+
initSync: initSync,
|
|
1199
|
+
proofToHex: proofToHex,
|
|
1200
|
+
proveGroth16: proveGroth16,
|
|
1201
|
+
publicToHex: publicToHex
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
const isNode$1 = typeof process !== 'undefined' && !!process.versions?.node;
|
|
1205
|
+
let wasmModule = null;
|
|
1206
|
+
async function loadWasm(wasmBinary) {
|
|
1207
|
+
if (wasmModule)
|
|
1208
|
+
return wasmModule;
|
|
1209
|
+
if (isNode$1) {
|
|
1210
|
+
if (wasmBinary) {
|
|
1211
|
+
initSync({ module: wasmBinary });
|
|
1212
|
+
}
|
|
1213
|
+
else {
|
|
1214
|
+
const path = await import('path');
|
|
1215
|
+
const { fileURLToPath } = await import('url');
|
|
1216
|
+
const fs = await import('fs');
|
|
1217
|
+
const dir = path.dirname(fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href))));
|
|
1218
|
+
const wasmPath = path.resolve(dir, '..', 'pkg', 'client_sdk_wasm_bg.wasm');
|
|
1219
|
+
const buffer = fs.readFileSync(wasmPath);
|
|
1220
|
+
initSync({ module: buffer });
|
|
1221
|
+
}
|
|
1222
|
+
wasmModule = wasmBindings;
|
|
1223
|
+
}
|
|
1224
|
+
else {
|
|
1225
|
+
if (wasmBinary) {
|
|
1226
|
+
initSync({ module: wasmBinary });
|
|
1227
|
+
}
|
|
1228
|
+
else {
|
|
1229
|
+
await __wbg_init();
|
|
1230
|
+
}
|
|
1231
|
+
wasmModule = wasmBindings;
|
|
1232
|
+
}
|
|
1233
|
+
return wasmModule;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
/** Off-chain ZK layout table. Must stay byte-identical to `libs/zk::ZkLayoutParams`. */
|
|
1237
|
+
const BINDING_ZK_NONCE = 3n;
|
|
1238
|
+
const SIX_BY_SIX_BINDING_ZK_NONCE = 7n;
|
|
1239
|
+
const V2_PUBLIC_INPUT_PREFIX_LEN = 7;
|
|
1240
|
+
const NOTE_AUDIT_LEN$1 = 12;
|
|
1241
|
+
const NOTE_OUTPUT_LEN = 6;
|
|
1242
|
+
var BundledZkCircuit;
|
|
1243
|
+
(function (BundledZkCircuit) {
|
|
1244
|
+
BundledZkCircuit["TwoByTwo"] = "2x2";
|
|
1245
|
+
BundledZkCircuit["SixBySix"] = "6x6";
|
|
1246
|
+
})(BundledZkCircuit || (BundledZkCircuit = {}));
|
|
1247
|
+
function isDevZkCircuitConfig(config) {
|
|
1248
|
+
return !('circuit' in config);
|
|
1249
|
+
}
|
|
1250
|
+
const DEFAULT_ZK_CONFIG_NONCE = BINDING_ZK_NONCE;
|
|
1251
|
+
const DEFAULT_ZK_CIRCUITS = {
|
|
1252
|
+
[BINDING_ZK_NONCE.toString()]: {
|
|
1253
|
+
nIns: 2,
|
|
1254
|
+
nOuts: 2,
|
|
1255
|
+
publicNInputs: 1,
|
|
1256
|
+
publicNOutputs: 1,
|
|
1257
|
+
circuit: BundledZkCircuit.TwoByTwo,
|
|
1258
|
+
},
|
|
1259
|
+
[SIX_BY_SIX_BINDING_ZK_NONCE.toString()]: {
|
|
1260
|
+
nIns: 6,
|
|
1261
|
+
nOuts: 6,
|
|
1262
|
+
publicNInputs: 1,
|
|
1263
|
+
publicNOutputs: 1,
|
|
1264
|
+
circuit: BundledZkCircuit.SixBySix,
|
|
1265
|
+
},
|
|
1266
|
+
};
|
|
1267
|
+
function layoutFromCircuitConfig(config) {
|
|
1268
|
+
return zkLayoutFromShapeBinding(config.nIns, config.nOuts, config.publicNInputs, config.publicNOutputs, config.nIns + config.nOuts, NOTE_AUDIT_LEN$1, NOTE_OUTPUT_LEN);
|
|
1269
|
+
}
|
|
1270
|
+
function resolveZkCircuitConfig(zkCircuits, zkConfigNonce) {
|
|
1271
|
+
const nonce = zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
|
|
1272
|
+
const circuits = zkCircuits ?? DEFAULT_ZK_CIRCUITS;
|
|
1273
|
+
const config = circuits[nonce.toString()];
|
|
1274
|
+
if (!config) {
|
|
1275
|
+
throw new Error(`no zk circuit configured for nonce ${nonce.toString()}`);
|
|
1276
|
+
}
|
|
1277
|
+
return { nonce, config };
|
|
1278
|
+
}
|
|
1279
|
+
function bundledCircuitFileNames(circuit) {
|
|
1280
|
+
if (circuit === BundledZkCircuit.SixBySix) {
|
|
1281
|
+
return {
|
|
1282
|
+
graphFileName: 'main_6x6.graph.bin',
|
|
1283
|
+
r1csFileName: 'main_6x6.r1cs.gz',
|
|
1284
|
+
provingKeyFileName: 'main_6x6_proving_key.bin',
|
|
1285
|
+
verificationKeyFileName: 'main_6x6_verification_key.json',
|
|
1286
|
+
zkeyFileName: 'main_6x6_final.zkey',
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
return {
|
|
1290
|
+
graphFileName: 'main.graph.bin',
|
|
1291
|
+
r1csFileName: 'main.r1cs.gz',
|
|
1292
|
+
provingKeyFileName: 'main_proving_key.bin',
|
|
1293
|
+
verificationKeyFileName: 'main_verification_key.json',
|
|
1294
|
+
zkeyFileName: 'main_final.zkey',
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
function zkLayoutFromShapeBinding(nIns, nOuts, publicNInputs, publicNOutputs, nAuditSlots, noteAuditLen, noteOutputLen) {
|
|
1298
|
+
const auditOffset = nIns + nOuts + nOuts * 2;
|
|
1299
|
+
const outputNoteOffset = auditOffset + 2 + nAuditSlots;
|
|
1300
|
+
return {
|
|
1301
|
+
nIns,
|
|
1302
|
+
nOuts,
|
|
1303
|
+
publicNInputs,
|
|
1304
|
+
publicNOutputs,
|
|
1305
|
+
nAuditSlots,
|
|
1306
|
+
noteAuditLen,
|
|
1307
|
+
noteOutputLen,
|
|
1308
|
+
publicInputPrefixLen: V2_PUBLIC_INPUT_PREFIX_LEN,
|
|
1309
|
+
auditOffset,
|
|
1310
|
+
outputNoteOffset,
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
function ciphertextFieldCount(layout) {
|
|
1314
|
+
return layout.nAuditSlots * layout.noteAuditLen + layout.nOuts * layout.noteOutputLen;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
const ZK_ARTIFACT_GITHUB_REPO = "Polynom-Labs/stellar-privacy-layer-contracts";
|
|
1318
|
+
const ZK_SDK_PACKAGE_VERSION = "0.8.0";
|
|
1319
|
+
function defaultZkArtifactBaseUrl(version = ZK_SDK_PACKAGE_VERSION) {
|
|
1320
|
+
const tag = version.startsWith("v") ? version.slice(1) : version;
|
|
1321
|
+
return `https://github.com/${ZK_ARTIFACT_GITHUB_REPO}/releases/download/v${tag}`;
|
|
1322
|
+
}
|
|
1323
|
+
function trimArtifactBaseUrl(value) {
|
|
1324
|
+
let trimmed = value.trim();
|
|
1325
|
+
while (trimmed.endsWith("/")) {
|
|
1326
|
+
trimmed = trimmed.slice(0, -1);
|
|
1327
|
+
}
|
|
1328
|
+
return trimmed;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
const isNode = typeof process !== 'undefined' && !!process.versions?.node;
|
|
1332
|
+
function toUint8Array(src) {
|
|
1333
|
+
if (src instanceof Uint8Array) {
|
|
1334
|
+
return src;
|
|
1335
|
+
}
|
|
1336
|
+
if (src instanceof ArrayBuffer) {
|
|
1337
|
+
return new Uint8Array(src);
|
|
1338
|
+
}
|
|
1339
|
+
return new Uint8Array(src.buffer, src.byteOffset, src.byteLength);
|
|
1340
|
+
}
|
|
1341
|
+
function nodeEnvArtifactBaseUrl() {
|
|
1342
|
+
if (!isNode) {
|
|
1343
|
+
return undefined;
|
|
1344
|
+
}
|
|
1345
|
+
const fromEnv = process.env.ZK_ARTIFACT_BASE_URL?.trim();
|
|
1346
|
+
return fromEnv ? trimArtifactBaseUrl(fromEnv) : undefined;
|
|
1347
|
+
}
|
|
1348
|
+
function resolveZkArtifactBaseUrl(explicit) {
|
|
1349
|
+
const trimmed = explicit?.trim();
|
|
1350
|
+
if (trimmed) {
|
|
1351
|
+
return trimArtifactBaseUrl(trimmed);
|
|
1352
|
+
}
|
|
1353
|
+
return nodeEnvArtifactBaseUrl() ?? defaultZkArtifactBaseUrl();
|
|
1354
|
+
}
|
|
1355
|
+
function isHttpUrl(value) {
|
|
1356
|
+
return /^https?:\/\//i.test(value) || value.startsWith('file:');
|
|
1357
|
+
}
|
|
1358
|
+
async function readNodeAssetFile(filePath) {
|
|
1359
|
+
const fs = await import('fs');
|
|
1360
|
+
if (!fs.existsSync(filePath)) {
|
|
1361
|
+
throw new Error(`Circuit artifact not found at ${filePath}. Set zkArtifactBaseUrl or ZK_ARTIFACT_BASE_URL.`);
|
|
1362
|
+
}
|
|
1363
|
+
return fs.readFileSync(filePath);
|
|
1364
|
+
}
|
|
1365
|
+
async function fetchArtifact(url) {
|
|
1366
|
+
const response = await fetch(url);
|
|
1367
|
+
if (!response.ok) {
|
|
1368
|
+
throw new Error(`Failed to fetch ${url}: ${String(response.status)}`);
|
|
1369
|
+
}
|
|
1370
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
1371
|
+
}
|
|
1372
|
+
function remoteArtifactHref(base, fileName) {
|
|
1373
|
+
const withSlash = `${trimArtifactBaseUrl(base)}/`;
|
|
1374
|
+
if (isHttpUrl(withSlash)) {
|
|
1375
|
+
return new URL(fileName, withSlash).href;
|
|
1376
|
+
}
|
|
1377
|
+
if (typeof window !== 'undefined') {
|
|
1378
|
+
return new URL(fileName, new URL(withSlash, window.location.href)).href;
|
|
1379
|
+
}
|
|
1380
|
+
return `${withSlash}${fileName}`;
|
|
1381
|
+
}
|
|
1382
|
+
async function loadNamedArtifact(base, fileName) {
|
|
1383
|
+
if (isNode && !isHttpUrl(base)) {
|
|
1384
|
+
const nodePath = await import('path');
|
|
1385
|
+
return readNodeAssetFile(nodePath.resolve(base, fileName));
|
|
1386
|
+
}
|
|
1387
|
+
return fetchArtifact(remoteArtifactHref(base, fileName));
|
|
1388
|
+
}
|
|
1389
|
+
async function loadBundledCircuitArtifacts(circuit, artifactBaseUrl) {
|
|
1390
|
+
const base = resolveZkArtifactBaseUrl(artifactBaseUrl);
|
|
1391
|
+
const names = bundledCircuitFileNames(circuit);
|
|
1392
|
+
const [graph, r1cs, provingKey] = await Promise.all([
|
|
1393
|
+
loadNamedArtifact(base, names.graphFileName),
|
|
1394
|
+
loadNamedArtifact(base, names.r1csFileName),
|
|
1395
|
+
loadNamedArtifact(base, names.provingKeyFileName),
|
|
1396
|
+
]);
|
|
1397
|
+
return { graph, r1cs, provingKey };
|
|
1398
|
+
}
|
|
1399
|
+
async function resolveCircuitArtifacts(config, artifactBaseUrl) {
|
|
1400
|
+
if (isDevZkCircuitConfig(config)) {
|
|
1401
|
+
return {
|
|
1402
|
+
graph: toUint8Array(config.circuitGraph),
|
|
1403
|
+
r1cs: toUint8Array(config.r1cs),
|
|
1404
|
+
provingKey: toUint8Array(config.provingKey),
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
return loadBundledCircuitArtifacts(config.circuit, artifactBaseUrl);
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
/** Bech32 HRP for {@link DecodedEphemeralKey} only (`x ‖ y`, 64 bytes). */
|
|
1411
|
+
const DECODED_EPHEMERAL_HRP = 'epk1';
|
|
1412
|
+
/** Bech32 HRP for {@link DecodedDepositorSharedSecretPreimage} (96 bytes). */
|
|
1413
|
+
const DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP = 'epk_dep_pre1';
|
|
1414
|
+
const FIELD_BYTES = 32;
|
|
1415
|
+
const EPK_POINT_PAYLOAD = FIELD_BYTES * 2;
|
|
1416
|
+
const EPK_DEP_PRE_PAYLOAD = FIELD_BYTES * 3;
|
|
1417
|
+
const BECH32_LONG_LIMIT = 1023;
|
|
1418
|
+
function normalizeHex$2(hex) {
|
|
1419
|
+
const s = hex.trim().replace(/^0x/i, '');
|
|
1420
|
+
if (!/^[0-9a-fA-F]*$/.test(s)) {
|
|
1421
|
+
throw new Error('ephemeral-key: hex must contain only 0-9, a-f');
|
|
1422
|
+
}
|
|
1423
|
+
return s.length % 2 === 0 ? s : `0${s}`;
|
|
1424
|
+
}
|
|
1425
|
+
function hexToBytes(hex) {
|
|
1426
|
+
const norm = normalizeHex$2(hex);
|
|
1427
|
+
const out = new Uint8Array(norm.length / 2);
|
|
1428
|
+
for (let i = 0; i < out.length; i++) {
|
|
1429
|
+
out[i] = parseInt(norm.slice(i * 2, i * 2 + 2), 16);
|
|
1430
|
+
}
|
|
1431
|
+
return out;
|
|
1432
|
+
}
|
|
1433
|
+
function bytesToHex(bytes) {
|
|
1434
|
+
let s = '';
|
|
1435
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1436
|
+
s += bytes[i].toString(16).padStart(2, '0');
|
|
1437
|
+
}
|
|
1438
|
+
return s;
|
|
1439
|
+
}
|
|
1440
|
+
function concat2(a, b) {
|
|
1441
|
+
const out = new Uint8Array(a.length + b.length);
|
|
1442
|
+
out.set(a, 0);
|
|
1443
|
+
out.set(b, a.length);
|
|
1444
|
+
return out;
|
|
1445
|
+
}
|
|
1446
|
+
function concat3(a, b, c) {
|
|
1447
|
+
const out = new Uint8Array(a.length + b.length + c.length);
|
|
1448
|
+
out.set(a, 0);
|
|
1449
|
+
out.set(b, a.length);
|
|
1450
|
+
out.set(c, a.length + b.length);
|
|
1451
|
+
return out;
|
|
1452
|
+
}
|
|
1453
|
+
function require32(label, bytes) {
|
|
1454
|
+
if (bytes.length !== FIELD_BYTES) {
|
|
1455
|
+
throw new Error(`ephemeral-key: ${label} must encode exactly ${FIELD_BYTES} bytes, got ${bytes.length}`);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* 32-byte big-endian integer as hex (64 chars), **< 2^253**, so BabyJub ECDH matches
|
|
1460
|
+
* `circuits/encryption.circom` `Num2Bits(253)` and `libs/cryptography` `scalar_mul_253`.
|
|
1461
|
+
*/
|
|
1462
|
+
function generateRandomScalarHex32() {
|
|
1463
|
+
const g = globalThis.crypto;
|
|
1464
|
+
if (!g?.getRandomValues) {
|
|
1465
|
+
throw new Error('ephemeral-key: crypto.getRandomValues is required');
|
|
1466
|
+
}
|
|
1467
|
+
const max = 1n << 253n;
|
|
1468
|
+
for (let attempt = 0; attempt < 65536; attempt++) {
|
|
1469
|
+
const b = new Uint8Array(32);
|
|
1470
|
+
g.getRandomValues(b);
|
|
1471
|
+
const hex = bytesToHex(b);
|
|
1472
|
+
if (BigInt(`0x${hex}`) < max) {
|
|
1473
|
+
return hex;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
throw new Error('ephemeral-key: failed to sample scalar < 2^253');
|
|
1477
|
+
}
|
|
1478
|
+
/** Bech32 `epk1`: encodes `x ‖ y` (64 bytes). */
|
|
1479
|
+
function encodeDecodedEphemeralKey(decoded) {
|
|
1480
|
+
const xb = hexToBytes(decoded.x);
|
|
1481
|
+
const yb = hexToBytes(decoded.y);
|
|
1482
|
+
require32('x', xb);
|
|
1483
|
+
require32('y', yb);
|
|
1484
|
+
const payload = concat2(xb, yb);
|
|
1485
|
+
const words = bech32.toWords(payload);
|
|
1486
|
+
return bech32.encode(DECODED_EPHEMERAL_HRP, words, BECH32_LONG_LIMIT);
|
|
1487
|
+
}
|
|
1488
|
+
function decodeDecodedEphemeralKey(encoded) {
|
|
1489
|
+
const { prefix, words } = bech32.decode(encoded, BECH32_LONG_LIMIT);
|
|
1490
|
+
if (prefix !== DECODED_EPHEMERAL_HRP) {
|
|
1491
|
+
throw new Error(`ephemeral-key: expected HRP ${DECODED_EPHEMERAL_HRP}, got ${JSON.stringify(prefix)}`);
|
|
1492
|
+
}
|
|
1493
|
+
const bytes = new Uint8Array(bech32.fromWords(words));
|
|
1494
|
+
if (bytes.length !== EPK_POINT_PAYLOAD) {
|
|
1495
|
+
throw new Error(`ephemeral-key: epk1 payload must be ${EPK_POINT_PAYLOAD} bytes, got ${bytes.length}`);
|
|
1496
|
+
}
|
|
1497
|
+
return {
|
|
1498
|
+
x: bytesToHex(bytes.subarray(0, FIELD_BYTES)),
|
|
1499
|
+
y: bytesToHex(bytes.subarray(FIELD_BYTES)),
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
/** Bech32 `epk_dep_pre1`: `randomNonceScalar ‖ recipient.x ‖ recipient.y`. */
|
|
1503
|
+
function encodeDepositorSharedSecretPreimage(decoded) {
|
|
1504
|
+
const sb = hexToBytes(decoded.randomNonceScalar);
|
|
1505
|
+
const xb = hexToBytes(decoded.recipientStealthAddress.x);
|
|
1506
|
+
const yb = hexToBytes(decoded.recipientStealthAddress.y);
|
|
1507
|
+
require32('randomNonceScalar', sb);
|
|
1508
|
+
require32('recipientStealthAddress.x', xb);
|
|
1509
|
+
require32('recipientStealthAddress.y', yb);
|
|
1510
|
+
const payload = concat3(sb, xb, yb);
|
|
1511
|
+
const words = bech32.toWords(payload);
|
|
1512
|
+
return bech32.encode(DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP, words, BECH32_LONG_LIMIT);
|
|
1513
|
+
}
|
|
1514
|
+
function decodeDepositorSharedSecretPreimage(encoded) {
|
|
1515
|
+
const { prefix, words } = bech32.decode(encoded, BECH32_LONG_LIMIT);
|
|
1516
|
+
if (prefix !== DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP) {
|
|
1517
|
+
throw new Error(`ephemeral-key: expected HRP ${DEPOSITOR_SHARED_SECRET_PREIMAGE_HRP}, got ${JSON.stringify(prefix)}`);
|
|
1518
|
+
}
|
|
1519
|
+
const bytes = new Uint8Array(bech32.fromWords(words));
|
|
1520
|
+
if (bytes.length !== EPK_DEP_PRE_PAYLOAD) {
|
|
1521
|
+
throw new Error(`ephemeral-key: epk_dep_pre1 payload must be ${EPK_DEP_PRE_PAYLOAD} bytes, got ${bytes.length}`);
|
|
1522
|
+
}
|
|
1523
|
+
return {
|
|
1524
|
+
randomNonceScalar: bytesToHex(bytes.subarray(0, FIELD_BYTES)),
|
|
1525
|
+
recipientStealthAddress: {
|
|
1526
|
+
x: bytesToHex(bytes.subarray(FIELD_BYTES, FIELD_BYTES * 2)),
|
|
1527
|
+
y: bytesToHex(bytes.subarray(FIELD_BYTES * 2)),
|
|
1528
|
+
},
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
/**
|
|
1533
|
+
* Depositor: `randomNonceScalar * recipientStealthPoint` (same as circom `ECDH` with depositor scalar).
|
|
1534
|
+
*/
|
|
1535
|
+
function sharedSecretFromDepositorPreimage(ecdhShared, preimage) {
|
|
1536
|
+
const out = ecdhShared(preimage.randomNonceScalar, preimage.recipientStealthAddress.x, preimage.recipientStealthAddress.y);
|
|
1537
|
+
return { x: out.x, y: out.y };
|
|
1538
|
+
}
|
|
1539
|
+
/**
|
|
1540
|
+
* Recipient: `recipientScalar * ephemeralKey` (same shared point as depositor path when keys match).
|
|
1541
|
+
*/
|
|
1542
|
+
function sharedSecretFromRecipientPreimage(ecdhShared, preimage) {
|
|
1543
|
+
const out = ecdhShared(preimage.recipientScalar, preimage.ephemeralKey.x, preimage.ephemeralKey.y);
|
|
1544
|
+
return { x: out.x, y: out.y };
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
const TRANSACTION_N_AUDIT_SLOTS = 4;
|
|
1548
|
+
const DEFAULT_APPLICATION_ID = '101';
|
|
1549
|
+
/** BabyJub audit public key (decimal Fr) used in BDD / local demo when env is unset. */
|
|
1550
|
+
const DEMO_AUDIT_PUBLIC_KEY = [
|
|
1551
|
+
'21605515851820432880964235241069234202284600780825340516808373216881770219365',
|
|
1552
|
+
'18856460861531942120859708048677603751294231190189224157283439874962410808705',
|
|
1553
|
+
];
|
|
1554
|
+
function isActiveWithdraw(slot) {
|
|
1555
|
+
return slot.value !== '0';
|
|
1556
|
+
}
|
|
1557
|
+
function isActiveDeposit(slot) {
|
|
1558
|
+
return slot.value !== '0';
|
|
1559
|
+
}
|
|
1560
|
+
function resolveWithdrawSlot(slot) {
|
|
1561
|
+
if (slot === 'dummy') {
|
|
1562
|
+
return null;
|
|
1563
|
+
}
|
|
1564
|
+
return slot;
|
|
1565
|
+
}
|
|
1566
|
+
function resolveDepositSlot(slot) {
|
|
1567
|
+
if (slot === 'dummy') {
|
|
1568
|
+
return null;
|
|
1569
|
+
}
|
|
1570
|
+
return slot;
|
|
1571
|
+
}
|
|
1572
|
+
function buildUniformAuditParams(applicationId = DEFAULT_APPLICATION_ID, auditPublicKey, _nAuditSlots = TRANSACTION_N_AUDIT_SLOTS) {
|
|
1573
|
+
return {
|
|
1574
|
+
applicationId,
|
|
1575
|
+
noteAuditPublicKey: auditPublicKey,
|
|
1576
|
+
auditEphemeralScalar: randomFrDecimal253(),
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
function resolveSlotApplicationIds(audit, withdrawSlots, depositSlots) {
|
|
1580
|
+
return {
|
|
1581
|
+
inputApplicationIds: withdrawSlots.map((slot) => {
|
|
1582
|
+
const resolved = resolveWithdrawSlot(slot);
|
|
1583
|
+
return resolved && isActiveWithdraw(resolved) ? audit.applicationId : '0';
|
|
1584
|
+
}),
|
|
1585
|
+
outputApplicationIds: depositSlots.map((slot) => {
|
|
1586
|
+
const resolved = resolveDepositSlot(slot);
|
|
1587
|
+
return resolved && isActiveDeposit(resolved) ? audit.applicationId : '0';
|
|
1588
|
+
}),
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Prefer an explicit key, then `NOTE_AUDIT_PUBLIC_KEY_X`/`_Y` env (decimal Fr),
|
|
1593
|
+
* then the built-in demo key. Used by CLI/`demo.sh` for per-app audit pubkeys.
|
|
1594
|
+
*/
|
|
1595
|
+
function resolveAuditPublicKeyFromEnv(auditPublicKey) {
|
|
1596
|
+
if (auditPublicKey) {
|
|
1597
|
+
return auditPublicKey;
|
|
1598
|
+
}
|
|
1599
|
+
const x = process.env.NOTE_AUDIT_PUBLIC_KEY_X?.trim();
|
|
1600
|
+
const y = process.env.NOTE_AUDIT_PUBLIC_KEY_Y?.trim();
|
|
1601
|
+
if (x && y) {
|
|
1602
|
+
return [x, y];
|
|
1603
|
+
}
|
|
1604
|
+
return DEMO_AUDIT_PUBLIC_KEY;
|
|
1605
|
+
}
|
|
1606
|
+
function resolveTransactionAuditParams(applicationId, auditPublicKey, nAuditSlots = TRANSACTION_N_AUDIT_SLOTS) {
|
|
1607
|
+
return buildUniformAuditParams(applicationId, resolveAuditPublicKeyFromEnv(auditPublicKey), nAuditSlots);
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
/** Matches `Transaction(20, 2, 2, publicNInputs, publicNOutputs, 4, 12, 6)` in `circuits/main.circom`. */
|
|
1611
|
+
const TRANSACTION_TREE_DEPTH = 20;
|
|
1612
|
+
const TRANSACTION_N_INS = 2;
|
|
1613
|
+
const TRANSACTION_N_OUTS = 2;
|
|
1614
|
+
/** Must match `Transaction(..., publicNInputs, publicNOutputs)` and contract `get_public_slot_config`. */
|
|
1615
|
+
const TRANSACTION_PUBLIC_N_INPUTS = 1;
|
|
1616
|
+
const TRANSACTION_PUBLIC_N_OUTPUTS = 1;
|
|
1617
|
+
/** BN254 scalar field modulus (ark `Fr`, circom signals). */
|
|
1618
|
+
const BN254_SCALAR_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
|
|
1619
|
+
/**
|
|
1620
|
+
* BabyJub ECDH in `circuits/encryption.circom` uses `Num2Bits(253)`; scalars must be < 2^253
|
|
1621
|
+
* (matches `libs/cryptography` `scalar_mul_253`).
|
|
1622
|
+
*/
|
|
1623
|
+
const BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE = 1n << 253n;
|
|
1624
|
+
function padPublicLegs(legs, publicNInputs, publicNOutputs) {
|
|
1625
|
+
const padPairs = (values, expected, label) => {
|
|
1626
|
+
if (values.length > expected) {
|
|
1627
|
+
throw new Error(`withdrawal-transaction-input: expected at most ${expected} ${label}`);
|
|
1628
|
+
}
|
|
1629
|
+
return [
|
|
1630
|
+
...values,
|
|
1631
|
+
...Array.from({ length: expected - values.length }, () => ['0', '0']),
|
|
1632
|
+
];
|
|
1633
|
+
};
|
|
1634
|
+
const padAmounts = (values, expected, label) => {
|
|
1635
|
+
if (values.length > expected) {
|
|
1636
|
+
throw new Error(`withdrawal-transaction-input: expected at most ${expected} ${label}`);
|
|
1637
|
+
}
|
|
1638
|
+
return [...values, ...Array.from({ length: expected - values.length }, () => '0')];
|
|
1639
|
+
};
|
|
1640
|
+
return {
|
|
1641
|
+
publicWithdrawnAssets: padPairs(legs.publicWithdrawnAssets, publicNOutputs, 'public withdrawn assets'),
|
|
1642
|
+
publicDepositedAssets: padPairs(legs.publicDepositedAssets, publicNInputs, 'public deposited assets'),
|
|
1643
|
+
publicDeposits: padAmounts(legs.publicDeposits, publicNInputs, 'public deposits'),
|
|
1644
|
+
publicWithdrawals: padAmounts(legs.publicWithdrawals, publicNOutputs, 'public withdrawals'),
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
function normalizeHex$1(hex) {
|
|
1648
|
+
const s = hex.trim().replace(/^0x/i, '');
|
|
1649
|
+
if (!/^[0-9a-fA-F]*$/.test(s)) {
|
|
1650
|
+
throw new Error('withdrawal-transaction-input: invalid hex');
|
|
1651
|
+
}
|
|
1652
|
+
return s.length % 2 === 0 ? s : `0${s}`;
|
|
1653
|
+
}
|
|
1654
|
+
/** 32-byte field coordinate (hex, no 0x) → decimal string mod BN254 scalar field. */
|
|
1655
|
+
function coordHexToDecimal(hex) {
|
|
1656
|
+
const h = normalizeHex$1(hex);
|
|
1657
|
+
if (h.length > 64) {
|
|
1658
|
+
throw new Error('withdrawal-transaction-input: coordinate hex too long');
|
|
1659
|
+
}
|
|
1660
|
+
const v = BigInt(`0x${h}`);
|
|
1661
|
+
return (v % BN254_SCALAR_MOD).toString(10);
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* 32-byte big-endian scalar hex → decimal for circom `ephemeralKeyScalar` / ECDH `priv`.
|
|
1665
|
+
* Integer must be < 2^253 (not reduced mod r — values ≥ 2^253 are rejected).
|
|
1666
|
+
*/
|
|
1667
|
+
function scalarHexToFrDecimal(hex) {
|
|
1668
|
+
const h = normalizeHex$1(hex);
|
|
1669
|
+
if (h.length > 64) {
|
|
1670
|
+
throw new Error('withdrawal-transaction-input: scalar hex too long');
|
|
1671
|
+
}
|
|
1672
|
+
const v = BigInt(`0x${h.padStart(64, '0').slice(-64)}`);
|
|
1673
|
+
if (v >= BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE) {
|
|
1674
|
+
throw new Error('depositor ephemeral scalar must be < 2^253 (BabyJub Num2Bits); resample with random-scalar');
|
|
1675
|
+
}
|
|
1676
|
+
return v.toString(10);
|
|
1677
|
+
}
|
|
1678
|
+
/**
|
|
1679
|
+
* Stellar G-address Ed25519 payload (32 bytes as 64 hex, optional 0x) → two circom public decimals
|
|
1680
|
+
* (`withdrawAddressHi` / `withdrawAddressLo`). No mod-r; each half fits in 128 bits.
|
|
1681
|
+
*/
|
|
1682
|
+
function ed25519PubkeyPayloadHexToWithdrawFrDecimals(hex) {
|
|
1683
|
+
const h = normalizeHex$1(hex).padStart(64, '0').slice(-64);
|
|
1684
|
+
const hi = BigInt(`0x${h.slice(0, 32)}`);
|
|
1685
|
+
const lo = BigInt(`0x${h.slice(32, 64)}`);
|
|
1686
|
+
return { hi: hi.toString(10), lo: lo.toString(10) };
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Stellar contract id (`C…`) → two circom field decimals for `asset[0]`, `asset[1]` (same 32-byte split as accounts).
|
|
1690
|
+
*/
|
|
1691
|
+
function stellarContractAddressToAssetFrDecimals(address) {
|
|
1692
|
+
const raw = stellarSdk.StrKey.decodeContract(address);
|
|
1693
|
+
const hex = Buffer.from(raw).toString('hex');
|
|
1694
|
+
const { hi, lo } = ed25519PubkeyPayloadHexToWithdrawFrDecimals(hex);
|
|
1695
|
+
return [hi, lo];
|
|
1696
|
+
}
|
|
1697
|
+
/** Uniform random `Fr` as decimal (32 random bytes, mod r). For Poseidon-only inputs (e.g. nullifiers). */
|
|
1698
|
+
function randomFrDecimal() {
|
|
1699
|
+
const hex = generateRandomScalarHex32();
|
|
1700
|
+
const v = BigInt(`0x${normalizeHex$1(hex)}`);
|
|
1701
|
+
return (v % BN254_SCALAR_MOD).toString(10);
|
|
1702
|
+
}
|
|
1703
|
+
/** Random scalar < 2^253 for BabyJub ECDH / `Num2Bits(253)` (uses {@link generateRandomScalarHex32}). */
|
|
1704
|
+
function randomFrDecimal253() {
|
|
1705
|
+
const hex = generateRandomScalarHex32();
|
|
1706
|
+
return BigInt(`0x${normalizeHex$1(hex)}`).toString(10);
|
|
1707
|
+
}
|
|
1708
|
+
/** Spend-scalar hex in `(0, BabyJub subgroup order)` so dummy withdraws satisfy `LessThan(l)`. */
|
|
1709
|
+
function randomCanonicalBabyJubScalarHex() {
|
|
1710
|
+
const hex = generateRandomScalarHex32();
|
|
1711
|
+
const reduced = canonicalBabyJubScalarFromInteger(BigInt(`0x${normalizeHex$1(hex)}`));
|
|
1712
|
+
return reduced.toString(16).padStart(64, '0');
|
|
1713
|
+
}
|
|
1714
|
+
function zerosTreeSiblings() {
|
|
1715
|
+
return Array(TRANSACTION_TREE_DEPTH).fill('0');
|
|
1716
|
+
}
|
|
1717
|
+
function dummyWithdraw(wasm) {
|
|
1718
|
+
const nullifier = randomFrDecimal();
|
|
1719
|
+
const secretHex = generateRandomScalarHex32();
|
|
1720
|
+
const secret = (BigInt(`0x${normalizeHex$1(secretHex)}`) % BN254_SCALAR_MOD).toString(10);
|
|
1721
|
+
const scalarHex = randomCanonicalBabyJubScalarHex();
|
|
1722
|
+
const privKeyScalar = scalarHexToFrDecimal(scalarHex);
|
|
1723
|
+
const pt = wasm.ecdhEphemeralPublicKeyFromScalarHex(scalarHex);
|
|
1724
|
+
return {
|
|
1725
|
+
value: '0',
|
|
1726
|
+
nullifier,
|
|
1727
|
+
secret,
|
|
1728
|
+
asset: ['0', '0'],
|
|
1729
|
+
applicationId: '0',
|
|
1730
|
+
ownerPub: [coordHexToDecimal(pt.x), coordHexToDecimal(pt.y)],
|
|
1731
|
+
privKeyScalar,
|
|
1732
|
+
paddingRandom: randomFrDecimal(),
|
|
1733
|
+
escrowNonce: '0',
|
|
1734
|
+
recipientStellar: ['0', '0'],
|
|
1735
|
+
stateSiblings: zerosTreeSiblings(),
|
|
1736
|
+
stateIndex: '0',
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
function dummyDeposit(wasm) {
|
|
1740
|
+
const nullifier = randomFrDecimal();
|
|
1741
|
+
const ephemeralKeyScalar = randomFrDecimal253();
|
|
1742
|
+
const skHex = generateRandomScalarHex32();
|
|
1743
|
+
const pt = wasm.ecdhEphemeralPublicKeyFromScalarHex(skHex);
|
|
1744
|
+
return {
|
|
1745
|
+
value: '0',
|
|
1746
|
+
nullifier,
|
|
1747
|
+
ephemeralKeyScalar,
|
|
1748
|
+
asset: ['0', '0'],
|
|
1749
|
+
applicationId: '0',
|
|
1750
|
+
recipientPublicKeys: [coordHexToDecimal(pt.x), coordHexToDecimal(pt.y)],
|
|
1751
|
+
escrowNonce: '0',
|
|
1752
|
+
recipientStellar: ['0', '0'],
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
function resolveWithdraw(slot, wasm) {
|
|
1756
|
+
return slot === 'dummy' ? dummyWithdraw(wasm) : slot;
|
|
1757
|
+
}
|
|
1758
|
+
function resolveDeposit(slot, wasm) {
|
|
1759
|
+
return slot === 'dummy' ? dummyDeposit(wasm) : slot;
|
|
1760
|
+
}
|
|
1761
|
+
function padWithdrawSlots(slots, nIns) {
|
|
1762
|
+
if (slots.length > nIns) {
|
|
1763
|
+
throw new Error(`withdrawal-transaction-input: expected at most ${nIns} withdraw slots`);
|
|
1764
|
+
}
|
|
1765
|
+
return [...slots, ...Array.from({ length: nIns - slots.length }, () => 'dummy')];
|
|
1766
|
+
}
|
|
1767
|
+
function padDepositSlots(slots, nOuts) {
|
|
1768
|
+
if (slots.length > nOuts) {
|
|
1769
|
+
throw new Error(`withdrawal-transaction-input: expected at most ${nOuts} deposit slots`);
|
|
1770
|
+
}
|
|
1771
|
+
return [...slots, ...Array.from({ length: nOuts - slots.length }, () => 'dummy')];
|
|
1772
|
+
}
|
|
1773
|
+
function buildTransactionWitnessInput(publicParams, publicLegs, withdrawSlots, depositSlots, audit, wasm, dims = {}) {
|
|
1774
|
+
const nIns = dims.nIns ?? TRANSACTION_N_INS;
|
|
1775
|
+
const nOuts = dims.nOuts ?? TRANSACTION_N_OUTS;
|
|
1776
|
+
const publicNInputs = dims.publicNInputs ?? TRANSACTION_PUBLIC_N_INPUTS;
|
|
1777
|
+
const publicNOutputs = dims.publicNOutputs ?? TRANSACTION_PUBLIC_N_OUTPUTS;
|
|
1778
|
+
const paddedWithdraws = padWithdrawSlots(withdrawSlots, nIns);
|
|
1779
|
+
const paddedDeposits = padDepositSlots(depositSlots, nOuts);
|
|
1780
|
+
const paddedLegs = padPublicLegs(publicLegs, publicNInputs, publicNOutputs);
|
|
1781
|
+
const withdraws = paddedWithdraws.map((slot) => resolveWithdraw(slot, wasm));
|
|
1782
|
+
const deposits = paddedDeposits.map((slot) => resolveDeposit(slot, wasm));
|
|
1783
|
+
const appIds = resolveSlotApplicationIds(audit, paddedWithdraws, paddedDeposits);
|
|
1784
|
+
return {
|
|
1785
|
+
stateRoot: publicParams.stateRoot,
|
|
1786
|
+
withdrawAddressHi: publicParams.withdrawAddressHi,
|
|
1787
|
+
withdrawAddressLo: publicParams.withdrawAddressLo,
|
|
1788
|
+
escrowRecipientHi: publicParams.escrowRecipientHi ?? '0',
|
|
1789
|
+
escrowRecipientLo: publicParams.escrowRecipientLo ?? '0',
|
|
1790
|
+
sweepOutputOwnerPubX: publicParams.sweepOutputOwnerPubX ?? '0',
|
|
1791
|
+
sweepOutputOwnerPubY: publicParams.sweepOutputOwnerPubY ?? '0',
|
|
1792
|
+
privKeyScalars: withdraws.map((w) => w.privKeyScalar),
|
|
1793
|
+
ownerPubs: withdraws.map((w) => w.ownerPub),
|
|
1794
|
+
paddingRandoms: withdraws.map((w) => w.paddingRandom),
|
|
1795
|
+
withdrawnValues: withdraws.map((w) => w.value),
|
|
1796
|
+
withdrawnNullifiers: withdraws.map((w) => w.nullifier),
|
|
1797
|
+
withdrawnSecrets: withdraws.map((w) => w.secret),
|
|
1798
|
+
withdrawnAssets: withdraws.map((w) => w.asset),
|
|
1799
|
+
withdrawnEscrowNonces: withdraws.map((w) => w.escrowNonce),
|
|
1800
|
+
inputRecipientStellar: withdraws.map((w) => w.recipientStellar),
|
|
1801
|
+
stateSiblings: withdraws.map((w) => w.stateSiblings),
|
|
1802
|
+
stateIndex: withdraws.map((w) => w.stateIndex),
|
|
1803
|
+
depositedValues: deposits.map((d) => d.value),
|
|
1804
|
+
depositedNullifiers: deposits.map((d) => d.nullifier),
|
|
1805
|
+
depositedAssets: deposits.map((d) => d.asset),
|
|
1806
|
+
depositedEphemeralKeyScalars: deposits.map((d) => d.ephemeralKeyScalar),
|
|
1807
|
+
depositedRecipientPublicKeys: deposits.map((d) => d.recipientPublicKeys),
|
|
1808
|
+
depositedEscrowNonces: deposits.map((d) => d.escrowNonce ?? '0'),
|
|
1809
|
+
outputRecipientStellar: deposits.map((d) => d.recipientStellar ?? ['0', '0']),
|
|
1810
|
+
inputApplicationIds: appIds.inputApplicationIds,
|
|
1811
|
+
outputApplicationIds: appIds.outputApplicationIds,
|
|
1812
|
+
auditEphemeralScalar: audit.auditEphemeralScalar,
|
|
1813
|
+
noteAuditPublicKey: audit.noteAuditPublicKey,
|
|
1814
|
+
publicWithdrawnAssets: paddedLegs.publicWithdrawnAssets,
|
|
1815
|
+
publicDepositedAssets: paddedLegs.publicDepositedAssets,
|
|
1816
|
+
publicDeposits: paddedLegs.publicDeposits,
|
|
1817
|
+
publicWithdrawals: paddedLegs.publicWithdrawals,
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
/** `stpl1…` stealth address → `[x, y]` as decimal field strings for `depositedRecipientPublicKeys`. */
|
|
1821
|
+
function recipientPublicKeysDecimalFromStealthAddress(stealthAddress) {
|
|
1822
|
+
const { x, y } = decodeStealthAddress(stealthAddress);
|
|
1823
|
+
return [coordHexToDecimal(x), coordHexToDecimal(y)];
|
|
1824
|
+
}
|
|
1825
|
+
/** First withdraw leg: Merkle witness + owner public key (hex) and matching scalar. */
|
|
1826
|
+
function withdrawObjectFromMerkleWitness(witness, ownerPubHex, applicationId, privKeyScalar) {
|
|
1827
|
+
return {
|
|
1828
|
+
value: witness.value,
|
|
1829
|
+
nullifier: witness.nullifier,
|
|
1830
|
+
secret: witness.secret,
|
|
1831
|
+
asset: witness.withdrawnAsset,
|
|
1832
|
+
applicationId,
|
|
1833
|
+
ownerPub: [coordHexToDecimal(ownerPubHex.x), coordHexToDecimal(ownerPubHex.y)],
|
|
1834
|
+
privKeyScalar,
|
|
1835
|
+
paddingRandom: randomFrDecimal(),
|
|
1836
|
+
escrowNonce: '0',
|
|
1837
|
+
recipientStellar: ['0', '0'],
|
|
1838
|
+
stateSiblings: witness.stateSiblings,
|
|
1839
|
+
stateIndex: witness.stateIndex,
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
const FR_SIZE$1 = 32;
|
|
1844
|
+
const CIPHERTEXT_BLOB_HEADER_BYTES = 4;
|
|
1845
|
+
const FR_MODULUS$1 = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
|
|
1846
|
+
function moduleFr$1(value) {
|
|
1847
|
+
const reduced = value % FR_MODULUS$1;
|
|
1848
|
+
return reduced >= 0n ? reduced : reduced + FR_MODULUS$1;
|
|
1849
|
+
}
|
|
1850
|
+
function fieldToBytes(value) {
|
|
1851
|
+
let remaining = moduleFr$1(value);
|
|
1852
|
+
const buf = Buffer.alloc(FR_SIZE$1);
|
|
1853
|
+
for (let index = FR_SIZE$1 - 1; index >= 0; index -= 1) {
|
|
1854
|
+
buf[index] = Number(remaining & 0xffn);
|
|
1855
|
+
remaining >>= 8n;
|
|
1856
|
+
}
|
|
1857
|
+
return buf;
|
|
1858
|
+
}
|
|
1859
|
+
function decimalOrHexToBigint(value) {
|
|
1860
|
+
const trimmed = value.trim();
|
|
1861
|
+
if (/^0x/i.test(trimmed) || /^[0-9a-f]{64}$/i.test(trimmed)) {
|
|
1862
|
+
return BigInt(`0x${trimmed.replace(/^0x/i, '')}`);
|
|
1863
|
+
}
|
|
1864
|
+
return BigInt(trimmed);
|
|
1865
|
+
}
|
|
1866
|
+
function encodeCiphertextBlob(fields) {
|
|
1867
|
+
const prefix = Buffer.alloc(CIPHERTEXT_BLOB_HEADER_BYTES);
|
|
1868
|
+
prefix.writeUInt32BE(fields.length);
|
|
1869
|
+
return Buffer.concat([
|
|
1870
|
+
prefix,
|
|
1871
|
+
...fields.map((field) => fieldToBytes(decimalOrHexToBigint(field))),
|
|
1872
|
+
]);
|
|
1873
|
+
}
|
|
1874
|
+
function encodeTransactionCiphertextBlob(parameters) {
|
|
1875
|
+
const expected = ciphertextFieldCount(parameters.layout);
|
|
1876
|
+
const fields = [];
|
|
1877
|
+
for (const slot of parameters.auditCiphertexts) {
|
|
1878
|
+
fields.push(...slot);
|
|
1879
|
+
}
|
|
1880
|
+
for (const note of parameters.outputNoteCiphertexts) {
|
|
1881
|
+
fields.push(...note);
|
|
1882
|
+
}
|
|
1883
|
+
if (fields.length !== expected) {
|
|
1884
|
+
throw new Error(`ciphertext blob expected ${String(expected)} fields, got ${String(fields.length)}`);
|
|
1885
|
+
}
|
|
1886
|
+
return encodeCiphertextBlob(fields);
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
const NOTE_AUDIT_LEN = 12;
|
|
1890
|
+
const STREAM_DOMAIN = 1n;
|
|
1891
|
+
const FR_SIZE = 32;
|
|
1892
|
+
const FR_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
|
|
1893
|
+
const ONE = 1n;
|
|
1894
|
+
const ZERO = 0n;
|
|
1895
|
+
let primitivesPromise;
|
|
1896
|
+
function primitives() {
|
|
1897
|
+
primitivesPromise ?? (primitivesPromise = Promise.all([
|
|
1898
|
+
circomlibjs.buildBabyjub(),
|
|
1899
|
+
circomlibjs.buildMimc7(),
|
|
1900
|
+
circomlibjs.buildPoseidon(),
|
|
1901
|
+
]).then(([babyJub, mimc7, poseidon]) => ({ babyJub, mimc7, poseidon })));
|
|
1902
|
+
return primitivesPromise;
|
|
1903
|
+
}
|
|
1904
|
+
function normalizeHex(value) {
|
|
1905
|
+
return value.trim().replace(/^0x/i, '').padStart(FR_SIZE * 2, '0');
|
|
1906
|
+
}
|
|
1907
|
+
function bigintFromHex(value) {
|
|
1908
|
+
return BigInt(`0x${normalizeHex(value)}`);
|
|
1909
|
+
}
|
|
1910
|
+
function bigintFromDecimal(value) {
|
|
1911
|
+
const parsed = BigInt(value);
|
|
1912
|
+
return parsed % FR_MODULUS;
|
|
1913
|
+
}
|
|
1914
|
+
function bigintToFrHex(value) {
|
|
1915
|
+
let v = moduleFr(value);
|
|
1916
|
+
const buf = Buffer.alloc(FR_SIZE);
|
|
1917
|
+
for (let index = FR_SIZE - 1; index >= 0; index -= 1) {
|
|
1918
|
+
buf[index] = Number(v & 0xffn);
|
|
1919
|
+
v >>= 8n;
|
|
1920
|
+
}
|
|
1921
|
+
return buf.toString('hex');
|
|
1922
|
+
}
|
|
1923
|
+
function frDecimalToHex(value) {
|
|
1924
|
+
return bigintToFrHex(bigintFromDecimal(value));
|
|
1925
|
+
}
|
|
1926
|
+
function frDecimalFromBigint(value) {
|
|
1927
|
+
const reduced = value >= ZERO ? value % FR_MODULUS : (value % FR_MODULUS) + FR_MODULUS;
|
|
1928
|
+
return reduced.toString(10);
|
|
1929
|
+
}
|
|
1930
|
+
function fieldBigint(field, value) {
|
|
1931
|
+
return BigInt(field.toString(value));
|
|
1932
|
+
}
|
|
1933
|
+
function moduleFr(value) {
|
|
1934
|
+
const reduced = value % FR_MODULUS;
|
|
1935
|
+
return reduced >= ZERO ? reduced : reduced + FR_MODULUS;
|
|
1936
|
+
}
|
|
1937
|
+
function mimcHash(parameters) {
|
|
1938
|
+
return fieldBigint(parameters.mimc7.F, parameters.mimc7.hash(parameters.mimc7.F.e(parameters.message), parameters.mimc7.F.e(parameters.key)));
|
|
1939
|
+
}
|
|
1940
|
+
function poseidonHash(poseidon, inputs) {
|
|
1941
|
+
return fieldBigint(poseidon.F, poseidon(inputs.map((input) => poseidon.F.e(input))));
|
|
1942
|
+
}
|
|
1943
|
+
function deriveEncCipherKey(poseidon, sharedX) {
|
|
1944
|
+
return poseidonHash(poseidon, [DOM_ENC, sharedX]);
|
|
1945
|
+
}
|
|
1946
|
+
function sharedKeyFromDepositor(parameters) {
|
|
1947
|
+
const scalarHex = frDecimalToHex(parameters.ephemeralKeyScalarDecimal);
|
|
1948
|
+
const shared = parameters.ecdhShared(scalarHex, frDecimalToHex(parameters.recipientPublicKey[0]), frDecimalToHex(parameters.recipientPublicKey[1]));
|
|
1949
|
+
return [bigintFromHex(shared.x), bigintFromHex(shared.y)];
|
|
1950
|
+
}
|
|
1951
|
+
function poseidonMacTag(parameters) {
|
|
1952
|
+
return poseidonHash(parameters.poseidon, [
|
|
1953
|
+
parameters.domain,
|
|
1954
|
+
parameters.key,
|
|
1955
|
+
parameters.nonce,
|
|
1956
|
+
...parameters.message,
|
|
1957
|
+
]);
|
|
1958
|
+
}
|
|
1959
|
+
function outputNoteTag(parameters) {
|
|
1960
|
+
return poseidonMacTag({
|
|
1961
|
+
poseidon: parameters.poseidon,
|
|
1962
|
+
domain: DOM_OUTPUT_NOTE_TAG,
|
|
1963
|
+
key: parameters.key,
|
|
1964
|
+
nonce: parameters.nonce,
|
|
1965
|
+
message: parameters.plaintext,
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
function encryptFields(parameters) {
|
|
1969
|
+
return parameters.plaintext.map((field, index) => {
|
|
1970
|
+
const seed = poseidonHash(parameters.poseidon, [
|
|
1971
|
+
parameters.nonce,
|
|
1972
|
+
STREAM_DOMAIN,
|
|
1973
|
+
BigInt(index),
|
|
1974
|
+
]);
|
|
1975
|
+
const stream = mimcHash({
|
|
1976
|
+
mimc7: parameters.mimc7,
|
|
1977
|
+
message: seed,
|
|
1978
|
+
key: parameters.key,
|
|
1979
|
+
});
|
|
1980
|
+
return moduleFr(field + stream);
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
function plaintextBigints(plaintext) {
|
|
1984
|
+
return [
|
|
1985
|
+
bigintFromDecimal(plaintext.value),
|
|
1986
|
+
bigintFromDecimal(plaintext.assetHi),
|
|
1987
|
+
bigintFromDecimal(plaintext.assetLo),
|
|
1988
|
+
bigintFromDecimal(plaintext.nullifier),
|
|
1989
|
+
bigintFromDecimal(plaintext.secret),
|
|
1990
|
+
bigintFromDecimal(plaintext.applicationId),
|
|
1991
|
+
];
|
|
1992
|
+
}
|
|
1993
|
+
async function secretFromDepositEphemeralScalarDecimal(ephemeralKeyScalarDecimal) {
|
|
1994
|
+
const { poseidon } = await primitives();
|
|
1995
|
+
const secret = poseidonHash(poseidon, [bigintFromDecimal(ephemeralKeyScalarDecimal)]);
|
|
1996
|
+
return frDecimalFromBigint(secret);
|
|
1997
|
+
}
|
|
1998
|
+
async function encryptOutputNoteForDeposit(parameters) {
|
|
1999
|
+
const { babyJub, mimc7, poseidon } = await primitives();
|
|
2000
|
+
const secret = await secretFromDepositEphemeralScalarDecimal(parameters.deposit.ephemeralKeyScalar);
|
|
2001
|
+
const plaintext = {
|
|
2002
|
+
value: parameters.deposit.value,
|
|
2003
|
+
assetHi: parameters.deposit.asset[0],
|
|
2004
|
+
assetLo: parameters.deposit.asset[1],
|
|
2005
|
+
nullifier: parameters.deposit.nullifier,
|
|
2006
|
+
secret,
|
|
2007
|
+
applicationId: parameters.deposit.applicationId,
|
|
2008
|
+
};
|
|
2009
|
+
const [sharedX, sharedY] = sharedKeyFromDepositor({
|
|
2010
|
+
babyJub,
|
|
2011
|
+
ecdhShared: parameters.ecdhShared,
|
|
2012
|
+
ephemeralKeyScalarDecimal: parameters.deposit.ephemeralKeyScalar,
|
|
2013
|
+
recipientPublicKey: parameters.deposit.recipientPublicKeys,
|
|
2014
|
+
});
|
|
2015
|
+
const key = deriveEncCipherKey(poseidon, sharedX);
|
|
2016
|
+
const nonce = sharedY;
|
|
2017
|
+
const message = plaintextBigints(plaintext);
|
|
2018
|
+
const ciphertext = encryptFields({ mimc7, poseidon, key, nonce, plaintext: message });
|
|
2019
|
+
const tag = outputNoteTag({ poseidon, key, nonce, plaintext: message });
|
|
2020
|
+
return {
|
|
2021
|
+
ciphertext: ciphertext.map((field) => frDecimalFromBigint(field)),
|
|
2022
|
+
tag: frDecimalFromBigint(tag),
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
function ownerModeFromEscrowNonce(escrowNonce) {
|
|
2026
|
+
return bigintFromDecimal(escrowNonce) === ZERO ? ZERO : ONE;
|
|
2027
|
+
}
|
|
2028
|
+
function emptyFlagFromValue(value) {
|
|
2029
|
+
return bigintFromDecimal(value) === ZERO ? ONE : ZERO;
|
|
2030
|
+
}
|
|
2031
|
+
function precommitmentFromOpening(parameters) {
|
|
2032
|
+
const ownerKeyHash = poseidonHash(parameters.poseidon, [
|
|
2033
|
+
parameters.ownerPub[0],
|
|
2034
|
+
parameters.ownerPub[1],
|
|
2035
|
+
]);
|
|
2036
|
+
return poseidonHash(parameters.poseidon, [
|
|
2037
|
+
parameters.secret,
|
|
2038
|
+
parameters.ownerMode,
|
|
2039
|
+
ownerKeyHash,
|
|
2040
|
+
]);
|
|
2041
|
+
}
|
|
2042
|
+
async function encryptNoteAuditSlot(parameters) {
|
|
2043
|
+
if (parameters.plaintext.length !== NOTE_AUDIT_LEN) {
|
|
2044
|
+
throw new Error(`encryptNoteAuditSlot: expected ${String(NOTE_AUDIT_LEN)} limbs`);
|
|
2045
|
+
}
|
|
2046
|
+
const { babyJub, mimc7, poseidon } = await primitives();
|
|
2047
|
+
const [sharedX, sharedY] = sharedKeyFromDepositor({
|
|
2048
|
+
babyJub,
|
|
2049
|
+
ecdhShared: parameters.ecdhShared,
|
|
2050
|
+
ephemeralKeyScalarDecimal: parameters.auditEphemeralScalar,
|
|
2051
|
+
recipientPublicKey: parameters.auditPublicKey,
|
|
2052
|
+
});
|
|
2053
|
+
const key = deriveEncCipherKey(poseidon, sharedX);
|
|
2054
|
+
const nonce = sharedY;
|
|
2055
|
+
const message = parameters.plaintext.map((field) => bigintFromDecimal(field));
|
|
2056
|
+
const ciphertext = encryptFields({ mimc7, poseidon, key, nonce, plaintext: message });
|
|
2057
|
+
const tag = poseidonMacTag({
|
|
2058
|
+
poseidon,
|
|
2059
|
+
domain: DOM_AUDIT_TAG,
|
|
2060
|
+
key,
|
|
2061
|
+
nonce,
|
|
2062
|
+
message,
|
|
2063
|
+
});
|
|
2064
|
+
return {
|
|
2065
|
+
ciphertext: ciphertext.map((field) => frDecimalFromBigint(field)),
|
|
2066
|
+
tag: frDecimalFromBigint(tag),
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
function auditPlaintextForWithdraw(parameters) {
|
|
2070
|
+
const value = parameters.witness.withdrawnValues[parameters.index] ?? '0';
|
|
2071
|
+
const ownerPub = parameters.witness.ownerPubs[parameters.index] ?? ['0', '0'];
|
|
2072
|
+
const escrowNonce = parameters.witness.withdrawnEscrowNonces[parameters.index] ?? '0';
|
|
2073
|
+
const secret = bigintFromDecimal(parameters.witness.withdrawnSecrets[parameters.index] ?? '0');
|
|
2074
|
+
const ownerMode = ownerModeFromEscrowNonce(escrowNonce);
|
|
2075
|
+
const precommitment = precommitmentFromOpening({
|
|
2076
|
+
poseidon: parameters.poseidon,
|
|
2077
|
+
secret,
|
|
2078
|
+
ownerMode,
|
|
2079
|
+
ownerPub: [bigintFromDecimal(ownerPub[0]), bigintFromDecimal(ownerPub[1])],
|
|
2080
|
+
});
|
|
2081
|
+
const asset = parameters.witness.withdrawnAssets[parameters.index] ?? ['0', '0'];
|
|
2082
|
+
const stellar = parameters.witness.inputRecipientStellar[parameters.index] ?? ['0', '0'];
|
|
2083
|
+
return [
|
|
2084
|
+
frDecimalFromBigint(emptyFlagFromValue(value)),
|
|
2085
|
+
value,
|
|
2086
|
+
asset[0],
|
|
2087
|
+
asset[1],
|
|
2088
|
+
frDecimalFromBigint(precommitment),
|
|
2089
|
+
parameters.witness.withdrawnNullifiers[parameters.index] ?? '0',
|
|
2090
|
+
ownerPub[0],
|
|
2091
|
+
ownerPub[1],
|
|
2092
|
+
parameters.witness.inputApplicationIds[parameters.index] ?? '0',
|
|
2093
|
+
stellar[0],
|
|
2094
|
+
stellar[1],
|
|
2095
|
+
escrowNonce,
|
|
2096
|
+
];
|
|
2097
|
+
}
|
|
2098
|
+
async function auditPlaintextForDeposit(parameters) {
|
|
2099
|
+
const value = parameters.witness.depositedValues[parameters.index] ?? '0';
|
|
2100
|
+
const ownerPub = parameters.witness.depositedRecipientPublicKeys[parameters.index] ?? ['0', '0'];
|
|
2101
|
+
const escrowNonce = parameters.witness.depositedEscrowNonces[parameters.index] ?? '0';
|
|
2102
|
+
const secret = poseidonHash(parameters.poseidon, [
|
|
2103
|
+
bigintFromDecimal(parameters.witness.depositedEphemeralKeyScalars[parameters.index] ?? '0'),
|
|
2104
|
+
]);
|
|
2105
|
+
const ownerMode = ownerModeFromEscrowNonce(escrowNonce);
|
|
2106
|
+
const precommitment = precommitmentFromOpening({
|
|
2107
|
+
poseidon: parameters.poseidon,
|
|
2108
|
+
secret,
|
|
2109
|
+
ownerMode,
|
|
2110
|
+
ownerPub: [bigintFromDecimal(ownerPub[0]), bigintFromDecimal(ownerPub[1])],
|
|
2111
|
+
});
|
|
2112
|
+
const asset = parameters.witness.depositedAssets[parameters.index] ?? ['0', '0'];
|
|
2113
|
+
const stellar = parameters.witness.outputRecipientStellar[parameters.index] ?? ['0', '0'];
|
|
2114
|
+
return [
|
|
2115
|
+
frDecimalFromBigint(emptyFlagFromValue(value)),
|
|
2116
|
+
value,
|
|
2117
|
+
asset[0],
|
|
2118
|
+
asset[1],
|
|
2119
|
+
frDecimalFromBigint(precommitment),
|
|
2120
|
+
parameters.witness.depositedNullifiers[parameters.index] ?? '0',
|
|
2121
|
+
ownerPub[0],
|
|
2122
|
+
ownerPub[1],
|
|
2123
|
+
parameters.witness.outputApplicationIds[parameters.index] ?? '0',
|
|
2124
|
+
stellar[0],
|
|
2125
|
+
stellar[1],
|
|
2126
|
+
escrowNonce,
|
|
2127
|
+
];
|
|
2128
|
+
}
|
|
2129
|
+
async function encryptTransactionCiphertextBlob(parameters) {
|
|
2130
|
+
const { poseidon } = await primitives();
|
|
2131
|
+
const nIns = parameters.witness.withdrawnValues.length;
|
|
2132
|
+
const nOuts = parameters.witness.depositedValues.length;
|
|
2133
|
+
const nAuditSlots = parameters.layout.nAuditSlots;
|
|
2134
|
+
const auditCiphertexts = [];
|
|
2135
|
+
for (let slot = 0; slot < nAuditSlots; slot += 1) {
|
|
2136
|
+
const plaintext = slot < nIns
|
|
2137
|
+
? auditPlaintextForWithdraw({
|
|
2138
|
+
poseidon,
|
|
2139
|
+
witness: parameters.witness,
|
|
2140
|
+
index: slot,
|
|
2141
|
+
})
|
|
2142
|
+
: await auditPlaintextForDeposit({
|
|
2143
|
+
poseidon,
|
|
2144
|
+
witness: parameters.witness,
|
|
2145
|
+
index: slot - nIns,
|
|
2146
|
+
});
|
|
2147
|
+
const encrypted = await encryptNoteAuditSlot({
|
|
2148
|
+
auditPublicKey: parameters.witness.noteAuditPublicKey,
|
|
2149
|
+
auditEphemeralScalar: parameters.witness.auditEphemeralScalar,
|
|
2150
|
+
plaintext,
|
|
2151
|
+
ecdhShared: parameters.ecdhShared,
|
|
2152
|
+
});
|
|
2153
|
+
auditCiphertexts.push(encrypted.ciphertext);
|
|
2154
|
+
}
|
|
2155
|
+
const outputNoteCiphertexts = [];
|
|
2156
|
+
for (let index = 0; index < nOuts; index += 1) {
|
|
2157
|
+
const deposit = {
|
|
2158
|
+
value: parameters.witness.depositedValues[index] ?? '0',
|
|
2159
|
+
nullifier: parameters.witness.depositedNullifiers[index] ?? '0',
|
|
2160
|
+
ephemeralKeyScalar: parameters.witness.depositedEphemeralKeyScalars[index] ?? '0',
|
|
2161
|
+
asset: parameters.witness.depositedAssets[index] ?? ['0', '0'],
|
|
2162
|
+
applicationId: parameters.witness.outputApplicationIds[index] ?? '0',
|
|
2163
|
+
recipientPublicKeys: parameters.witness.depositedRecipientPublicKeys[index] ?? ['0', '0'],
|
|
2164
|
+
};
|
|
2165
|
+
const encrypted = await encryptOutputNoteForDeposit({
|
|
2166
|
+
deposit,
|
|
2167
|
+
ecdhShared: parameters.ecdhShared,
|
|
2168
|
+
});
|
|
2169
|
+
outputNoteCiphertexts.push([...encrypted.ciphertext]);
|
|
2170
|
+
}
|
|
2171
|
+
const blob = encodeTransactionCiphertextBlob({
|
|
2172
|
+
layout: parameters.layout,
|
|
2173
|
+
auditCiphertexts,
|
|
2174
|
+
outputNoteCiphertexts,
|
|
2175
|
+
});
|
|
2176
|
+
return {
|
|
2177
|
+
ciphertextHex: blob.toString('hex'),
|
|
2178
|
+
auditCiphertexts,
|
|
2179
|
+
outputNoteCiphertexts,
|
|
2180
|
+
};
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
/** Stroops amount as a decimal field string. Reject JS `number` so 18-decimal values cannot silently narrow. */
|
|
2184
|
+
function coinValueDecimal(amount) {
|
|
2185
|
+
if (typeof amount === "number") {
|
|
2186
|
+
throw new TypeError("coin value must be bigint or decimal string, not number");
|
|
2187
|
+
}
|
|
2188
|
+
if (typeof amount === "bigint") {
|
|
2189
|
+
if (amount < 0n) {
|
|
2190
|
+
throw new RangeError("amount must be a non-negative integer");
|
|
2191
|
+
}
|
|
2192
|
+
return amount.toString(10);
|
|
2193
|
+
}
|
|
2194
|
+
if (typeof amount !== "string") {
|
|
2195
|
+
throw new TypeError("coin value must be bigint or decimal string, not number");
|
|
2196
|
+
}
|
|
2197
|
+
const trimmed = amount.trim();
|
|
2198
|
+
if (!/^[0-9]+$/.test(trimmed)) {
|
|
2199
|
+
throw new TypeError("coin value decimal string must be a non-negative integer");
|
|
2200
|
+
}
|
|
2201
|
+
return trimmed;
|
|
2202
|
+
}
|
|
2203
|
+
function generatedCoinFromWasm(value) {
|
|
2204
|
+
const raw = value;
|
|
2205
|
+
const precommitementHex = raw.precommitementHex ?? raw.precommitement_hex;
|
|
2206
|
+
if (!precommitementHex) {
|
|
2207
|
+
throw new Error("generated coin missing precommitement hex");
|
|
2208
|
+
}
|
|
2209
|
+
return {
|
|
2210
|
+
coin: raw.coin,
|
|
2211
|
+
commitment_hex: raw.commitment_hex,
|
|
2212
|
+
precommitement_hex: precommitementHex,
|
|
2213
|
+
precommitementHex,
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
class PrivacyPoolSDK {
|
|
2217
|
+
constructor(wasm, options, resolved) {
|
|
2218
|
+
this.wasm = wasm;
|
|
2219
|
+
this.options = options;
|
|
2220
|
+
this.zkConfigNonce = resolved.nonce;
|
|
2221
|
+
this.circuitConfig = resolved.config;
|
|
2222
|
+
this.layout = layoutFromCircuitConfig(resolved.config);
|
|
2223
|
+
}
|
|
2224
|
+
getZkConfigNonce() {
|
|
2225
|
+
return this.zkConfigNonce;
|
|
2226
|
+
}
|
|
2227
|
+
getLayout() {
|
|
2228
|
+
return this.layout;
|
|
2229
|
+
}
|
|
2230
|
+
/**
|
|
2231
|
+
* Initialize the SDK by loading WASM modules.
|
|
2232
|
+
*
|
|
2233
|
+
* In Node.js, proving artifacts load from `zkArtifactBaseUrl` or
|
|
2234
|
+
* `ZK_ARTIFACT_BASE_URL`, defaulting to this package's GitHub release.
|
|
2235
|
+
* In browser, pass pre-loaded WASM via options:
|
|
2236
|
+
* ```ts
|
|
2237
|
+
* const sdk = await PrivacyPoolSDK.init({
|
|
2238
|
+
* wasmBinary: await fetch('/pkg/client_sdk_wasm_bg.wasm').then(r => r.arrayBuffer()),
|
|
2239
|
+
* zkArtifactBaseUrl: 'https://github.com/Polynom-Labs/stellar-privacy-layer-contracts/releases/download/v0.8.0',
|
|
2240
|
+
* });
|
|
2241
|
+
* ```
|
|
2242
|
+
*/
|
|
2243
|
+
static async init(options) {
|
|
2244
|
+
const opts = options ?? {};
|
|
2245
|
+
const wasm = await loadWasm(opts.wasmBinary);
|
|
2246
|
+
const resolved = resolveZkCircuitConfig(opts.zkCircuits, opts.zkConfigNonce);
|
|
2247
|
+
const sdk = new PrivacyPoolSDK(wasm, opts, resolved);
|
|
2248
|
+
sdk.circuitArtifacts = await resolveCircuitArtifacts(resolved.config, opts.zkArtifactBaseUrl);
|
|
2249
|
+
return sdk;
|
|
2250
|
+
}
|
|
2251
|
+
/**
|
|
2252
|
+
* Uniform 32-byte scalar as lowercase hex (Web Crypto). Same in browser and Node 19+.
|
|
2253
|
+
*/
|
|
2254
|
+
static generateRandomScalarHex32() {
|
|
2255
|
+
return generateRandomScalarHex32();
|
|
2256
|
+
}
|
|
2257
|
+
/**
|
|
2258
|
+
* Text to sign with a Stellar wallet for spend-key derivation (UTF-8). No WASM required.
|
|
2259
|
+
* The message binds network, pool, registry, and schema version (H6).
|
|
2260
|
+
*/
|
|
2261
|
+
static buildStealthAddressSignMessage(address, domain, nonce = DEFAULT_STEALTH_SIGN_NONCE) {
|
|
2262
|
+
return buildStealthAddressSignMessage(address, domain, nonce);
|
|
2263
|
+
}
|
|
2264
|
+
/**
|
|
2265
|
+
* Generate a new coin with random nullifier, secret, and random owner-pub field elements (dev / self-contained tests).
|
|
2266
|
+
* @param amount Decimal field element as `bigint` or decimal `string` (never JS `number`).
|
|
2267
|
+
* @param assetHiDecimal / assetLoDecimal Decimal Fr strings for Stellar asset contract id (two limbs).
|
|
2268
|
+
*/
|
|
2269
|
+
generateCoin(amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
|
|
2270
|
+
return generatedCoinFromWasm(this.wasm.generateCoin(coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
|
|
2271
|
+
}
|
|
2272
|
+
/**
|
|
2273
|
+
* Generate a coin with the same commitment shape as on-chain deposit: pass owner public key (hex x, y).
|
|
2274
|
+
* @param amount Decimal field element (`bigint` | `string`).
|
|
2275
|
+
*/
|
|
2276
|
+
generateCoinWithOwnerPub(ownerPub, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
|
|
2277
|
+
return generatedCoinFromWasm(this.wasm.generateCoinWithOwnerPubHex(ownerPub.x, ownerPub.y, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
|
|
2278
|
+
}
|
|
2279
|
+
/**
|
|
2280
|
+
* Coin for a depositor `ephemeralKeyScalar` (32-byte hex): `coin.secret = Poseidon255(1)(scalar)` as in `deposit.circom`.
|
|
2281
|
+
* @param amount Decimal field element (`bigint` | `string`).
|
|
2282
|
+
*/
|
|
2283
|
+
generateCoinFromDepositEphemeralScalarHex(scalarHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
|
|
2284
|
+
return generatedCoinFromWasm(this.wasm.generateCoinFromDepositEphemeralScalarHex(scalarHex, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
|
|
2285
|
+
}
|
|
2286
|
+
/**
|
|
2287
|
+
* Aligned deposit coin: `secret = Poseidon₁(scalar)` and owner pub from recipient hex coords.
|
|
2288
|
+
* @param amount Decimal field element (`bigint` | `string`).
|
|
2289
|
+
*/
|
|
2290
|
+
generateCoinForDepositWithOwnerPubHex(scalarHex, ownerXHex, ownerYHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
|
|
2291
|
+
return generatedCoinFromWasm(this.wasm.generateCoinForDepositWithOwnerPubHex(scalarHex, ownerXHex, ownerYHex, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
|
|
2292
|
+
}
|
|
2293
|
+
/**
|
|
2294
|
+
* Merkle root, path, and coin fields for the first withdraw leg (Rust LeanIMT + Poseidon).
|
|
2295
|
+
*/
|
|
2296
|
+
buildWithdrawMerkleWitness(coin, state) {
|
|
2297
|
+
const coinJson = JSON.stringify(coin);
|
|
2298
|
+
const stateJson = JSON.stringify(state);
|
|
2299
|
+
const resultJson = this.wasm.buildWithdrawMerkleWitness(coinJson, stateJson);
|
|
2300
|
+
return JSON.parse(resultJson);
|
|
2301
|
+
}
|
|
2302
|
+
/**
|
|
2303
|
+
* Full `Transaction(20,2,2)` withdrawal proof: one real withdraw + dummies, using coin/state and depositor ECDH point (hex).
|
|
2304
|
+
*
|
|
2305
|
+
* Optional **partial public withdraw**: spend the full coin commitment `coin.value` (V), send `publicWithdrawStroops` (W) to the
|
|
2306
|
+
* Stellar receiver, and re-deposit the remainder (V−W) as a new private note to `changeRecipientStealthAddress` (same circuit balance).
|
|
2307
|
+
*/
|
|
2308
|
+
async proveWithdrawal(coin, state, params) {
|
|
2309
|
+
const witness = this.buildWithdrawMerkleWitness(coin, state);
|
|
2310
|
+
const ownerPubHex = this.ecdhEphemeralPublicKeyFromScalarHex(BigInt(params.privKeyScalar).toString(16).padStart(64, "0"));
|
|
2311
|
+
const w0 = withdrawObjectFromMerkleWitness(witness, ownerPubHex, params.applicationId ?? coin.application_id ?? "0", params.privKeyScalar);
|
|
2312
|
+
const fullV = BigInt(coin.value);
|
|
2313
|
+
let publicWithdrawals;
|
|
2314
|
+
let deposits;
|
|
2315
|
+
if (params.publicWithdrawStroops !== undefined) {
|
|
2316
|
+
const W = params.publicWithdrawStroops;
|
|
2317
|
+
if (params.changeRecipientStealthAddress === undefined) {
|
|
2318
|
+
throw new Error("proveWithdrawal: changeRecipientStealthAddress is required when publicWithdrawStroops is set");
|
|
2319
|
+
}
|
|
2320
|
+
if (W <= 0n || W >= fullV) {
|
|
2321
|
+
throw new Error("proveWithdrawal: publicWithdrawStroops must be strictly between 0 and coin.value");
|
|
2322
|
+
}
|
|
2323
|
+
const change = fullV - W;
|
|
2324
|
+
publicWithdrawals = [W.toString()];
|
|
2325
|
+
const changeDeposit = {
|
|
2326
|
+
value: change.toString(),
|
|
2327
|
+
nullifier: randomFrDecimal(),
|
|
2328
|
+
ephemeralKeyScalar: randomFrDecimal253(),
|
|
2329
|
+
asset: [coin.asset_hi, coin.asset_lo],
|
|
2330
|
+
applicationId: params.applicationId ?? "0",
|
|
2331
|
+
recipientPublicKeys: recipientPublicKeysDecimalFromStealthAddress(params.changeRecipientStealthAddress),
|
|
2332
|
+
};
|
|
2333
|
+
deposits = [changeDeposit];
|
|
2334
|
+
}
|
|
2335
|
+
else {
|
|
2336
|
+
publicWithdrawals = [coin.value];
|
|
2337
|
+
deposits = [];
|
|
2338
|
+
}
|
|
2339
|
+
// On-chain `transact` sends `publicWithdrawals` / `publicWithdrawnAssets` to the withdraw account.
|
|
2340
|
+
const publicLegs = {
|
|
2341
|
+
publicWithdrawnAssets: [[coin.asset_hi, coin.asset_lo]],
|
|
2342
|
+
publicDepositedAssets: [["0", "0"]],
|
|
2343
|
+
publicDeposits: ["0"],
|
|
2344
|
+
publicWithdrawals,
|
|
2345
|
+
};
|
|
2346
|
+
return this.proveTransactionSlots({
|
|
2347
|
+
stateRoot: witness.stateRoot,
|
|
2348
|
+
withdrawAddressHi: params.withdrawAddressHi,
|
|
2349
|
+
withdrawAddressLo: params.withdrawAddressLo,
|
|
2350
|
+
privKeyScalar: params.privKeyScalar,
|
|
2351
|
+
}, publicLegs, [w0], deposits, params.audit ??
|
|
2352
|
+
resolveTransactionAuditParams(params.applicationId ?? coin.application_id ?? DEFAULT_APPLICATION_ID, DEMO_AUDIT_PUBLIC_KEY, this.layout.nAuditSlots));
|
|
2353
|
+
}
|
|
2354
|
+
/**
|
|
2355
|
+
* Convert a snarkjs proof JSON to hex bytes for Soroban.
|
|
2356
|
+
*/
|
|
2357
|
+
proofToHex(proof) {
|
|
2358
|
+
return this.wasm.proofToHex(JSON.stringify(proof));
|
|
2359
|
+
}
|
|
2360
|
+
/**
|
|
2361
|
+
* Convert snarkjs public signals to hex bytes for Soroban.
|
|
2362
|
+
*/
|
|
2363
|
+
publicToHex(publicSignals) {
|
|
2364
|
+
return this.wasm.publicToHex(JSON.stringify(publicSignals));
|
|
2365
|
+
}
|
|
2366
|
+
/**
|
|
2367
|
+
* BabyJubJub ephemeral point from a 32-byte scalar (hex). For custom {@link WithdrawObject} / tests.
|
|
2368
|
+
*/
|
|
2369
|
+
ecdhEphemeralPublicKeyFromScalarHex(scalarHex) {
|
|
2370
|
+
return this.wasm.ecdhEphemeralPublicKeyFromScalarHex(scalarHex);
|
|
2371
|
+
}
|
|
2372
|
+
/** Circuit `ECDH`: scalar (32-byte hex) × recipient BabyJub point → shared key hex coords. */
|
|
2373
|
+
ecdhSharedKey(scalarHex, recipientPubXHex, recipientPubYHex) {
|
|
2374
|
+
return this.wasm.ecdhSharedKey(scalarHex, recipientPubXHex, recipientPubYHex);
|
|
2375
|
+
}
|
|
2376
|
+
/**
|
|
2377
|
+
* `Transaction(20,nIns,nOuts)` proof from high-level legs: maps to witness input (incl. `"dummy"` ECDH via WASM), then Groth16 → Soroban hex.
|
|
2378
|
+
* Slots are padded to the selected ZK config layout (bundled 2×2 or 6×6).
|
|
2379
|
+
*/
|
|
2380
|
+
async proveTransaction(publicParams, publicLegs, withdraws, deposits, audit) {
|
|
2381
|
+
return this.proveTransactionSlots(publicParams, publicLegs, withdraws, deposits, audit);
|
|
2382
|
+
}
|
|
2383
|
+
async proveTransactionSlots(publicParams, publicLegs, withdraws, deposits, audit) {
|
|
2384
|
+
const wasmEcdh = {
|
|
2385
|
+
ecdhEphemeralPublicKeyFromScalarHex: (h) => this.wasm.ecdhEphemeralPublicKeyFromScalarHex(h),
|
|
2386
|
+
};
|
|
2387
|
+
const { nIns, nOuts, publicNInputs, publicNOutputs } = this.layout;
|
|
2388
|
+
const witnessInput = buildTransactionWitnessInput(publicParams, publicLegs, withdraws, deposits, audit, wasmEcdh, { nIns, nOuts, publicNInputs, publicNOutputs });
|
|
2389
|
+
const artifacts = this.circuitArtifacts;
|
|
2390
|
+
if (!artifacts) {
|
|
2391
|
+
throw new Error("PrivacyPoolSDK circuit artifacts are not loaded");
|
|
2392
|
+
}
|
|
2393
|
+
const proved = this.wasm.proveGroth16(artifacts.graph, artifacts.provingKey, artifacts.r1cs, JSON.stringify(witnessInput));
|
|
2394
|
+
const encrypted = await encryptTransactionCiphertextBlob({
|
|
2395
|
+
witness: witnessInput,
|
|
2396
|
+
layout: this.layout,
|
|
2397
|
+
ecdhShared: (priv, pubX, pubY) => this.wasm.ecdhSharedKey(priv, pubX, pubY),
|
|
2398
|
+
});
|
|
2399
|
+
return {
|
|
2400
|
+
proof_hex: proved.proof_hex,
|
|
2401
|
+
public_hex: proved.public_hex,
|
|
2402
|
+
ciphertext_hex: encrypted.ciphertextHex,
|
|
2403
|
+
output_note_ephemeral_scalars: witnessInput.depositedEphemeralKeyScalars,
|
|
2404
|
+
};
|
|
2405
|
+
}
|
|
2406
|
+
/**
|
|
2407
|
+
* Calculate owner-bound nullifier hash: Poseidon(DOM_NULLIFIER, nullifier, privKeyScalar)
|
|
2408
|
+
* @param nullifier Nullifier decimal string from coin data
|
|
2409
|
+
* @param privKeyScalar Spend scalar decimal string
|
|
2410
|
+
* @returns Hex string (0x...) of the hash bytes
|
|
2411
|
+
*/
|
|
2412
|
+
calculateNullifierHash(nullifier, privKeyScalar) {
|
|
2413
|
+
return this.wasm.calculateNullifierHash(nullifier, privKeyScalar);
|
|
2414
|
+
}
|
|
2415
|
+
/**
|
|
2416
|
+
* Ed25519 signature from signing {@link buildStealthAddressSignMessage}: **128 hex chars** (optional `0x`)
|
|
2417
|
+
* or **base64** (64 raw bytes after decode). Domain-separated SHA-256 → scalar → ECDH → `stpl1` Bech32.
|
|
2418
|
+
*/
|
|
2419
|
+
async generateStealthAddressFromStellarSignature(signature, domain) {
|
|
2420
|
+
return stealthAddressFromStellarSignature((h) => this.wasm.ecdhEphemeralPublicKeyFromScalarHex(h), encodeStealthAddress, signature, domain);
|
|
2421
|
+
}
|
|
2422
|
+
encodeDecodedEphemeralKey(decoded) {
|
|
2423
|
+
return encodeDecodedEphemeralKey(decoded);
|
|
2424
|
+
}
|
|
2425
|
+
decodeDecodedEphemeralKey(encoded) {
|
|
2426
|
+
return decodeDecodedEphemeralKey(encoded);
|
|
2427
|
+
}
|
|
2428
|
+
encodeDepositorSharedSecretPreimage(decoded) {
|
|
2429
|
+
return encodeDepositorSharedSecretPreimage(decoded);
|
|
2430
|
+
}
|
|
2431
|
+
decodeDepositorSharedSecretPreimage(encoded) {
|
|
2432
|
+
return decodeDepositorSharedSecretPreimage(encoded);
|
|
2433
|
+
}
|
|
2434
|
+
sharedSecretFromDepositorPreimage(preimage) {
|
|
2435
|
+
return sharedSecretFromDepositorPreimage((a, b, c) => this.wasm.ecdhSharedKey(a, b, c), preimage);
|
|
2436
|
+
}
|
|
2437
|
+
sharedSecretFromRecipientPreimage(preimage) {
|
|
2438
|
+
return sharedSecretFromRecipientPreimage((a, b, c) => this.wasm.ecdhSharedKey(a, b, c), preimage);
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
function signatureBase64ToBytes(signature) {
|
|
2443
|
+
const value = signature.trim();
|
|
2444
|
+
if (/^(0x)?[0-9a-fA-F]{128}$/.test(value)) {
|
|
2445
|
+
return Buffer.from(value.replace(/^0x/i, ''), 'hex');
|
|
2446
|
+
}
|
|
2447
|
+
return Buffer.from(value, 'base64');
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
function addressToScVal(address) {
|
|
2451
|
+
return stellarSdk.nativeToScVal(address, { type: 'address' });
|
|
2452
|
+
}
|
|
2453
|
+
function passageIdHexToScVal(passageIdHex) {
|
|
2454
|
+
const bytes = Buffer.from(passageIdHex.replace(/^0x/, ''), 'hex');
|
|
2455
|
+
return stellarSdk.xdr.ScVal.scvBytes(bytes);
|
|
2456
|
+
}
|
|
2457
|
+
function signatureToScVal(signature) {
|
|
2458
|
+
return stellarSdk.xdr.ScVal.scvBytes(signature);
|
|
2459
|
+
}
|
|
2460
|
+
function helperSubmitWithPassageOperation(helperContractId, params, passageId, expiresAtLedger, signature) {
|
|
2461
|
+
return stellarSdk.Operation.invokeContractFunction({
|
|
2462
|
+
contract: helperContractId,
|
|
2463
|
+
function: 'submit_with_passage',
|
|
2464
|
+
args: [
|
|
2465
|
+
addressToScVal(params.owner),
|
|
2466
|
+
stellarSdk.nativeToScVal(params.nonce, { type: 'u64' }),
|
|
2467
|
+
stellarSdk.xdr.ScVal.scvBytes(params.proofBytes),
|
|
2468
|
+
stellarSdk.xdr.ScVal.scvBytes(params.publicSignalsBytes),
|
|
2469
|
+
passageIdHexToScVal(passageId),
|
|
2470
|
+
stellarSdk.xdr.ScVal.scvU32(expiresAtLedger),
|
|
2471
|
+
signatureToScVal(signatureBase64ToBytes(signature)),
|
|
2472
|
+
],
|
|
2473
|
+
});
|
|
2474
|
+
}
|
|
2475
|
+
async function submitTransaction(params) {
|
|
2476
|
+
const server = new stellarSdk.rpc.Server(params.rpcUrl);
|
|
2477
|
+
const sourceAccount = await server.getAccount(params.source.publicKey());
|
|
2478
|
+
let builder = new stellarSdk.TransactionBuilder(sourceAccount, {
|
|
2479
|
+
fee: params.fee,
|
|
2480
|
+
networkPassphrase: params.networkPassphrase,
|
|
2481
|
+
});
|
|
2482
|
+
for (const operation of params.operations) {
|
|
2483
|
+
builder = builder.addOperation(operation);
|
|
2484
|
+
}
|
|
2485
|
+
const tx = builder.setTimeout(300).build();
|
|
2486
|
+
const prepared = await prepareTransactionAllowingNonRootAuth(server, params.rpcUrl, tx, params.source, params.networkPassphrase)
|
|
2487
|
+
;
|
|
2488
|
+
prepared.sign(params.source);
|
|
2489
|
+
const result = await server.sendTransaction(prepared);
|
|
2490
|
+
const status = String(result.status);
|
|
2491
|
+
if (status !== 'PENDING' && status !== 'SUCCESS') {
|
|
2492
|
+
throw new Error(`transaction submit failed: ${status}`);
|
|
2493
|
+
}
|
|
2494
|
+
await waitForTransaction(server, result.hash);
|
|
2495
|
+
return result.hash;
|
|
2496
|
+
}
|
|
2497
|
+
async function prepareTransactionAllowingNonRootAuth(server, rpcUrl, tx, source, networkPassphrase) {
|
|
2498
|
+
const recording = await simulateTransactionRecordingNonRoot(rpcUrl, tx.toXDR());
|
|
2499
|
+
const validUntilLedgerSeq = recording.latestLedger + 1000;
|
|
2500
|
+
const signedAuth = await Promise.all(recording.auth.map((entry) => {
|
|
2501
|
+
if (entry.credentials().switch()
|
|
2502
|
+
=== stellarSdk.xdr.SorobanCredentialsType.sorobanCredentialsSourceAccount()) {
|
|
2503
|
+
return entry;
|
|
2504
|
+
}
|
|
2505
|
+
return stellarSdk.authorizeEntry(entry, source, validUntilLedgerSeq, networkPassphrase);
|
|
2506
|
+
}));
|
|
2507
|
+
const invokeOp = tx.operations[0];
|
|
2508
|
+
if (tx.operations.length !== 1 || invokeOp.type !== 'invokeHostFunction') {
|
|
2509
|
+
throw new Error('Soroban transactions must contain exactly one invokeHostFunction operation');
|
|
2510
|
+
}
|
|
2511
|
+
const sourceAccount = await server.getAccount(source.publicKey());
|
|
2512
|
+
const signedTx = new stellarSdk.TransactionBuilder(sourceAccount, {
|
|
2513
|
+
fee: tx.fee,
|
|
2514
|
+
networkPassphrase,
|
|
2515
|
+
})
|
|
2516
|
+
.addOperation(stellarSdk.Operation.invokeHostFunction({
|
|
2517
|
+
func: invokeOp.func,
|
|
2518
|
+
auth: signedAuth,
|
|
2519
|
+
source: invokeOp.source,
|
|
2520
|
+
}))
|
|
2521
|
+
.setTimeout(300)
|
|
2522
|
+
.build();
|
|
2523
|
+
const enforcedSimulation = await server.simulateTransaction(signedTx);
|
|
2524
|
+
if (stellarSdk.rpc.Api.isSimulationError(enforcedSimulation)) {
|
|
2525
|
+
throw new Error(`signed authorization simulation failed: ${enforcedSimulation.error}`);
|
|
2526
|
+
}
|
|
2527
|
+
return stellarSdk.rpc.assembleTransaction(signedTx, enforcedSimulation).build();
|
|
2528
|
+
}
|
|
2529
|
+
async function simulateTransactionRecordingNonRoot(rpcUrl, transactionXdr) {
|
|
2530
|
+
const response = await fetch(rpcUrl, {
|
|
2531
|
+
method: 'POST',
|
|
2532
|
+
headers: { 'content-type': 'application/json' },
|
|
2533
|
+
body: JSON.stringify({
|
|
2534
|
+
jsonrpc: '2.0',
|
|
2535
|
+
id: 1,
|
|
2536
|
+
method: 'simulateTransaction',
|
|
2537
|
+
params: {
|
|
2538
|
+
transaction: transactionXdr,
|
|
2539
|
+
authMode: 'record_allow_nonroot',
|
|
2540
|
+
},
|
|
2541
|
+
}),
|
|
2542
|
+
});
|
|
2543
|
+
const body = await response.json();
|
|
2544
|
+
if (!response.ok || body.error) {
|
|
2545
|
+
throw new Error(`transaction simulation failed: ${body.error?.message ?? response.statusText}`);
|
|
2546
|
+
}
|
|
2547
|
+
if (!body.result) {
|
|
2548
|
+
throw new Error('transaction simulation failed: missing RPC result');
|
|
2549
|
+
}
|
|
2550
|
+
if (body.result.error) {
|
|
2551
|
+
throw new Error(`transaction simulation failed: ${body.result.error}`);
|
|
2552
|
+
}
|
|
2553
|
+
const latestLedger = Number(body.result.latestLedger);
|
|
2554
|
+
if (!Number.isFinite(latestLedger)) {
|
|
2555
|
+
throw new Error('transaction simulation failed: missing latest ledger');
|
|
2556
|
+
}
|
|
2557
|
+
return {
|
|
2558
|
+
latestLedger,
|
|
2559
|
+
auth: (body.result.results?.[0]?.auth ?? []).map((entry) => stellarSdk.xdr.SorobanAuthorizationEntry.fromXDR(entry, 'base64')),
|
|
2560
|
+
};
|
|
2561
|
+
}
|
|
2562
|
+
async function waitForTransaction(server, hash) {
|
|
2563
|
+
for (let attempt = 0; attempt < 60; attempt++) {
|
|
2564
|
+
const result = await server.getTransaction(hash);
|
|
2565
|
+
const status = String(result.status);
|
|
2566
|
+
if (status === 'SUCCESS') {
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2569
|
+
if (status === 'FAILED') {
|
|
2570
|
+
throw new Error(`transaction failed: ${hash}`);
|
|
2571
|
+
}
|
|
2572
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2573
|
+
}
|
|
2574
|
+
throw new Error(`transaction did not confirm in time: ${hash}`);
|
|
2575
|
+
}
|
|
2576
|
+
async function submitApprovedKytPassage(params) {
|
|
2577
|
+
if (params.source.publicKey() !== params.owner) {
|
|
2578
|
+
throw new Error('KYT submit source keypair must match pool transact owner');
|
|
2579
|
+
}
|
|
2580
|
+
return submitTransaction({
|
|
2581
|
+
source: params.source,
|
|
2582
|
+
networkPassphrase: params.networkPassphrase,
|
|
2583
|
+
rpcUrl: params.rpcUrl,
|
|
2584
|
+
fee: '12000000',
|
|
2585
|
+
operations: [
|
|
2586
|
+
helperSubmitWithPassageOperation(params.kytSubmitHelperId, params, params.passageId, params.expiresAtLedger, params.signature),
|
|
2587
|
+
],
|
|
2588
|
+
});
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
/** Pool Merkle tree depth (matches `coin::TREE_DEPTH` / `Transaction` circuit). */
|
|
2592
|
+
/** Default coin value in stroops (1 XLM); matches Rust `coin::COIN_VALUE`. */
|
|
2593
|
+
const COIN_VALUE_STROOPS = 1000000000;
|
|
2594
|
+
|
|
2595
|
+
function resolveApplicationIdFromCli(parsed) {
|
|
2596
|
+
return parsed['application-id'] ?? process.env.APPLICATION_ID ?? DEFAULT_APPLICATION_ID;
|
|
2597
|
+
}
|
|
2598
|
+
function resolveZkInit(parsed) {
|
|
2599
|
+
const explicit = parsed['profile'] ?? process.env.ZK_PROFILE;
|
|
2600
|
+
if (explicit && (explicit === '6x6' || explicit === 'six' || explicit === '6')) {
|
|
2601
|
+
return { zkConfigNonce: SIX_BY_SIX_BINDING_ZK_NONCE };
|
|
2602
|
+
}
|
|
2603
|
+
const envNonce = (process.env.ZK_CONFIG_NONCE ?? '').trim();
|
|
2604
|
+
if (envNonce === '6' || envNonce === '7') {
|
|
2605
|
+
return { zkConfigNonce: SIX_BY_SIX_BINDING_ZK_NONCE };
|
|
2606
|
+
}
|
|
2607
|
+
if (envNonce === '3' || envNonce === '2' || envNonce === '') {
|
|
2608
|
+
return { zkConfigNonce: BINDING_ZK_NONCE };
|
|
2609
|
+
}
|
|
2610
|
+
return { zkConfigNonce: BINDING_ZK_NONCE };
|
|
2611
|
+
}
|
|
2612
|
+
async function main() {
|
|
2613
|
+
const args = process.argv.slice(2);
|
|
2614
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
2615
|
+
printUsage();
|
|
2616
|
+
process.exit(0);
|
|
2617
|
+
}
|
|
2618
|
+
const command = args[0];
|
|
2619
|
+
if (command === 'withdraw') {
|
|
2620
|
+
await handleWithdraw(args.slice(1));
|
|
2621
|
+
}
|
|
2622
|
+
else if (command === 'deposit-proof') {
|
|
2623
|
+
await handleDepositProof(args.slice(1));
|
|
2624
|
+
}
|
|
2625
|
+
else if (command === 'generate') {
|
|
2626
|
+
await handleGenerate(args.slice(1));
|
|
2627
|
+
}
|
|
2628
|
+
else if (command === 'random-scalar') {
|
|
2629
|
+
handleRandomScalar();
|
|
2630
|
+
}
|
|
2631
|
+
else if (command === 'stealth-sign-message') {
|
|
2632
|
+
handleStealthSignMessage(args.slice(1));
|
|
2633
|
+
}
|
|
2634
|
+
else if (command === 'stealth-from-signature') {
|
|
2635
|
+
await handleStealthFromSignature(args.slice(1));
|
|
2636
|
+
}
|
|
2637
|
+
else if (command === 'priv-scalar-from-signature') {
|
|
2638
|
+
await handlePrivScalarFromSignature(args.slice(1));
|
|
2639
|
+
}
|
|
2640
|
+
else if (command === 'stealth-pubkey-hex') {
|
|
2641
|
+
handleStealthPubkeyHex(args.slice(1));
|
|
2642
|
+
}
|
|
2643
|
+
else if (command === 'kyt-submit-approved') {
|
|
2644
|
+
await handleKytSubmitApproved(args.slice(1));
|
|
2645
|
+
}
|
|
2646
|
+
else {
|
|
2647
|
+
console.error(`Unknown command: ${command}`);
|
|
2648
|
+
printUsage();
|
|
2649
|
+
process.exit(1);
|
|
2650
|
+
}
|
|
2651
|
+
// Exit explicitly so the process doesn't hang (e.g. snarkjs workers / timers)
|
|
2652
|
+
process.exit(0);
|
|
2653
|
+
}
|
|
2654
|
+
function printUsage() {
|
|
2655
|
+
console.error(`Usage: client-sdk-cli <command> [options]
|
|
2656
|
+
|
|
2657
|
+
Commands:
|
|
2658
|
+
generate Generate a new coin
|
|
2659
|
+
--output, -o <file> Output coin to file (default: stdout)
|
|
2660
|
+
--amount <stroops> Coin value in stroops (u64); default: 1000000000 (1 XLM)
|
|
2661
|
+
--token <C...> Stellar asset contract id (or set TOKEN_ADDRESS)
|
|
2662
|
+
--application-id <dec> Application id (decimal Fr); default: APPLICATION_ID env or 101
|
|
2663
|
+
--scalar <hex> 32-byte depositor ephemeral scalar (64 hex, optional 0x); coin.secret = Poseidon₁(scalar) (deposit.circom)
|
|
2664
|
+
--stealth <stpl1...> With --scalar: ECDH(scalar, recipient) shared secret + aligned commitment (requires WASM ecdhSharedKey)
|
|
2665
|
+
|
|
2666
|
+
random-scalar Print random 32-byte hex scalar with integer < 2^253 (BabyJub / circom)
|
|
2667
|
+
|
|
2668
|
+
stealth-sign-message Print message to sign (Stellar wallet / stellar CLI)
|
|
2669
|
+
--address <G...> Stellar account address (required)
|
|
2670
|
+
--network-passphrase <string> Network passphrase bound into the spend scalar
|
|
2671
|
+
--pool <C...> Pool contract id bound into the spend scalar
|
|
2672
|
+
--registry <C...> Registry contract id bound into the spend scalar
|
|
2673
|
+
--schema-version <u32> Note schema version (default: 1)
|
|
2674
|
+
--nonce <string> Nonce label (default: "main address")
|
|
2675
|
+
|
|
2676
|
+
stealth-from-signature Derive stpl1 stealth address from Ed25519 signature
|
|
2677
|
+
--signature <value> 128 hex chars (optional 0x) or standard base64 (64 bytes)
|
|
2678
|
+
--signature-file <path> Read signature from file (whitespace trimmed)
|
|
2679
|
+
--network-passphrase <string> Spend-scalar domain (same as stealth-sign-message)
|
|
2680
|
+
--pool <C...>
|
|
2681
|
+
--registry <C...>
|
|
2682
|
+
--schema-version <u32>
|
|
2683
|
+
|
|
2684
|
+
priv-scalar-from-signature Print privKeyScalar (decimal Fr) from domain-separated spend digest
|
|
2685
|
+
|
|
2686
|
+
stealth-pubkey-hex Decode a stpl1 stealth address into its BabyJubJub public key
|
|
2687
|
+
--stealth <stpl1...> Stealth address to decode
|
|
2688
|
+
Prints x hex on line 1, y hex on line 2 (registry public_key_x/public_key_y)
|
|
2689
|
+
|
|
2690
|
+
kyt-submit-approved Submit helper.register_passage + pool.transact with signed non-root auth
|
|
2691
|
+
--source-secret <S...> Secret seed for the depositing/withdrawing Stellar account (or SOURCE_SECRET env)
|
|
2692
|
+
--owner <G...> Same account address passed to pool.transact as from
|
|
2693
|
+
--pool <C...> Privacy pool contract id
|
|
2694
|
+
--helper <C...> KYT submit helper contract id
|
|
2695
|
+
--proof <hex> Groth16 proof bytes
|
|
2696
|
+
--public-signals <hex> Public signals bytes
|
|
2697
|
+
--passage-id <hex> KYT passage id
|
|
2698
|
+
--expires-at-ledger <u32> Passage expiration ledger
|
|
2699
|
+
--signature <hex|base64> KYT registry signature
|
|
2700
|
+
--rpc-url <url> Soroban RPC URL
|
|
2701
|
+
--network <testnet|public|local> or --network-passphrase <string>
|
|
2702
|
+
|
|
2703
|
+
withdraw Generate a withdrawal proof
|
|
2704
|
+
--coin <file> Path to coin JSON file
|
|
2705
|
+
--state <file> Path to state JSON file
|
|
2706
|
+
--withdraw-pubkey-hex <hex> Stellar account Ed25519 payload (32 bytes = 64 hex); splits to public withdrawAddressHi/Lo
|
|
2707
|
+
--withdraw-address-hi <dec> Optional: override high u128 (decimal) if not using --withdraw-pubkey-hex
|
|
2708
|
+
--withdraw-address-lo <dec> Optional: override low u128 (decimal)
|
|
2709
|
+
--priv-key-scalar <dec> privKeyScalar (decimal Fr)
|
|
2710
|
+
--ephemeral-x <hex> Depositor ECDH point x (64 hex chars, optional 0x)
|
|
2711
|
+
--ephemeral-y <hex> Depositor ECDH point y (64 hex chars, optional 0x)
|
|
2712
|
+
--public-withdraw-stroops <u64> Optional: amount to withdraw publicly (must be < coin.value). Rest stays in pool as change.
|
|
2713
|
+
--change-stealth <stpl1...> Required with --public-withdraw-stroops: stealth for the change note (same as deposit)
|
|
2714
|
+
--application-id <dec> Application id (decimal Fr); default: APPLICATION_ID env or 101
|
|
2715
|
+
--profile <2x2|6x6> Circuit artifact profile (default: ZK_PROFILE / ZK_CONFIG_NONCE=7 → 6x6)
|
|
2716
|
+
--output-proof <file> Write proof hex to file
|
|
2717
|
+
--output-public <file> Write public signals hex to file
|
|
2718
|
+
--output-ciphertext <file> Write userspace MiMC ciphertext blob hex to file
|
|
2719
|
+
|
|
2720
|
+
deposit-proof Groth16 proof for deposit-only transact (dummy withdraws, 1 deposit + dummy outs)
|
|
2721
|
+
--state-root <dec> Current Merkle root (decimal Fr), must match pool on submit
|
|
2722
|
+
--stealth <stpl1...> Recipient stealth address (decodes to recipient public key)
|
|
2723
|
+
--token <C...> Asset contract id (or set TOKEN_ADDRESS)
|
|
2724
|
+
--coin <file> With --ephemeral-scalar-hex: use coin nullifier/value (aligned with generate --scalar --stealth)
|
|
2725
|
+
--ephemeral-scalar-hex <hex> Same 32-byte hex as deposit / generate --scalar
|
|
2726
|
+
--value <dec> Deposit amount (stroops as decimal Fr); optional if --coin (must match coin)
|
|
2727
|
+
--application-id <dec> Application id (decimal Fr); default: APPLICATION_ID env or 101
|
|
2728
|
+
--profile <2x2|6x6> Circuit artifact profile (default: ZK_PROFILE / ZK_CONFIG_NONCE=7 → 6x6)
|
|
2729
|
+
--output-proof <file> Write proof hex to file
|
|
2730
|
+
--output-public <file> Write public signals hex to file
|
|
2731
|
+
--output-ciphertext <file> Write userspace MiMC ciphertext blob hex to file
|
|
2732
|
+
|
|
2733
|
+
Output (withdraw, deposit-proof):
|
|
2734
|
+
Prints proof_hex on first line and public_hex on second line to stdout.
|
|
2735
|
+
`);
|
|
2736
|
+
}
|
|
2737
|
+
function requireParsedArg(parsed, name) {
|
|
2738
|
+
const value = parsed[name]?.trim();
|
|
2739
|
+
if (!value) {
|
|
2740
|
+
console.error(`Error: --${name} is required`);
|
|
2741
|
+
process.exit(1);
|
|
2742
|
+
}
|
|
2743
|
+
return value;
|
|
2744
|
+
}
|
|
2745
|
+
function networkPassphraseFromArgs(parsed) {
|
|
2746
|
+
if (parsed['network-passphrase']) {
|
|
2747
|
+
return parsed['network-passphrase'];
|
|
2748
|
+
}
|
|
2749
|
+
const network = parsed['network'] ?? 'testnet';
|
|
2750
|
+
if (network === 'testnet') {
|
|
2751
|
+
return stellarSdk.Networks.TESTNET;
|
|
2752
|
+
}
|
|
2753
|
+
if (network === 'public') {
|
|
2754
|
+
return stellarSdk.Networks.PUBLIC;
|
|
2755
|
+
}
|
|
2756
|
+
if (network === 'local') {
|
|
2757
|
+
return 'Standalone Network ; February 2017';
|
|
2758
|
+
}
|
|
2759
|
+
console.error('Error: --network must be testnet, public, or local');
|
|
2760
|
+
process.exit(1);
|
|
2761
|
+
}
|
|
2762
|
+
function hexToBuffer(label, value) {
|
|
2763
|
+
const hex = value.trim().replace(/^0x/i, '');
|
|
2764
|
+
if (!/^[0-9a-fA-F]*$/.test(hex) || hex.length % 2 !== 0) {
|
|
2765
|
+
console.error(`Error: --${label} must be even-length hex`);
|
|
2766
|
+
process.exit(1);
|
|
2767
|
+
}
|
|
2768
|
+
return Buffer.from(hex, 'hex');
|
|
2769
|
+
}
|
|
2770
|
+
function parseArgs(args) {
|
|
2771
|
+
const parsed = {};
|
|
2772
|
+
for (let i = 0; i < args.length; i++) {
|
|
2773
|
+
const arg = args[i];
|
|
2774
|
+
if (arg.startsWith('--')) {
|
|
2775
|
+
const key = arg.slice(2);
|
|
2776
|
+
const value = args[i + 1];
|
|
2777
|
+
if (value && !value.startsWith('--')) {
|
|
2778
|
+
parsed[key] = value;
|
|
2779
|
+
i++;
|
|
2780
|
+
}
|
|
2781
|
+
else {
|
|
2782
|
+
parsed[key] = 'true';
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
else if (arg === '-o' && i + 1 < args.length) {
|
|
2786
|
+
parsed['output'] = args[++i];
|
|
2787
|
+
}
|
|
2788
|
+
else if (arg === '-a' && i + 1 < args.length) {
|
|
2789
|
+
parsed['address'] = args[++i];
|
|
2790
|
+
}
|
|
2791
|
+
else if (arg === '-n' && i + 1 < args.length) {
|
|
2792
|
+
parsed['nonce'] = args[++i];
|
|
2793
|
+
}
|
|
2794
|
+
else if (arg === '-s' && i + 1 < args.length) {
|
|
2795
|
+
parsed['signature'] = args[++i];
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
return parsed;
|
|
2799
|
+
}
|
|
2800
|
+
function requireSpendScalarDomain(parsed) {
|
|
2801
|
+
const networkPassphrase = parsed['network-passphrase'] ?? networkPassphraseFromArgs(parsed);
|
|
2802
|
+
const poolContract = requireParsedArg(parsed, 'pool');
|
|
2803
|
+
const registryContract = requireParsedArg(parsed, 'registry');
|
|
2804
|
+
const schemaRaw = parsed['schema-version'];
|
|
2805
|
+
const domain = {
|
|
2806
|
+
networkPassphrase,
|
|
2807
|
+
poolContract,
|
|
2808
|
+
registryContract,
|
|
2809
|
+
};
|
|
2810
|
+
if (schemaRaw !== undefined) {
|
|
2811
|
+
domain.schemaVersion = Number(schemaRaw);
|
|
2812
|
+
}
|
|
2813
|
+
else {
|
|
2814
|
+
domain.schemaVersion = OWNER_BOUND_NOTE_SCHEMA_VERSION;
|
|
2815
|
+
}
|
|
2816
|
+
return domain;
|
|
2817
|
+
}
|
|
2818
|
+
function handleStealthSignMessage(args) {
|
|
2819
|
+
const parsed = parseArgs(args);
|
|
2820
|
+
const address = parsed['address'];
|
|
2821
|
+
if (!address) {
|
|
2822
|
+
console.error('Error: --address <G...> is required');
|
|
2823
|
+
process.exit(1);
|
|
2824
|
+
}
|
|
2825
|
+
const nonce = parsed['nonce'] ?? DEFAULT_STEALTH_SIGN_NONCE;
|
|
2826
|
+
console.log(buildStealthAddressSignMessage(address, requireSpendScalarDomain(parsed), nonce));
|
|
2827
|
+
}
|
|
2828
|
+
async function handleStealthFromSignature(args) {
|
|
2829
|
+
const parsed = parseArgs(args);
|
|
2830
|
+
let sig = parsed['signature'];
|
|
2831
|
+
if (parsed['signature-file']) {
|
|
2832
|
+
sig = fs__namespace.readFileSync(parsed['signature-file'], 'utf-8');
|
|
2833
|
+
}
|
|
2834
|
+
if (!sig) {
|
|
2835
|
+
console.error('Error: --signature <hex> or --signature-file <path> is required');
|
|
2836
|
+
process.exit(1);
|
|
2837
|
+
}
|
|
2838
|
+
const sdk = await PrivacyPoolSDK.init();
|
|
2839
|
+
const stealth = await sdk.generateStealthAddressFromStellarSignature(sig, requireSpendScalarDomain(parsed));
|
|
2840
|
+
console.log(stealth);
|
|
2841
|
+
}
|
|
2842
|
+
function handleStealthPubkeyHex(args) {
|
|
2843
|
+
const parsed = parseArgs(args);
|
|
2844
|
+
const stealth = requireParsedArg(parsed, 'stealth');
|
|
2845
|
+
const { x, y } = decodeStealthAddress(stealth);
|
|
2846
|
+
console.log(x);
|
|
2847
|
+
console.log(y);
|
|
2848
|
+
}
|
|
2849
|
+
async function handlePrivScalarFromSignature(args) {
|
|
2850
|
+
const parsed = parseArgs(args);
|
|
2851
|
+
let sig = parsed['signature'];
|
|
2852
|
+
if (parsed['signature-file']) {
|
|
2853
|
+
sig = fs__namespace.readFileSync(parsed['signature-file'], 'utf-8');
|
|
2854
|
+
}
|
|
2855
|
+
if (!sig) {
|
|
2856
|
+
console.error('Error: --signature <hex> or --signature-file <path> is required');
|
|
2857
|
+
process.exit(1);
|
|
2858
|
+
}
|
|
2859
|
+
const dec = await privKeyScalarDecimalFromStellarSignature(sig.trim(), requireSpendScalarDomain(parsed));
|
|
2860
|
+
console.log(dec);
|
|
2861
|
+
}
|
|
2862
|
+
function handleRandomScalar() {
|
|
2863
|
+
console.log(PrivacyPoolSDK.generateRandomScalarHex32());
|
|
2864
|
+
}
|
|
2865
|
+
async function handleKytSubmitApproved(args) {
|
|
2866
|
+
const parsed = parseArgs(args);
|
|
2867
|
+
const sourceSecret = parsed['source-secret']?.trim() ?? process.env.SOURCE_SECRET?.trim();
|
|
2868
|
+
if (!sourceSecret) {
|
|
2869
|
+
console.error('Error: --source-secret <S...> or SOURCE_SECRET env is required');
|
|
2870
|
+
process.exit(1);
|
|
2871
|
+
}
|
|
2872
|
+
const txHash = await submitApprovedKytPassage({
|
|
2873
|
+
source: stellarSdk.Keypair.fromSecret(sourceSecret),
|
|
2874
|
+
networkPassphrase: networkPassphraseFromArgs(parsed),
|
|
2875
|
+
rpcUrl: parsed['rpc-url'] ?? 'https://soroban-testnet.stellar.org',
|
|
2876
|
+
poolContract: requireParsedArg(parsed, 'pool'),
|
|
2877
|
+
owner: requireParsedArg(parsed, 'owner'),
|
|
2878
|
+
nonce: BigInt(parsed['nonce'] ?? '0'),
|
|
2879
|
+
proofBytes: hexToBuffer('proof', requireParsedArg(parsed, 'proof')),
|
|
2880
|
+
publicSignalsBytes: hexToBuffer('public-signals', requireParsedArg(parsed, 'public-signals')),
|
|
2881
|
+
kytSubmitHelperId: requireParsedArg(parsed, 'helper'),
|
|
2882
|
+
passageId: requireParsedArg(parsed, 'passage-id'),
|
|
2883
|
+
expiresAtLedger: Number(requireParsedArg(parsed, 'expires-at-ledger')),
|
|
2884
|
+
signature: requireParsedArg(parsed, 'signature'),
|
|
2885
|
+
});
|
|
2886
|
+
console.log(txHash);
|
|
2887
|
+
}
|
|
2888
|
+
function parseStroopsU64(label, raw, _min) {
|
|
2889
|
+
if (!/^\d+$/.test(raw)) {
|
|
2890
|
+
console.error(`Error: ${label} must be a non-negative decimal integer`);
|
|
2891
|
+
process.exit(1);
|
|
2892
|
+
}
|
|
2893
|
+
return BigInt(raw);
|
|
2894
|
+
}
|
|
2895
|
+
function parseCoinValueDecimal(label, raw, defaultStroops) {
|
|
2896
|
+
if (raw === undefined) {
|
|
2897
|
+
return defaultStroops.toString(10);
|
|
2898
|
+
}
|
|
2899
|
+
if (!/^\d+$/.test(raw)) {
|
|
2900
|
+
console.error(`Error: ${label} must be a non-negative decimal integer`);
|
|
2901
|
+
process.exit(1);
|
|
2902
|
+
}
|
|
2903
|
+
return raw;
|
|
2904
|
+
}
|
|
2905
|
+
async function handleGenerate(args) {
|
|
2906
|
+
const parsed = parseArgs(args);
|
|
2907
|
+
const sdk = await PrivacyPoolSDK.init();
|
|
2908
|
+
const scalar = parsed['scalar'];
|
|
2909
|
+
const stealth = parsed['stealth'];
|
|
2910
|
+
const amount = parseCoinValueDecimal('--amount', parsed['amount'], BigInt(COIN_VALUE_STROOPS));
|
|
2911
|
+
const applicationId = resolveApplicationIdFromCli(parsed);
|
|
2912
|
+
const token = parsed['token'] ?? process.env.TOKEN_ADDRESS;
|
|
2913
|
+
if (!token) {
|
|
2914
|
+
console.error('Error: --token <C...> or TOKEN_ADDRESS is required');
|
|
2915
|
+
process.exit(1);
|
|
2916
|
+
}
|
|
2917
|
+
const [assetHi, assetLo] = stellarContractAddressToAssetFrDecimals(token);
|
|
2918
|
+
if (stealth && !scalar) {
|
|
2919
|
+
console.error('Error: --stealth requires --scalar');
|
|
2920
|
+
process.exit(1);
|
|
2921
|
+
}
|
|
2922
|
+
let coin;
|
|
2923
|
+
if (scalar && stealth) {
|
|
2924
|
+
const { x, y } = decodeStealthAddress(stealth);
|
|
2925
|
+
coin = sdk.generateCoinForDepositWithOwnerPubHex(scalar, x, y, amount, assetHi, assetLo, applicationId);
|
|
2926
|
+
}
|
|
2927
|
+
else if (scalar) {
|
|
2928
|
+
coin = sdk.generateCoinFromDepositEphemeralScalarHex(scalar, amount, assetHi, assetLo, applicationId);
|
|
2929
|
+
}
|
|
2930
|
+
else {
|
|
2931
|
+
coin = sdk.generateCoin(amount, assetHi, assetLo, applicationId);
|
|
2932
|
+
}
|
|
2933
|
+
const json = JSON.stringify(coin, null, 2);
|
|
2934
|
+
if (parsed['output']) {
|
|
2935
|
+
fs__namespace.writeFileSync(parsed['output'], json);
|
|
2936
|
+
console.error(`Coin saved to: ${parsed['output']}`);
|
|
2937
|
+
console.error(`Commitment: ${coin.commitment_hex}`);
|
|
2938
|
+
}
|
|
2939
|
+
else {
|
|
2940
|
+
console.log(json);
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
async function handleWithdraw(args) {
|
|
2944
|
+
const parsed = parseArgs(args);
|
|
2945
|
+
if (!parsed['coin']) {
|
|
2946
|
+
console.error('Error: --coin <file> is required');
|
|
2947
|
+
process.exit(1);
|
|
2948
|
+
}
|
|
2949
|
+
if (!parsed['state']) {
|
|
2950
|
+
console.error('Error: --state <file> is required');
|
|
2951
|
+
process.exit(1);
|
|
2952
|
+
}
|
|
2953
|
+
let withdrawHi = parsed['withdraw-address-hi'];
|
|
2954
|
+
let withdrawLo = parsed['withdraw-address-lo'];
|
|
2955
|
+
const pkHex = parsed['withdraw-pubkey-hex'];
|
|
2956
|
+
if (pkHex) {
|
|
2957
|
+
const parts = ed25519PubkeyPayloadHexToWithdrawFrDecimals(pkHex);
|
|
2958
|
+
withdrawHi = parts.hi;
|
|
2959
|
+
withdrawLo = parts.lo;
|
|
2960
|
+
}
|
|
2961
|
+
if (withdrawHi === undefined || withdrawLo === undefined) {
|
|
2962
|
+
console.error('Error: provide --withdraw-pubkey-hex <64 hex> or both --withdraw-address-hi and --withdraw-address-lo');
|
|
2963
|
+
process.exit(1);
|
|
2964
|
+
}
|
|
2965
|
+
if (!parsed['priv-key-scalar']) {
|
|
2966
|
+
console.error('Error: --priv-key-scalar <dec> is required');
|
|
2967
|
+
process.exit(1);
|
|
2968
|
+
}
|
|
2969
|
+
if (!parsed['ephemeral-x'] || !parsed['ephemeral-y']) {
|
|
2970
|
+
console.error('Error: --ephemeral-x <hex> and --ephemeral-y <hex> are required');
|
|
2971
|
+
process.exit(1);
|
|
2972
|
+
}
|
|
2973
|
+
const coinFile = JSON.parse(fs__namespace.readFileSync(parsed['coin'], 'utf-8'));
|
|
2974
|
+
const coin = coinFile.coin || coinFile;
|
|
2975
|
+
const state = JSON.parse(fs__namespace.readFileSync(parsed['state'], 'utf-8'));
|
|
2976
|
+
const pubW = parsed['public-withdraw-stroops'];
|
|
2977
|
+
const changeStealth = parsed['change-stealth'];
|
|
2978
|
+
if (pubW !== undefined && changeStealth === undefined) {
|
|
2979
|
+
console.error('Error: --change-stealth <stpl1...> is required when using --public-withdraw-stroops');
|
|
2980
|
+
process.exit(1);
|
|
2981
|
+
}
|
|
2982
|
+
if (pubW === undefined && changeStealth !== undefined) {
|
|
2983
|
+
console.error('Error: --change-stealth is only valid with --public-withdraw-stroops');
|
|
2984
|
+
process.exit(1);
|
|
2985
|
+
}
|
|
2986
|
+
const { zkConfigNonce } = resolveZkInit(parsed);
|
|
2987
|
+
const sdk = await PrivacyPoolSDK.init({ zkConfigNonce });
|
|
2988
|
+
const applicationId = resolveApplicationIdFromCli(parsed);
|
|
2989
|
+
const audit = resolveTransactionAuditParams(applicationId, undefined, sdk.getLayout().nAuditSlots);
|
|
2990
|
+
const result = await sdk.proveWithdrawal(coin, state, {
|
|
2991
|
+
withdrawAddressHi: withdrawHi,
|
|
2992
|
+
withdrawAddressLo: withdrawLo,
|
|
2993
|
+
privKeyScalar: parsed['priv-key-scalar'],
|
|
2994
|
+
ephemeralXHex: parsed['ephemeral-x'],
|
|
2995
|
+
ephemeralYHex: parsed['ephemeral-y'],
|
|
2996
|
+
applicationId,
|
|
2997
|
+
audit,
|
|
2998
|
+
...(pubW !== undefined
|
|
2999
|
+
? {
|
|
3000
|
+
publicWithdrawStroops: parseStroopsU64('--public-withdraw-stroops', pubW),
|
|
3001
|
+
changeRecipientStealthAddress: changeStealth,
|
|
3002
|
+
}
|
|
3003
|
+
: {}),
|
|
3004
|
+
});
|
|
3005
|
+
// Output proof and public hex to stdout (newline-separated)
|
|
3006
|
+
console.log(result.proof_hex);
|
|
3007
|
+
console.log(result.public_hex);
|
|
3008
|
+
// Optionally write to files
|
|
3009
|
+
if (parsed['output-proof']) {
|
|
3010
|
+
fs__namespace.writeFileSync(parsed['output-proof'], result.proof_hex);
|
|
3011
|
+
console.error(`Proof written to: ${parsed['output-proof']}`);
|
|
3012
|
+
}
|
|
3013
|
+
if (parsed['output-public']) {
|
|
3014
|
+
fs__namespace.writeFileSync(parsed['output-public'], result.public_hex);
|
|
3015
|
+
console.error(`Public signals written to: ${parsed['output-public']}`);
|
|
3016
|
+
}
|
|
3017
|
+
if (parsed['output-ciphertext'] && result.ciphertext_hex) {
|
|
3018
|
+
fs__namespace.writeFileSync(parsed['output-ciphertext'], result.ciphertext_hex);
|
|
3019
|
+
console.error(`Ciphertext written to: ${parsed['output-ciphertext']}`);
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
async function handleDepositProof(args) {
|
|
3023
|
+
const parsed = parseArgs(args);
|
|
3024
|
+
if (!parsed['state-root']) {
|
|
3025
|
+
console.error('Error: --state-root <dec> is required');
|
|
3026
|
+
process.exit(1);
|
|
3027
|
+
}
|
|
3028
|
+
if (!parsed['stealth']) {
|
|
3029
|
+
console.error('Error: --stealth <stpl1...> is required');
|
|
3030
|
+
process.exit(1);
|
|
3031
|
+
}
|
|
3032
|
+
const token = parsed['token'] ?? process.env.TOKEN_ADDRESS;
|
|
3033
|
+
if (!token) {
|
|
3034
|
+
console.error('Error: --token <C...> or TOKEN_ADDRESS is required');
|
|
3035
|
+
process.exit(1);
|
|
3036
|
+
}
|
|
3037
|
+
const [assetHi, assetLo] = stellarContractAddressToAssetFrDecimals(token);
|
|
3038
|
+
const { zkConfigNonce } = resolveZkInit(parsed);
|
|
3039
|
+
const sdk = await PrivacyPoolSDK.init({ zkConfigNonce });
|
|
3040
|
+
const applicationId = resolveApplicationIdFromCli(parsed);
|
|
3041
|
+
const audit = resolveTransactionAuditParams(applicationId, undefined, sdk.getLayout().nAuditSlots);
|
|
3042
|
+
const recipientPublicKeys = recipientPublicKeysDecimalFromStealthAddress(parsed['stealth']);
|
|
3043
|
+
const coinPath = parsed['coin'];
|
|
3044
|
+
const ephemeralScalarHex = parsed['ephemeral-scalar-hex'];
|
|
3045
|
+
let deposit;
|
|
3046
|
+
if (coinPath || ephemeralScalarHex) {
|
|
3047
|
+
if (!coinPath || !ephemeralScalarHex) {
|
|
3048
|
+
console.error('Error: aligned deposit proof requires both --coin <file> and --ephemeral-scalar-hex <hex>');
|
|
3049
|
+
process.exit(1);
|
|
3050
|
+
}
|
|
3051
|
+
const coinFile = JSON.parse(fs__namespace.readFileSync(coinPath, 'utf-8'));
|
|
3052
|
+
const c = coinFile.coin || coinFile;
|
|
3053
|
+
const value = parsed['value'] ?? c.value;
|
|
3054
|
+
if (parsed['value'] && parsed['value'] !== c.value) {
|
|
3055
|
+
console.error('Error: --value must match coin.value when using --coin');
|
|
3056
|
+
process.exit(1);
|
|
3057
|
+
}
|
|
3058
|
+
const ah = c.asset_hi ?? assetHi;
|
|
3059
|
+
const al = c.asset_lo ?? assetLo;
|
|
3060
|
+
deposit = {
|
|
3061
|
+
value,
|
|
3062
|
+
nullifier: c.nullifier,
|
|
3063
|
+
ephemeralKeyScalar: scalarHexToFrDecimal(ephemeralScalarHex),
|
|
3064
|
+
recipientPublicKeys,
|
|
3065
|
+
asset: [ah, al],
|
|
3066
|
+
applicationId,
|
|
3067
|
+
};
|
|
3068
|
+
}
|
|
3069
|
+
else {
|
|
3070
|
+
if (!parsed['value']) {
|
|
3071
|
+
console.error('Error: --value <dec> is required (unless using --coin and --ephemeral-scalar-hex)');
|
|
3072
|
+
process.exit(1);
|
|
3073
|
+
}
|
|
3074
|
+
deposit = {
|
|
3075
|
+
value: parsed['value'],
|
|
3076
|
+
nullifier: randomFrDecimal(),
|
|
3077
|
+
ephemeralKeyScalar: randomFrDecimal253(),
|
|
3078
|
+
recipientPublicKeys,
|
|
3079
|
+
asset: [assetHi, assetLo],
|
|
3080
|
+
applicationId,
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
const publicLegs = {
|
|
3084
|
+
publicWithdrawnAssets: [['0', '0']],
|
|
3085
|
+
publicDepositedAssets: [[deposit.asset[0], deposit.asset[1]]],
|
|
3086
|
+
publicDeposits: [deposit.value],
|
|
3087
|
+
publicWithdrawals: ['0'],
|
|
3088
|
+
};
|
|
3089
|
+
const result = await sdk.proveTransaction({
|
|
3090
|
+
stateRoot: parsed['state-root'],
|
|
3091
|
+
withdrawAddressHi: '0',
|
|
3092
|
+
withdrawAddressLo: '0',
|
|
3093
|
+
privKeyScalar: randomFrDecimal253(),
|
|
3094
|
+
}, publicLegs, ['dummy', 'dummy'], [deposit, 'dummy'], audit);
|
|
3095
|
+
console.log(result.proof_hex);
|
|
3096
|
+
console.log(result.public_hex);
|
|
3097
|
+
if (parsed['output-proof']) {
|
|
3098
|
+
fs__namespace.writeFileSync(parsed['output-proof'], result.proof_hex);
|
|
3099
|
+
console.error(`Proof written to: ${parsed['output-proof']}`);
|
|
3100
|
+
}
|
|
3101
|
+
if (parsed['output-public']) {
|
|
3102
|
+
fs__namespace.writeFileSync(parsed['output-public'], result.public_hex);
|
|
3103
|
+
console.error(`Public signals written to: ${parsed['output-public']}`);
|
|
3104
|
+
}
|
|
3105
|
+
if (parsed['output-ciphertext'] && result.ciphertext_hex) {
|
|
3106
|
+
fs__namespace.writeFileSync(parsed['output-ciphertext'], result.ciphertext_hex);
|
|
3107
|
+
console.error(`Ciphertext written to: ${parsed['output-ciphertext']}`);
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
main().catch((err) => {
|
|
3111
|
+
console.error('Error:', err.message || err);
|
|
3112
|
+
process.exit(1);
|
|
3113
|
+
});
|
|
3114
|
+
//# sourceMappingURL=cli.js.map
|