@quantovainc/qcore 0.1.4 → 0.1.5

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
@@ -1,14 +1,56 @@
1
1
  # QCore.js
2
2
 
3
+ [![ci](https://github.com/Quantova/QCore.js/actions/workflows/ci.yml/badge.svg)](https://github.com/Quantova/QCore.js/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/@quantovainc/qcore)](https://www.npmjs.com/package/@quantovainc/qcore)
5
+
6
+ The post quantum client core for Quantova. It signs with ML-DSA-65, builds Q1 addresses, and runs the same compiled core in the browser and in Node.
7
+
3
8
  **This npm account and this package belong to Quantova Inc. It is the official Quantova client SDK. Any similarly named package published under a different account is not this one and is not from Quantova.**
4
9
 
5
- QCore.js is the official Quantova client core for JavaScript, built and published by Quantova Inc. The key derivation, the post quantum signing, and every RPC request body are handled by a single compiled core module and are never rewritten in JavaScript. The page does the fetch and the core does everything that has to be exactly right. That matters because a second implementation of the signing would be a second chance to be wrong with a user's money, so there is only ever the one.
10
+ ## Install
11
+
12
+ ```
13
+ npm install @quantovainc/qcore
14
+ ```
15
+
16
+ ## Quickstart
17
+
18
+ ```js
19
+ const { Client, core, generateSeed } = require('@quantovainc/qcore');
20
+
21
+ async function main() {
22
+ // Create an account. The seed is the only backup and it never leaves the device.
23
+ const seed = generateSeed();
24
+ const phrase = core.mnemonicFromSeed(seed);
25
+
26
+ // Derive the Q1 address for account index zero, and a recipient at index one.
27
+ const from = core.address(seed, 0n);
28
+ const to = core.address(seed, 1n);
29
+ console.log('from', from);
30
+
31
+ // Open a client against a node you trust. Plaintext http is allowed only to a loopback node.
32
+ const client = new Client('http://127.0.0.1:8645');
6
33
 
7
- This is the official Quantova Inc package. On npm it installs as @quantovainc/qcore, which is the Quantova Inc account. Any package under a different scope or publisher, however close the name looks, is not this one and is not from Quantova.
34
+ // A fixed ceiling your app is willing to pay in fees, chosen here and never read back from the gateway.
35
+ const MAX_FEE_QUON = '2000';
36
+
37
+ // Amounts and the ceiling are decimal strings or BigInt values, never JavaScript numbers.
38
+ const { signed, outcome } = await client.transfer(seed, 0, to, '1000', MAX_FEE_QUON);
39
+ console.log('submitted', signed.tx_id, outcome.verdict);
40
+ }
41
+
42
+ main().catch((err) => { console.error(err.message); process.exit(1); });
43
+ ```
44
+
45
+ A runnable copy of this lives in [examples/quickstart.js](examples/quickstart.js).
46
+
47
+ ## Security posture
48
+
49
+ Every signature is ML-DSA-65 as standardised in FIPS 204, and addresses and transaction digests are built with SHA-3. There is no elliptic curve anywhere in this package, no secp256k1 and no ECDSA. When you send through the Client you pass a maximum fee that you choose yourself, and the Client refuses to sign when the gateway reports a fee above it, so a hostile gateway cannot inflate the fee and drain the balance. The Client also refuses an unsafe transport. A remote gateway must be reached over https, and plaintext http is allowed only to a loopback node, because a fee and a nonce read back over plaintext could be rewritten on the wire. This package is at the testnet and pre audit stage, so please do not read it as audited.
8
50
 
9
51
  ## What this library is for
10
52
 
11
- Any browser app, extension, or Node service that holds a Quantova account, reads the chain, and sends signed transactions. A wallet, a dApp, a block explorer front end, or a faucet page. Your code does the network fetch it prefers and QCore.js derives the key, signs the transaction, and builds the request body, so nothing about the cryptography or the byte layout of a transaction is written in JavaScript.
53
+ Any browser app, extension, or Node service that holds a Quantova account, reads the chain, and sends signed transactions. A wallet, a dApp, a block explorer front end, or a faucet page. Your code does the network fetch it prefers and QCore.js derives the key, signs the transaction, and builds the request body, so nothing about the cryptography or the byte layout of a transaction is written in JavaScript. A second implementation of the signing would be a second chance to be wrong with a user's money, so there is only ever the one.
12
54
 
13
55
  ## The post quantum cryptography it handles
14
56
 
@@ -100,6 +142,10 @@ const signed = JSON.parse(
100
142
 
101
143
  The compiled core in this package is only the shipping form of the one Rust core, built so the same key derivation, signing, and wire code runs in a browser and in Node. It is not a virtual machine and it is not what the chain runs. The Quantova chain executes only the QVM, the container machine that runs contracts. The compiled core here is a client side build artifact and nothing more.
102
144
 
145
+ ## TypeScript
146
+
147
+ The package ships TypeScript types in `index.d.ts`, so the Client, the core functions, and the Network helpers are all typed with no extra install.
148
+
103
149
  ## Builds
104
150
 
105
151
  The package ships one guarded surface, the Client with the fee cap and the amount guard, over two
@@ -115,17 +161,33 @@ and `@quantovainc/qcore/pkg-node` for the nodejs build. Those export only the co
115
161
  Client, no fee cap, and no amount guard, and are for an advanced caller who assembles the fee comparison
116
162
  alone. The default browser, import, node, and require resolutions never reach them.
117
163
 
118
- To build both from source.
164
+ To build both from source, run the two documented builds or `npm run build`.
119
165
 
120
166
  ```
121
167
  wasm-pack build --target bundler --out-dir pkg
122
168
  wasm-pack build --target nodejs --out-dir pkg-node
123
169
  ```
124
170
 
125
- ## A note on releases
171
+ The offline test scripts run with `npm test`, and they need no network and no running gateway.
172
+
173
+ ## Conformance vectors
174
+
175
+ The frozen conformance vectors live in the [conformance](conformance) folder of this repository. The address derivation is in `conformance/address.derivation.json`, the transfer in `conformance/transaction.transfer.json`, and the payable call in `conformance/transaction.payable.json`. The offline test `test-conformance.js` signs from these vectors and checks the binding matches them byte for byte, so a change that would move a signed transaction fails the test.
176
+
177
+ ## Examples
178
+
179
+ The [examples](examples) folder holds a runnable quickstart that mirrors the one above. For fuller starters, see the Q-Scaffold and the Q-Forge repositories under the Quantova org, at https://github.com/Quantova/Q-Scaffold and https://github.com/Quantova/Q-Forge.
180
+
181
+ ## Releases and provenance
182
+
183
+ Releases are published from CI by the publish workflow in `.github/workflows/publish.yml`, which runs on a pushed git tag. It builds the two wasm targets, runs the offline tests, and publishes with npm provenance, so the copy of the package on the registry carries a verified provenance badge that ties it to this exact public commit. Nothing is published to a registry without an explicit tag. This package handles a user's keys and a published version cannot be taken back.
184
+
185
+ For the provenance publish to run, the maintainer adds an `NPM_TOKEN` automation secret to the repository, or configures npm trusted publishing for the package so the workflow authenticates over OIDC with no stored token.
186
+
187
+ ## Security
126
188
 
127
- Nothing here is published to a registry without an explicit release. This package handles a user's keys and a published version cannot be taken back.
189
+ To report a vulnerability, see [SECURITY.md](SECURITY.md). Please report privately and not through a public issue.
128
190
 
129
191
  ## Ownership and license
130
192
 
131
- QCore.js is built and owned by Quantova Inc, and the signing lives in one compiled core and is never rewritten in JavaScript. It carries none of the industry stack and inherits nothing from it. It is released under the Apache 2.0 and MIT licenses so any wallet, explorer, or service may build on it, and the copyright stays with Quantova Inc.
193
+ QCore.js is built and owned by Quantova Inc, and the signing lives in one compiled core and is never rewritten in JavaScript. It carries none of the industry stack and inherits nothing from it. It is dual licensed under the Apache 2.0 and MIT licenses so any wallet, explorer, or service may build on it, and the copyright stays with Quantova Inc.
package/index.d.ts CHANGED
@@ -39,6 +39,7 @@ export interface QCore {
39
39
  address(seed_hex: string, index: bigint): string;
40
40
  block_by_height_body(height: bigint): string;
41
41
  buildSignedOrderCall(
42
+ chain_id: bigint,
42
43
  contract: string,
43
44
  selector_hex: string,
44
45
  scheme_off: bigint,
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@quantovainc/qcore",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Quantova's post quantum client core for JavaScript. Signs with ML-DSA-65 and builds Q1 addresses.",
5
+ "scripts": {
6
+ "build": "wasm-pack build --target bundler --out-dir pkg && wasm-pack build --target nodejs --out-dir pkg-node",
7
+ "test": "node test-conformance.js && node test-sign-validate.js && node test-transport.js && node test-fee-cap.js"
8
+ },
5
9
  "keywords": [
6
10
  "post-quantum",
7
11
  "quantum-resistant",
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.4",
5
+ "version": "0.1.5",
6
6
  "license": "Apache-2.0 OR MIT",
7
7
  "repository": {
8
8
  "type": "git",
package/pkg/qcore_js.d.ts CHANGED
@@ -7,7 +7,7 @@ export function address(seed_hex: string, index: bigint): string;
7
7
 
8
8
  export function block_by_height_body(height: bigint): string;
9
9
 
10
- export function buildSignedOrderCall(contract: string, selector_hex: string, scheme_off: bigint, ptr_off: bigint, field_offs_csv: string, fields_csv: string, region_off: bigint, owner_seed_hex: string, owner_index: bigint, nonce: bigint): string;
10
+ export function buildSignedOrderCall(chain_id: bigint, contract: string, selector_hex: string, scheme_off: bigint, ptr_off: bigint, field_offs_csv: string, fields_csv: string, region_off: bigint, owner_seed_hex: string, owner_index: bigint, nonce: bigint): string;
11
11
 
12
12
  export function chainIdFromName(name: string): bigint;
13
13
 
@@ -61,6 +61,7 @@ export function block_by_height_body(height) {
61
61
  }
62
62
 
63
63
  /**
64
+ * @param {bigint} chain_id
64
65
  * @param {string} contract
65
66
  * @param {string} selector_hex
66
67
  * @param {bigint} scheme_off
@@ -73,7 +74,7 @@ export function block_by_height_body(height) {
73
74
  * @param {bigint} nonce
74
75
  * @returns {string}
75
76
  */
76
- export function buildSignedOrderCall(contract, selector_hex, scheme_off, ptr_off, field_offs_csv, fields_csv, region_off, owner_seed_hex, owner_index, nonce) {
77
+ export function buildSignedOrderCall(chain_id, contract, selector_hex, scheme_off, ptr_off, field_offs_csv, fields_csv, region_off, owner_seed_hex, owner_index, nonce) {
77
78
  let deferred7_0;
78
79
  let deferred7_1;
79
80
  try {
@@ -87,7 +88,7 @@ export function buildSignedOrderCall(contract, selector_hex, scheme_off, ptr_off
87
88
  const len3 = WASM_VECTOR_LEN;
88
89
  const ptr4 = passStringToWasm0(owner_seed_hex, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
89
90
  const len4 = WASM_VECTOR_LEN;
90
- const ret = wasm.buildSignedOrderCall(ptr0, len0, ptr1, len1, scheme_off, ptr_off, ptr2, len2, ptr3, len3, region_off, ptr4, len4, owner_index, nonce);
91
+ const ret = wasm.buildSignedOrderCall(chain_id, ptr0, len0, ptr1, len1, scheme_off, ptr_off, ptr2, len2, ptr3, len3, region_off, ptr4, len4, owner_index, nonce);
91
92
  var ptr6 = ret[0];
92
93
  var len6 = ret[1];
93
94
  if (ret[3]) {
Binary file
@@ -4,7 +4,7 @@ export const memory: WebAssembly.Memory;
4
4
  export const account_body: (a: number, b: number) => [number, number];
5
5
  export const address: (a: number, b: number, c: bigint) => [number, number, number, number];
6
6
  export const block_by_height_body: (a: bigint) => [number, number];
7
- export const buildSignedOrderCall: (a: number, b: number, c: number, d: number, e: bigint, f: bigint, g: number, h: number, i: number, j: number, k: bigint, l: number, m: number, n: bigint, o: bigint) => [number, number, number, number];
7
+ export const buildSignedOrderCall: (a: bigint, b: number, c: number, d: number, e: number, f: bigint, g: bigint, h: number, i: number, j: number, k: number, l: bigint, m: number, n: number, o: bigint, p: bigint) => [number, number, number, number];
8
8
  export const chainIdFromName: (a: number, b: number) => bigint;
9
9
  export const contractAddress: (a: number, b: number, c: bigint) => [number, number];
10
10
  export const eventsBody: (a: bigint) => [number, number];
@@ -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.4",
4
+ "version": "0.1.5",
5
5
  "license": "Apache-2.0 OR MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -7,7 +7,7 @@ export function address(seed_hex: string, index: bigint): string;
7
7
 
8
8
  export function block_by_height_body(height: bigint): string;
9
9
 
10
- export function buildSignedOrderCall(contract: string, selector_hex: string, scheme_off: bigint, ptr_off: bigint, field_offs_csv: string, fields_csv: string, region_off: bigint, owner_seed_hex: string, owner_index: bigint, nonce: bigint): string;
10
+ export function buildSignedOrderCall(chain_id: bigint, contract: string, selector_hex: string, scheme_off: bigint, ptr_off: bigint, field_offs_csv: string, fields_csv: string, region_off: bigint, owner_seed_hex: string, owner_index: bigint, nonce: bigint): string;
11
11
 
12
12
  export function chainIdFromName(name: string): bigint;
13
13
 
@@ -66,6 +66,7 @@ function block_by_height_body(height) {
66
66
  exports.block_by_height_body = block_by_height_body;
67
67
 
68
68
  /**
69
+ * @param {bigint} chain_id
69
70
  * @param {string} contract
70
71
  * @param {string} selector_hex
71
72
  * @param {bigint} scheme_off
@@ -78,7 +79,7 @@ exports.block_by_height_body = block_by_height_body;
78
79
  * @param {bigint} nonce
79
80
  * @returns {string}
80
81
  */
81
- function buildSignedOrderCall(contract, selector_hex, scheme_off, ptr_off, field_offs_csv, fields_csv, region_off, owner_seed_hex, owner_index, nonce) {
82
+ function buildSignedOrderCall(chain_id, contract, selector_hex, scheme_off, ptr_off, field_offs_csv, fields_csv, region_off, owner_seed_hex, owner_index, nonce) {
82
83
  let deferred7_0;
83
84
  let deferred7_1;
84
85
  try {
@@ -92,7 +93,7 @@ function buildSignedOrderCall(contract, selector_hex, scheme_off, ptr_off, field
92
93
  const len3 = WASM_VECTOR_LEN;
93
94
  const ptr4 = passStringToWasm0(owner_seed_hex, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
94
95
  const len4 = WASM_VECTOR_LEN;
95
- const ret = wasm.buildSignedOrderCall(ptr0, len0, ptr1, len1, scheme_off, ptr_off, ptr2, len2, ptr3, len3, region_off, ptr4, len4, owner_index, nonce);
96
+ const ret = wasm.buildSignedOrderCall(chain_id, ptr0, len0, ptr1, len1, scheme_off, ptr_off, ptr2, len2, ptr3, len3, region_off, ptr4, len4, owner_index, nonce);
96
97
  var ptr6 = ret[0];
97
98
  var len6 = ret[1];
98
99
  if (ret[3]) {
Binary file
package/shared.js CHANGED
@@ -286,9 +286,18 @@ function makeClient(core) {
286
286
  async callSignedOrder(callerSeedHex, callerIndex, contract, selectorHex, layout, fields, ownerSeedHex, ownerIndex, meterLimit, maxFeeQuon) {
287
287
  if (!core.valid_address(contract)) throw new Error('the contract is not a q1 address');
288
288
  const ceiling = feeCeiling(maxFeeQuon);
289
+ const info = await this.nodeInfo();
290
+ this._guardMainnet();
291
+ const chainId = signingChainId(info);
292
+ const fee = info && info.fee && info.fee.transfer_quon;
293
+ if (fee == null) throw new Error('the gateway did not report a transfer fee');
294
+ if (BigInt(fee) > ceiling) {
295
+ throw new Error(`the gateway fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
296
+ }
289
297
  const signer = core.orderSigner(ownerSeedHex, BigInt(ownerIndex));
290
298
  const nonce = await this.contractNonce(contract, signer);
291
299
  const order = JSON.parse(core.buildSignedOrderCall(
300
+ chainId,
292
301
  contract,
293
302
  selectorHex,
294
303
  BigInt(layout.schemeOff),
@@ -300,14 +309,6 @@ function makeClient(core) {
300
309
  BigInt(ownerIndex),
301
310
  nonce,
302
311
  ));
303
- const info = await this.nodeInfo();
304
- this._guardMainnet();
305
- const chainId = signingChainId(info);
306
- const fee = info && info.fee && info.fee.transfer_quon;
307
- if (fee == null) throw new Error('the gateway did not report a transfer fee');
308
- if (BigInt(fee) > ceiling) {
309
- throw new Error(`the gateway fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
310
- }
311
312
  const from = core.address(callerSeedHex, BigInt(callerIndex));
312
313
  const acct = await this.account(from);
313
314
  if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');