@quantovainc/qcore 0.1.32 → 0.2.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/package.json +2 -2
- package/pkg/package.json +1 -1
- package/pkg/qcore_js_bg.wasm +0 -0
- package/pkg-node/package.json +1 -1
- package/pkg-node/qcore_js_bg.wasm +0 -0
- package/shared.js +51 -11
- package/shared.mjs +51 -11
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quantovainc/qcore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Quantova's post quantum client core for JavaScript. Signs with ML-DSA-65 and builds Q1 addresses.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "bash scripts/build.sh",
|
|
7
|
-
"test": "node test-types.js && node test-conformance.js && node test-order-nonce.js && node test-chain-binding.js && node test-sign-validate.js && node test-transport.js && node test-fee-cap.js && node test-network.js && node test-typed-order.js && node test-asset-guards.js"
|
|
7
|
+
"test": "node test-entry-parity.mjs && node test-browser.mjs && node test-types.js && node test-conformance.js && node test-order-nonce.js && node test-chain-binding.js && node test-sign-validate.js && node test-transport.js && node test-fee-cap.js && node test-network.js && node test-typed-order.js && node test-asset-guards.js && node test-account-index.js"
|
|
8
8
|
},
|
|
9
9
|
"keywords": [
|
|
10
10
|
"post-quantum",
|
package/pkg/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "qcore-js",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"description": "The JavaScript binding for the Quantova client core, generated over QCore with wasm-bindgen. The core does the key derivation, the post quantum signing, and the wire, the page does the fetch.",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.2.0",
|
|
6
6
|
"license": "Apache-2.0 OR MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
package/pkg/qcore_js_bg.wasm
CHANGED
|
Binary file
|
package/pkg-node/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qcore-js",
|
|
3
3
|
"description": "The JavaScript binding for the Quantova client core, generated over QCore with wasm-bindgen. The core does the key derivation, the post quantum signing, and the wire, the page does the fetch.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
Binary file
|
package/shared.js
CHANGED
|
@@ -28,7 +28,15 @@ function checkAmount(amount) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function accountIndex(index) {
|
|
31
|
-
|
|
31
|
+
if (typeof index === 'number' && !Number.isSafeInteger(index)) {
|
|
32
|
+
throw new Error('the account index must be a whole number in the safe integer range, a number that large silently rounds and would sign with a different account key');
|
|
33
|
+
}
|
|
34
|
+
let i;
|
|
35
|
+
try {
|
|
36
|
+
i = BigInt(index);
|
|
37
|
+
} catch {
|
|
38
|
+
throw new Error('the account index must be a whole number');
|
|
39
|
+
}
|
|
32
40
|
if (i < 0n || i > 0xffffffffffffffffn) {
|
|
33
41
|
throw new Error('the account index must fit in an unsigned 64 bit integer');
|
|
34
42
|
}
|
|
@@ -323,11 +331,34 @@ function makeClient(core) {
|
|
|
323
331
|
_checkedNonce(reported, expected, key) {
|
|
324
332
|
const n = accountNonce(reported);
|
|
325
333
|
if (!this._nextNonces) this._nextNonces = new Map();
|
|
334
|
+
if (!this._signedNonces) this._signedNonces = new Map();
|
|
326
335
|
const local = key != null ? this._nextNonces.get(key) : undefined;
|
|
327
336
|
const e = expected != null ? BigInt(expected) : local;
|
|
328
|
-
|
|
329
|
-
if (
|
|
330
|
-
|
|
337
|
+
let slot;
|
|
338
|
+
if (e == null) {
|
|
339
|
+
slot = n;
|
|
340
|
+
} else {
|
|
341
|
+
if (n > e) throw new Error(`the gateway reported nonce ${n} above the expected ${e}; refusing so a signature cannot be banked for a nonce the account has not reached`);
|
|
342
|
+
slot = expected != null ? e : n;
|
|
343
|
+
}
|
|
344
|
+
return slot;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Refuse a second, different transaction at a nonce this session already
|
|
348
|
+
// signed at. A gateway can broadcast a transaction and still report it
|
|
349
|
+
// rejected, or walk the nonce it reports back to a slot already used.
|
|
350
|
+
// Either way two valid signatures for one slot let it choose which the
|
|
351
|
+
// chain runs. An identical retry is harmless and still allowed.
|
|
352
|
+
_guardSigned(key, slot, txHex) {
|
|
353
|
+
if (key == null) return;
|
|
354
|
+
if (!this._signedNonces) this._signedNonces = new Map();
|
|
355
|
+
let held = this._signedNonces.get(key);
|
|
356
|
+
if (!held) { held = new Map(); this._signedNonces.set(key, held); }
|
|
357
|
+
const seen = held.get(String(slot));
|
|
358
|
+
if (seen != null && seen !== txHex) {
|
|
359
|
+
throw new Error(`a different transaction was already signed for nonce ${slot} in this session; one nonce carries one signature`);
|
|
360
|
+
}
|
|
361
|
+
held.set(String(slot), txHex);
|
|
331
362
|
}
|
|
332
363
|
|
|
333
364
|
_remember(key, used, outcome) {
|
|
@@ -342,7 +373,8 @@ function makeClient(core) {
|
|
|
342
373
|
if (this._headFloor) {
|
|
343
374
|
const { height, at } = this._headFloor;
|
|
344
375
|
if (head < height) throw new Error(`the gateway reports head ${head} below the ${height} it reported earlier, refusing to sign`);
|
|
345
|
-
const
|
|
376
|
+
const elapsed = now > at ? now - at : 0n;
|
|
377
|
+
const allowed = (elapsed + HEAD_SLACK_SECS) * HEAD_BLOCKS_PER_SEC;
|
|
346
378
|
if (head > height + allowed) throw new Error(`the gateway head leapt from ${height} to ${head} faster than blocks are made, refusing to sign`);
|
|
347
379
|
} else {
|
|
348
380
|
this._headFloor = { height: head, at: now };
|
|
@@ -365,9 +397,10 @@ function makeClient(core) {
|
|
|
365
397
|
const info = await this.nodeInfo();
|
|
366
398
|
this._guardMainnet();
|
|
367
399
|
const chainId = this._signingChainId(info);
|
|
368
|
-
const
|
|
369
|
-
if (
|
|
370
|
-
|
|
400
|
+
const reported = info && info.fee && info.fee.transfer_quon;
|
|
401
|
+
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
402
|
+
const fee = gatewayFee(reported);
|
|
403
|
+
if (fee > ceiling) {
|
|
371
404
|
throw new Error(`the gateway fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
372
405
|
}
|
|
373
406
|
const from = core.address(seedHex, accountIndex(index));
|
|
@@ -377,6 +410,7 @@ function makeClient(core) {
|
|
|
377
410
|
const signed = JSON.parse(
|
|
378
411
|
core.sign_transfer(seedHex, accountIndex(index), to, String(amount), nonce, String(fee), chainId, this._validity(info))
|
|
379
412
|
);
|
|
413
|
+
this._guardSigned(from, nonce, signed.tx_hex);
|
|
380
414
|
const outcome = await this.submit(signed.tx_hex);
|
|
381
415
|
this._remember(from, nonce, outcome);
|
|
382
416
|
return { signed, outcome };
|
|
@@ -387,9 +421,10 @@ function makeClient(core) {
|
|
|
387
421
|
const info = await this.nodeInfo();
|
|
388
422
|
this._guardMainnet();
|
|
389
423
|
const chainId = this._signingChainId(info);
|
|
390
|
-
const
|
|
391
|
-
if (
|
|
392
|
-
|
|
424
|
+
const reported = info && info.fee && info.fee.transfer_quon;
|
|
425
|
+
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
426
|
+
const fee = gatewayFee(reported);
|
|
427
|
+
if (fee > ceiling) {
|
|
393
428
|
throw new Error(`the gateway fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
394
429
|
}
|
|
395
430
|
const from = core.address(seedHex, accountIndex(index));
|
|
@@ -397,6 +432,7 @@ function makeClient(core) {
|
|
|
397
432
|
if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
|
|
398
433
|
const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
399
434
|
const signed = JSON.parse(core.signRegister(seedHex, accountIndex(index), nonce, String(fee), chainId, this._validity(info)));
|
|
435
|
+
this._guardSigned(from, nonce, signed.tx_hex);
|
|
400
436
|
const outcome = await this.submit(signed.tx_hex);
|
|
401
437
|
this._remember(from, nonce, outcome);
|
|
402
438
|
return { signed, outcome };
|
|
@@ -421,6 +457,7 @@ function makeClient(core) {
|
|
|
421
457
|
const signed = JSON.parse(
|
|
422
458
|
core.sign_call(seedHex, accountIndex(index), target, argsHex, nonce, meterLimitOf(meterLimit), String(fee), chainId, this._validity(info))
|
|
423
459
|
);
|
|
460
|
+
this._guardSigned(from, nonce, signed.tx_hex);
|
|
424
461
|
const outcome = await this.submit(signed.tx_hex);
|
|
425
462
|
this._remember(from, nonce, outcome);
|
|
426
463
|
return { signed, outcome };
|
|
@@ -447,6 +484,7 @@ function makeClient(core) {
|
|
|
447
484
|
const signed = JSON.parse(
|
|
448
485
|
core.signAssetCall(seedHex, accountIndex(index), target, argsHex, assetIssuer, String(amount), nonce, meterLimitOf(meterLimit), String(fee), chainId, this._validity(info))
|
|
449
486
|
);
|
|
487
|
+
this._guardSigned(from, nonce, signed.tx_hex);
|
|
450
488
|
const outcome = await this.submit(signed.tx_hex);
|
|
451
489
|
this._remember(from, nonce, outcome);
|
|
452
490
|
return { signed, outcome };
|
|
@@ -472,6 +510,7 @@ function makeClient(core) {
|
|
|
472
510
|
const signed = JSON.parse(
|
|
473
511
|
core.signPayableCall(seedHex, accountIndex(index), target, argsHex, nonce, meterLimitOf(meterLimit), String(fee), String(value), chainId, this._validity(info))
|
|
474
512
|
);
|
|
513
|
+
this._guardSigned(from, nonce, signed.tx_hex);
|
|
475
514
|
const outcome = await this.submit(signed.tx_hex);
|
|
476
515
|
this._remember(from, nonce, outcome);
|
|
477
516
|
return { signed, outcome };
|
|
@@ -535,6 +574,7 @@ function makeClient(core) {
|
|
|
535
574
|
const signed = JSON.parse(
|
|
536
575
|
core.sign_call(callerSeedHex, accountIndex(callerIndex), contract, order.call_args, accountNonceUsed, meterLimitOf(meterLimit), String(fee), chainId, this._validity(info))
|
|
537
576
|
);
|
|
577
|
+
this._guardSigned(from, nonce, signed.tx_hex);
|
|
538
578
|
const outcome = await this.submit(signed.tx_hex);
|
|
539
579
|
this._remember(from, accountNonceUsed, outcome);
|
|
540
580
|
this._remember(orderKey, nonce, outcome);
|
package/shared.mjs
CHANGED
|
@@ -28,7 +28,15 @@ function checkAmount(amount) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function accountIndex(index) {
|
|
31
|
-
|
|
31
|
+
if (typeof index === 'number' && !Number.isSafeInteger(index)) {
|
|
32
|
+
throw new Error('the account index must be a whole number in the safe integer range, a number that large silently rounds and would sign with a different account key');
|
|
33
|
+
}
|
|
34
|
+
let i;
|
|
35
|
+
try {
|
|
36
|
+
i = BigInt(index);
|
|
37
|
+
} catch {
|
|
38
|
+
throw new Error('the account index must be a whole number');
|
|
39
|
+
}
|
|
32
40
|
if (i < 0n || i > 0xffffffffffffffffn) {
|
|
33
41
|
throw new Error('the account index must fit in an unsigned 64 bit integer');
|
|
34
42
|
}
|
|
@@ -323,11 +331,34 @@ function makeClient(core) {
|
|
|
323
331
|
_checkedNonce(reported, expected, key) {
|
|
324
332
|
const n = accountNonce(reported);
|
|
325
333
|
if (!this._nextNonces) this._nextNonces = new Map();
|
|
334
|
+
if (!this._signedNonces) this._signedNonces = new Map();
|
|
326
335
|
const local = key != null ? this._nextNonces.get(key) : undefined;
|
|
327
336
|
const e = expected != null ? BigInt(expected) : local;
|
|
328
|
-
|
|
329
|
-
if (
|
|
330
|
-
|
|
337
|
+
let slot;
|
|
338
|
+
if (e == null) {
|
|
339
|
+
slot = n;
|
|
340
|
+
} else {
|
|
341
|
+
if (n > e) throw new Error(`the gateway reported nonce ${n} above the expected ${e}; refusing so a signature cannot be banked for a nonce the account has not reached`);
|
|
342
|
+
slot = expected != null ? e : n;
|
|
343
|
+
}
|
|
344
|
+
return slot;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Refuse a second, different transaction at a nonce this session already
|
|
348
|
+
// signed at. A gateway can broadcast a transaction and still report it
|
|
349
|
+
// rejected, or walk the nonce it reports back to a slot already used.
|
|
350
|
+
// Either way two valid signatures for one slot let it choose which the
|
|
351
|
+
// chain runs. An identical retry is harmless and still allowed.
|
|
352
|
+
_guardSigned(key, slot, txHex) {
|
|
353
|
+
if (key == null) return;
|
|
354
|
+
if (!this._signedNonces) this._signedNonces = new Map();
|
|
355
|
+
let held = this._signedNonces.get(key);
|
|
356
|
+
if (!held) { held = new Map(); this._signedNonces.set(key, held); }
|
|
357
|
+
const seen = held.get(String(slot));
|
|
358
|
+
if (seen != null && seen !== txHex) {
|
|
359
|
+
throw new Error(`a different transaction was already signed for nonce ${slot} in this session; one nonce carries one signature`);
|
|
360
|
+
}
|
|
361
|
+
held.set(String(slot), txHex);
|
|
331
362
|
}
|
|
332
363
|
|
|
333
364
|
_remember(key, used, outcome) {
|
|
@@ -342,7 +373,8 @@ function makeClient(core) {
|
|
|
342
373
|
if (this._headFloor) {
|
|
343
374
|
const { height, at } = this._headFloor;
|
|
344
375
|
if (head < height) throw new Error(`the gateway reports head ${head} below the ${height} it reported earlier, refusing to sign`);
|
|
345
|
-
const
|
|
376
|
+
const elapsed = now > at ? now - at : 0n;
|
|
377
|
+
const allowed = (elapsed + HEAD_SLACK_SECS) * HEAD_BLOCKS_PER_SEC;
|
|
346
378
|
if (head > height + allowed) throw new Error(`the gateway head leapt from ${height} to ${head} faster than blocks are made, refusing to sign`);
|
|
347
379
|
} else {
|
|
348
380
|
this._headFloor = { height: head, at: now };
|
|
@@ -365,9 +397,10 @@ function makeClient(core) {
|
|
|
365
397
|
const info = await this.nodeInfo();
|
|
366
398
|
this._guardMainnet();
|
|
367
399
|
const chainId = this._signingChainId(info);
|
|
368
|
-
const
|
|
369
|
-
if (
|
|
370
|
-
|
|
400
|
+
const reported = info && info.fee && info.fee.transfer_quon;
|
|
401
|
+
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
402
|
+
const fee = gatewayFee(reported);
|
|
403
|
+
if (fee > ceiling) {
|
|
371
404
|
throw new Error(`the gateway fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
372
405
|
}
|
|
373
406
|
const from = core.address(seedHex, accountIndex(index));
|
|
@@ -377,6 +410,7 @@ function makeClient(core) {
|
|
|
377
410
|
const signed = JSON.parse(
|
|
378
411
|
core.sign_transfer(seedHex, accountIndex(index), to, String(amount), nonce, String(fee), chainId, this._validity(info))
|
|
379
412
|
);
|
|
413
|
+
this._guardSigned(from, nonce, signed.tx_hex);
|
|
380
414
|
const outcome = await this.submit(signed.tx_hex);
|
|
381
415
|
this._remember(from, nonce, outcome);
|
|
382
416
|
return { signed, outcome };
|
|
@@ -387,9 +421,10 @@ function makeClient(core) {
|
|
|
387
421
|
const info = await this.nodeInfo();
|
|
388
422
|
this._guardMainnet();
|
|
389
423
|
const chainId = this._signingChainId(info);
|
|
390
|
-
const
|
|
391
|
-
if (
|
|
392
|
-
|
|
424
|
+
const reported = info && info.fee && info.fee.transfer_quon;
|
|
425
|
+
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
426
|
+
const fee = gatewayFee(reported);
|
|
427
|
+
if (fee > ceiling) {
|
|
393
428
|
throw new Error(`the gateway fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
394
429
|
}
|
|
395
430
|
const from = core.address(seedHex, accountIndex(index));
|
|
@@ -397,6 +432,7 @@ function makeClient(core) {
|
|
|
397
432
|
if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
|
|
398
433
|
const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
399
434
|
const signed = JSON.parse(core.signRegister(seedHex, accountIndex(index), nonce, String(fee), chainId, this._validity(info)));
|
|
435
|
+
this._guardSigned(from, nonce, signed.tx_hex);
|
|
400
436
|
const outcome = await this.submit(signed.tx_hex);
|
|
401
437
|
this._remember(from, nonce, outcome);
|
|
402
438
|
return { signed, outcome };
|
|
@@ -421,6 +457,7 @@ function makeClient(core) {
|
|
|
421
457
|
const signed = JSON.parse(
|
|
422
458
|
core.sign_call(seedHex, accountIndex(index), target, argsHex, nonce, meterLimitOf(meterLimit), String(fee), chainId, this._validity(info))
|
|
423
459
|
);
|
|
460
|
+
this._guardSigned(from, nonce, signed.tx_hex);
|
|
424
461
|
const outcome = await this.submit(signed.tx_hex);
|
|
425
462
|
this._remember(from, nonce, outcome);
|
|
426
463
|
return { signed, outcome };
|
|
@@ -447,6 +484,7 @@ function makeClient(core) {
|
|
|
447
484
|
const signed = JSON.parse(
|
|
448
485
|
core.signAssetCall(seedHex, accountIndex(index), target, argsHex, assetIssuer, String(amount), nonce, meterLimitOf(meterLimit), String(fee), chainId, this._validity(info))
|
|
449
486
|
);
|
|
487
|
+
this._guardSigned(from, nonce, signed.tx_hex);
|
|
450
488
|
const outcome = await this.submit(signed.tx_hex);
|
|
451
489
|
this._remember(from, nonce, outcome);
|
|
452
490
|
return { signed, outcome };
|
|
@@ -472,6 +510,7 @@ function makeClient(core) {
|
|
|
472
510
|
const signed = JSON.parse(
|
|
473
511
|
core.signPayableCall(seedHex, accountIndex(index), target, argsHex, nonce, meterLimitOf(meterLimit), String(fee), String(value), chainId, this._validity(info))
|
|
474
512
|
);
|
|
513
|
+
this._guardSigned(from, nonce, signed.tx_hex);
|
|
475
514
|
const outcome = await this.submit(signed.tx_hex);
|
|
476
515
|
this._remember(from, nonce, outcome);
|
|
477
516
|
return { signed, outcome };
|
|
@@ -535,6 +574,7 @@ function makeClient(core) {
|
|
|
535
574
|
const signed = JSON.parse(
|
|
536
575
|
core.sign_call(callerSeedHex, accountIndex(callerIndex), contract, order.call_args, accountNonceUsed, meterLimitOf(meterLimit), String(fee), chainId, this._validity(info))
|
|
537
576
|
);
|
|
577
|
+
this._guardSigned(from, nonce, signed.tx_hex);
|
|
538
578
|
const outcome = await this.submit(signed.tx_hex);
|
|
539
579
|
this._remember(from, accountNonceUsed, outcome);
|
|
540
580
|
this._remember(orderKey, nonce, outcome);
|