@qorechain/wallet-adapter 0.2.0 → 0.2.1
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 +26 -9
- package/package.json +1 -1
- package/src/index.d.ts +6 -3
- package/src/index.js +1 -1
- package/src/signbytes.js +44 -21
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ tx = TxRaw{ body, authInfo, [sigC] }
|
|
|
34
34
|
The extension type URL is `/qorechain.pqc.v1.PQCHybridSignature` and algorithm
|
|
35
35
|
`1` = ML-DSA-87. The sign-bytes form `v` is chosen per network — see below.
|
|
36
36
|
|
|
37
|
-
## Hybrid sign-bytes: v1 and v2 (chain v3.1.98)
|
|
37
|
+
## Hybrid sign-bytes: v1 and v2 (chain v3.2.0, testnet v3.1.98)
|
|
38
38
|
|
|
39
39
|
The ML-DSA key signs one of two byte forms (B0 = body without the PQC extension,
|
|
40
40
|
A = AuthInfo bytes):
|
|
@@ -46,23 +46,40 @@ v2: "qorechain-pqc-hybrid-v2" ‖ BE64(len chainId) ‖ chainId ‖ BE3
|
|
|
46
46
|
|
|
47
47
|
v2 adds a domain tag (a signature the key made in any other context can never be
|
|
48
48
|
valid transaction sign-bytes) and binds the chain-id. **A network accepts exactly
|
|
49
|
-
one form at any height.** The networks that existed before
|
|
49
|
+
one form at any height.** The networks that existed before v2
|
|
50
50
|
(`qorechain-vladi` mainnet, `qorechain-diana` testnet) verify v1 until the
|
|
51
|
-
|
|
52
|
-
different heights. Today the testnet verifies v2 and **mainnet stays
|
|
53
|
-
its own upgrade**. Any other chain verifies v2 from its first block.
|
|
51
|
+
upgrade plan that carries the switch is applied on them and v2 from then on; they
|
|
52
|
+
upgrade at different heights. Today the testnet verifies v2 and **mainnet stays
|
|
53
|
+
on v1 until its own upgrade**. Any other chain verifies v2 from its first block.
|
|
54
|
+
|
|
55
|
+
**The switch ships under two plan names.** The release is `v3.2.0`, but the
|
|
56
|
+
testnet already took the same handler under the earlier name `v3.1.98` and keeps
|
|
57
|
+
that record forever, so the chain registers both (`SIGN_BYTES_V2_UPGRADES =
|
|
58
|
+
['v3.2.0', 'v3.1.98']`). A client must ask for **every** name and sign v2 if the
|
|
59
|
+
numeric height of **any** of them is > 0. Asking for one name only resolves v1 on
|
|
60
|
+
a network that upgraded under the other, and every hybrid transaction is then
|
|
61
|
+
refused with `pqc` code 21.
|
|
54
62
|
|
|
55
63
|
`signBytesVersion` (on `QoreChainSigner`, per `signHybrid` call, and on
|
|
56
64
|
`signHybridEth`) takes:
|
|
57
65
|
|
|
58
66
|
- `'auto'` (default) — a non-legacy chain signs v2 with no network call. On
|
|
59
67
|
`qorechain-vladi` / `qorechain-diana` the adapter asks the network
|
|
60
|
-
`GET {rest}/cosmos/upgrade/v1beta1/applied_plan/
|
|
61
|
-
|
|
68
|
+
`GET {rest}/cosmos/upgrade/v1beta1/applied_plan/{name}` for every name in
|
|
69
|
+
`SIGN_BYTES_V2_UPGRADES` (`v3.2.0`, then `v3.1.98`) and signs v2 iff the
|
|
70
|
+
returned height of any of them is > 0 (compared numerically: a network that has
|
|
71
|
+
not taken a plan answers `{"height":"0"}` or `{}`). The names are asked in
|
|
72
|
+
order and the first positive height wins, so a network on the current release
|
|
73
|
+
costs one request and one that upgraded under the earlier name costs two.
|
|
62
74
|
The answer is cached per (rest, chain-id) for 60 s. **Pass `rest` (the LCD URL)**;
|
|
63
|
-
without it, or if
|
|
75
|
+
without it, or if a query fails, signing throws instead of guessing.
|
|
64
76
|
- `'v1'` / `'v2'` — used as given, no network call.
|
|
65
77
|
|
|
78
|
+
**Upgrading to 0.2.1.** 0.2.0 asked for `v3.1.98` alone, which resolves v1 on a
|
|
79
|
+
mainnet that upgraded as `v3.2.0` and gets every hybrid transaction refused with
|
|
80
|
+
`pqc` code 21. Upgrade before the mainnet upgrade height; nothing else in the
|
|
81
|
+
resolver changed and no call site needs touching.
|
|
82
|
+
|
|
66
83
|
**Upgrading to 0.2.0.** `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. 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.
|
|
67
84
|
|
|
68
85
|
Signed results are the usual `TxRaw` `Uint8Array`, with `.signBytesVersion`
|
|
@@ -156,7 +173,7 @@ Keplr / any-signDirect adapter + PQC framing:
|
|
|
156
173
|
- `derivePqcKeyFromWallet(wallet, chainId, address)` — deterministic ML-DSA-87 key from a wallet signature.
|
|
157
174
|
- `hybridSignBytesV1(b0, auth)`, `hybridSignBytesV2(chainId, b0, auth)`, `hybridSignBytes(version, chainId, b0, auth)` — the two sign-bytes forms and a dispatcher (version required; the old implicit `frame()` is removed).
|
|
158
175
|
- `signBytesVersionFor(chainId, v2AppliedHeight)`, `resolveSignBytesVersion({ chainId, rest?, signBytesVersion?, fetch?, ttlMs?, forceRefresh? })`, `clearSignBytesCache()`, `isHybridSignBytesRejection(errOrResult)`.
|
|
159
|
-
- Constants `HYBRID_SIGN_BYTES_V2_DOMAIN`, `
|
|
176
|
+
- Constants `HYBRID_SIGN_BYTES_V2_DOMAIN`, `SIGN_BYTES_V2_UPGRADES` (`["v3.2.0", "v3.1.98"]` — every plan name that switches a network to v2), `SIGN_BYTES_V2_UPGRADE` (`"v3.2.0"`, the primary name = `SIGN_BYTES_V2_UPGRADES[0]`), `LEGACY_SIGN_BYTES_CHAINS`.
|
|
160
177
|
- `encodePqcHybridSignature(algId, sig)` — proto encoder for the extension.
|
|
161
178
|
- `qoreChainInfo({ chainId?, rpc, rest })` — Keplr chain descriptor; `qoreEvmChainParams(...)` / `addQoreEvmToWallet(provider, opts)` — MetaMask (EIP-3085) EVM descriptor.
|
|
162
179
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qorechain/wallet-adapter",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Drop-in adapter to add QoreChain to any Cosmos wallet (Keplr, Leap, Cosmostation, …) and sign its PQC-required transactions. The wallet signs an ordinary SIGN_MODE_DIRECT SignDoc; the adapter layers a standard FIPS-204 ML-DSA-87 hybrid signature into the tx body, so no wallet code changes are needed.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/index.d.ts
CHANGED
|
@@ -56,11 +56,14 @@ export function authSignBytes(p: { programId: string; accounts: SvmAccountMeta[]
|
|
|
56
56
|
export function buildPhantomSvmEnvelope(p: { wallet: any; programId?: string; accounts: SvmAccountMeta[]; data: Uint8Array; recentBlockhashHex: string }): Promise<any>;
|
|
57
57
|
export function buildPhantomTransfer(p: { wallet: any; fromSvmAddr: string; toSvmAddr: string; lamports: number | bigint; recentBlockhashHex: string }): Promise<any>;
|
|
58
58
|
export function registerAuthenticatorMsg(p: { owner: string; phantomPubkey: Uint8Array; permissions?: string[]; expiryUnix: number | bigint; label?: string }): { typeUrl: string; value: any };
|
|
59
|
-
// --- Per-network hybrid PQC sign-bytes (v1 legacy / v2, chain v3.1.98) ---
|
|
59
|
+
// --- Per-network hybrid PQC sign-bytes (v1 legacy / v2, chain v3.2.0 / testnet v3.1.98) ---
|
|
60
60
|
export type SignBytesVersion = 'v1' | 'v2';
|
|
61
61
|
export type SignBytesVersionOption = SignBytesVersion | 'auto';
|
|
62
62
|
export const HYBRID_SIGN_BYTES_V2_DOMAIN: 'qorechain-pqc-hybrid-v2';
|
|
63
|
-
|
|
63
|
+
/** Every upgrade plan name that switches a network to v2, most recent first. */
|
|
64
|
+
export const SIGN_BYTES_V2_UPGRADES: readonly ['v3.2.0', 'v3.1.98'];
|
|
65
|
+
/** The primary (current release) plan name. */
|
|
66
|
+
export const SIGN_BYTES_V2_UPGRADE: 'v3.2.0';
|
|
64
67
|
export const LEGACY_SIGN_BYTES_CHAINS: readonly string[];
|
|
65
68
|
/** v1: BE32(len b0) ‖ b0 ‖ BE32(len authInfo) ‖ authInfo. */
|
|
66
69
|
export function hybridSignBytesV1(b0: Uint8Array, authInfo: Uint8Array): Uint8Array;
|
|
@@ -68,7 +71,7 @@ export function hybridSignBytesV1(b0: Uint8Array, authInfo: Uint8Array): Uint8Ar
|
|
|
68
71
|
export function hybridSignBytesV2(chainId: string, b0: Uint8Array, authInfo: Uint8Array): Uint8Array;
|
|
69
72
|
/** Version-dispatching builder; `version` is required. */
|
|
70
73
|
export function hybridSignBytes(version: SignBytesVersion, chainId: string, b0: Uint8Array, authInfo: Uint8Array): Uint8Array;
|
|
71
|
-
/** Mirror of the chain's SignBytesVersionFor; the height is compared numerically ("0" → not applied). */
|
|
74
|
+
/** Mirror of the chain's SignBytesVersionFor; pass the greatest applied height over SIGN_BYTES_V2_UPGRADES. The height is compared numerically ("0" → not applied). */
|
|
72
75
|
export function signBytesVersionFor(chainId: string, v2AppliedHeight: string | number | bigint | null | undefined): SignBytesVersion;
|
|
73
76
|
export function resolveSignBytesVersion(opts: {
|
|
74
77
|
chainId: string;
|
package/src/index.js
CHANGED
|
@@ -30,7 +30,7 @@ import { hybridSignBytes, resolveSignBytesVersion } from './signbytes.js';
|
|
|
30
30
|
export { encodePqcHybridSignature, HYBRID_SIG_TYPE_URL, ALGORITHM_ML_DSA_87 };
|
|
31
31
|
// Per-network hybrid sign-bytes (v1 legacy / v2) + resolver + rejection detector.
|
|
32
32
|
export {
|
|
33
|
-
HYBRID_SIGN_BYTES_V2_DOMAIN, SIGN_BYTES_V2_UPGRADE, LEGACY_SIGN_BYTES_CHAINS,
|
|
33
|
+
HYBRID_SIGN_BYTES_V2_DOMAIN, SIGN_BYTES_V2_UPGRADE, SIGN_BYTES_V2_UPGRADES, LEGACY_SIGN_BYTES_CHAINS,
|
|
34
34
|
hybridSignBytesV1, hybridSignBytesV2, hybridSignBytes,
|
|
35
35
|
signBytesVersionFor, resolveSignBytesVersion, clearSignBytesCache,
|
|
36
36
|
isHybridSignBytesRejection,
|
package/src/signbytes.js
CHANGED
|
@@ -10,15 +10,26 @@
|
|
|
10
10
|
// B0 = TxBody WITHOUT the PQC extension option, A = AuthInfo bytes verbatim.
|
|
11
11
|
// Byte-identical to the chain's x/pqc/types.HybridSignBytesLegacy / HybridSignBytes.
|
|
12
12
|
//
|
|
13
|
-
// Which form to sign: a network that existed before
|
|
14
|
-
// (qorechain-vladi mainnet, qorechain-diana testnet) verifies v1 until
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
13
|
+
// Which form to sign: a network that existed before the release that introduced
|
|
14
|
+
// v2 (qorechain-vladi mainnet, qorechain-diana testnet) verifies v1 until that
|
|
15
|
+
// upgrade plan is applied on it, and v2 from then on. Any other chain verifies
|
|
16
|
+
// v2 from its first block. The two networks upgrade at different heights, so a
|
|
17
|
+
// client must ASK the target network (applied_plan) rather than hardcode a form.
|
|
18
|
+
// That is what resolveSignBytesVersion does.
|
|
19
|
+
//
|
|
20
|
+
// The switch ships under TWO plan names: the release is "v3.2.0", but the
|
|
21
|
+
// testnet already took the same handler under the earlier name "v3.1.98" and
|
|
22
|
+
// keeps that record forever. The chain registers both (x/pqc/types
|
|
23
|
+
// SignBytesV2Upgrades), so a client must ask for EVERY name and sign v2 if the
|
|
24
|
+
// numeric height of ANY of them is greater than zero. Asking for one name only
|
|
25
|
+
// resolves v1 on a network that upgraded under the other, and every hybrid
|
|
26
|
+
// transaction is then refused with pqc code 21.
|
|
19
27
|
|
|
20
28
|
export const HYBRID_SIGN_BYTES_V2_DOMAIN = 'qorechain-pqc-hybrid-v2';
|
|
21
|
-
|
|
29
|
+
/** Every upgrade plan name that switches a network to v2 sign-bytes, most recent first. */
|
|
30
|
+
export const SIGN_BYTES_V2_UPGRADES = Object.freeze(['v3.2.0', 'v3.1.98']);
|
|
31
|
+
/** The primary (current release) plan name; see SIGN_BYTES_V2_UPGRADES for all of them. */
|
|
32
|
+
export const SIGN_BYTES_V2_UPGRADE = SIGN_BYTES_V2_UPGRADES[0];
|
|
22
33
|
export const LEGACY_SIGN_BYTES_CHAINS = Object.freeze(['qorechain-vladi', 'qorechain-diana']);
|
|
23
34
|
|
|
24
35
|
const te = new TextEncoder();
|
|
@@ -86,10 +97,11 @@ function toHeight(h) {
|
|
|
86
97
|
}
|
|
87
98
|
|
|
88
99
|
/**
|
|
89
|
-
* The form a client must sign for `chainId`, given the height at which
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
100
|
+
* The form a client must sign for `chainId`, given the height at which a v2
|
|
101
|
+
* sign-bytes upgrade plan was applied on it (0 / "0" / missing = none applied;
|
|
102
|
+
* pass the greatest height over SIGN_BYTES_V2_UPGRADES). Mirrors the chain's
|
|
103
|
+
* SignBytesVersionFor. Heights are compared NUMERICALLY: the node returns the
|
|
104
|
+
* height as a string, and "0" is truthy.
|
|
93
105
|
*/
|
|
94
106
|
export function signBytesVersionFor(chainId, v2AppliedHeight) {
|
|
95
107
|
if (toHeight(v2AppliedHeight) > 0n) return 'v2';
|
|
@@ -107,10 +119,13 @@ function normRest(rest) { return String(rest).replace(/\/+$/, ''); }
|
|
|
107
119
|
* Resolve the hybrid sign-bytes form for a network.
|
|
108
120
|
* - 'v1' | 'v2' are returned as-is (no network).
|
|
109
121
|
* - 'auto' (default): a chain that is not a legacy network gets 'v2' with no
|
|
110
|
-
* HTTP; a legacy network is asked `GET {rest}/cosmos/upgrade/v1beta1/applied_plan/
|
|
111
|
-
* and signs v2 iff the
|
|
112
|
-
*
|
|
113
|
-
*
|
|
122
|
+
* HTTP; a legacy network is asked `GET {rest}/cosmos/upgrade/v1beta1/applied_plan/{name}`
|
|
123
|
+
* for EVERY name in SIGN_BYTES_V2_UPGRADES and signs v2 iff the numeric
|
|
124
|
+
* height of ANY of them is > 0. The names are asked in order and the first
|
|
125
|
+
* positive height wins, so a network on the current release costs one
|
|
126
|
+
* request and one that upgraded under the earlier name costs two. Answers
|
|
127
|
+
* are cached per (rest, chainId) for `ttlMs`; `forceRefresh` bypasses the cache.
|
|
128
|
+
* Throws (never guesses) when a legacy network has no `rest` or a query fails.
|
|
114
129
|
*/
|
|
115
130
|
export async function resolveSignBytesVersion({
|
|
116
131
|
chainId, rest, signBytesVersion = 'auto', fetch = globalThis.fetch, ttlMs = 60_000, forceRefresh = false,
|
|
@@ -138,15 +153,23 @@ export async function resolveSignBytesVersion({
|
|
|
138
153
|
if (typeof fetch !== 'function') {
|
|
139
154
|
throw new Error(`Cannot query ${base}: no fetch implementation available; ${hint}.`);
|
|
140
155
|
}
|
|
141
|
-
const
|
|
156
|
+
const plans = `${base}/cosmos/upgrade/v1beta1/applied_plan/{${SIGN_BYTES_V2_UPGRADES.join(',')}}`;
|
|
142
157
|
let version;
|
|
143
158
|
try {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
159
|
+
// Ask for every plan name; the first positive height decides (v2). Only when
|
|
160
|
+
// ALL of them answer 0 / {} is the network still on v1.
|
|
161
|
+
let applied = 0n;
|
|
162
|
+
for (const name of SIGN_BYTES_V2_UPGRADES) {
|
|
163
|
+
const url = `${base}/cosmos/upgrade/v1beta1/applied_plan/${name}`;
|
|
164
|
+
const res = await fetch(url, { headers: { accept: 'application/json' } });
|
|
165
|
+
if (!res || !res.ok) throw new Error(`HTTP ${res ? res.status : 'no response'} for ${name}`);
|
|
166
|
+
const body = await res.json();
|
|
167
|
+
applied = toHeight(body?.height ?? '0');
|
|
168
|
+
if (applied > 0n) break;
|
|
169
|
+
}
|
|
170
|
+
version = signBytesVersionFor(chainId, applied);
|
|
148
171
|
} catch (e) {
|
|
149
|
-
throw new Error(`Cannot determine the hybrid sign-bytes form for ${chainId} from ${
|
|
172
|
+
throw new Error(`Cannot determine the hybrid sign-bytes form for ${chainId} from ${plans} (${e && e.message ? e.message : e}); ${hint}.`);
|
|
150
173
|
}
|
|
151
174
|
cache.set(key, { version, at: Date.now() });
|
|
152
175
|
return version;
|