@quantovainc/qcore 0.1.33 → 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 CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@quantovainc/qcore",
3
- "version": "0.1.33",
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 && node test-account-index.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.1.33",
5
+ "version": "0.2.0",
6
6
  "license": "Apache-2.0 OR MIT",
7
7
  "repository": {
8
8
  "type": "git",
Binary file
@@ -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.1.33",
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
@@ -331,11 +331,34 @@ function makeClient(core) {
331
331
  _checkedNonce(reported, expected, key) {
332
332
  const n = accountNonce(reported);
333
333
  if (!this._nextNonces) this._nextNonces = new Map();
334
+ if (!this._signedNonces) this._signedNonces = new Map();
334
335
  const local = key != null ? this._nextNonces.get(key) : undefined;
335
336
  const e = expected != null ? BigInt(expected) : local;
336
- if (e == null) return n;
337
- 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`);
338
- return expected != null ? e : n;
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);
339
362
  }
340
363
 
341
364
  _remember(key, used, outcome) {
@@ -387,6 +410,7 @@ function makeClient(core) {
387
410
  const signed = JSON.parse(
388
411
  core.sign_transfer(seedHex, accountIndex(index), to, String(amount), nonce, String(fee), chainId, this._validity(info))
389
412
  );
413
+ this._guardSigned(from, nonce, signed.tx_hex);
390
414
  const outcome = await this.submit(signed.tx_hex);
391
415
  this._remember(from, nonce, outcome);
392
416
  return { signed, outcome };
@@ -408,6 +432,7 @@ function makeClient(core) {
408
432
  if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
409
433
  const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
410
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);
411
436
  const outcome = await this.submit(signed.tx_hex);
412
437
  this._remember(from, nonce, outcome);
413
438
  return { signed, outcome };
@@ -432,6 +457,7 @@ function makeClient(core) {
432
457
  const signed = JSON.parse(
433
458
  core.sign_call(seedHex, accountIndex(index), target, argsHex, nonce, meterLimitOf(meterLimit), String(fee), chainId, this._validity(info))
434
459
  );
460
+ this._guardSigned(from, nonce, signed.tx_hex);
435
461
  const outcome = await this.submit(signed.tx_hex);
436
462
  this._remember(from, nonce, outcome);
437
463
  return { signed, outcome };
@@ -458,6 +484,7 @@ function makeClient(core) {
458
484
  const signed = JSON.parse(
459
485
  core.signAssetCall(seedHex, accountIndex(index), target, argsHex, assetIssuer, String(amount), nonce, meterLimitOf(meterLimit), String(fee), chainId, this._validity(info))
460
486
  );
487
+ this._guardSigned(from, nonce, signed.tx_hex);
461
488
  const outcome = await this.submit(signed.tx_hex);
462
489
  this._remember(from, nonce, outcome);
463
490
  return { signed, outcome };
@@ -483,6 +510,7 @@ function makeClient(core) {
483
510
  const signed = JSON.parse(
484
511
  core.signPayableCall(seedHex, accountIndex(index), target, argsHex, nonce, meterLimitOf(meterLimit), String(fee), String(value), chainId, this._validity(info))
485
512
  );
513
+ this._guardSigned(from, nonce, signed.tx_hex);
486
514
  const outcome = await this.submit(signed.tx_hex);
487
515
  this._remember(from, nonce, outcome);
488
516
  return { signed, outcome };
@@ -546,6 +574,7 @@ function makeClient(core) {
546
574
  const signed = JSON.parse(
547
575
  core.sign_call(callerSeedHex, accountIndex(callerIndex), contract, order.call_args, accountNonceUsed, meterLimitOf(meterLimit), String(fee), chainId, this._validity(info))
548
576
  );
577
+ this._guardSigned(from, nonce, signed.tx_hex);
549
578
  const outcome = await this.submit(signed.tx_hex);
550
579
  this._remember(from, accountNonceUsed, outcome);
551
580
  this._remember(orderKey, nonce, outcome);
package/shared.mjs CHANGED
@@ -331,11 +331,34 @@ function makeClient(core) {
331
331
  _checkedNonce(reported, expected, key) {
332
332
  const n = accountNonce(reported);
333
333
  if (!this._nextNonces) this._nextNonces = new Map();
334
+ if (!this._signedNonces) this._signedNonces = new Map();
334
335
  const local = key != null ? this._nextNonces.get(key) : undefined;
335
336
  const e = expected != null ? BigInt(expected) : local;
336
- if (e == null) return n;
337
- 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`);
338
- return expected != null ? e : n;
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);
339
362
  }
340
363
 
341
364
  _remember(key, used, outcome) {
@@ -387,6 +410,7 @@ function makeClient(core) {
387
410
  const signed = JSON.parse(
388
411
  core.sign_transfer(seedHex, accountIndex(index), to, String(amount), nonce, String(fee), chainId, this._validity(info))
389
412
  );
413
+ this._guardSigned(from, nonce, signed.tx_hex);
390
414
  const outcome = await this.submit(signed.tx_hex);
391
415
  this._remember(from, nonce, outcome);
392
416
  return { signed, outcome };
@@ -408,6 +432,7 @@ function makeClient(core) {
408
432
  if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
409
433
  const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
410
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);
411
436
  const outcome = await this.submit(signed.tx_hex);
412
437
  this._remember(from, nonce, outcome);
413
438
  return { signed, outcome };
@@ -432,6 +457,7 @@ function makeClient(core) {
432
457
  const signed = JSON.parse(
433
458
  core.sign_call(seedHex, accountIndex(index), target, argsHex, nonce, meterLimitOf(meterLimit), String(fee), chainId, this._validity(info))
434
459
  );
460
+ this._guardSigned(from, nonce, signed.tx_hex);
435
461
  const outcome = await this.submit(signed.tx_hex);
436
462
  this._remember(from, nonce, outcome);
437
463
  return { signed, outcome };
@@ -458,6 +484,7 @@ function makeClient(core) {
458
484
  const signed = JSON.parse(
459
485
  core.signAssetCall(seedHex, accountIndex(index), target, argsHex, assetIssuer, String(amount), nonce, meterLimitOf(meterLimit), String(fee), chainId, this._validity(info))
460
486
  );
487
+ this._guardSigned(from, nonce, signed.tx_hex);
461
488
  const outcome = await this.submit(signed.tx_hex);
462
489
  this._remember(from, nonce, outcome);
463
490
  return { signed, outcome };
@@ -483,6 +510,7 @@ function makeClient(core) {
483
510
  const signed = JSON.parse(
484
511
  core.signPayableCall(seedHex, accountIndex(index), target, argsHex, nonce, meterLimitOf(meterLimit), String(fee), String(value), chainId, this._validity(info))
485
512
  );
513
+ this._guardSigned(from, nonce, signed.tx_hex);
486
514
  const outcome = await this.submit(signed.tx_hex);
487
515
  this._remember(from, nonce, outcome);
488
516
  return { signed, outcome };
@@ -546,6 +574,7 @@ function makeClient(core) {
546
574
  const signed = JSON.parse(
547
575
  core.sign_call(callerSeedHex, accountIndex(callerIndex), contract, order.call_args, accountNonceUsed, meterLimitOf(meterLimit), String(fee), chainId, this._validity(info))
548
576
  );
577
+ this._guardSigned(from, nonce, signed.tx_hex);
549
578
  const outcome = await this.submit(signed.tx_hex);
550
579
  this._remember(from, accountNonceUsed, outcome);
551
580
  this._remember(orderKey, nonce, outcome);