@qorechain/connect 0.1.0 → 0.2.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 +21 -0
- package/package.json +25 -5
- package/src/index.js +28 -7
package/README.md
CHANGED
|
@@ -30,5 +30,26 @@ PQC tx committed **code 0** (ML-DSA accepted); SVM read OK.
|
|
|
30
30
|
*before* the wallet `signDirect`s it, so the wallet needs zero changes.
|
|
31
31
|
- **SVM** → reads native; `tx svm execute` is a cosmos tx → same PQC layer.
|
|
32
32
|
|
|
33
|
+
## Hybrid sign-bytes form (v1 / v2, chain v3.1.98)
|
|
34
|
+
|
|
35
|
+
The ML-DSA-87 signature covers one of two byte forms and a network accepts exactly
|
|
36
|
+
one at any height: v1 `BE32(len B0) ‖ B0 ‖ BE32(len A) ‖ A`, or v2, which prefixes
|
|
37
|
+
`"qorechain-pqc-hybrid-v2" ‖ BE64(len chainId) ‖ chainId`. `qorechain-vladi`
|
|
38
|
+
(mainnet) and `qorechain-diana` (testnet) verify v1 until the `v3.1.98` upgrade is
|
|
39
|
+
applied on them and v2 afterwards; testnet is on v2, **mainnet stays on v1 until
|
|
40
|
+
its own upgrade**. Other chains verify v2 from genesis.
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
await QoreConnect.sendCosmos(keplr, { address, pubkey, mlsk, acct, seq, to, amount,
|
|
44
|
+
chainId: 'qorechain-vladi', rpc, rest: lcdUrl /* or signBytesVersion: 'v1' | 'v2' */ });
|
|
45
|
+
// -> { vm:'cosmos', code, hash, pqc:true, signBytesVersion: 'v1' | 'v2' }
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`signBytesVersion: 'auto'` (default) asks `rest` (or `QORE_REST`) through
|
|
49
|
+
`@qorechain/wallet-adapter`'s resolver and caches the answer for 60 s; on vladi /
|
|
50
|
+
diana without `rest` it throws instead of guessing. With `'auto'`, a refusal with
|
|
51
|
+
`pqc` code 21 re-resolves once and, if the answer changed, re-signs and broadcasts
|
|
52
|
+
once more. Explicit `'v1'` / `'v2'` never retries.
|
|
53
|
+
|
|
33
54
|
## License
|
|
34
55
|
Apache-2.0
|
package/package.json
CHANGED
|
@@ -1,12 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qorechain/connect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "One universal wallet connector for QoreChain across all 3 VMs (EVM, Cosmos, SVM). Detects any wallet by its standard interface (EIP-1193, OfflineDirectSigner, Solana Wallet Standard) and auto-layers the FIPS-204 PQC signature only where the chain requires it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
|
-
"files": [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
"files": [
|
|
9
|
+
"src",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "node --test"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/qorechain/qorechain-connect.git"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"@qorechain/wallet-adapter": "^0.2.0",
|
|
24
|
+
"ethers": "^6",
|
|
25
|
+
"@cosmjs/proto-signing": "^0.32"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@qorechain/wallet-adapter": "^0.2.0",
|
|
29
|
+
"@noble/post-quantum": "^0.6.1",
|
|
30
|
+
"cosmjs-types": "^0.9.0"
|
|
31
|
+
}
|
|
12
32
|
}
|
package/src/index.js
CHANGED
|
@@ -4,8 +4,13 @@ import { TxBody, AuthInfo, TxRaw, SignerInfo, ModeInfo, Fee, SignDoc } from 'cos
|
|
|
4
4
|
import { SignMode } from 'cosmjs-types/cosmos/tx/signing/v1beta1/signing.js';
|
|
5
5
|
import { PubKey } from 'cosmjs-types/cosmos/crypto/secp256k1/keys.js';
|
|
6
6
|
import { MsgSend } from 'cosmjs-types/cosmos/bank/v1beta1/tx.js';
|
|
7
|
-
|
|
8
|
-
const
|
|
7
|
+
import { hybridSignBytes, resolveSignBytesVersion, isHybridSignBytesRejection } from '@qorechain/wallet-adapter';
|
|
8
|
+
const CHAIN=process.env.QORE_CHAIN_ID||'qorechain-diana', RPC=process.env.QORE_RPC||'http://127.0.0.1:26657', SVMRPC=process.env.QORE_SVM_RPC||'http://127.0.0.1:8899', REST=process.env.QORE_REST||undefined;
|
|
9
|
+
// Hybrid PQC sign-bytes come in two forms and a network accepts exactly one at a time:
|
|
10
|
+
// v1 (legacy): BE32(len b0) ‖ b0 ‖ BE32(len a) ‖ a
|
|
11
|
+
// v2: "qorechain-pqc-hybrid-v2" ‖ BE64(len chainId) ‖ chainId ‖ v1-layout
|
|
12
|
+
// qorechain-vladi / qorechain-diana verify v1 until their v3.1.98 upgrade is applied, v2 after;
|
|
13
|
+
// any other chain verifies v2. 'auto' asks the LCD (`rest`), see @qorechain/wallet-adapter.
|
|
9
14
|
const encExt=(id,sig)=>{const vi=n=>{const b=[];while(n>0x7f){b.push((n&0x7f)|0x80);n>>>=7;}b.push(n);return b;};return Uint8Array.from([0x08,...vi(id),0x12,...vi(sig.length),...sig]);};
|
|
10
15
|
|
|
11
16
|
export class QoreConnect {
|
|
@@ -23,19 +28,35 @@ export class QoreConnect {
|
|
|
23
28
|
return {vm:'evm',hash,pqc:false};
|
|
24
29
|
}
|
|
25
30
|
// Cosmos (PQC-required): wallet signDirect's the final body; connector layers ML-DSA-87.
|
|
26
|
-
|
|
31
|
+
// signBytesVersion: 'auto' (default; needs `rest` = LCD URL on vladi/diana) | 'v1' | 'v2'.
|
|
32
|
+
// With 'auto', a refusal with pqc code 21 re-resolves once and, if the answer changed,
|
|
33
|
+
// re-signs and broadcasts once more; any other outcome is returned as-is.
|
|
34
|
+
static async sendCosmos(w,{address,pubkey,mlsk,acct,seq,to,amount,chainId=CHAIN,rpc=RPC,rest=REST,signBytesVersion='auto',fetch:fx}){
|
|
35
|
+
const mode=signBytesVersion??'auto';
|
|
36
|
+
const ro={chainId,rest,signBytesVersion:mode,...(fx?{fetch:fx}:{})};
|
|
37
|
+
const post=fx||globalThis.fetch;
|
|
38
|
+
let version=await resolveSignBytesVersion(ro);
|
|
39
|
+
let r=await this._cosmosOnce(w,{address,pubkey,mlsk,acct,seq,to,amount,chainId,rpc,version,post});
|
|
40
|
+
if(mode==='auto'&&isHybridSignBytesRejection(r)){
|
|
41
|
+
const fresh=await resolveSignBytesVersion({...ro,forceRefresh:true});
|
|
42
|
+
if(fresh!==version){version=fresh;r=await this._cosmosOnce(w,{address,pubkey,mlsk,acct,seq,to,amount,chainId,rpc,version,post});}
|
|
43
|
+
}
|
|
44
|
+
return {vm:'cosmos',code:r.code,hash:r.hash,pqc:true,signBytesVersion:version,...(r.code?{codespace:r.codespace,log:r.log}:{})};
|
|
45
|
+
}
|
|
46
|
+
static async _cosmosOnce(w,{address,pubkey,mlsk,acct,seq,to,amount,chainId,rpc,version,post}){
|
|
27
47
|
const pubAny={typeUrl:'/cosmos.crypto.secp256k1.PubKey',value:PubKey.encode(PubKey.fromPartial({key:pubkey})).finish()};
|
|
28
48
|
const msg={typeUrl:'/cosmos.bank.v1beta1.MsgSend',value:MsgSend.encode(MsgSend.fromPartial({fromAddress:address,toAddress:to,amount:[{denom:'uqor',amount}]})).finish()};
|
|
29
49
|
const authInfo=AuthInfo.fromPartial({signerInfos:[SignerInfo.fromPartial({publicKey:pubAny,modeInfo:ModeInfo.fromPartial({single:{mode:SignMode.SIGN_MODE_DIRECT}}),sequence:BigInt(seq)})],fee:Fee.fromPartial({amount:[{denom:'uqor',amount:'25000'}],gasLimit:200000n})});
|
|
30
50
|
const aib=AuthInfo.encode(authInfo).finish();
|
|
31
51
|
const b0=TxBody.encode(TxBody.fromPartial({messages:[msg]})).finish();
|
|
32
|
-
const pqcSig=ml_dsa87.sign(
|
|
52
|
+
const pqcSig=ml_dsa87.sign(hybridSignBytes(version,chainId,b0,aib),mlsk,{extraEntropy:false}); // <-- the PQC layer (deterministic, as the chain requires)
|
|
33
53
|
const bodyExt=TxBody.encode(TxBody.fromPartial({messages:[msg],extensionOptions:[{typeUrl:'/qorechain.pqc.v1.PQCHybridSignature',value:encExt(1,pqcSig)}]})).finish();
|
|
34
|
-
const signDoc=SignDoc.fromPartial({bodyBytes:bodyExt,authInfoBytes:aib,chainId
|
|
54
|
+
const signDoc=SignDoc.fromPartial({bodyBytes:bodyExt,authInfoBytes:aib,chainId,accountNumber:BigInt(acct)});
|
|
35
55
|
const {signature}=await w.signDirect(address,signDoc); // <-- wallet's standard signature
|
|
36
56
|
const txRaw=TxRaw.encode(TxRaw.fromPartial({bodyBytes:bodyExt,authInfoBytes:aib,signatures:[Uint8Array.from(Buffer.from(signature.signature,'base64'))]})).finish();
|
|
37
|
-
const r=await(await
|
|
38
|
-
|
|
57
|
+
const r=await(await post(rpc,{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({jsonrpc:'2.0',id:1,method:'broadcast_tx_sync',params:{tx:Buffer.from(txRaw).toString('base64')}})})).json();
|
|
58
|
+
if(!r.result) throw new Error(`broadcast_tx_sync failed: ${JSON.stringify(r.error||r)}`);
|
|
59
|
+
return r.result;
|
|
39
60
|
}
|
|
40
61
|
// SVM: Solana wallets read natively; execute goes through the cosmos-PQC path.
|
|
41
62
|
static async readSvm(w){
|