@quantovainc/qcore 0.1.30 → 0.1.31

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/README.md CHANGED
@@ -91,7 +91,7 @@ Quantova is post quantum from the ground. There is no elliptic curve anywhere in
91
91
 
92
92
  3. The address. A Quantova address is the Bech32m Q1 string that renders the SHA3 256 hash of the scheme byte together with the whole module lattice public key of one thousand nine hundred fifty two bytes. The entire public key is bound into the address. Nothing is truncated to a twenty byte hash and there is no key recovery from a signature, so one address names exactly one post quantum key.
93
93
 
94
- 4. The transaction. A transaction body carries the sender, the nonce, the meter limit, the fee, and the call. The bytes a signature stands over are the SHA3 256 hash of the canonical body followed by a fixed Quantova transaction domain tag, so a transaction signature can never be replayed as another kind of signed message. QCore.js assembles the body, signs the digest inside the compiled core, and returns the wrapper bytes and the transaction id ready for the gateway.
94
+ 4. The transaction. A transaction body carries the sender, the nonce, the meter limit, the fee, and the call. The bytes a signature stands over are the SHA3 256 hash of the canonical body, and a fixed Quantova transaction domain tag is bound in as the post quantum signing context, so a transaction signature can never be replayed as another kind of signed message. QCore.js assembles the body, signs the digest inside the compiled core, and returns the wrapper bytes and the transaction id ready for the gateway.
95
95
 
96
96
  5. The payable call. `core.signPayableCall` carries a native value and an explicit chain id alongside the sender, the nonce, the meter limit, the fee, and the call, so a call to a contract can move value in the same signed body a transfer does, and the same signature can be pinned to one network. The sender and the target never enter that signature as the rendered Q1 string, since a display convention can change over time. Each is parsed back to its raw thirty two byte payload first, and that payload, never the string, is what the preimage carries, so the signature stays bound to the account and never to how an address happened to be printed.
97
97
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantovainc/qcore",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
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",
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.29",
5
+ "version": "0.1.31",
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.29",
4
+ "version": "0.1.31",
5
5
  "license": "Apache-2.0 OR MIT",
6
6
  "repository": {
7
7
  "type": "git",
Binary file
package/shared.js CHANGED
@@ -275,7 +275,22 @@ function makeClient(core) {
275
275
 
276
276
  nodeInfo() { return this._call('node_info', '{}'); }
277
277
  head() { return this._call('head', '{}'); }
278
- account(address) { return this._call('get_account', core.account_body(address)); }
278
+ async account(address) {
279
+ const acct = await this._call('get_account', core.account_body(address));
280
+ if (acct && acct.address != null && acct.address !== address) {
281
+ throw new Error(`the gateway answered for ${acct.address} when asked about ${address}, refusing to trust it`);
282
+ }
283
+ return acct;
284
+ }
285
+ _checkedNonce(reported, expected) {
286
+ const n = accountNonce(reported);
287
+ if (expected != null) {
288
+ const e = BigInt(expected);
289
+ if (n < e) throw new Error(`the gateway reported nonce ${n} below the expected ${e}; refusing so a replayed lower nonce cannot force a second payment`);
290
+ if (n > e + 16n) throw new Error(`the gateway reported nonce ${n} far above the expected ${e}; refusing`);
291
+ }
292
+ return n;
293
+ }
279
294
  transaction(txId) { return this._call('get_transaction', core.transaction_body(txId)); }
280
295
  block(height) { return this._call('get_block', core.block_by_height_body(BigInt(height))); }
281
296
  submit(txHex) { return this._call('submit_transaction', core.submit_body(txHex)); }
@@ -285,7 +300,7 @@ function makeClient(core) {
285
300
 
286
301
  address(seedHex, index) { return core.address(seedHex, accountIndex(index)); }
287
302
 
288
- async transfer(seedHex, index, to, amount, maxFeeQuon) {
303
+ async transfer(seedHex, index, to, amount, maxFeeQuon, expectedNonce) {
289
304
  if (!core.valid_address(to)) throw new Error('the recipient is not a q1 address');
290
305
  checkAmount(amount);
291
306
  const ceiling = feeCeiling(maxFeeQuon);
@@ -300,8 +315,9 @@ function makeClient(core) {
300
315
  const from = core.address(seedHex, accountIndex(index));
301
316
  const acct = await this.account(from);
302
317
  if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
318
+ const nonce = this._checkedNonce(acct.nonce, expectedNonce);
303
319
  const signed = JSON.parse(
304
- core.sign_transfer(seedHex, accountIndex(index), to, String(amount), accountNonce(acct.nonce), String(fee), chainId)
320
+ core.sign_transfer(seedHex, accountIndex(index), to, String(amount), nonce, String(fee), chainId)
305
321
  );
306
322
  const outcome = await this.submit(signed.tx_hex);
307
323
  return { signed, outcome };