@qorechain/sdk 0.6.1 → 0.8.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/README.md +141 -13
- package/dist/index.cjs +2675 -423
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1623 -195
- package/dist/index.d.ts +1623 -195
- package/dist/index.js +2638 -424
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -108,8 +108,8 @@ console.log(result.transactionHash);
|
|
|
108
108
|
QoreChain supports post-quantum cryptography via ML-DSA-87 (Dilithium-5) and a
|
|
109
109
|
hybrid posture. The key/sign/verify primitives are available today through
|
|
110
110
|
`generatePqcKeypair`, `pqcSign`, `pqcVerify`, and the pluggable `PqcSigner` /
|
|
111
|
-
`HybridSigner
|
|
112
|
-
|
|
111
|
+
`HybridSigner`, and hybrid transactions end-to-end through `buildHybridTx` /
|
|
112
|
+
`signAndBroadcastHybrid`.
|
|
113
113
|
|
|
114
114
|
```ts
|
|
115
115
|
import { generatePqcKeypair, pqcSign, pqcVerify } from "@qorechain/sdk";
|
|
@@ -120,6 +120,49 @@ const signature = pqcSign(keypair.secretKey, message);
|
|
|
120
120
|
const ok = pqcVerify(keypair.publicKey, message, signature);
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
+
#### Hybrid sign-bytes: v1 / v2 per network (v0.8.0)
|
|
124
|
+
|
|
125
|
+
The ML-DSA-87 half of a hybrid transaction signs the body *without* the PQC
|
|
126
|
+
extension (`B0`) and the AuthInfo bytes (`A`), in one of two forms:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
v1: BE32(len B0) ‖ B0 ‖ BE32(len A) ‖ A
|
|
130
|
+
v2: "qorechain-pqc-hybrid-v2" ‖ BE64(len chainId) ‖ chainId ‖ BE32(len B0) ‖ B0 ‖ BE32(len A) ‖ A
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
A network verifies exactly **one** form at any height. `qorechain-diana`
|
|
134
|
+
(testnet) switched to v2 at its v3.1.98 upgrade; `qorechain-vladi` (mainnet)
|
|
135
|
+
stays on v1 until its own upgrade; chains born later are v2 from genesis. So pass
|
|
136
|
+
the network's REST endpoint and let the SDK ask (`signBytesVersion: "auto"`, the
|
|
137
|
+
default):
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { signAndBroadcastHybrid, isHybridSignBytesRejection } from "@qorechain/sdk";
|
|
141
|
+
|
|
142
|
+
const res = await signAndBroadcastHybrid({
|
|
143
|
+
registry, signer, pqcKeypair, messages, fee,
|
|
144
|
+
chainId: "qorechain-diana",
|
|
145
|
+
rest: "https://api-testnet.qore.host", // picks v1 or v2 for this network
|
|
146
|
+
accountNumber, sequence,
|
|
147
|
+
transport, // e.g. a connected StargateClient
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- `"auto"` reads `GET {rest}/cosmos/upgrade/v1beta1/applied_plan/v3.1.98` (v2 when
|
|
152
|
+
the applied height is above 0) and caches the answer for about a minute.
|
|
153
|
+
- On `qorechain-vladi` / `qorechain-diana`, `"auto"` without `rest` — or with a
|
|
154
|
+
node that cannot be asked — **throws** rather than guess. Force a form with
|
|
155
|
+
`signBytesVersion: "v1"` or `"v2"`.
|
|
156
|
+
- `signAndBroadcastHybrid` and `EthNativeSigner.signAndBroadcast` re-resolve and
|
|
157
|
+
retry once on a `pqc` code 21 refusal. If you broadcast yourself, catch the
|
|
158
|
+
refusal with `isHybridSignBytesRejection(err)` and rebuild with
|
|
159
|
+
`buildHybridTx({ ..., forceRefreshSignBytesVersion: true })`.
|
|
160
|
+
- `built.signBytesVersion` reports the form a built transaction used.
|
|
161
|
+
- The same rule covers the PQC key-migration payload (`migrationSignBytes`) and
|
|
162
|
+
the bridge attestation payload (`bridgeAttestationSignBytes`).
|
|
163
|
+
- `rest` is optional in the TypeScript types, so a caller that forgets it compiles cleanly and only fails at runtime on `qorechain-vladi` / `qorechain-diana`. Cover your wiring with a runtime test, not just a type check.
|
|
164
|
+
- In unit tests, pass `signBytesVersion: "v1"` or `"v2"` explicitly (or inject `fetch`): `"auto"` asks the network, so a test that omits it silently depends on a live node.
|
|
165
|
+
|
|
123
166
|
### CosmWasm contracts
|
|
124
167
|
|
|
125
168
|
Interact with CosmWasm contracts via thin wrappers over
|
|
@@ -334,11 +377,7 @@ post-quantum signature. The account parser (`parseEthPubkeyAny`) reads
|
|
|
334
377
|
account number / sequence from an eth_secp256k1 on-chain pubkey.
|
|
335
378
|
|
|
336
379
|
```ts
|
|
337
|
-
import {
|
|
338
|
-
deriveUnifiedAccount,
|
|
339
|
-
unifiedAccountFromPhantomSignature,
|
|
340
|
-
signHybridEth,
|
|
341
|
-
} from "@qorechain/sdk";
|
|
380
|
+
import { deriveUnifiedAccount, resolveSignBytesVersion, signHybridEth } from "@qorechain/sdk";
|
|
342
381
|
|
|
343
382
|
const account = await deriveUnifiedAccount(mnemonic);
|
|
344
383
|
account.cosmos; // "qor1…" — QoreChain Native lane
|
|
@@ -346,22 +385,34 @@ account.evm; // "0x…" — EIP-55 checksummed
|
|
|
346
385
|
account.svm; // "<base58>" — 20 bytes + 12 zero pad
|
|
347
386
|
|
|
348
387
|
// Sign a QoreChain Native tx from the unified eth key (hybrid PQC path).
|
|
388
|
+
// signHybridEth is synchronous: resolve the sign-bytes form for the network
|
|
389
|
+
// first (v1 on mainnet until its v3.1.98 upgrade, v2 after).
|
|
390
|
+
const signBytesVersion = await resolveSignBytesVersion({
|
|
391
|
+
chainId: "qorechain-vladi",
|
|
392
|
+
rest: "https://api.qore.host",
|
|
393
|
+
});
|
|
349
394
|
const signed = signHybridEth({
|
|
350
|
-
|
|
351
|
-
pqc: account.pqc,
|
|
395
|
+
account, // privateKey, publicKey and the pqc keypair
|
|
352
396
|
messages: [msg.cosmos.send(/* … */)],
|
|
353
397
|
chainId: "qorechain-vladi",
|
|
354
398
|
accountNumber,
|
|
355
399
|
sequence,
|
|
356
400
|
fee,
|
|
401
|
+
signBytesVersion,
|
|
402
|
+
encodeMessage: (m) => registry.encodeAsAny(m),
|
|
357
403
|
});
|
|
358
404
|
|
|
359
|
-
//
|
|
360
|
-
//
|
|
361
|
-
const fromPhantom = unifiedAccountFromPhantomSignature(phantomSignature);
|
|
362
|
-
// or connectPhantomUnified(provider) to run the connect → sign → derive flow.
|
|
405
|
+
// Or let EthNativeSigner resolve and retry for you:
|
|
406
|
+
// new EthNativeSigner(account, { rest: "https://api.qore.host" }).signAndBroadcast(transport, params)
|
|
363
407
|
```
|
|
364
408
|
|
|
409
|
+
`unifiedAccountFromSeed` uses the 32 bytes it is given **as** the spend key, so
|
|
410
|
+
pass real secret entropy — a CSPRNG seed or a mnemonic-derived key — never a
|
|
411
|
+
wallet signature or any other value a third party can request. (The signature-
|
|
412
|
+
derived helpers `unifiedAccountFromPhantomSignature` / `connectPhantomUnified`
|
|
413
|
+
were removed in v0.8.0 for exactly that reason and now throw.) To let an
|
|
414
|
+
external wallet key act for an account, use the authenticator lanes below.
|
|
415
|
+
|
|
365
416
|
See the [unified-wallet](../../docs/docs/guides/unified-wallet.md) guide.
|
|
366
417
|
|
|
367
418
|
### Cross-VM message reads
|
|
@@ -377,6 +428,83 @@ const params = await client.crossvm.params();
|
|
|
377
428
|
const status = await client.qor.getCrossVmMessage("42");
|
|
378
429
|
```
|
|
379
430
|
|
|
431
|
+
## Authenticator lanes (v0.7.0 / chain v3.1.85)
|
|
432
|
+
|
|
433
|
+
A linked external key — a Phantom **ed25519** key, or a MetaMask **secp256k1**
|
|
434
|
+
key bound **by address** — can spend from the ONE canonical PQC account through
|
|
435
|
+
a **relayer** that submits the tx and pays the fee (its own hybrid-PQC signature
|
|
436
|
+
satisfies the ante). The external key **never produces an ML-DSA co-signature**;
|
|
437
|
+
its signature over the domain-separated, replay-bound sign-bytes **is** the
|
|
438
|
+
authorization. Every lane runs under least-privilege, spending-limit and
|
|
439
|
+
revocable terms enforced on-chain.
|
|
440
|
+
|
|
441
|
+
Three messages carry the lanes:
|
|
442
|
+
|
|
443
|
+
| Message | Type URL | Composer |
|
|
444
|
+
|---|---|---|
|
|
445
|
+
| `MsgExecuteEVM` | `/qorechain.abstractaccount.v1.MsgExecuteEVM` | `msg.abstractaccount.executeEvm` |
|
|
446
|
+
| `MsgExecuteCosmos` | `/qorechain.abstractaccount.v1.MsgExecuteCosmos` | `msg.abstractaccount.executeCosmos` |
|
|
447
|
+
| `MsgRotatePQCKey` | `/qorechain.pqc.v1.MsgRotatePQCKey` | `msg.pqc.rotatePqcKey` |
|
|
448
|
+
|
|
449
|
+
**Sign-bytes helpers** rebuild the exact digest the chain re-derives, so you can
|
|
450
|
+
verify byte-for-byte before signing: `evmAuthSignBytes` / `cosmosAuthSignBytes`
|
|
451
|
+
(32-byte SHA-256 digests) and `rotationSignBytes` (the domain-separated string
|
|
452
|
+
both keys sign).
|
|
453
|
+
|
|
454
|
+
**NONCE semantics:**
|
|
455
|
+
|
|
456
|
+
- `MsgExecuteEVM.nonce` = the account's **current EVM nonce** (the relayer is a
|
|
457
|
+
different account than the owner, so its envelope does **not** bump the
|
|
458
|
+
account's nonce — pass the value as-is, do **not** `+1`).
|
|
459
|
+
- `MsgExecuteCosmos.nonce` = the **per-authenticator sequence** for
|
|
460
|
+
`(account, pubkey)`, a store counter distinct from the account's own sequence.
|
|
461
|
+
|
|
462
|
+
**Wallet builders** sign the digest and return a ready-to-broadcast message:
|
|
463
|
+
`buildPhantomExecuteEvm` / `buildPhantomExecuteCosmos` (ed25519),
|
|
464
|
+
`buildMetaMaskExecuteEvm` / `buildMetaMaskExecuteCosmos` (secp256k1-by-address),
|
|
465
|
+
and `registerEthAuthenticatorMsg` to link an EVM key first.
|
|
466
|
+
|
|
467
|
+
**Permission taxonomy & errors.** Query the on-chain permission schema with
|
|
468
|
+
`client.query.getPermissionSchema()` (REST) / `client.grpc.permissionSchema()`
|
|
469
|
+
(gRPC) and compare a candidate action against it before submitting. Failed lanes
|
|
470
|
+
surface structured codes via `decodeTxError` — codespace `abstractaccount`: `5`
|
|
471
|
+
SpendingLimitExceeded, `6` SessionKeyExpired, `10` PermissionDenied, `11`
|
|
472
|
+
AuthenticatorReplay; codespace `pqc`: `21` HybridVerifyFailed.
|
|
473
|
+
|
|
474
|
+
**Key rotation.** Migrate a legacy `shake256(mnemonic)` key to the canonical,
|
|
475
|
+
address-bound key with `rotatePqcKeyMsgFromMnemonic` (dual-signs over
|
|
476
|
+
`rotationSignBytes`); `derivePqcLegacy` re-derives the old key for the old-key
|
|
477
|
+
half of the signature.
|
|
478
|
+
|
|
479
|
+
```ts
|
|
480
|
+
import {
|
|
481
|
+
buildPhantomExecuteEvm,
|
|
482
|
+
rotatePqcKeyMsgFromMnemonic,
|
|
483
|
+
} from "@qorechain/sdk";
|
|
484
|
+
|
|
485
|
+
// Phantom ed25519 authenticator spends from the canonical account; the relayer
|
|
486
|
+
// broadcasts and pays. `nonce` is the account's CURRENT EVM nonce (no +1).
|
|
487
|
+
const execMsg = await buildPhantomExecuteEvm({
|
|
488
|
+
wallet, // { publicKey, signMessage }
|
|
489
|
+
relayer: "qor1relayer…",
|
|
490
|
+
chainId: "qorechain-diana",
|
|
491
|
+
account: "qor1canonical…",
|
|
492
|
+
to: "0xRecipient…",
|
|
493
|
+
value: "1000000000000000000", // 1 QOR in aqor (wei)
|
|
494
|
+
nonce: await client.qor.getTransactionCount("0xAccount…"),
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
// One-shot: migrate a legacy shake256(mnemonic) key to the address-bound key.
|
|
498
|
+
const { msg: rotateMsg } = rotatePqcKeyMsgFromMnemonic({
|
|
499
|
+
account: "qor1canonical…",
|
|
500
|
+
mnemonic,
|
|
501
|
+
chainId: "qorechain-diana",
|
|
502
|
+
});
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
See the [Authenticators guide](../../docs/docs/guides/authenticators.md) for the
|
|
506
|
+
full lifecycle (register → spend → rotate → revoke).
|
|
507
|
+
|
|
380
508
|
## Network reference
|
|
381
509
|
|
|
382
510
|
- Mainnet chain id: `qorechain-vladi` (live).
|