@qorechain/wallet-adapter 0.1.2 → 0.1.3
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 +27 -0
- package/package.json +4 -2
- package/src/index.js +4 -0
- package/src/wallet.js +97 -0
package/README.md
CHANGED
|
@@ -85,3 +85,30 @@ await fetch(`${rpc}`, { method:'POST', body: JSON.stringify({
|
|
|
85
85
|
## License
|
|
86
86
|
|
|
87
87
|
Apache-2.0
|
|
88
|
+
|
|
89
|
+
## Unified wallet generation (all 3 addresses)
|
|
90
|
+
|
|
91
|
+
Every account is one 20-byte identity rendered as three encodings that share a
|
|
92
|
+
single on-chain balance. Generate an eth-native wallet and get all three at once,
|
|
93
|
+
plus the ML-DSA-87 (Dilithium-5) key for the PQC-hybrid Cosmos ante:
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
import { generateQoreWallet, walletFromMnemonic } from "@qorechain/wallet-adapter";
|
|
97
|
+
|
|
98
|
+
const w = await generateQoreWallet(); // random 24-word mnemonic
|
|
99
|
+
// const w = await walletFromMnemonic(existing); // or recover
|
|
100
|
+
w.cosmos // qor1… (bech32)
|
|
101
|
+
w.evm // 0x… (EIP-55) (hex — EVM-native, spendable via eth_sendRawTransaction)
|
|
102
|
+
w.svm // <base58> (base58 of the 20 bytes + 12 zero-byte pad)
|
|
103
|
+
w.privateKey // 0x… (32B)
|
|
104
|
+
w.pqc // { publicKey, secretKey } ML-DSA-87
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The key is **eth-native** (address = `keccak256(pubkey)[12:]`), so it is spendable
|
|
108
|
+
on the EVM lane; `cosmos` and `svm` are just other encodings of the same 20 bytes,
|
|
109
|
+
under which the chain reads one `x/bank` balance. The account can sign EVM txs
|
|
110
|
+
(EIP-155) **and** PQC-hybrid Cosmos txs (the chain's Cosmos ante handles
|
|
111
|
+
`eth_secp256k1` and the hybrid decorator keys off the address).
|
|
112
|
+
|
|
113
|
+
`addressesFrom20(bytes20)` / `qoreAddresses({cosmos|evm|hex})` derive the three
|
|
114
|
+
encodings from a known account (for explorers / backends).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qorechain/wallet-adapter",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
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",
|
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@qorechain/pqc": "^0.1.1",
|
|
34
|
-
"cosmjs-types": "^0.9.0"
|
|
34
|
+
"cosmjs-types": "^0.9.0",
|
|
35
|
+
"@cosmjs/crypto": "^0.32.0",
|
|
36
|
+
"@cosmjs/encoding": "^0.32.0"
|
|
35
37
|
},
|
|
36
38
|
"devDependencies": {
|
|
37
39
|
"@noble/post-quantum": "^0.6.1"
|
package/src/index.js
CHANGED
|
@@ -29,6 +29,10 @@ export {
|
|
|
29
29
|
base58Encode, base58Decode, SYSTEM_PROGRAM_ID, systemTransferData,
|
|
30
30
|
authSignBytes, buildPhantomSvmEnvelope, buildPhantomTransfer, registerAuthenticatorMsg,
|
|
31
31
|
} from './phantom.js';
|
|
32
|
+
// Unified wallet generation: one eth-native key → cosmos/evm/svm addresses + PQC key.
|
|
33
|
+
export {
|
|
34
|
+
generateQoreWallet, walletFromMnemonic, addressesFrom20, qoreAddresses,
|
|
35
|
+
} from './wallet.js';
|
|
32
36
|
import { TxBody, AuthInfo, TxRaw, SignerInfo, ModeInfo, Fee } from 'cosmjs-types/cosmos/tx/v1beta1/tx.js';
|
|
33
37
|
import { SignMode } from 'cosmjs-types/cosmos/tx/signing/v1beta1/signing.js';
|
|
34
38
|
import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx.js';
|
package/src/wallet.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// @qorechain/wallet-adapter — unified wallet generation.
|
|
2
|
+
//
|
|
3
|
+
// One eth-native secp256k1 keypair → the SAME 20-byte account rendered as all
|
|
4
|
+
// THREE QoreChain address encodings, so a wallet never "has funds on Cosmos but
|
|
5
|
+
// not EVM" again. The 20 bytes are the Ethereum derivation `keccak256(pubkey)[12:]`
|
|
6
|
+
// so the key is natively spendable on the EVM lane; the Cosmos (`qor1…`) and SVM
|
|
7
|
+
// (base58) forms are just other encodings of those same 20 bytes — the chain reads
|
|
8
|
+
// one x/bank balance for the account, visible under all three.
|
|
9
|
+
//
|
|
10
|
+
// {
|
|
11
|
+
// mnemonic, privateKey (0x hex, 32B),
|
|
12
|
+
// pubkey (33B compressed),
|
|
13
|
+
// addressBytes (20B),
|
|
14
|
+
// cosmos: "qor1…", // bech32
|
|
15
|
+
// evm: "0x…" (EIP-55), // hex
|
|
16
|
+
// svm: "<base58>", // base58(20B ‖ 12 zero bytes) = 32-byte SVM addr
|
|
17
|
+
// pqc: { publicKey, secretKey } // ML-DSA-87 (Dilithium-5), for the hybrid ante
|
|
18
|
+
// }
|
|
19
|
+
//
|
|
20
|
+
// Eth-native accounts are fully supported by the chain (the Cosmos ante's
|
|
21
|
+
// SigVerification handles eth_secp256k1 and the PQC hybrid decorator keys off the
|
|
22
|
+
// address), so the same wallet signs EVM txs (EIP-155) AND PQC-hybrid Cosmos txs.
|
|
23
|
+
|
|
24
|
+
import { Bip39, Random, Slip10, Slip10Curve, stringToPath, Secp256k1, Keccak256, EnglishMnemonic } from '@cosmjs/crypto';
|
|
25
|
+
import { toBech32, toHex, fromHex, fromBech32 } from '@cosmjs/encoding';
|
|
26
|
+
import { mldsa, shake256 } from '@qorechain/pqc';
|
|
27
|
+
import { base58Encode } from './phantom.js';
|
|
28
|
+
|
|
29
|
+
// Ethereum HD path (coin-type 60) — makes the 20-byte address the keccak
|
|
30
|
+
// derivation, so the key is EVM-native (spendable via eth_sendRawTransaction).
|
|
31
|
+
const ETH_HD_PATH = "m/44'/60'/0'/0/0";
|
|
32
|
+
const HRP = 'qor';
|
|
33
|
+
|
|
34
|
+
// EIP-55 mixed-case checksum for a 20-byte hex address (no 0x).
|
|
35
|
+
function toEip55(hex20) {
|
|
36
|
+
const lower = hex20.toLowerCase();
|
|
37
|
+
const hash = toHex(new Keccak256(new TextEncoder().encode(lower)).digest());
|
|
38
|
+
let out = '0x';
|
|
39
|
+
for (let i = 0; i < lower.length; i++) {
|
|
40
|
+
out += parseInt(hash[i], 16) >= 8 ? lower[i].toUpperCase() : lower[i];
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Derive the three encodings + the PQC key from a 20-byte account address.
|
|
46
|
+
// Exposed so SDKs / backends can render all three from a known account too.
|
|
47
|
+
export function addressesFrom20(addr20) {
|
|
48
|
+
if (addr20.length !== 20) throw new Error('address must be 20 bytes');
|
|
49
|
+
const hex = toHex(addr20);
|
|
50
|
+
const svmBytes = new Uint8Array(32);
|
|
51
|
+
svmBytes.set(addr20, 0); // right-pad with 12 zero bytes → the unified 32-byte SVM address
|
|
52
|
+
return { addressBytes: addr20, cosmos: toBech32(HRP, addr20), evm: toEip55(hex), svm: base58Encode(svmBytes) };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function derivePqc(cosmosAddr, mnemonic) {
|
|
56
|
+
const seed = shake256(new TextEncoder().encode(`qorechain:pqc:v1|${cosmosAddr}|${mnemonic}`), 32);
|
|
57
|
+
return mldsa.keygen(seed); // deterministic + recoverable from {address, mnemonic}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function fromMnemonicObj(mnemonic) {
|
|
61
|
+
const seed = await Bip39.mnemonicToSeed(new EnglishMnemonic(mnemonic));
|
|
62
|
+
const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, stringToPath(ETH_HD_PATH));
|
|
63
|
+
const kp = await Secp256k1.makeKeypair(privkey);
|
|
64
|
+
const uncompressed = kp.pubkey; // 65 bytes, 0x04 || X || Y
|
|
65
|
+
const compressed = Secp256k1.compressPubkey(uncompressed); // 33 bytes
|
|
66
|
+
// Ethereum address = last 20 bytes of keccak256 of the 64-byte pubkey (drop 0x04).
|
|
67
|
+
const addr20 = new Keccak256(uncompressed.slice(1)).digest().slice(12);
|
|
68
|
+
const enc = addressesFrom20(addr20);
|
|
69
|
+
return {
|
|
70
|
+
mnemonic,
|
|
71
|
+
privateKey: '0x' + toHex(privkey),
|
|
72
|
+
pubkey: '0x' + toHex(compressed),
|
|
73
|
+
...enc,
|
|
74
|
+
pqc: derivePqc(enc.cosmos, mnemonic),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Generate a fresh unified QoreChain wallet (random 24-word mnemonic). */
|
|
79
|
+
export async function generateQoreWallet(strength = 256) {
|
|
80
|
+
const mnemonic = Bip39.encode(Random.getBytes(strength / 8)).toString();
|
|
81
|
+
return fromMnemonicObj(mnemonic);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Recover a unified QoreChain wallet from an existing BIP39 mnemonic. */
|
|
85
|
+
export async function walletFromMnemonic(mnemonic) {
|
|
86
|
+
return fromMnemonicObj(mnemonic);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Convenience: the three address encodings of an existing account. */
|
|
90
|
+
export function qoreAddresses({ cosmos, evm, hex }) {
|
|
91
|
+
let addr20;
|
|
92
|
+
if (evm) addr20 = fromHex(evm.replace(/^0x/, ''));
|
|
93
|
+
else if (hex) addr20 = fromHex(hex.replace(/^0x/, ''));
|
|
94
|
+
else if (cosmos) addr20 = fromBech32(cosmos).data;
|
|
95
|
+
else throw new Error('provide one of {cosmos, evm, hex}');
|
|
96
|
+
return addressesFrom20(addr20);
|
|
97
|
+
}
|