@qorechain/wallet-adapter 0.1.4 → 0.1.7
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 +112 -5
- package/package.json +1 -1
- package/src/authenticator.js +314 -0
- package/src/index.d.ts +73 -0
- package/src/index.js +9 -1
- package/src/wallet.js +34 -0
package/README.md
CHANGED
|
@@ -72,15 +72,24 @@ await fetch(`${rpc}`, { method:'POST', body: JSON.stringify({
|
|
|
72
72
|
| **Keplr** | `experimentalSuggestChain` + `signDirect` | ✅ supported |
|
|
73
73
|
| **Leap / Cosmostation** | same `signDirect` interface | ✅ supported (any wallet exposing `signDirect`) |
|
|
74
74
|
| **MetaMask** | uses QoreChain's **EVM** path (chainId 9800) — structurally PQC-exempt | ✅ works natively, no adapter needed |
|
|
75
|
-
| **Phantom** |
|
|
75
|
+
| **Phantom** | derive a unified account from a Phantom signature (`walletFromSeed`) | ✅ connect → qor1/0x/svm, receive on any, spend on any (incl. hybrid PQC) |
|
|
76
76
|
|
|
77
77
|
## API
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
- `
|
|
81
|
-
- `
|
|
79
|
+
Wallet generation & unified addresses:
|
|
80
|
+
- `generateQoreWallet(strength?)` / `walletFromMnemonic(mnemonic)` / `walletFromSeed(seed32)` — a unified wallet `{ mnemonic, privateKey, pubkey, cosmos, evm, svm, pqc }`.
|
|
81
|
+
- `addressesFrom20(bytes20)` / `qoreAddresses({cosmos|evm|hex})` — the three encodings of a known account.
|
|
82
|
+
|
|
83
|
+
eth-native Cosmos signing (chain ≥ v3.1.83):
|
|
84
|
+
- `signClassicalEth({ key, chainId, accountNumber, sequence, messages, fee, memo?, timeoutHeight? })` → `TxRaw` bytes (classical, e.g. PQC key registration).
|
|
85
|
+
- `signHybridEth({ ... })` → `TxRaw` bytes (eth_secp256k1 + ML-DSA-87 hybrid).
|
|
86
|
+
- `ETHSECP256K1_PUBKEY_TYPE` — the eth pubkey type URL.
|
|
87
|
+
|
|
88
|
+
Keplr / any-signDirect adapter + PQC framing:
|
|
82
89
|
- `QoreChainSigner#signHybrid({ messages, fee, sequence, memo?, timeoutHeight? })` → `TxRaw` bytes.
|
|
83
|
-
- `
|
|
90
|
+
- `derivePqcKeyFromWallet(wallet, chainId, address)` — deterministic ML-DSA-87 key from a wallet signature.
|
|
91
|
+
- `frame(b0, auth)` — QoreChain hybrid sign-bytes framing; `encodePqcHybridSignature(algId, sig)` — proto encoder for the extension.
|
|
92
|
+
- `qoreChainInfo({ chainId?, rpc, rest })` — Keplr chain descriptor; `qoreEvmChainParams(...)` / `addQoreEvmToWallet(provider, opts)` — MetaMask (EIP-3085) EVM descriptor.
|
|
84
93
|
|
|
85
94
|
## License
|
|
86
95
|
|
|
@@ -112,3 +121,101 @@ under which the chain reads one `x/bank` balance. The account can sign EVM txs
|
|
|
112
121
|
|
|
113
122
|
`addressesFrom20(bytes20)` / `qoreAddresses({cosmos|evm|hex})` derive the three
|
|
114
123
|
encodings from a known account (for explorers / backends).
|
|
124
|
+
|
|
125
|
+
### Derive from a seed (Phantom & other non-mnemonic flows)
|
|
126
|
+
|
|
127
|
+
`walletFromSeed(seed32)` builds the same unified wallet from any 32 bytes (the
|
|
128
|
+
seed becomes the secp256k1 key). Because a wallet's signature over a fixed message
|
|
129
|
+
is deterministic, you can derive **one canonical QoreChain account from a Phantom
|
|
130
|
+
(ed25519) signature** — a Phantom user connects once and gets three usable
|
|
131
|
+
QoreChain addresses they fully control:
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
import { walletFromSeed } from "@qorechain/wallet-adapter";
|
|
135
|
+
import { shake256 } from "@qorechain/pqc";
|
|
136
|
+
|
|
137
|
+
const msg = new TextEncoder().encode("QoreChain unified account derivation v1");
|
|
138
|
+
const { signature } = await window.solana.signMessage(msg); // Phantom, deterministic
|
|
139
|
+
const w = await walletFromSeed(shake256(signature, 32)); // → qor1 / 0x / svm + pqc
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## eth-native Cosmos signing (requires chain ≥ v3.1.83)
|
|
143
|
+
|
|
144
|
+
The unified account **signs on the Cosmos lane too**, with the `eth_secp256k1`
|
|
145
|
+
scheme (secp256k1 over `keccak256(signBytes)`, pubkey
|
|
146
|
+
`/cosmos.evm.crypto.v1.ethsecp256k1.PubKey`). `signClassicalEth` builds a
|
|
147
|
+
classical-only tx (for the one-time, bootstrap-exempt PQC key registration);
|
|
148
|
+
`signHybridEth` adds the ML-DSA-87 hybrid signature the ante requires for
|
|
149
|
+
everything else. Both return broadcast-ready `TxRaw` bytes.
|
|
150
|
+
|
|
151
|
+
```js
|
|
152
|
+
import { walletFromMnemonic, signClassicalEth, signHybridEth } from "@qorechain/wallet-adapter";
|
|
153
|
+
|
|
154
|
+
const key = await walletFromMnemonic(mnemonic); // has { privateKey, pubkey, pqc }
|
|
155
|
+
const fee = { amount: [{ denom: "uqor", amount: "30000" }], gasLimit: 300000n };
|
|
156
|
+
|
|
157
|
+
// 1) one-time: register the account's ML-DSA-87 key (classical, PQC-exempt)
|
|
158
|
+
const regTx = await signClassicalEth({ key, chainId, accountNumber, sequence,
|
|
159
|
+
messages: [{ typeUrl: "/qorechain.pqc.v1.MsgRegisterPQCKeyV2", value: registerMsgBytes }],
|
|
160
|
+
fee: { amount: [{ denom: "uqor", amount: "600000" }], gasLimit: 6000000n } });
|
|
161
|
+
|
|
162
|
+
// 2) thereafter: hybrid eth_secp256k1 + ML-DSA-87 (e.g. a bank MsgSend)
|
|
163
|
+
const sendTx = await signHybridEth({ key, chainId, accountNumber, sequence,
|
|
164
|
+
messages: [{ typeUrl: "/cosmos.bank.v1beta1.MsgSend", value: msgSendBytes }], fee });
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
> **Requires QoreChain ≥ v3.1.83** — that release registers the `eth_secp256k1`
|
|
168
|
+
> pubkey on the node's interface registry so eth-native Cosmos txs decode. Both
|
|
169
|
+
> `qorechain-diana` (testnet) and `qorechain-vladi` (mainnet) run it.
|
|
170
|
+
> `@qorechain/chain-bridge` wraps this server-side (`keyType: 'eth_secp256k1'`,
|
|
171
|
+
> auto-registers the PQC key on first send). **Proven live** on QoreChain: register
|
|
172
|
+
> (code 0) + hybrid send (code 0) + an EVM transfer from the same key, one balance.
|
|
173
|
+
|
|
174
|
+
## Authenticator lanes + key rotation (v3.1.85)
|
|
175
|
+
|
|
176
|
+
Let a linked external key (Phantom ed25519 / a secp256k1 key) **spend from the
|
|
177
|
+
one unified PQC-required account** under least-privilege, spend-limited terms —
|
|
178
|
+
via a relayer, with no ML-DSA co-signature from the external key. Owner links the
|
|
179
|
+
key once (`registerAuthenticatorMsg`, hybrid-signed); thereafter the external key
|
|
180
|
+
authorizes actions on three lanes:
|
|
181
|
+
|
|
182
|
+
- **SVM** — `buildPhantomSvmEnvelope` / `buildPhantomTransfer` (post to `sendTransaction`).
|
|
183
|
+
- **EVM** — `buildPhantomExecuteEvm` → `MsgExecuteEVM` (relayer broadcasts).
|
|
184
|
+
- **Native** — `buildPhantomExecuteCosmos` → `MsgExecuteCosmos` (relayer broadcasts).
|
|
185
|
+
|
|
186
|
+
```js
|
|
187
|
+
import { buildPhantomExecuteEvm, buildPhantomExecuteCosmos } from "@qorechain/wallet-adapter";
|
|
188
|
+
|
|
189
|
+
// nonce = the account's CURRENT EVM nonce (eth_getTransactionCount(account0x)).
|
|
190
|
+
// The relayer is a DIFFERENT account than the owner, so it does NOT pre-increment it.
|
|
191
|
+
const evmMsg = await buildPhantomExecuteEvm({
|
|
192
|
+
wallet: phantom, relayer: relayerAddr, chainId, account: qor1,
|
|
193
|
+
to: "0x…", value: "100000000000000000" /* wei */, nonce });
|
|
194
|
+
|
|
195
|
+
const sendMsg = await buildPhantomExecuteCosmos({
|
|
196
|
+
wallet: phantom, relayer: relayerAddr, chainId, account: qor1,
|
|
197
|
+
to: "qor1…", amount: "250uqor", nonce: authSeq /* per-authenticator sequence */ });
|
|
198
|
+
// → hand each msg to your relayer; it broadcasts with its own hybrid-PQC signature.
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Errors (codespace `abstractaccount`): `5` spending-limit, `6` session-key expired
|
|
202
|
+
(render "re-link"), `10` permission-denied, `11` replay. Fetch the live scope
|
|
203
|
+
taxonomy over REST: `GET /qorechain/abstractaccount/v1/permission_schema`.
|
|
204
|
+
|
|
205
|
+
**Key rotation** (`MsgRotatePQCKey`) — migrate a legacy chain-bridge key
|
|
206
|
+
(`shake256(mnemonic)`) to the canonical address-bound key of the SAME algorithm:
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
import { rotatePqcKeyMsgFromMnemonic } from "@qorechain/wallet-adapter";
|
|
210
|
+
const { msg, oldKeypair } = rotatePqcKeyMsgFromMnemonic({ account: qor1, mnemonic, chainId });
|
|
211
|
+
// broadcast `msg` from the account, cosigned (hybrid) with `oldKeypair` (still the
|
|
212
|
+
// registered key until the rotation lands) — e.g. a QoreChainSigner whose pqc=oldKeypair.
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
For a **MetaMask / EVM** key use `registerEthAuthenticatorMsg` (link by 0x address) + `buildMetaMaskExecuteEvm` / `buildMetaMaskExecuteCosmos` — the key signs the digest with `personal_sign` (EIP-191) and the chain verifies by ecrecover, so no raw pubkey is needed. Live-proven: a MetaMask-signed EVM transfer from the unified account committed on QoreChain.
|
|
216
|
+
|
|
217
|
+
> **Requires QoreChain ≥ v3.1.85.** Auth sign-bytes (`evmAuthSignBytes`,
|
|
218
|
+
> `cosmosAuthSignBytes`) are rebuilt byte-for-byte from the chain and guarded by
|
|
219
|
+
> tests. For a **secp256k1** authenticator the chain uses cosmos `VerifySignature`
|
|
220
|
+
> (sha256-based), NOT MetaMask `personal_sign` — sign the digest with a
|
|
221
|
+
> cosmos-style secp256k1 signer, not `personal_sign`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qorechain/wallet-adapter",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
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",
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
// @qorechain/wallet-adapter — v3.1.85 authenticator lanes (EVM + Native/Cosmos)
|
|
2
|
+
// and same-algorithm PQC key rotation.
|
|
3
|
+
//
|
|
4
|
+
// v3.1.84 introduced the SVM authenticator lane (see phantom.js). v3.1.85 adds
|
|
5
|
+
// two more lanes so a linked external key (Phantom ed25519 / a secp256k1 key)
|
|
6
|
+
// can spend from the ONE unified PQC-required account under least-privilege,
|
|
7
|
+
// spend-limited, revocable terms — via a relayer, WITHOUT the external key ever
|
|
8
|
+
// producing an ML-DSA co-signature:
|
|
9
|
+
//
|
|
10
|
+
// • EVM lane — MsgExecuteEVM: EVM call/transfer from the account's 0x addr.
|
|
11
|
+
// • Native lane — MsgExecuteCosmos: bank send from the account (Cosmos).
|
|
12
|
+
//
|
|
13
|
+
// The relayer submits + pays fees (its own hybrid-PQC signature satisfies the
|
|
14
|
+
// ante on the envelope); the authenticator's signature over the domain-separated,
|
|
15
|
+
// replay-bound sign-bytes IS the authorization. The digests below are rebuilt
|
|
16
|
+
// BYTE-FOR-BYTE from the chain (x/abstractaccount/types/{evm,cosmos}_sign.go) — a
|
|
17
|
+
// mismatch is rejected on-chain (codespace abstractaccount, code 11 replay / 10
|
|
18
|
+
// permission / 5 spending-limit / 6 session-expired).
|
|
19
|
+
//
|
|
20
|
+
// Chain signature check (keeper.VerifyAuthenticatorSignature): for scheme
|
|
21
|
+
// "ed25519" it is ed25519.Verify(pubkey, digest, sig) — so a Phantom
|
|
22
|
+
// `signMessage(digest)` matches directly (the builders below). For "secp256k1"
|
|
23
|
+
// the chain uses cosmos secp256k1 VerifySignature (sha256-based, NOT MetaMask
|
|
24
|
+
// personal_sign), so a MetaMask personal_sign will NOT verify — provide the
|
|
25
|
+
// digest to a cosmos-style secp256k1 signer instead.
|
|
26
|
+
|
|
27
|
+
import { mldsa, shake256 } from '@qorechain/pqc';
|
|
28
|
+
|
|
29
|
+
// ---- byte helpers (match the chain's binary.BigEndian + length-prefix framing) ----
|
|
30
|
+
|
|
31
|
+
const enc = new TextEncoder();
|
|
32
|
+
|
|
33
|
+
function be64(n) {
|
|
34
|
+
const b = new Uint8Array(8);
|
|
35
|
+
let v = BigInt(n);
|
|
36
|
+
for (let i = 7; i >= 0; i--) { b[i] = Number(v & 0xffn); v >>= 8n; }
|
|
37
|
+
return b;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function concat(parts) {
|
|
41
|
+
let len = 0; for (const p of parts) len += p.length;
|
|
42
|
+
const out = new Uint8Array(len); let o = 0;
|
|
43
|
+
for (const p of parts) { out.set(p, o); o += p.length; }
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// length-prefixed field: BE64(len) ‖ bytes
|
|
48
|
+
function lp(bytes) { return concat([be64(bytes.length), bytes]); }
|
|
49
|
+
|
|
50
|
+
function toBytes(x) {
|
|
51
|
+
if (x instanceof Uint8Array) return x;
|
|
52
|
+
if (typeof x === 'string') return enc.encode(x);
|
|
53
|
+
return new Uint8Array(x);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function sha256(bytes) {
|
|
57
|
+
if (typeof globalThis.crypto?.subtle?.digest === 'function') {
|
|
58
|
+
return new Uint8Array(await globalThis.crypto.subtle.digest('SHA-256', bytes));
|
|
59
|
+
}
|
|
60
|
+
const { createHash } = await import('crypto');
|
|
61
|
+
return new Uint8Array(createHash('sha256').update(bytes).digest());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function toHexLower(bytes) {
|
|
65
|
+
let s = ''; for (const b of bytes) s += b.toString(16).padStart(2, '0');
|
|
66
|
+
return s;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---- sign-bytes (the digest an authenticator signs) ----
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* evmAuthSignBytes rebuilds the 32-byte digest the chain re-derives for a
|
|
73
|
+
* MsgExecuteEVM (types.EVMAuthSignBytes):
|
|
74
|
+
* sha256( "qorechain-evm-auth-v1"
|
|
75
|
+
* ‖ LP(chainId) ‖ LP(account) ‖ LP(pubkey)
|
|
76
|
+
* ‖ LP(to) ‖ LP(value) ‖ LP(data) ‖ BE64(nonce) )
|
|
77
|
+
* `to` is the 0x-hex recipient string, `value` is the decimal wei (aqor) string,
|
|
78
|
+
* `data` is the raw calldata (Uint8Array). `pubkey` is the authenticator's raw
|
|
79
|
+
* public key (32 bytes for ed25519). Returns Uint8Array(32) — what the wallet signs.
|
|
80
|
+
*
|
|
81
|
+
* NONCE: the account's CURRENT EVM nonce (eth_getTransactionCount(account0x)).
|
|
82
|
+
* In production the relayer is a DIFFERENT account than the owner, so the relayer
|
|
83
|
+
* envelope does NOT bump the account's nonce — use the current value as-is. (Only
|
|
84
|
+
* if relayer === owner does the envelope pre-increment it, needing current+1.)
|
|
85
|
+
*/
|
|
86
|
+
export async function evmAuthSignBytes({ chainId, account, pubkey, to = '', value = '0', data = new Uint8Array(0), nonce }) {
|
|
87
|
+
const body = concat([
|
|
88
|
+
enc.encode('qorechain-evm-auth-v1'),
|
|
89
|
+
lp(toBytes(chainId)), lp(toBytes(account)), lp(toBytes(pubkey)),
|
|
90
|
+
lp(toBytes(to)), lp(toBytes(value)), lp(toBytes(data)),
|
|
91
|
+
be64(nonce),
|
|
92
|
+
]);
|
|
93
|
+
return sha256(body);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* cosmosAuthSignBytes rebuilds the 32-byte digest the chain re-derives for a
|
|
98
|
+
* MsgExecuteCosmos (types.CosmosAuthSignBytes):
|
|
99
|
+
* sha256( "qorechain-cosmos-auth-v1"
|
|
100
|
+
* ‖ LP(chainId) ‖ LP(account) ‖ LP(pubkey)
|
|
101
|
+
* ‖ LP(to) ‖ LP(amount) ‖ BE64(nonce) )
|
|
102
|
+
* `to` is the bech32 recipient, `amount` is the CANONICAL sdk.Coins string
|
|
103
|
+
* (sorted, e.g. "100uqor"). Returns Uint8Array(32).
|
|
104
|
+
*
|
|
105
|
+
* NONCE: the per-authenticator sequence for (account, pubkey) — a store counter
|
|
106
|
+
* distinct from the account's own sequence, incremented on each successful
|
|
107
|
+
* Native-lane spend. (Query it from the chain / track it client-side.)
|
|
108
|
+
*/
|
|
109
|
+
export async function cosmosAuthSignBytes({ chainId, account, pubkey, to, amount, nonce }) {
|
|
110
|
+
const body = concat([
|
|
111
|
+
enc.encode('qorechain-cosmos-auth-v1'),
|
|
112
|
+
lp(toBytes(chainId)), lp(toBytes(account)), lp(toBytes(pubkey)),
|
|
113
|
+
lp(toBytes(to)), lp(toBytes(amount)),
|
|
114
|
+
be64(nonce),
|
|
115
|
+
]);
|
|
116
|
+
return sha256(body);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* rotationSignBytes returns the domain-separated STRING both the old and the new
|
|
121
|
+
* key sign for a MsgRotatePQCKey (types.RotationSignBytes):
|
|
122
|
+
* "qorechain-pqc-rotate-v1|<chainId>|<algorithmId>|<account>|<oldHex>|<newHex>"
|
|
123
|
+
* oldHex/newHex are lowercase hex of the public keys. Sign `utf8(this string)`.
|
|
124
|
+
*/
|
|
125
|
+
export function rotationSignBytes(chainId, algorithmId, account, oldPub, newPub) {
|
|
126
|
+
return `qorechain-pqc-rotate-v1|${chainId}|${algorithmId}|${account}|${toHexLower(oldPub)}|${toHexLower(newPub)}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ---- message composers (shapes for cosmjs registry / *.fromPartial) ----
|
|
130
|
+
|
|
131
|
+
/** parse a single-coin amount string like "100uqor" → [{denom,amount}]. */
|
|
132
|
+
function parseCoins(amount) {
|
|
133
|
+
const m = /^([0-9]+)([a-zA-Z][a-zA-Z0-9/:._-]*)$/.exec(String(amount).trim());
|
|
134
|
+
if (!m) throw new Error(`invalid amount "${amount}" (expected e.g. "100uqor")`);
|
|
135
|
+
return [{ denom: m[2], amount: m[1] }];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** MsgExecuteEVM — the relayer broadcasts this (it is the message `relayer`/fee payer). */
|
|
139
|
+
export function executeEvmMsg({ relayer, account, scheme, pubkey, signature, to = '', value = '0', data = new Uint8Array(0), gasLimit, nonce }) {
|
|
140
|
+
return {
|
|
141
|
+
typeUrl: '/qorechain.abstractaccount.v1.MsgExecuteEVM',
|
|
142
|
+
value: {
|
|
143
|
+
relayer, account, scheme,
|
|
144
|
+
pubkey: toBytes(pubkey), signature: toBytes(signature),
|
|
145
|
+
to, value, data: toBytes(data),
|
|
146
|
+
gasLimit: BigInt(gasLimit), nonce: BigInt(nonce),
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** MsgExecuteCosmos — the relayer broadcasts this. `amount` is "100uqor"-style. */
|
|
152
|
+
export function executeCosmosMsg({ relayer, account, scheme, pubkey, signature, to, amount, nonce }) {
|
|
153
|
+
return {
|
|
154
|
+
typeUrl: '/qorechain.abstractaccount.v1.MsgExecuteCosmos',
|
|
155
|
+
value: {
|
|
156
|
+
relayer, account, scheme,
|
|
157
|
+
pubkey: toBytes(pubkey), signature: toBytes(signature),
|
|
158
|
+
to, amount: parseCoins(amount), nonce: BigInt(nonce),
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** MsgRevokeAuthenticator — owner-signed; instantly disables a linked key. */
|
|
164
|
+
export function revokeAuthenticatorMsg({ owner, account = owner, scheme, pubkey }) {
|
|
165
|
+
return {
|
|
166
|
+
typeUrl: '/qorechain.abstractaccount.v1.MsgRevokeAuthenticator',
|
|
167
|
+
value: { owner, accountAddress: account, scheme, pubkey: toBytes(pubkey) },
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** MsgRotatePQCKey — sender-signed (hybrid, with the OLD key); dual-signed payload. */
|
|
172
|
+
export function rotatePqcKeyMsg({ sender, oldPublicKey, newPublicKey, oldSignature, newSignature }) {
|
|
173
|
+
return {
|
|
174
|
+
typeUrl: '/qorechain.pqc.v1.MsgRotatePQCKey',
|
|
175
|
+
value: {
|
|
176
|
+
sender,
|
|
177
|
+
oldPublicKey: toBytes(oldPublicKey), newPublicKey: toBytes(newPublicKey),
|
|
178
|
+
oldSignature: toBytes(oldSignature), newSignature: toBytes(newSignature),
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ---- Phantom (ed25519) envelope builders for the EVM + Native lanes ----
|
|
184
|
+
|
|
185
|
+
function walletPubkey(wallet) {
|
|
186
|
+
return wallet.publicKey?.toBytes ? wallet.publicKey.toBytes() : new Uint8Array(wallet.publicKey);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* buildPhantomExecuteEvm signs the EVM auth digest with a Phantom-style ed25519
|
|
191
|
+
* wallet and returns a MsgExecuteEVM ready for the relayer to broadcast. The
|
|
192
|
+
* relayer address is the fee payer (a DIFFERENT account than `account`).
|
|
193
|
+
*/
|
|
194
|
+
export async function buildPhantomExecuteEvm({ wallet, relayer, chainId, account, to = '', value = '0', data = new Uint8Array(0), gasLimit = 100000, nonce }) {
|
|
195
|
+
const pubkey = walletPubkey(wallet);
|
|
196
|
+
const digest = await evmAuthSignBytes({ chainId, account, pubkey, to, value, data, nonce });
|
|
197
|
+
const { signature } = await wallet.signMessage(digest);
|
|
198
|
+
return executeEvmMsg({ relayer, account, scheme: 'ed25519', pubkey, signature, to, value, data, gasLimit, nonce });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* buildPhantomExecuteCosmos signs the Native auth digest with a Phantom-style
|
|
203
|
+
* ed25519 wallet and returns a MsgExecuteCosmos ready for the relayer. `amount`
|
|
204
|
+
* is a single-coin string like "100uqor".
|
|
205
|
+
*/
|
|
206
|
+
export async function buildPhantomExecuteCosmos({ wallet, relayer, chainId, account, to, amount, nonce }) {
|
|
207
|
+
const pubkey = walletPubkey(wallet);
|
|
208
|
+
const digest = await cosmosAuthSignBytes({ chainId, account, pubkey, to, amount, nonce });
|
|
209
|
+
const { signature } = await wallet.signMessage(digest);
|
|
210
|
+
return executeCosmosMsg({ relayer, account, scheme: 'ed25519', pubkey, signature, to, amount, nonce });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ---- MetaMask (EIP-191 personal_sign / secp256k1) envelope builders ----
|
|
214
|
+
//
|
|
215
|
+
// A browser EVM wallet exposes only its 20-byte address + `personal_sign`, never
|
|
216
|
+
// the raw public key, so the account is linked by its ETH ADDRESS (scheme
|
|
217
|
+
// "secp256k1", 20-byte pubkey). The chain verifies with EIP-191 + ecrecover
|
|
218
|
+
// (v3.1.85). The digest the wallet signs is the SAME one the Phantom/cosmos
|
|
219
|
+
// paths use — only the signing scheme differs.
|
|
220
|
+
|
|
221
|
+
function hexToBytes(hex) {
|
|
222
|
+
hex = String(hex).replace(/^0x/, '');
|
|
223
|
+
const o = new Uint8Array(hex.length / 2);
|
|
224
|
+
for (let i = 0; i < o.length; i++) o[i] = parseInt(hex.substr(i * 2, 2), 16);
|
|
225
|
+
return o;
|
|
226
|
+
}
|
|
227
|
+
function bytesToHex0x(b) { let s = '0x'; for (const x of b) s += x.toString(16).padStart(2, '0'); return s; }
|
|
228
|
+
|
|
229
|
+
// personal_sign over the 32-byte digest via an EIP-1193 provider (e.g. MetaMask).
|
|
230
|
+
async function ethPersonalSign(provider, address, digest) {
|
|
231
|
+
const sigHex = await provider.request({ method: 'personal_sign', params: [bytesToHex0x(digest), address] });
|
|
232
|
+
return hexToBytes(sigHex); // 65 bytes r‖s‖v (v = 27/28)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* registerEthAuthenticatorMsg builds the owner-signed MsgRegisterAuthenticator
|
|
237
|
+
* that links a MetaMask / EVM key (by its 0x address) to the owner's account.
|
|
238
|
+
* `ethAddress` is the 0x-hex 20-byte address.
|
|
239
|
+
*/
|
|
240
|
+
export function registerEthAuthenticatorMsg({ owner, account = owner, ethAddress, permissions = ['evm'], expiryUnix, label = 'metamask' }) {
|
|
241
|
+
return {
|
|
242
|
+
typeUrl: '/qorechain.abstractaccount.v1.MsgRegisterAuthenticator',
|
|
243
|
+
value: {
|
|
244
|
+
owner, accountAddress: account, scheme: 'secp256k1',
|
|
245
|
+
pubkey: hexToBytes(ethAddress), permissions, expiryUnix: BigInt(expiryUnix), label,
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** buildMetaMaskExecuteEvm: MetaMask (EIP-191) → MsgExecuteEVM ready for the relayer. */
|
|
251
|
+
export async function buildMetaMaskExecuteEvm({ provider, address, relayer, chainId, account, to = '', value = '0', data = new Uint8Array(0), gasLimit = 100000, nonce }) {
|
|
252
|
+
const pubkey = hexToBytes(address); // 20-byte eth address = the authenticator pubkey
|
|
253
|
+
const digest = await evmAuthSignBytes({ chainId, account, pubkey, to, value, data, nonce });
|
|
254
|
+
const signature = await ethPersonalSign(provider, address, digest);
|
|
255
|
+
return executeEvmMsg({ relayer, account, scheme: 'secp256k1', pubkey, signature, to, value, data, gasLimit, nonce });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** buildMetaMaskExecuteCosmos: MetaMask (EIP-191) → MsgExecuteCosmos ready for the relayer. */
|
|
259
|
+
export async function buildMetaMaskExecuteCosmos({ provider, address, relayer, chainId, account, to, amount, nonce }) {
|
|
260
|
+
const pubkey = hexToBytes(address);
|
|
261
|
+
const digest = await cosmosAuthSignBytes({ chainId, account, pubkey, to, amount, nonce });
|
|
262
|
+
const signature = await ethPersonalSign(provider, address, digest);
|
|
263
|
+
return executeCosmosMsg({ relayer, account, scheme: 'secp256k1', pubkey, signature, to, amount, nonce });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ---- key rotation (legacy → canonical migration) ----
|
|
267
|
+
|
|
268
|
+
const CANONICAL = 'adapter'; // shake256("qorechain:pqc:v1|addr|mnemonic") (SDK/wallet-adapter)
|
|
269
|
+
const LEGACY = 'bridge'; // shake256(mnemonic) (chain-bridge/faucet-api)
|
|
270
|
+
|
|
271
|
+
function derivePqcByScheme(scheme, account, mnemonic) {
|
|
272
|
+
if (scheme === LEGACY || scheme === 'mnemonic-only') {
|
|
273
|
+
return mldsa.keygen(shake256(enc.encode(mnemonic), 32));
|
|
274
|
+
}
|
|
275
|
+
if (scheme === CANONICAL || scheme === '' || scheme === undefined) {
|
|
276
|
+
return mldsa.keygen(shake256(enc.encode(`qorechain:pqc:v1|${account}|${mnemonic}`), 32));
|
|
277
|
+
}
|
|
278
|
+
throw new Error(`unknown derivation "${scheme}" (use adapter|bridge)`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* rotatePqcKeyMsgFromMnemonic builds a MsgRotatePQCKey that rotates an account's
|
|
283
|
+
* ML-DSA-87 key (SAME algorithm) from one derivation to another — the canonical
|
|
284
|
+
* use is migrating a LEGACY chain-bridge key (`shake256(mnemonic)`) to the
|
|
285
|
+
* canonical address-bound key (`shake256("qorechain:pqc:v1|addr|mnemonic")`), so
|
|
286
|
+
* a wallet whose key was registered by a backend can move to the standard
|
|
287
|
+
* derivation. Both keys dual-sign the domain-separated rotation bytes.
|
|
288
|
+
*
|
|
289
|
+
* The returned message must be broadcast BY the account, cosigned (hybrid) with
|
|
290
|
+
* the OLD key (it is still the registered key until the rotation lands) — i.e.
|
|
291
|
+
* sign the envelope with a QoreChainSigner whose `pqc` is the OLD keypair.
|
|
292
|
+
*
|
|
293
|
+
* @returns {{ msg: object, oldKeypair: {publicKey,secretKey}, newKeypair: {publicKey,secretKey} }}
|
|
294
|
+
*/
|
|
295
|
+
export function rotatePqcKeyMsgFromMnemonic({ account, mnemonic, chainId, algorithmId = 1, oldDerivation = LEGACY, newDerivation = CANONICAL }) {
|
|
296
|
+
const oldKp = derivePqcByScheme(oldDerivation, account, mnemonic);
|
|
297
|
+
const newKp = derivePqcByScheme(newDerivation, account, mnemonic);
|
|
298
|
+
if (toHexLower(oldKp.publicKey) === toHexLower(newKp.publicKey)) {
|
|
299
|
+
throw new Error('old and new derivations produce the same key — rotation would be a no-op');
|
|
300
|
+
}
|
|
301
|
+
const sb = enc.encode(rotationSignBytes(chainId, algorithmId, account, oldKp.publicKey, newKp.publicKey));
|
|
302
|
+
const msg = rotatePqcKeyMsg({
|
|
303
|
+
sender: account,
|
|
304
|
+
oldPublicKey: oldKp.publicKey, newPublicKey: newKp.publicKey,
|
|
305
|
+
oldSignature: mldsa.sign(oldKp.secretKey, sb),
|
|
306
|
+
newSignature: mldsa.sign(newKp.secretKey, sb),
|
|
307
|
+
});
|
|
308
|
+
return { msg, oldKeypair: oldKp, newKeypair: newKp };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** derivePqcLegacy exposes the LEGACY (chain-bridge) derivation for a mnemonic. */
|
|
312
|
+
export function derivePqcLegacy(mnemonic) {
|
|
313
|
+
return mldsa.keygen(shake256(enc.encode(mnemonic), 32));
|
|
314
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
export const HYBRID_SIG_TYPE_URL: string;
|
|
2
2
|
export const ALGORITHM_ML_DSA_87: number;
|
|
3
3
|
|
|
4
|
+
// --- Unified wallet generation (one eth-native key → qor1 / 0x / svm) ---
|
|
5
|
+
export interface PqcKeypair { publicKey: Uint8Array; secretKey: Uint8Array; }
|
|
6
|
+
export interface QoreAddresses {
|
|
7
|
+
addressBytes: Uint8Array;
|
|
8
|
+
cosmos: string; // qor1… (bech32)
|
|
9
|
+
evm: string; // 0x… (EIP-55)
|
|
10
|
+
svm: string; // base58
|
|
11
|
+
}
|
|
12
|
+
export interface QoreWallet extends QoreAddresses {
|
|
13
|
+
mnemonic: string | null;
|
|
14
|
+
privateKey: string; // 0x-hex, 32 bytes
|
|
15
|
+
pubkey: string; // 0x-hex, 33-byte compressed secp256k1
|
|
16
|
+
pqc: PqcKeypair; // ML-DSA-87 (Dilithium-5)
|
|
17
|
+
}
|
|
18
|
+
export function generateQoreWallet(strength?: number): Promise<QoreWallet>;
|
|
19
|
+
export function walletFromMnemonic(mnemonic: string): Promise<QoreWallet>;
|
|
20
|
+
export function walletFromSeed(seed: Uint8Array | string): Promise<QoreWallet>;
|
|
21
|
+
export function addressesFrom20(addr20: Uint8Array): QoreAddresses;
|
|
22
|
+
export function qoreAddresses(opts: { cosmos?: string; evm?: string; hex?: string }): QoreAddresses;
|
|
23
|
+
|
|
24
|
+
// --- eth-native (eth_secp256k1) Cosmos signing (requires chain >= v3.1.83) ---
|
|
25
|
+
export const ETHSECP256K1_PUBKEY_TYPE: string;
|
|
26
|
+
export interface EthSignKey { privateKey: Uint8Array | string; pubkey: Uint8Array | string; pqc?: PqcKeypair; }
|
|
27
|
+
export interface EthSignArgs {
|
|
28
|
+
key: EthSignKey;
|
|
29
|
+
chainId: string;
|
|
30
|
+
accountNumber: number | bigint;
|
|
31
|
+
messages: Array<{ typeUrl: string; value: Uint8Array }>;
|
|
32
|
+
fee: any;
|
|
33
|
+
sequence: number | bigint;
|
|
34
|
+
memo?: string;
|
|
35
|
+
timeoutHeight?: bigint;
|
|
36
|
+
}
|
|
37
|
+
/** Classical-only eth_secp256k1 Cosmos tx (e.g. the bootstrap MsgRegisterPQCKeyV2). */
|
|
38
|
+
export function signClassicalEth(args: EthSignArgs): Promise<Uint8Array>;
|
|
39
|
+
/** Hybrid eth_secp256k1 + ML-DSA-87 Cosmos tx (key.pqc required). */
|
|
40
|
+
export function signHybridEth(args: EthSignArgs): Promise<Uint8Array>;
|
|
41
|
+
|
|
42
|
+
// --- EVM network descriptors (EIP-3085 / MetaMask) ---
|
|
43
|
+
export function qoreEvmChainParams(opts?: { evmChainId?: number; rpcUrl?: string; wsUrl?: string; explorerUrl?: string; testnet?: boolean }): any;
|
|
44
|
+
export function addQoreEvmToWallet(provider: any, opts?: any): Promise<any>;
|
|
45
|
+
|
|
4
46
|
// --- Phantom / any-ed25519-wallet support ---
|
|
5
47
|
export const SYSTEM_PROGRAM_ID: string;
|
|
6
48
|
export function base58Encode(bytes: Uint8Array): string;
|
|
@@ -19,3 +61,34 @@ export class QoreChainSigner {
|
|
|
19
61
|
constructor(opts: { wallet: any; chainId: string; address: string; pubkeySecp256k1: Uint8Array; accountNumber: number | bigint; pqc: { publicKey: Uint8Array; secretKey: Uint8Array } });
|
|
20
62
|
signHybrid(opts: { messages: any[]; fee: any; memo?: string; sequence: number | bigint; timeoutHeight?: bigint }): Promise<Uint8Array>;
|
|
21
63
|
}
|
|
64
|
+
|
|
65
|
+
// --- v3.1.85 authenticator lanes (EVM + Native/Cosmos) + PQC key rotation (requires chain >= v3.1.85) ---
|
|
66
|
+
export interface PqcKeypairFull { publicKey: Uint8Array; secretKey: Uint8Array; }
|
|
67
|
+
/** 32-byte digest an authenticator signs to authorize a MsgExecuteEVM. */
|
|
68
|
+
export function evmAuthSignBytes(p: { chainId: string; account: string; pubkey: Uint8Array; to?: string; value?: string; data?: Uint8Array; nonce: number | bigint }): Promise<Uint8Array>;
|
|
69
|
+
/** 32-byte digest an authenticator signs to authorize a MsgExecuteCosmos. */
|
|
70
|
+
export function cosmosAuthSignBytes(p: { chainId: string; account: string; pubkey: Uint8Array; to: string; amount: string; nonce: number | bigint }): Promise<Uint8Array>;
|
|
71
|
+
/** Domain-separated STRING both keys sign for a MsgRotatePQCKey (sign utf8 of it). */
|
|
72
|
+
export function rotationSignBytes(chainId: string, algorithmId: number, account: string, oldPub: Uint8Array, newPub: Uint8Array): string;
|
|
73
|
+
/** MsgExecuteEVM (relayer broadcasts + pays fees). */
|
|
74
|
+
export function executeEvmMsg(p: { relayer: string; account: string; scheme: string; pubkey: Uint8Array; signature: Uint8Array; to?: string; value?: string; data?: Uint8Array; gasLimit: number | bigint; nonce: number | bigint }): { typeUrl: string; value: any };
|
|
75
|
+
/** MsgExecuteCosmos (relayer broadcasts). `amount` is a single-coin string e.g. "100uqor". */
|
|
76
|
+
export function executeCosmosMsg(p: { relayer: string; account: string; scheme: string; pubkey: Uint8Array; signature: Uint8Array; to: string; amount: string; nonce: number | bigint }): { typeUrl: string; value: any };
|
|
77
|
+
/** MsgRevokeAuthenticator (owner-signed) — instantly disables a linked key. */
|
|
78
|
+
export function revokeAuthenticatorMsg(p: { owner: string; account?: string; scheme: string; pubkey: Uint8Array }): { typeUrl: string; value: any };
|
|
79
|
+
/** MsgRotatePQCKey (sender-signed hybrid with the OLD key). */
|
|
80
|
+
export function rotatePqcKeyMsg(p: { sender: string; oldPublicKey: Uint8Array; newPublicKey: Uint8Array; oldSignature: Uint8Array; newSignature: Uint8Array }): { typeUrl: string; value: any };
|
|
81
|
+
/** Phantom (ed25519) → MsgExecuteEVM ready for the relayer. */
|
|
82
|
+
export function buildPhantomExecuteEvm(p: { wallet: any; relayer: string; chainId: string; account: string; to?: string; value?: string; data?: Uint8Array; gasLimit?: number | bigint; nonce: number | bigint }): Promise<{ typeUrl: string; value: any }>;
|
|
83
|
+
/** Phantom (ed25519) → MsgExecuteCosmos ready for the relayer. */
|
|
84
|
+
export function buildPhantomExecuteCosmos(p: { wallet: any; relayer: string; chainId: string; account: string; to: string; amount: string; nonce: number | bigint }): Promise<{ typeUrl: string; value: any }>;
|
|
85
|
+
/** Build a MsgRotatePQCKey to migrate a key between derivations (default legacy→canonical). Broadcast cosigned with the OLD keypair. */
|
|
86
|
+
export function rotatePqcKeyMsgFromMnemonic(p: { account: string; mnemonic: string; chainId: string; algorithmId?: number; oldDerivation?: 'adapter' | 'bridge'; newDerivation?: 'adapter' | 'bridge' }): { msg: { typeUrl: string; value: any }; oldKeypair: PqcKeypairFull; newKeypair: PqcKeypairFull };
|
|
87
|
+
/** The LEGACY (chain-bridge) ML-DSA-87 derivation `shake256(mnemonic)`. */
|
|
88
|
+
export function derivePqcLegacy(mnemonic: string): PqcKeypairFull;
|
|
89
|
+
/** Link a MetaMask / EVM key (by 0x address) as a scoped authenticator (owner-signed). */
|
|
90
|
+
export function registerEthAuthenticatorMsg(p: { owner: string; account?: string; ethAddress: string; permissions?: string[]; expiryUnix: number | bigint; label?: string }): { typeUrl: string; value: any };
|
|
91
|
+
/** MetaMask (EIP-191 personal_sign) → MsgExecuteEVM ready for the relayer. `provider` is EIP-1193. */
|
|
92
|
+
export function buildMetaMaskExecuteEvm(p: { provider: any; address: string; relayer: string; chainId: string; account: string; to?: string; value?: string; data?: Uint8Array; gasLimit?: number | bigint; nonce: number | bigint }): Promise<{ typeUrl: string; value: any }>;
|
|
93
|
+
/** MetaMask (EIP-191 personal_sign) → MsgExecuteCosmos ready for the relayer. */
|
|
94
|
+
export function buildMetaMaskExecuteCosmos(p: { provider: any; address: string; relayer: string; chainId: string; account: string; to: string; amount: string; nonce: number | bigint }): Promise<{ typeUrl: string; value: any }>;
|
package/src/index.js
CHANGED
|
@@ -31,12 +31,20 @@ export {
|
|
|
31
31
|
} from './phantom.js';
|
|
32
32
|
// Unified wallet generation: one eth-native key → cosmos/evm/svm addresses + PQC key.
|
|
33
33
|
export {
|
|
34
|
-
generateQoreWallet, walletFromMnemonic, addressesFrom20, qoreAddresses,
|
|
34
|
+
generateQoreWallet, walletFromMnemonic, walletFromSeed, addressesFrom20, qoreAddresses,
|
|
35
35
|
} from './wallet.js';
|
|
36
36
|
// eth-native (eth_secp256k1) Cosmos signing — classical (register) + hybrid (PQC).
|
|
37
37
|
export {
|
|
38
38
|
signClassicalEth, signHybridEth, ETHSECP256K1_PUBKEY_TYPE,
|
|
39
39
|
} from './sign-eth.js';
|
|
40
|
+
// v3.1.85 authenticator lanes (EVM + Native/Cosmos) + PQC key rotation.
|
|
41
|
+
export {
|
|
42
|
+
evmAuthSignBytes, cosmosAuthSignBytes, rotationSignBytes,
|
|
43
|
+
executeEvmMsg, executeCosmosMsg, revokeAuthenticatorMsg, rotatePqcKeyMsg,
|
|
44
|
+
buildPhantomExecuteEvm, buildPhantomExecuteCosmos,
|
|
45
|
+
registerEthAuthenticatorMsg, buildMetaMaskExecuteEvm, buildMetaMaskExecuteCosmos,
|
|
46
|
+
rotatePqcKeyMsgFromMnemonic, derivePqcLegacy,
|
|
47
|
+
} from './authenticator.js';
|
|
40
48
|
import { TxBody, AuthInfo, TxRaw, SignerInfo, ModeInfo, Fee } from 'cosmjs-types/cosmos/tx/v1beta1/tx.js';
|
|
41
49
|
import { SignMode } from 'cosmjs-types/cosmos/tx/signing/v1beta1/signing.js';
|
|
42
50
|
import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx.js';
|
package/src/wallet.js
CHANGED
|
@@ -86,6 +86,40 @@ export async function walletFromMnemonic(mnemonic) {
|
|
|
86
86
|
return fromMnemonicObj(mnemonic);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Derive a unified QoreChain wallet directly from a 32-byte seed (no mnemonic).
|
|
91
|
+
*
|
|
92
|
+
* The seed is used as the secp256k1 private key, so a caller can derive one
|
|
93
|
+
* canonical eth-native account deterministically from any 32 bytes — e.g.
|
|
94
|
+
* `shake256(phantomSignature)` for the Phantom "connect → 3 addresses" flow, or
|
|
95
|
+
* an HKDF/KDF output from another wallet. Same 20-byte identity model as
|
|
96
|
+
* `walletFromMnemonic`: qor1 / 0x / svm all resolve to the same account and the
|
|
97
|
+
* same balance, and the key signs on every interface (incl. hybrid PQC).
|
|
98
|
+
*
|
|
99
|
+
* The ML-DSA-87 key is bound to `qorechain:pqc:v1|<qor1>|seed:<hex>` so it is
|
|
100
|
+
* recoverable from the same seed and never collides with a mnemonic wallet.
|
|
101
|
+
* `seed` accepts a 32-byte Uint8Array or a (0x-)hex string.
|
|
102
|
+
*/
|
|
103
|
+
export async function walletFromSeed(seed) {
|
|
104
|
+
const privkey = typeof seed === 'string' ? fromHex(seed.replace(/^0x/, '')) : seed;
|
|
105
|
+
if (!(privkey instanceof Uint8Array) || privkey.length !== 32) {
|
|
106
|
+
throw new Error('seed must be 32 bytes (Uint8Array or hex)');
|
|
107
|
+
}
|
|
108
|
+
const kp = await Secp256k1.makeKeypair(privkey); // validates the scalar is in [1, n-1]
|
|
109
|
+
const uncompressed = kp.pubkey;
|
|
110
|
+
const compressed = Secp256k1.compressPubkey(uncompressed);
|
|
111
|
+
const addr20 = new Keccak256(uncompressed.slice(1)).digest().slice(12);
|
|
112
|
+
const enc = addressesFrom20(addr20);
|
|
113
|
+
const pqcSeed = shake256(new TextEncoder().encode(`qorechain:pqc:v1|${enc.cosmos}|seed:${toHex(privkey)}`), 32);
|
|
114
|
+
return {
|
|
115
|
+
mnemonic: null,
|
|
116
|
+
privateKey: '0x' + toHex(privkey),
|
|
117
|
+
pubkey: '0x' + toHex(compressed),
|
|
118
|
+
...enc,
|
|
119
|
+
pqc: mldsa.keygen(pqcSeed),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
89
123
|
/** Convenience: the three address encodings of an existing account. */
|
|
90
124
|
export function qoreAddresses({ cosmos, evm, hex }) {
|
|
91
125
|
let addr20;
|