@auditable/privacy-pool-zk-sdk 0.8.0 → 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/cli.js +769 -591
- package/dist/cli.js.map +1 -1
- package/dist/domain-separators.d.ts +6 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.mjs +1122 -889
- package/dist/index.mjs.map +1 -1
- package/dist/kyt-flow.d.ts +2 -0
- package/dist/kyt-passage.d.ts +17 -3
- package/dist/output-note-encryption.d.ts +31 -2
- package/dist/prover.d.ts +3 -3
- package/dist/sdk.d.ts +22 -18
- package/dist/transaction-audit.d.ts +8 -12
- package/dist/types.d.ts +12 -4
- package/dist/withdrawal-transaction-input.d.ts +33 -42
- package/dist/witness.d.ts +2 -1
- package/dist/zk-artifact-url.d.ts +4 -0
- package/dist/zk-layout.d.ts +84 -0
- package/package.json +4 -9
- package/pkg/client_sdk_wasm.d.ts +6 -0
- package/pkg/client_sdk_wasm.js +39 -0
- package/pkg/client_sdk_wasm_bg.wasm +0 -0
- package/pkg/client_sdk_wasm_bg.wasm.d.ts +1 -0
- package/assets/main.wasm +0 -0
- package/assets/main_final.zkey +0 -0
- package/assets/witness_calculator.js +0 -383
package/dist/cli.js
CHANGED
|
@@ -3,32 +3,27 @@
|
|
|
3
3
|
|
|
4
4
|
var fs = require('fs');
|
|
5
5
|
var stellarSdk = require('@stellar/stellar-sdk');
|
|
6
|
-
var
|
|
6
|
+
var circomlibjs = require('circomlibjs');
|
|
7
7
|
|
|
8
8
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
9
9
|
function _interopNamespaceDefault(e) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
24
|
}
|
|
25
25
|
|
|
26
26
|
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
27
|
-
var snarkjs__namespace = /*#__PURE__*/_interopNamespaceDefault(snarkjs);
|
|
28
|
-
|
|
29
|
-
function getDefaultExportFromCjs (x) {
|
|
30
|
-
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
31
|
-
}
|
|
32
27
|
|
|
33
28
|
var dist = {};
|
|
34
29
|
|
|
@@ -205,7 +200,7 @@ dist.bech32m = getLibraryFromEncoding('bech32m');
|
|
|
205
200
|
const STEALTH_ADDRESS_HRP = 'stpl1';
|
|
206
201
|
/** BIP-173 default 90 is too small for 64-byte payload (32+32 field coords) after 8→5 bit conversion. */
|
|
207
202
|
const BECH32_STEALTH_LIMIT = 1023;
|
|
208
|
-
function normalizeHex$
|
|
203
|
+
function normalizeHex$3(hex) {
|
|
209
204
|
const s = hex.trim().replace(/^0x/i, '');
|
|
210
205
|
if (!/^[0-9a-fA-F]*$/.test(s)) {
|
|
211
206
|
throw new Error('stealth-address: hex must contain only 0-9, a-f');
|
|
@@ -213,7 +208,7 @@ function normalizeHex$2(hex) {
|
|
|
213
208
|
return s.length % 2 === 0 ? s : `0${s}`;
|
|
214
209
|
}
|
|
215
210
|
function hexToBytes$2(hex) {
|
|
216
|
-
const norm = normalizeHex$
|
|
211
|
+
const norm = normalizeHex$3(hex);
|
|
217
212
|
const out = new Uint8Array(norm.length / 2);
|
|
218
213
|
for (let i = 0; i < out.length; i++) {
|
|
219
214
|
out[i] = parseInt(norm.slice(i * 2, i * 2 + 2), 16);
|
|
@@ -297,6 +292,9 @@ function buildStealthAddressSignMessage(address, domain, nonce = DEFAULT_STEALTH
|
|
|
297
292
|
].join('\n');
|
|
298
293
|
}
|
|
299
294
|
|
|
295
|
+
const DOM_ENC = 1346719299n;
|
|
296
|
+
const DOM_AUDIT_TAG = 1346458695n;
|
|
297
|
+
const DOM_OUTPUT_NOTE_TAG = 1347310663n;
|
|
300
298
|
const BABYJUB_SUBGROUP_ORDER = 2736030358979909402780800718157159386076813972158567259200215660948447373041n;
|
|
301
299
|
/**
|
|
302
300
|
* Map any integer into `(0, l)` so it satisfies circom `LessThan(252)` vs
|
|
@@ -774,6 +772,38 @@ function proofToHex(proof_json) {
|
|
|
774
772
|
}
|
|
775
773
|
}
|
|
776
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
|
+
|
|
777
807
|
/**
|
|
778
808
|
* Convert snarkjs public signals JSON to hex bytes for Soroban contract.
|
|
779
809
|
* @param {string} public_json
|
|
@@ -984,6 +1014,13 @@ function isLikeNone(x) {
|
|
|
984
1014
|
return x === undefined || x === null;
|
|
985
1015
|
}
|
|
986
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
|
+
|
|
987
1024
|
function passStringToWasm0(arg, malloc, realloc) {
|
|
988
1025
|
if (realloc === undefined) {
|
|
989
1026
|
const buf = cachedTextEncoder.encode(arg);
|
|
@@ -1146,29 +1183,30 @@ async function __wbg_init(module_or_path) {
|
|
|
1146
1183
|
}
|
|
1147
1184
|
|
|
1148
1185
|
var wasmBindings = /*#__PURE__*/Object.freeze({
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
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
|
|
1164
1202
|
});
|
|
1165
1203
|
|
|
1166
|
-
const isNode$
|
|
1204
|
+
const isNode$1 = typeof process !== 'undefined' && !!process.versions?.node;
|
|
1167
1205
|
let wasmModule = null;
|
|
1168
1206
|
async function loadWasm(wasmBinary) {
|
|
1169
1207
|
if (wasmModule)
|
|
1170
1208
|
return wasmModule;
|
|
1171
|
-
if (isNode$
|
|
1209
|
+
if (isNode$1) {
|
|
1172
1210
|
if (wasmBinary) {
|
|
1173
1211
|
initSync({ module: wasmBinary });
|
|
1174
1212
|
}
|
|
@@ -1195,464 +1233,178 @@ async function loadWasm(wasmBinary) {
|
|
|
1195
1233
|
return wasmModule;
|
|
1196
1234
|
}
|
|
1197
1235
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
} else if (code == 5) {
|
|
1229
|
-
err = "Not enough memory.\n";
|
|
1230
|
-
} else if (code == 6) {
|
|
1231
|
-
err = "Input signal array access exceeds the size.\n";
|
|
1232
|
-
} else if (code == 7) {
|
|
1233
|
-
err = "Out of bounds array access.\n";
|
|
1234
|
-
} else {
|
|
1235
|
-
err = "Unknown error.\n";
|
|
1236
|
-
}
|
|
1237
|
-
throw new Error(err + errStr);
|
|
1238
|
-
},
|
|
1239
|
-
printErrorMessage : function() {
|
|
1240
|
-
errStr += getMessage() + "\n";
|
|
1241
|
-
// console.error(getMessage());
|
|
1242
|
-
},
|
|
1243
|
-
writeBufferMessage : function() {
|
|
1244
|
-
const msg = getMessage();
|
|
1245
|
-
// Any calls to `log()` will always end with a `\n`, so that's when we print and reset
|
|
1246
|
-
if (msg === "\n") {
|
|
1247
|
-
console.log(msgStr);
|
|
1248
|
-
msgStr = "";
|
|
1249
|
-
} else {
|
|
1250
|
-
// If we've buffered other content, put a space in between the items
|
|
1251
|
-
if (msgStr !== "") {
|
|
1252
|
-
msgStr += " ";
|
|
1253
|
-
}
|
|
1254
|
-
// Then append the message to the message we are creating
|
|
1255
|
-
msgStr += msg;
|
|
1256
|
-
}
|
|
1257
|
-
},
|
|
1258
|
-
showSharedRWMemory : function() {
|
|
1259
|
-
printSharedRWMemory ();
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
}
|
|
1263
|
-
});
|
|
1264
|
-
|
|
1265
|
-
const sanityCheck =
|
|
1266
|
-
options;
|
|
1267
|
-
// options &&
|
|
1268
|
-
// (
|
|
1269
|
-
// options.sanityCheck ||
|
|
1270
|
-
// options.logGetSignal ||
|
|
1271
|
-
// options.logSetSignal ||
|
|
1272
|
-
// options.logStartComponent ||
|
|
1273
|
-
// options.logFinishComponent
|
|
1274
|
-
// );
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
wc = new WitnessCalculator(instance, sanityCheck);
|
|
1278
|
-
return wc;
|
|
1279
|
-
|
|
1280
|
-
function getMessage() {
|
|
1281
|
-
var message = "";
|
|
1282
|
-
var c = instance.exports.getMessageChar();
|
|
1283
|
-
while ( c != 0 ) {
|
|
1284
|
-
message += String.fromCharCode(c);
|
|
1285
|
-
c = instance.exports.getMessageChar();
|
|
1286
|
-
}
|
|
1287
|
-
return message;
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
function printSharedRWMemory () {
|
|
1291
|
-
const shared_rw_memory_size = instance.exports.getFieldNumLen32();
|
|
1292
|
-
const arr = new Uint32Array(shared_rw_memory_size);
|
|
1293
|
-
for (let j=0; j<shared_rw_memory_size; j++) {
|
|
1294
|
-
arr[shared_rw_memory_size-1-j] = instance.exports.readSharedRWMemory(j);
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
// If we've buffered other content, put a space in between the items
|
|
1298
|
-
if (msgStr !== "") {
|
|
1299
|
-
msgStr += " ";
|
|
1300
|
-
}
|
|
1301
|
-
// Then append the value to the message we are creating
|
|
1302
|
-
msgStr += (fromArray32(arr).toString());
|
|
1303
|
-
}
|
|
1304
|
-
|
|
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
|
+
},
|
|
1305
1266
|
};
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
const arr = new Uint32Array(this.n32);
|
|
1316
|
-
for (let i=0; i<this.n32; i++) {
|
|
1317
|
-
arr[this.n32-1-i] = this.instance.exports.readSharedRWMemory(i);
|
|
1318
|
-
}
|
|
1319
|
-
this.prime = fromArray32(arr);
|
|
1320
|
-
|
|
1321
|
-
this.witnessSize = this.instance.exports.getWitnessSize();
|
|
1322
|
-
|
|
1323
|
-
this.sanityCheck = sanityCheck;
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
circom_version() {
|
|
1327
|
-
return this.instance.exports.getVersion();
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
async _doCalculateWitness(input_orig, sanityCheck) {
|
|
1331
|
-
//input is assumed to be a map from signals to arrays of bigints
|
|
1332
|
-
this.instance.exports.init((this.sanityCheck || sanityCheck) ? 1 : 0);
|
|
1333
|
-
let prefix = "";
|
|
1334
|
-
var input = new Object();
|
|
1335
|
-
//console.log("Input: ", input_orig);
|
|
1336
|
-
qualify_input(prefix,input_orig,input);
|
|
1337
|
-
//console.log("Input after: ",input);
|
|
1338
|
-
const keys = Object.keys(input);
|
|
1339
|
-
var input_counter = 0;
|
|
1340
|
-
keys.forEach( (k) => {
|
|
1341
|
-
const h = fnvHash(k);
|
|
1342
|
-
const hMSB = parseInt(h.slice(0,8), 16);
|
|
1343
|
-
const hLSB = parseInt(h.slice(8,16), 16);
|
|
1344
|
-
const fArr = flatArray(input[k]);
|
|
1345
|
-
let signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB);
|
|
1346
|
-
if (signalSize < 0){
|
|
1347
|
-
throw new Error(`Signal ${k} not found\n`);
|
|
1348
|
-
}
|
|
1349
|
-
if (fArr.length < signalSize) {
|
|
1350
|
-
throw new Error(`Not enough values for input signal ${k}\n`);
|
|
1351
|
-
}
|
|
1352
|
-
if (fArr.length > signalSize) {
|
|
1353
|
-
throw new Error(`Too many values for input signal ${k}\n`);
|
|
1354
|
-
}
|
|
1355
|
-
for (let i=0; i<fArr.length; i++) {
|
|
1356
|
-
const arrFr = toArray32(normalize(fArr[i],this.prime),this.n32);
|
|
1357
|
-
for (let j=0; j<this.n32; j++) {
|
|
1358
|
-
this.instance.exports.writeSharedRWMemory(j,arrFr[this.n32-1-j]);
|
|
1359
|
-
}
|
|
1360
|
-
try {
|
|
1361
|
-
this.instance.exports.setInputSignal(hMSB, hLSB,i);
|
|
1362
|
-
input_counter++;
|
|
1363
|
-
} catch (err) {
|
|
1364
|
-
// console.log(`After adding signal ${i} of ${k}`)
|
|
1365
|
-
throw new Error(err);
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
});
|
|
1370
|
-
if (input_counter < this.instance.exports.getInputSize()) {
|
|
1371
|
-
throw new Error(`Not all inputs have been set. Only ${input_counter} out of ${this.instance.exports.getInputSize()}`);
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
async calculateWitness(input, sanityCheck) {
|
|
1376
|
-
|
|
1377
|
-
const w = [];
|
|
1378
|
-
await this._doCalculateWitness(input, sanityCheck);
|
|
1379
|
-
|
|
1380
|
-
for (let i=0; i<this.witnessSize; i++) {
|
|
1381
|
-
this.instance.exports.getWitness(i);
|
|
1382
|
-
const arr = new Uint32Array(this.n32);
|
|
1383
|
-
for (let j=0; j<this.n32; j++) {
|
|
1384
|
-
arr[this.n32-1-j] = this.instance.exports.readSharedRWMemory(j);
|
|
1385
|
-
}
|
|
1386
|
-
w.push(fromArray32(arr));
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
return w;
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
async calculateBinWitness(input, sanityCheck) {
|
|
1394
|
-
|
|
1395
|
-
const buff32 = new Uint32Array(this.witnessSize*this.n32);
|
|
1396
|
-
const buff = new Uint8Array( buff32.buffer);
|
|
1397
|
-
await this._doCalculateWitness(input, sanityCheck);
|
|
1398
|
-
|
|
1399
|
-
for (let i=0; i<this.witnessSize; i++) {
|
|
1400
|
-
this.instance.exports.getWitness(i);
|
|
1401
|
-
const pos = i*this.n32;
|
|
1402
|
-
for (let j=0; j<this.n32; j++) {
|
|
1403
|
-
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
return buff;
|
|
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()}`);
|
|
1408
1276
|
}
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
buff[2] = "n".charCodeAt(0);
|
|
1421
|
-
buff[3] = "s".charCodeAt(0);
|
|
1422
|
-
|
|
1423
|
-
//version 2
|
|
1424
|
-
buff32[1] = 2;
|
|
1425
|
-
|
|
1426
|
-
//number of sections: 2
|
|
1427
|
-
buff32[2] = 2;
|
|
1428
|
-
|
|
1429
|
-
//id section 1
|
|
1430
|
-
buff32[3] = 1;
|
|
1431
|
-
|
|
1432
|
-
const n8 = this.n32*4;
|
|
1433
|
-
//id section 1 length in 64bytes
|
|
1434
|
-
const idSection1length = 8 + n8;
|
|
1435
|
-
const idSection1lengthHex = idSection1length.toString(16);
|
|
1436
|
-
buff32[4] = parseInt(idSection1lengthHex.slice(0,8), 16);
|
|
1437
|
-
buff32[5] = parseInt(idSection1lengthHex.slice(8,16), 16);
|
|
1438
|
-
|
|
1439
|
-
//this.n32
|
|
1440
|
-
buff32[6] = n8;
|
|
1441
|
-
|
|
1442
|
-
//prime number
|
|
1443
|
-
this.instance.exports.getRawPrime();
|
|
1444
|
-
|
|
1445
|
-
var pos = 7;
|
|
1446
|
-
for (let j=0; j<this.n32; j++) {
|
|
1447
|
-
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
|
|
1448
|
-
}
|
|
1449
|
-
pos += this.n32;
|
|
1450
|
-
|
|
1451
|
-
// witness size
|
|
1452
|
-
buff32[pos] = this.witnessSize;
|
|
1453
|
-
pos++;
|
|
1454
|
-
|
|
1455
|
-
//id section 2
|
|
1456
|
-
buff32[pos] = 2;
|
|
1457
|
-
pos++;
|
|
1458
|
-
|
|
1459
|
-
// section 2 length
|
|
1460
|
-
const idSection2length = n8*this.witnessSize;
|
|
1461
|
-
const idSection2lengthHex = idSection2length.toString(16);
|
|
1462
|
-
buff32[pos] = parseInt(idSection2lengthHex.slice(0,8), 16);
|
|
1463
|
-
buff32[pos+1] = parseInt(idSection2lengthHex.slice(8,16), 16);
|
|
1464
|
-
|
|
1465
|
-
pos += 2;
|
|
1466
|
-
for (let i=0; i<this.witnessSize; i++) {
|
|
1467
|
-
this.instance.exports.getWitness(i);
|
|
1468
|
-
for (let j=0; j<this.n32; j++) {
|
|
1469
|
-
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
|
|
1470
|
-
}
|
|
1471
|
-
pos += this.n32;
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
return buff;
|
|
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
|
+
};
|
|
1475
1288
|
}
|
|
1476
|
-
|
|
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;
|
|
1477
1315
|
}
|
|
1478
1316
|
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
function qualify_input(prefix,input,input1) {
|
|
1492
|
-
if (Array.isArray(input)) {
|
|
1493
|
-
let a = flatArray(input);
|
|
1494
|
-
if (a.length > 0) {
|
|
1495
|
-
let t = typeof a[0];
|
|
1496
|
-
for (let i = 1; i<a.length; i++) {
|
|
1497
|
-
if (typeof a[i] != t){
|
|
1498
|
-
throw new Error(`Types are not the same in the key ${prefix}`);
|
|
1499
|
-
}
|
|
1500
|
-
}
|
|
1501
|
-
if (t == "object") {
|
|
1502
|
-
qualify_input_list(prefix,input,input1);
|
|
1503
|
-
} else {
|
|
1504
|
-
input1[prefix] = input;
|
|
1505
|
-
}
|
|
1506
|
-
} else {
|
|
1507
|
-
input1[prefix] = input;
|
|
1508
|
-
}
|
|
1509
|
-
} else if (typeof input == "object") {
|
|
1510
|
-
const keys = Object.keys(input);
|
|
1511
|
-
keys.forEach( (k) => {
|
|
1512
|
-
let new_prefix = prefix == ""? k : prefix + "." + k;
|
|
1513
|
-
qualify_input(new_prefix,input[k],input1);
|
|
1514
|
-
});
|
|
1515
|
-
} else {
|
|
1516
|
-
input1[prefix] = input;
|
|
1317
|
+
const ZK_ARTIFACT_GITHUB_REPO = "Polynom-Labs/stellar-privacy-layer-contracts";
|
|
1318
|
+
const ZK_SDK_PACKAGE_VERSION = "0.9.1-rc.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);
|
|
1517
1327
|
}
|
|
1328
|
+
return trimmed;
|
|
1518
1329
|
}
|
|
1519
1330
|
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
res.unshift( Number(rem % radix));
|
|
1525
|
-
rem = rem / radix;
|
|
1331
|
+
const isNode = typeof process !== 'undefined' && !!process.versions?.node;
|
|
1332
|
+
function toUint8Array(src) {
|
|
1333
|
+
if (src instanceof Uint8Array) {
|
|
1334
|
+
return src;
|
|
1526
1335
|
}
|
|
1527
|
-
if (
|
|
1528
|
-
|
|
1529
|
-
while (i>0) {
|
|
1530
|
-
res.unshift(0);
|
|
1531
|
-
i--;
|
|
1532
|
-
}
|
|
1336
|
+
if (src instanceof ArrayBuffer) {
|
|
1337
|
+
return new Uint8Array(src);
|
|
1533
1338
|
}
|
|
1534
|
-
return
|
|
1339
|
+
return new Uint8Array(src.buffer, src.byteOffset, src.byteLength);
|
|
1535
1340
|
}
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
const radix = BigInt(0x100000000);
|
|
1540
|
-
for (let i = 0; i<arr.length; i++) {
|
|
1541
|
-
res = res*radix + BigInt(arr[i]);
|
|
1341
|
+
function nodeEnvArtifactBaseUrl() {
|
|
1342
|
+
if (!isNode) {
|
|
1343
|
+
return undefined;
|
|
1542
1344
|
}
|
|
1543
|
-
|
|
1345
|
+
const fromEnv = process.env.ZK_ARTIFACT_BASE_URL?.trim();
|
|
1346
|
+
return fromEnv ? trimArtifactBaseUrl(fromEnv) : undefined;
|
|
1544
1347
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
return res;
|
|
1550
|
-
|
|
1551
|
-
function fillArray(res, a) {
|
|
1552
|
-
if (Array.isArray(a)) {
|
|
1553
|
-
for (let i=0; i<a.length; i++) {
|
|
1554
|
-
fillArray(res, a[i]);
|
|
1555
|
-
}
|
|
1556
|
-
} else {
|
|
1557
|
-
res.push(a);
|
|
1558
|
-
}
|
|
1348
|
+
function resolveZkArtifactBaseUrl(explicit) {
|
|
1349
|
+
const trimmed = explicit?.trim();
|
|
1350
|
+
if (trimmed) {
|
|
1351
|
+
return trimArtifactBaseUrl(trimmed);
|
|
1559
1352
|
}
|
|
1353
|
+
return nodeEnvArtifactBaseUrl() ?? defaultZkArtifactBaseUrl();
|
|
1560
1354
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
let res = BigInt(n) % prime;
|
|
1564
|
-
if (res < 0) res += prime;
|
|
1565
|
-
return res
|
|
1355
|
+
function isHttpUrl(value) {
|
|
1356
|
+
return /^https?:\/\//i.test(value) || value.startsWith('file:');
|
|
1566
1357
|
}
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
for (var i = 0; i < str.length; i++) {
|
|
1572
|
-
hash ^= BigInt(str[i].charCodeAt());
|
|
1573
|
-
hash *= BigInt(0x100000001B3);
|
|
1574
|
-
hash %= uint64_max;
|
|
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.`);
|
|
1575
1362
|
}
|
|
1576
|
-
|
|
1577
|
-
let n = 16 - shash.length;
|
|
1578
|
-
shash = '0'.repeat(n).concat(shash);
|
|
1579
|
-
return shash;
|
|
1363
|
+
return fs.readFileSync(filePath);
|
|
1580
1364
|
}
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
async function generateWitness(input, circuitWasm) {
|
|
1586
|
-
let wasmBuffer;
|
|
1587
|
-
let wc;
|
|
1588
|
-
if (isNode$1) {
|
|
1589
|
-
const fs = await import('fs');
|
|
1590
|
-
const nodePath = await import('path');
|
|
1591
|
-
const { fileURLToPath } = await import('url');
|
|
1592
|
-
const dir = nodePath.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))));
|
|
1593
|
-
const assetsDir = nodePath.resolve(dir, '..', 'assets');
|
|
1594
|
-
wc = { default: witnessCalculatorModule };
|
|
1595
|
-
if (circuitWasm) {
|
|
1596
|
-
wasmBuffer = circuitWasm;
|
|
1597
|
-
}
|
|
1598
|
-
else {
|
|
1599
|
-
const wasmPath = nodePath.resolve(assetsDir, 'main.wasm');
|
|
1600
|
-
if (!fs.existsSync(wasmPath)) {
|
|
1601
|
-
throw new Error(`Circuit WASM not found at ${wasmPath}. Run build.sh first.`);
|
|
1602
|
-
}
|
|
1603
|
-
wasmBuffer = fs.readFileSync(wasmPath);
|
|
1604
|
-
}
|
|
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)}`);
|
|
1605
1369
|
}
|
|
1606
|
-
|
|
1607
|
-
if (!circuitWasm) {
|
|
1608
|
-
throw new Error('In browser, you must pass circuitWasm to PrivacyPoolSDK.init(). ' +
|
|
1609
|
-
'Load the circuit WASM file via fetch() and pass the ArrayBuffer.');
|
|
1610
|
-
}
|
|
1611
|
-
wasmBuffer = circuitWasm;
|
|
1612
|
-
wc = { default: witnessCalculatorModule };
|
|
1613
|
-
}
|
|
1614
|
-
const calculator = await wc.default(wasmBuffer);
|
|
1615
|
-
const wtns = await calculator.calculateWTNSBin(input, 0);
|
|
1616
|
-
return wtns;
|
|
1370
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
1617
1371
|
}
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
* Normalize BufferSource to Uint8Array. Required for @iden3/binfileutils/fastfile:
|
|
1623
|
-
* they read via data.buffer and data.byteOffset, which ArrayBuffer does not have.
|
|
1624
|
-
*/
|
|
1625
|
-
function toUint8Array(src) {
|
|
1626
|
-
if (src instanceof Uint8Array)
|
|
1627
|
-
return src;
|
|
1628
|
-
if (src instanceof ArrayBuffer)
|
|
1629
|
-
return new Uint8Array(src);
|
|
1630
|
-
return new Uint8Array(src.buffer, src.byteOffset, src.byteLength);
|
|
1631
|
-
}
|
|
1632
|
-
async function generateProof(wtns, zkey) {
|
|
1633
|
-
let zkeyData;
|
|
1634
|
-
if (zkey) {
|
|
1635
|
-
zkeyData = toUint8Array(zkey);
|
|
1372
|
+
function remoteArtifactHref(base, fileName) {
|
|
1373
|
+
const withSlash = `${trimArtifactBaseUrl(base)}/`;
|
|
1374
|
+
if (isHttpUrl(withSlash)) {
|
|
1375
|
+
return new URL(fileName, withSlash).href;
|
|
1636
1376
|
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
const nodePath = await import('path');
|
|
1640
|
-
const { fileURLToPath } = await import('url');
|
|
1641
|
-
const dir = nodePath.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))));
|
|
1642
|
-
const zkeyPath = nodePath.resolve(dir, '..', 'assets', 'main_final.zkey');
|
|
1643
|
-
if (!fs.existsSync(zkeyPath)) {
|
|
1644
|
-
throw new Error(`Proving key not found at ${zkeyPath}. Run build.sh first.`);
|
|
1645
|
-
}
|
|
1646
|
-
const zkeyBuffer = fs.readFileSync(zkeyPath);
|
|
1647
|
-
zkeyData = toUint8Array(zkeyBuffer);
|
|
1377
|
+
if (typeof window !== 'undefined') {
|
|
1378
|
+
return new URL(fileName, new URL(withSlash, window.location.href)).href;
|
|
1648
1379
|
}
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
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
|
+
};
|
|
1652
1406
|
}
|
|
1653
|
-
|
|
1654
|
-
const { proof, publicSignals } = await snarkjs__namespace.groth16.prove({ type: 'mem', data: zkeyData }, { type: 'mem', data: wtnsData });
|
|
1655
|
-
return { proof, publicSignals };
|
|
1407
|
+
return loadBundledCircuitArtifacts(config.circuit, artifactBaseUrl);
|
|
1656
1408
|
}
|
|
1657
1409
|
|
|
1658
1410
|
/** Bech32 HRP for {@link DecodedEphemeralKey} only (`x ‖ y`, 64 bytes). */
|
|
@@ -1663,7 +1415,7 @@ const FIELD_BYTES = 32;
|
|
|
1663
1415
|
const EPK_POINT_PAYLOAD = FIELD_BYTES * 2;
|
|
1664
1416
|
const EPK_DEP_PRE_PAYLOAD = FIELD_BYTES * 3;
|
|
1665
1417
|
const BECH32_LONG_LIMIT = 1023;
|
|
1666
|
-
function normalizeHex$
|
|
1418
|
+
function normalizeHex$2(hex) {
|
|
1667
1419
|
const s = hex.trim().replace(/^0x/i, '');
|
|
1668
1420
|
if (!/^[0-9a-fA-F]*$/.test(s)) {
|
|
1669
1421
|
throw new Error('ephemeral-key: hex must contain only 0-9, a-f');
|
|
@@ -1671,7 +1423,7 @@ function normalizeHex$1(hex) {
|
|
|
1671
1423
|
return s.length % 2 === 0 ? s : `0${s}`;
|
|
1672
1424
|
}
|
|
1673
1425
|
function hexToBytes(hex) {
|
|
1674
|
-
const norm = normalizeHex$
|
|
1426
|
+
const norm = normalizeHex$2(hex);
|
|
1675
1427
|
const out = new Uint8Array(norm.length / 2);
|
|
1676
1428
|
for (let i = 0; i < out.length; i++) {
|
|
1677
1429
|
out[i] = parseInt(norm.slice(i * 2, i * 2 + 2), 16);
|
|
@@ -1792,6 +1544,7 @@ function sharedSecretFromRecipientPreimage(ecdhShared, preimage) {
|
|
|
1792
1544
|
return { x: out.x, y: out.y };
|
|
1793
1545
|
}
|
|
1794
1546
|
|
|
1547
|
+
const TRANSACTION_N_AUDIT_SLOTS = 4;
|
|
1795
1548
|
const DEFAULT_APPLICATION_ID = '101';
|
|
1796
1549
|
/** BabyJub audit public key (decimal Fr) used in BDD / local demo when env is unset. */
|
|
1797
1550
|
const DEMO_AUDIT_PUBLIC_KEY = [
|
|
@@ -1816,37 +1569,23 @@ function resolveDepositSlot(slot) {
|
|
|
1816
1569
|
}
|
|
1817
1570
|
return slot;
|
|
1818
1571
|
}
|
|
1819
|
-
function buildUniformAuditParams(applicationId = DEFAULT_APPLICATION_ID, auditPublicKey) {
|
|
1572
|
+
function buildUniformAuditParams(applicationId = DEFAULT_APPLICATION_ID, auditPublicKey, _nAuditSlots = TRANSACTION_N_AUDIT_SLOTS) {
|
|
1820
1573
|
return {
|
|
1821
1574
|
applicationId,
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
auditPublicKey,
|
|
1825
|
-
auditPublicKey,
|
|
1826
|
-
auditPublicKey,
|
|
1827
|
-
],
|
|
1828
|
-
auditEphemeralScalars: [
|
|
1829
|
-
randomFrDecimal253(),
|
|
1830
|
-
randomFrDecimal253(),
|
|
1831
|
-
randomFrDecimal253(),
|
|
1832
|
-
randomFrDecimal253(),
|
|
1833
|
-
],
|
|
1575
|
+
noteAuditPublicKey: auditPublicKey,
|
|
1576
|
+
auditEphemeralScalar: randomFrDecimal253(),
|
|
1834
1577
|
};
|
|
1835
1578
|
}
|
|
1836
1579
|
function resolveSlotApplicationIds(audit, withdrawSlots, depositSlots) {
|
|
1837
|
-
const w0 = resolveWithdrawSlot(withdrawSlots[0]);
|
|
1838
|
-
const w1 = resolveWithdrawSlot(withdrawSlots[1]);
|
|
1839
|
-
const d0 = resolveDepositSlot(depositSlots[0]);
|
|
1840
|
-
const d1 = resolveDepositSlot(depositSlots[1]);
|
|
1841
1580
|
return {
|
|
1842
|
-
inputApplicationIds:
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
outputApplicationIds:
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
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
|
+
}),
|
|
1850
1589
|
};
|
|
1851
1590
|
}
|
|
1852
1591
|
/**
|
|
@@ -1864,12 +1603,17 @@ function resolveAuditPublicKeyFromEnv(auditPublicKey) {
|
|
|
1864
1603
|
}
|
|
1865
1604
|
return DEMO_AUDIT_PUBLIC_KEY;
|
|
1866
1605
|
}
|
|
1867
|
-
function resolveTransactionAuditParams(applicationId, auditPublicKey) {
|
|
1868
|
-
return buildUniformAuditParams(applicationId, resolveAuditPublicKeyFromEnv(auditPublicKey));
|
|
1606
|
+
function resolveTransactionAuditParams(applicationId, auditPublicKey, nAuditSlots = TRANSACTION_N_AUDIT_SLOTS) {
|
|
1607
|
+
return buildUniformAuditParams(applicationId, resolveAuditPublicKeyFromEnv(auditPublicKey), nAuditSlots);
|
|
1869
1608
|
}
|
|
1870
1609
|
|
|
1871
1610
|
/** Matches `Transaction(20, 2, 2, publicNInputs, publicNOutputs, 4, 12, 6)` in `circuits/main.circom`. */
|
|
1872
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;
|
|
1873
1617
|
/** BN254 scalar field modulus (ark `Fr`, circom signals). */
|
|
1874
1618
|
const BN254_SCALAR_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
|
|
1875
1619
|
/**
|
|
@@ -1877,7 +1621,30 @@ const BN254_SCALAR_MOD = 2188824287183927522224640574525727508854836440041603434
|
|
|
1877
1621
|
* (matches `libs/cryptography` `scalar_mul_253`).
|
|
1878
1622
|
*/
|
|
1879
1623
|
const BN254_BABYJUB_SCALAR_MAX_EXCLUSIVE = 1n << 253n;
|
|
1880
|
-
function
|
|
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) {
|
|
1881
1648
|
const s = hex.trim().replace(/^0x/i, '');
|
|
1882
1649
|
if (!/^[0-9a-fA-F]*$/.test(s)) {
|
|
1883
1650
|
throw new Error('withdrawal-transaction-input: invalid hex');
|
|
@@ -1886,7 +1653,7 @@ function normalizeHex(hex) {
|
|
|
1886
1653
|
}
|
|
1887
1654
|
/** 32-byte field coordinate (hex, no 0x) → decimal string mod BN254 scalar field. */
|
|
1888
1655
|
function coordHexToDecimal(hex) {
|
|
1889
|
-
const h = normalizeHex(hex);
|
|
1656
|
+
const h = normalizeHex$1(hex);
|
|
1890
1657
|
if (h.length > 64) {
|
|
1891
1658
|
throw new Error('withdrawal-transaction-input: coordinate hex too long');
|
|
1892
1659
|
}
|
|
@@ -1898,7 +1665,7 @@ function coordHexToDecimal(hex) {
|
|
|
1898
1665
|
* Integer must be < 2^253 (not reduced mod r — values ≥ 2^253 are rejected).
|
|
1899
1666
|
*/
|
|
1900
1667
|
function scalarHexToFrDecimal(hex) {
|
|
1901
|
-
const h = normalizeHex(hex);
|
|
1668
|
+
const h = normalizeHex$1(hex);
|
|
1902
1669
|
if (h.length > 64) {
|
|
1903
1670
|
throw new Error('withdrawal-transaction-input: scalar hex too long');
|
|
1904
1671
|
}
|
|
@@ -1913,7 +1680,7 @@ function scalarHexToFrDecimal(hex) {
|
|
|
1913
1680
|
* (`withdrawAddressHi` / `withdrawAddressLo`). No mod-r; each half fits in 128 bits.
|
|
1914
1681
|
*/
|
|
1915
1682
|
function ed25519PubkeyPayloadHexToWithdrawFrDecimals(hex) {
|
|
1916
|
-
const h = normalizeHex(hex).padStart(64, '0').slice(-64);
|
|
1683
|
+
const h = normalizeHex$1(hex).padStart(64, '0').slice(-64);
|
|
1917
1684
|
const hi = BigInt(`0x${h.slice(0, 32)}`);
|
|
1918
1685
|
const lo = BigInt(`0x${h.slice(32, 64)}`);
|
|
1919
1686
|
return { hi: hi.toString(10), lo: lo.toString(10) };
|
|
@@ -1930,18 +1697,18 @@ function stellarContractAddressToAssetFrDecimals(address) {
|
|
|
1930
1697
|
/** Uniform random `Fr` as decimal (32 random bytes, mod r). For Poseidon-only inputs (e.g. nullifiers). */
|
|
1931
1698
|
function randomFrDecimal() {
|
|
1932
1699
|
const hex = generateRandomScalarHex32();
|
|
1933
|
-
const v = BigInt(`0x${normalizeHex(hex)}`);
|
|
1700
|
+
const v = BigInt(`0x${normalizeHex$1(hex)}`);
|
|
1934
1701
|
return (v % BN254_SCALAR_MOD).toString(10);
|
|
1935
1702
|
}
|
|
1936
1703
|
/** Random scalar < 2^253 for BabyJub ECDH / `Num2Bits(253)` (uses {@link generateRandomScalarHex32}). */
|
|
1937
1704
|
function randomFrDecimal253() {
|
|
1938
1705
|
const hex = generateRandomScalarHex32();
|
|
1939
|
-
return BigInt(`0x${normalizeHex(hex)}`).toString(10);
|
|
1706
|
+
return BigInt(`0x${normalizeHex$1(hex)}`).toString(10);
|
|
1940
1707
|
}
|
|
1941
1708
|
/** Spend-scalar hex in `(0, BabyJub subgroup order)` so dummy withdraws satisfy `LessThan(l)`. */
|
|
1942
1709
|
function randomCanonicalBabyJubScalarHex() {
|
|
1943
1710
|
const hex = generateRandomScalarHex32();
|
|
1944
|
-
const reduced = canonicalBabyJubScalarFromInteger(BigInt(`0x${normalizeHex(hex)}`));
|
|
1711
|
+
const reduced = canonicalBabyJubScalarFromInteger(BigInt(`0x${normalizeHex$1(hex)}`));
|
|
1945
1712
|
return reduced.toString(16).padStart(64, '0');
|
|
1946
1713
|
}
|
|
1947
1714
|
function zerosTreeSiblings() {
|
|
@@ -1950,7 +1717,7 @@ function zerosTreeSiblings() {
|
|
|
1950
1717
|
function dummyWithdraw(wasm) {
|
|
1951
1718
|
const nullifier = randomFrDecimal();
|
|
1952
1719
|
const secretHex = generateRandomScalarHex32();
|
|
1953
|
-
const secret = (BigInt(`0x${normalizeHex(secretHex)}`) % BN254_SCALAR_MOD).toString(10);
|
|
1720
|
+
const secret = (BigInt(`0x${normalizeHex$1(secretHex)}`) % BN254_SCALAR_MOD).toString(10);
|
|
1954
1721
|
const scalarHex = randomCanonicalBabyJubScalarHex();
|
|
1955
1722
|
const privKeyScalar = scalarHexToFrDecimal(scalarHex);
|
|
1956
1723
|
const pt = wasm.ecdhEphemeralPublicKeyFromScalarHex(scalarHex);
|
|
@@ -1991,12 +1758,29 @@ function resolveWithdraw(slot, wasm) {
|
|
|
1991
1758
|
function resolveDeposit(slot, wasm) {
|
|
1992
1759
|
return slot === 'dummy' ? dummyDeposit(wasm) : slot;
|
|
1993
1760
|
}
|
|
1994
|
-
function
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
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);
|
|
2000
1784
|
return {
|
|
2001
1785
|
stateRoot: publicParams.stateRoot,
|
|
2002
1786
|
withdrawAddressHi: publicParams.withdrawAddressHi,
|
|
@@ -2005,35 +1789,32 @@ function buildTransactionWitnessInput(publicParams, publicLegs, withdrawSlots, d
|
|
|
2005
1789
|
escrowRecipientLo: publicParams.escrowRecipientLo ?? '0',
|
|
2006
1790
|
sweepOutputOwnerPubX: publicParams.sweepOutputOwnerPubX ?? '0',
|
|
2007
1791
|
sweepOutputOwnerPubY: publicParams.sweepOutputOwnerPubY ?? '0',
|
|
2008
|
-
privKeyScalars:
|
|
2009
|
-
ownerPubs:
|
|
2010
|
-
paddingRandoms:
|
|
2011
|
-
withdrawnValues:
|
|
2012
|
-
withdrawnNullifiers:
|
|
2013
|
-
withdrawnSecrets:
|
|
2014
|
-
withdrawnAssets:
|
|
2015
|
-
withdrawnEscrowNonces:
|
|
2016
|
-
inputRecipientStellar:
|
|
2017
|
-
stateSiblings:
|
|
2018
|
-
stateIndex:
|
|
2019
|
-
depositedValues:
|
|
2020
|
-
depositedNullifiers:
|
|
2021
|
-
depositedAssets:
|
|
2022
|
-
depositedEphemeralKeyScalars:
|
|
2023
|
-
depositedRecipientPublicKeys:
|
|
2024
|
-
depositedEscrowNonces:
|
|
2025
|
-
outputRecipientStellar: [
|
|
2026
|
-
d0.recipientStellar ?? ['0', '0'],
|
|
2027
|
-
d1.recipientStellar ?? ['0', '0'],
|
|
2028
|
-
],
|
|
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']),
|
|
2029
1810
|
inputApplicationIds: appIds.inputApplicationIds,
|
|
2030
1811
|
outputApplicationIds: appIds.outputApplicationIds,
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
publicWithdrawnAssets:
|
|
2034
|
-
publicDepositedAssets:
|
|
2035
|
-
publicDeposits:
|
|
2036
|
-
publicWithdrawals:
|
|
1812
|
+
auditEphemeralScalar: audit.auditEphemeralScalar,
|
|
1813
|
+
noteAuditPublicKey: audit.noteAuditPublicKey,
|
|
1814
|
+
publicWithdrawnAssets: paddedLegs.publicWithdrawnAssets,
|
|
1815
|
+
publicDepositedAssets: paddedLegs.publicDepositedAssets,
|
|
1816
|
+
publicDeposits: paddedLegs.publicDeposits,
|
|
1817
|
+
publicWithdrawals: paddedLegs.publicWithdrawals,
|
|
2037
1818
|
};
|
|
2038
1819
|
}
|
|
2039
1820
|
/** `stpl1…` stealth address → `[x, y]` as decimal field strings for `depositedRecipientPublicKeys`. */
|
|
@@ -2059,23 +1840,363 @@ function withdrawObjectFromMerkleWitness(witness, ownerPubHex, applicationId, pr
|
|
|
2059
1840
|
};
|
|
2060
1841
|
}
|
|
2061
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
|
+
|
|
2062
2183
|
/** Stroops amount as a decimal field string. Reject JS `number` so 18-decimal values cannot silently narrow. */
|
|
2063
2184
|
function coinValueDecimal(amount) {
|
|
2064
|
-
if (typeof amount ===
|
|
2065
|
-
throw new TypeError(
|
|
2185
|
+
if (typeof amount === "number") {
|
|
2186
|
+
throw new TypeError("coin value must be bigint or decimal string, not number");
|
|
2066
2187
|
}
|
|
2067
|
-
if (typeof amount ===
|
|
2188
|
+
if (typeof amount === "bigint") {
|
|
2068
2189
|
if (amount < 0n) {
|
|
2069
|
-
throw new RangeError(
|
|
2190
|
+
throw new RangeError("amount must be a non-negative integer");
|
|
2070
2191
|
}
|
|
2071
2192
|
return amount.toString(10);
|
|
2072
2193
|
}
|
|
2073
|
-
if (typeof amount !==
|
|
2074
|
-
throw new TypeError(
|
|
2194
|
+
if (typeof amount !== "string") {
|
|
2195
|
+
throw new TypeError("coin value must be bigint or decimal string, not number");
|
|
2075
2196
|
}
|
|
2076
2197
|
const trimmed = amount.trim();
|
|
2077
2198
|
if (!/^[0-9]+$/.test(trimmed)) {
|
|
2078
|
-
throw new TypeError(
|
|
2199
|
+
throw new TypeError("coin value decimal string must be a non-negative integer");
|
|
2079
2200
|
}
|
|
2080
2201
|
return trimmed;
|
|
2081
2202
|
}
|
|
@@ -2083,7 +2204,7 @@ function generatedCoinFromWasm(value) {
|
|
|
2083
2204
|
const raw = value;
|
|
2084
2205
|
const precommitementHex = raw.precommitementHex ?? raw.precommitement_hex;
|
|
2085
2206
|
if (!precommitementHex) {
|
|
2086
|
-
throw new Error(
|
|
2207
|
+
throw new Error("generated coin missing precommitement hex");
|
|
2087
2208
|
}
|
|
2088
2209
|
return {
|
|
2089
2210
|
coin: raw.coin,
|
|
@@ -2093,27 +2214,39 @@ function generatedCoinFromWasm(value) {
|
|
|
2093
2214
|
};
|
|
2094
2215
|
}
|
|
2095
2216
|
class PrivacyPoolSDK {
|
|
2096
|
-
constructor(wasm, options) {
|
|
2217
|
+
constructor(wasm, options, resolved) {
|
|
2097
2218
|
this.wasm = wasm;
|
|
2098
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;
|
|
2099
2229
|
}
|
|
2100
2230
|
/**
|
|
2101
2231
|
* Initialize the SDK by loading WASM modules.
|
|
2102
2232
|
*
|
|
2103
|
-
* In Node.js,
|
|
2104
|
-
*
|
|
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:
|
|
2105
2236
|
* ```ts
|
|
2106
2237
|
* const sdk = await PrivacyPoolSDK.init({
|
|
2107
2238
|
* wasmBinary: await fetch('/pkg/client_sdk_wasm_bg.wasm').then(r => r.arrayBuffer()),
|
|
2108
|
-
*
|
|
2109
|
-
* zkey: await fetch('/assets/main_final.zkey').then(r => r.arrayBuffer()),
|
|
2239
|
+
* zkArtifactBaseUrl: 'https://github.com/Polynom-Labs/stellar-privacy-layer-contracts/releases/download/v0.8.0',
|
|
2110
2240
|
* });
|
|
2111
2241
|
* ```
|
|
2112
2242
|
*/
|
|
2113
2243
|
static async init(options) {
|
|
2114
2244
|
const opts = options ?? {};
|
|
2115
2245
|
const wasm = await loadWasm(opts.wasmBinary);
|
|
2116
|
-
|
|
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;
|
|
2117
2250
|
}
|
|
2118
2251
|
/**
|
|
2119
2252
|
* Uniform 32-byte scalar as lowercase hex (Web Crypto). Same in browser and Node 19+.
|
|
@@ -2133,28 +2266,28 @@ class PrivacyPoolSDK {
|
|
|
2133
2266
|
* @param amount Decimal field element as `bigint` or decimal `string` (never JS `number`).
|
|
2134
2267
|
* @param assetHiDecimal / assetLoDecimal Decimal Fr strings for Stellar asset contract id (two limbs).
|
|
2135
2268
|
*/
|
|
2136
|
-
generateCoin(amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal =
|
|
2269
|
+
generateCoin(amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
|
|
2137
2270
|
return generatedCoinFromWasm(this.wasm.generateCoin(coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
|
|
2138
2271
|
}
|
|
2139
2272
|
/**
|
|
2140
2273
|
* Generate a coin with the same commitment shape as on-chain deposit: pass owner public key (hex x, y).
|
|
2141
2274
|
* @param amount Decimal field element (`bigint` | `string`).
|
|
2142
2275
|
*/
|
|
2143
|
-
generateCoinWithOwnerPub(ownerPub, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal =
|
|
2276
|
+
generateCoinWithOwnerPub(ownerPub, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
|
|
2144
2277
|
return generatedCoinFromWasm(this.wasm.generateCoinWithOwnerPubHex(ownerPub.x, ownerPub.y, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
|
|
2145
2278
|
}
|
|
2146
2279
|
/**
|
|
2147
2280
|
* Coin for a depositor `ephemeralKeyScalar` (32-byte hex): `coin.secret = Poseidon255(1)(scalar)` as in `deposit.circom`.
|
|
2148
2281
|
* @param amount Decimal field element (`bigint` | `string`).
|
|
2149
2282
|
*/
|
|
2150
|
-
generateCoinFromDepositEphemeralScalarHex(scalarHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal =
|
|
2283
|
+
generateCoinFromDepositEphemeralScalarHex(scalarHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
|
|
2151
2284
|
return generatedCoinFromWasm(this.wasm.generateCoinFromDepositEphemeralScalarHex(scalarHex, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
|
|
2152
2285
|
}
|
|
2153
2286
|
/**
|
|
2154
2287
|
* Aligned deposit coin: `secret = Poseidon₁(scalar)` and owner pub from recipient hex coords.
|
|
2155
2288
|
* @param amount Decimal field element (`bigint` | `string`).
|
|
2156
2289
|
*/
|
|
2157
|
-
generateCoinForDepositWithOwnerPubHex(scalarHex, ownerXHex, ownerYHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal =
|
|
2290
|
+
generateCoinForDepositWithOwnerPubHex(scalarHex, ownerXHex, ownerYHex, amount, assetHiDecimal, assetLoDecimal, applicationIdDecimal = "0") {
|
|
2158
2291
|
return generatedCoinFromWasm(this.wasm.generateCoinForDepositWithOwnerPubHex(scalarHex, ownerXHex, ownerYHex, coinValueDecimal(amount), assetHiDecimal, assetLoDecimal, applicationIdDecimal));
|
|
2159
2292
|
}
|
|
2160
2293
|
/**
|
|
@@ -2174,18 +2307,18 @@ class PrivacyPoolSDK {
|
|
|
2174
2307
|
*/
|
|
2175
2308
|
async proveWithdrawal(coin, state, params) {
|
|
2176
2309
|
const witness = this.buildWithdrawMerkleWitness(coin, state);
|
|
2177
|
-
const ownerPubHex = this.ecdhEphemeralPublicKeyFromScalarHex(BigInt(params.privKeyScalar).toString(16).padStart(64,
|
|
2178
|
-
const w0 = withdrawObjectFromMerkleWitness(witness, ownerPubHex, params.applicationId ?? coin.application_id ??
|
|
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);
|
|
2179
2312
|
const fullV = BigInt(coin.value);
|
|
2180
2313
|
let publicWithdrawals;
|
|
2181
2314
|
let deposits;
|
|
2182
2315
|
if (params.publicWithdrawStroops !== undefined) {
|
|
2183
2316
|
const W = params.publicWithdrawStroops;
|
|
2184
2317
|
if (params.changeRecipientStealthAddress === undefined) {
|
|
2185
|
-
throw new Error(
|
|
2318
|
+
throw new Error("proveWithdrawal: changeRecipientStealthAddress is required when publicWithdrawStroops is set");
|
|
2186
2319
|
}
|
|
2187
2320
|
if (W <= 0n || W >= fullV) {
|
|
2188
|
-
throw new Error(
|
|
2321
|
+
throw new Error("proveWithdrawal: publicWithdrawStroops must be strictly between 0 and coin.value");
|
|
2189
2322
|
}
|
|
2190
2323
|
const change = fullV - W;
|
|
2191
2324
|
publicWithdrawals = [W.toString()];
|
|
@@ -2194,29 +2327,29 @@ class PrivacyPoolSDK {
|
|
|
2194
2327
|
nullifier: randomFrDecimal(),
|
|
2195
2328
|
ephemeralKeyScalar: randomFrDecimal253(),
|
|
2196
2329
|
asset: [coin.asset_hi, coin.asset_lo],
|
|
2197
|
-
applicationId: params.applicationId ??
|
|
2330
|
+
applicationId: params.applicationId ?? "0",
|
|
2198
2331
|
recipientPublicKeys: recipientPublicKeysDecimalFromStealthAddress(params.changeRecipientStealthAddress),
|
|
2199
2332
|
};
|
|
2200
|
-
deposits = [changeDeposit
|
|
2333
|
+
deposits = [changeDeposit];
|
|
2201
2334
|
}
|
|
2202
2335
|
else {
|
|
2203
2336
|
publicWithdrawals = [coin.value];
|
|
2204
|
-
deposits = [
|
|
2337
|
+
deposits = [];
|
|
2205
2338
|
}
|
|
2206
2339
|
// On-chain `transact` sends `publicWithdrawals` / `publicWithdrawnAssets` to the withdraw account.
|
|
2207
2340
|
const publicLegs = {
|
|
2208
2341
|
publicWithdrawnAssets: [[coin.asset_hi, coin.asset_lo]],
|
|
2209
|
-
publicDepositedAssets: [[
|
|
2210
|
-
publicDeposits: [
|
|
2342
|
+
publicDepositedAssets: [["0", "0"]],
|
|
2343
|
+
publicDeposits: ["0"],
|
|
2211
2344
|
publicWithdrawals,
|
|
2212
2345
|
};
|
|
2213
|
-
return this.
|
|
2346
|
+
return this.proveTransactionSlots({
|
|
2214
2347
|
stateRoot: witness.stateRoot,
|
|
2215
2348
|
withdrawAddressHi: params.withdrawAddressHi,
|
|
2216
2349
|
withdrawAddressLo: params.withdrawAddressLo,
|
|
2217
2350
|
privKeyScalar: params.privKeyScalar,
|
|
2218
|
-
}, publicLegs, [w0
|
|
2219
|
-
|
|
2351
|
+
}, publicLegs, [w0], deposits, params.audit ??
|
|
2352
|
+
resolveTransactionAuditParams(params.applicationId ?? coin.application_id ?? DEFAULT_APPLICATION_ID, DEMO_AUDIT_PUBLIC_KEY, this.layout.nAuditSlots));
|
|
2220
2353
|
}
|
|
2221
2354
|
/**
|
|
2222
2355
|
* Convert a snarkjs proof JSON to hex bytes for Soroban.
|
|
@@ -2241,23 +2374,33 @@ class PrivacyPoolSDK {
|
|
|
2241
2374
|
return this.wasm.ecdhSharedKey(scalarHex, recipientPubXHex, recipientPubYHex);
|
|
2242
2375
|
}
|
|
2243
2376
|
/**
|
|
2244
|
-
* `Transaction(20,
|
|
2245
|
-
*
|
|
2246
|
-
* @param publicParams Public inputs: `stateRoot`, `withdrawAddressHi` / `withdrawAddressLo`, `privKeyScalar` (decimal field strings).
|
|
2247
|
-
* @param publicLegs Public token legs (`publicWithdrawnAssets`, `publicDepositedAssets`, `publicDeposits`, `publicWithdrawals`).
|
|
2248
|
-
* @param withdraws Exactly two withdraw slots (`WithdrawObject` or `"dummy"`).
|
|
2249
|
-
* @param deposits Exactly two deposit slots (`DepositObject` or `"dummy"`).
|
|
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).
|
|
2250
2379
|
*/
|
|
2251
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) {
|
|
2252
2384
|
const wasmEcdh = {
|
|
2253
2385
|
ecdhEphemeralPublicKeyFromScalarHex: (h) => this.wasm.ecdhEphemeralPublicKeyFromScalarHex(h),
|
|
2254
2386
|
};
|
|
2255
|
-
const
|
|
2256
|
-
const
|
|
2257
|
-
const
|
|
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
|
+
});
|
|
2258
2399
|
return {
|
|
2259
|
-
proof_hex:
|
|
2260
|
-
public_hex:
|
|
2400
|
+
proof_hex: proved.proof_hex,
|
|
2401
|
+
public_hex: proved.public_hex,
|
|
2402
|
+
ciphertext_hex: encrypted.ciphertextHex,
|
|
2403
|
+
output_note_ephemeral_scalars: witnessInput.depositedEphemeralKeyScalars,
|
|
2261
2404
|
};
|
|
2262
2405
|
}
|
|
2263
2406
|
/**
|
|
@@ -2452,6 +2595,20 @@ const COIN_VALUE_STROOPS = 1000000000;
|
|
|
2452
2595
|
function resolveApplicationIdFromCli(parsed) {
|
|
2453
2596
|
return parsed['application-id'] ?? process.env.APPLICATION_ID ?? DEFAULT_APPLICATION_ID;
|
|
2454
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
|
+
}
|
|
2455
2612
|
async function main() {
|
|
2456
2613
|
const args = process.argv.slice(2);
|
|
2457
2614
|
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
@@ -2555,10 +2712,12 @@ Commands:
|
|
|
2555
2712
|
--public-withdraw-stroops <u64> Optional: amount to withdraw publicly (must be < coin.value). Rest stays in pool as change.
|
|
2556
2713
|
--change-stealth <stpl1...> Required with --public-withdraw-stroops: stealth for the change note (same as deposit)
|
|
2557
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)
|
|
2558
2716
|
--output-proof <file> Write proof hex to file
|
|
2559
2717
|
--output-public <file> Write public signals hex to file
|
|
2718
|
+
--output-ciphertext <file> Write userspace MiMC ciphertext blob hex to file
|
|
2560
2719
|
|
|
2561
|
-
deposit-proof Groth16 proof for deposit-only transact (
|
|
2720
|
+
deposit-proof Groth16 proof for deposit-only transact (dummy withdraws, 1 deposit + dummy outs)
|
|
2562
2721
|
--state-root <dec> Current Merkle root (decimal Fr), must match pool on submit
|
|
2563
2722
|
--stealth <stpl1...> Recipient stealth address (decodes to recipient public key)
|
|
2564
2723
|
--token <C...> Asset contract id (or set TOKEN_ADDRESS)
|
|
@@ -2566,8 +2725,10 @@ Commands:
|
|
|
2566
2725
|
--ephemeral-scalar-hex <hex> Same 32-byte hex as deposit / generate --scalar
|
|
2567
2726
|
--value <dec> Deposit amount (stroops as decimal Fr); optional if --coin (must match coin)
|
|
2568
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)
|
|
2569
2729
|
--output-proof <file> Write proof hex to file
|
|
2570
2730
|
--output-public <file> Write public signals hex to file
|
|
2731
|
+
--output-ciphertext <file> Write userspace MiMC ciphertext blob hex to file
|
|
2571
2732
|
|
|
2572
2733
|
Output (withdraw, deposit-proof):
|
|
2573
2734
|
Prints proof_hex on first line and public_hex on second line to stdout.
|
|
@@ -2724,6 +2885,13 @@ async function handleKytSubmitApproved(args) {
|
|
|
2724
2885
|
});
|
|
2725
2886
|
console.log(txHash);
|
|
2726
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
|
+
}
|
|
2727
2895
|
function parseCoinValueDecimal(label, raw, defaultStroops) {
|
|
2728
2896
|
if (raw === undefined) {
|
|
2729
2897
|
return defaultStroops.toString(10);
|
|
@@ -2815,9 +2983,10 @@ async function handleWithdraw(args) {
|
|
|
2815
2983
|
console.error('Error: --change-stealth is only valid with --public-withdraw-stroops');
|
|
2816
2984
|
process.exit(1);
|
|
2817
2985
|
}
|
|
2818
|
-
const
|
|
2986
|
+
const { zkConfigNonce } = resolveZkInit(parsed);
|
|
2987
|
+
const sdk = await PrivacyPoolSDK.init({ zkConfigNonce });
|
|
2819
2988
|
const applicationId = resolveApplicationIdFromCli(parsed);
|
|
2820
|
-
const audit = resolveTransactionAuditParams(applicationId);
|
|
2989
|
+
const audit = resolveTransactionAuditParams(applicationId, undefined, sdk.getLayout().nAuditSlots);
|
|
2821
2990
|
const result = await sdk.proveWithdrawal(coin, state, {
|
|
2822
2991
|
withdrawAddressHi: withdrawHi,
|
|
2823
2992
|
withdrawAddressLo: withdrawLo,
|
|
@@ -2828,7 +2997,7 @@ async function handleWithdraw(args) {
|
|
|
2828
2997
|
audit,
|
|
2829
2998
|
...(pubW !== undefined
|
|
2830
2999
|
? {
|
|
2831
|
-
publicWithdrawStroops: parseStroopsU64('--public-withdraw-stroops', pubW
|
|
3000
|
+
publicWithdrawStroops: parseStroopsU64('--public-withdraw-stroops', pubW),
|
|
2832
3001
|
changeRecipientStealthAddress: changeStealth,
|
|
2833
3002
|
}
|
|
2834
3003
|
: {}),
|
|
@@ -2845,6 +3014,10 @@ async function handleWithdraw(args) {
|
|
|
2845
3014
|
fs__namespace.writeFileSync(parsed['output-public'], result.public_hex);
|
|
2846
3015
|
console.error(`Public signals written to: ${parsed['output-public']}`);
|
|
2847
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
|
+
}
|
|
2848
3021
|
}
|
|
2849
3022
|
async function handleDepositProof(args) {
|
|
2850
3023
|
const parsed = parseArgs(args);
|
|
@@ -2862,9 +3035,10 @@ async function handleDepositProof(args) {
|
|
|
2862
3035
|
process.exit(1);
|
|
2863
3036
|
}
|
|
2864
3037
|
const [assetHi, assetLo] = stellarContractAddressToAssetFrDecimals(token);
|
|
2865
|
-
const
|
|
3038
|
+
const { zkConfigNonce } = resolveZkInit(parsed);
|
|
3039
|
+
const sdk = await PrivacyPoolSDK.init({ zkConfigNonce });
|
|
2866
3040
|
const applicationId = resolveApplicationIdFromCli(parsed);
|
|
2867
|
-
const audit = resolveTransactionAuditParams(applicationId);
|
|
3041
|
+
const audit = resolveTransactionAuditParams(applicationId, undefined, sdk.getLayout().nAuditSlots);
|
|
2868
3042
|
const recipientPublicKeys = recipientPublicKeysDecimalFromStealthAddress(parsed['stealth']);
|
|
2869
3043
|
const coinPath = parsed['coin'];
|
|
2870
3044
|
const ephemeralScalarHex = parsed['ephemeral-scalar-hex'];
|
|
@@ -2928,6 +3102,10 @@ async function handleDepositProof(args) {
|
|
|
2928
3102
|
fs__namespace.writeFileSync(parsed['output-public'], result.public_hex);
|
|
2929
3103
|
console.error(`Public signals written to: ${parsed['output-public']}`);
|
|
2930
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
|
+
}
|
|
2931
3109
|
}
|
|
2932
3110
|
main().catch((err) => {
|
|
2933
3111
|
console.error('Error:', err.message || err);
|