@qorechain/wallet-adapter 0.1.3 → 0.1.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qorechain/wallet-adapter",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
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.js CHANGED
@@ -33,6 +33,10 @@ export {
33
33
  export {
34
34
  generateQoreWallet, walletFromMnemonic, addressesFrom20, qoreAddresses,
35
35
  } from './wallet.js';
36
+ // eth-native (eth_secp256k1) Cosmos signing — classical (register) + hybrid (PQC).
37
+ export {
38
+ signClassicalEth, signHybridEth, ETHSECP256K1_PUBKEY_TYPE,
39
+ } from './sign-eth.js';
36
40
  import { TxBody, AuthInfo, TxRaw, SignerInfo, ModeInfo, Fee } from 'cosmjs-types/cosmos/tx/v1beta1/tx.js';
37
41
  import { SignMode } from 'cosmjs-types/cosmos/tx/signing/v1beta1/signing.js';
38
42
  import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx.js';
@@ -0,0 +1,91 @@
1
+ // @qorechain/wallet-adapter — eth-native (eth_secp256k1) Cosmos signing.
2
+ //
3
+ // A QoreChain account created eth-native (address = keccak(pubkey)[12:]) signs
4
+ // Cosmos SDK txs with the `eth_secp256k1` scheme: the classical signature is
5
+ // secp256k1 over the KECCAK-256 of the SignDoc (not sha256), and the account's
6
+ // pubkey is `/cosmos.evm.crypto.v1.ethsecp256k1.PubKey`. This is the same account
7
+ // that spends on the EVM lane, so its qor1/0x/svm forms are one identity.
8
+ //
9
+ // Mainnet requires the ML-DSA-87 hybrid extension in the tx body; signHybridEth
10
+ // adds it. signClassicalEth omits it (used for the one-time PQC key registration,
11
+ // which is bootstrap-exempt from the hybrid requirement).
12
+
13
+ import { Secp256k1, Keccak256 } from '@cosmjs/crypto';
14
+ import { fromHex } from '@cosmjs/encoding';
15
+ import { TxBody, AuthInfo, TxRaw, SignerInfo, ModeInfo, Fee, SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx.js';
16
+ import { SignMode } from 'cosmjs-types/cosmos/tx/signing/v1beta1/signing.js';
17
+ import { PubKey } from 'cosmjs-types/cosmos/crypto/secp256k1/keys.js';
18
+ import { mldsa } from '@qorechain/pqc';
19
+ import { frame, encodePqcHybridSignature, HYBRID_SIG_TYPE_URL, ALGORITHM_ML_DSA_87 } from './framing.js';
20
+
21
+ // cosmos/evm eth_secp256k1 pubkey type. Wire shape is identical to the cosmos
22
+ // secp256k1 PubKey ({1: bytes key}), only the typeUrl differs.
23
+ export const ETHSECP256K1_PUBKEY_TYPE = '/cosmos.evm.crypto.v1.ethsecp256k1.PubKey';
24
+
25
+ function toBytes(x) {
26
+ if (x instanceof Uint8Array) return x;
27
+ return fromHex(String(x).replace(/^0x/, ''));
28
+ }
29
+
30
+ function buildAuthInfo(compressedPubkey, sequence, fee) {
31
+ const pubAny = {
32
+ typeUrl: ETHSECP256K1_PUBKEY_TYPE,
33
+ value: PubKey.encode(PubKey.fromPartial({ key: toBytes(compressedPubkey) })).finish(),
34
+ };
35
+ const authInfo = AuthInfo.fromPartial({
36
+ signerInfos: [SignerInfo.fromPartial({
37
+ publicKey: pubAny,
38
+ modeInfo: ModeInfo.fromPartial({ single: { mode: SignMode.SIGN_MODE_DIRECT } }),
39
+ sequence: BigInt(sequence),
40
+ })],
41
+ fee: Fee.fromPartial(fee),
42
+ });
43
+ return AuthInfo.encode(authInfo).finish();
44
+ }
45
+
46
+ // eth_secp256k1 classical signature over a SignDoc: secp256k1 sign of keccak(signBytes),
47
+ // serialized as the 64-byte r‖s (low-s normalized by @cosmjs/crypto).
48
+ async function ethSign(signBytes, privateKey) {
49
+ const hash = new Keccak256(signBytes).digest();
50
+ const sig = await Secp256k1.createSignature(hash, toBytes(privateKey));
51
+ const out = new Uint8Array(64);
52
+ out.set(sig.r(32), 0);
53
+ out.set(sig.s(32), 32);
54
+ return out;
55
+ }
56
+
57
+ /**
58
+ * Classical-only eth_secp256k1 Cosmos tx (no PQC extension). Use for the one-time
59
+ * MsgRegisterPQCKeyV2 (bootstrap-exempt). `key` = { privateKey, pubkey } from
60
+ * generateQoreWallet/walletFromMnemonic.
61
+ */
62
+ export async function signClassicalEth({ key, chainId, accountNumber, messages, fee, sequence, memo = '', timeoutHeight = 0n }) {
63
+ const authInfoBytes = buildAuthInfo(key.pubkey, sequence, fee);
64
+ const bodyBytes = TxBody.encode(TxBody.fromPartial({ messages, memo, timeoutHeight })).finish();
65
+ const signBytes = SignDoc.encode(SignDoc.fromPartial({
66
+ bodyBytes, authInfoBytes, chainId, accountNumber: BigInt(accountNumber),
67
+ })).finish();
68
+ const classical = await ethSign(signBytes, key.privateKey);
69
+ return TxRaw.encode(TxRaw.fromPartial({ bodyBytes, authInfoBytes, signatures: [classical] })).finish();
70
+ }
71
+
72
+ /**
73
+ * Hybrid eth_secp256k1 + ML-DSA-87 Cosmos tx. `key` must include `pqc`
74
+ * ({publicKey, secretKey}) as produced by generateQoreWallet/walletFromMnemonic.
75
+ */
76
+ export async function signHybridEth({ key, chainId, accountNumber, messages, fee, sequence, memo = '', timeoutHeight = 0n }) {
77
+ const authInfoBytes = buildAuthInfo(key.pubkey, sequence, fee);
78
+ // B0 = body without the PQC extension.
79
+ const b0 = TxBody.encode(TxBody.fromPartial({ messages, memo, timeoutHeight })).finish();
80
+ // ML-DSA-87 over frame(B0, authInfo).
81
+ const pqcSig = mldsa.sign(key.pqc.secretKey, frame(b0, authInfoBytes));
82
+ const bodyWithExt = TxBody.encode(TxBody.fromPartial({
83
+ messages, memo, timeoutHeight,
84
+ extensionOptions: [{ typeUrl: HYBRID_SIG_TYPE_URL, value: encodePqcHybridSignature(ALGORITHM_ML_DSA_87, pqcSig) }],
85
+ })).finish();
86
+ const signBytes = SignDoc.encode(SignDoc.fromPartial({
87
+ bodyBytes: bodyWithExt, authInfoBytes, chainId, accountNumber: BigInt(accountNumber),
88
+ })).finish();
89
+ const classical = await ethSign(signBytes, key.privateKey);
90
+ return TxRaw.encode(TxRaw.fromPartial({ bodyBytes: bodyWithExt, authInfoBytes, signatures: [classical] })).finish();
91
+ }