Conturi și semnarea PQC
Conturile QoreChain sunt derivate dintr-un singur mnemonic BIP-39. Există două modele de cont, ambele pe deplin suportate:
- Derivare HD pe lane separate (legacy/implicit) — același mnemonic produce un cont nativ (coin type 118), un cont EVM (coin type 60) și un cont SVM (coin type 501) prin căi de derivare independente. Trei chei, trei adrese.
- Conturi unificate eth-native (SDK 0.6.0, chain v3.1.83) — O SINGURĂ
cheie
eth_secp256k1este O SINGURĂ identitate de 20 de bytes, redată în toate cele trei codificări de adresă, cu un singur sold comun. Vezi Conturi unificate.
Derivare HD (legacy/implicit, coin type 118)
import {
generateMnemonic,
validateMnemonic,
deriveNativeAccount,
deriveEvmAccount,
deriveSvmAccount,
} from "@qorechain/sdk";
const mnemonic = generateMnemonic(); // 12 words; pass 256 for 24 words
const native = await deriveNativeAccount(mnemonic);
console.log(native.address); // "qor1..." (secp256k1, bech32)
const evm = await deriveEvmAccount(mnemonic);
console.log(evm.address); // "0x..." (EIP-55 checksummed)
const svm = await deriveSvmAccount(mnemonic);
console.log(svm.address); // base58 ed25519 public key
Mnemonicul este validat (atât cuvintele, cât și suma de control) înainte ca
orice cheie să fie derivată, astfel încât o greșeală de tastare generează o
eroare, nu produce în tăcere un cont greșit. Poți valida explicit cu
validateMnemonic(mnemonic).
Scheme de derivare
| Tip | Curbă | Cale | Adresă |
|---|---|---|---|
| native | secp256k1 | m/44'/118'/0'/0/{i} | bech32 qor din ripemd160(sha256(pubkey)) |
| evm | secp256k1 | m/44'/60'/0'/0/{i} | 0x + keccak256(pubkey)[-20:], EIP-55 |
| svm | ed25519 | m/44'/501'/{i}'/0' | base58 al cheii publice de 32 de bytes |
Transmite un index de cont pentru a deriva conturi suplimentare. În TypeScript:
const second = await deriveNativeAccount(mnemonic, { accountIndex: 1 });
În Python/Go/Rust, indexul este un argument poziţional
(derive_native_account(mnemonic, 1) / DeriveNativeAccount(mnemonic, 1) /
derive_native_account(&mnemonic, 1)).
Notă privind testele de tip known-answer
Schemele de derivare sunt deterministe și acoperite de teste known-answer în toate cele patru SDK-uri, astfel încât același mnemonic produce adrese identice în TypeScript, Python, Go și Rust. Acest lucru îți permite să derivi într-un limbaj și să verifici în altul.
Această derivare pe lane separate (
deriveNativeAccountla coin type 118, plusderiveEvmAccount/deriveSvmAccount) este modelul legacy/implicit și rămâne suportat și neschimbat. Conturile unificate de mai jos sunt un model de identitate suplimentar, opțional.
Conturi unificate (eth-native)
Începând cu SDK 0.6.0 (chain v3.1.83), deriveUnifiedAccount(mnemonic, index = 0)
derivă O SINGURĂ cheie eth_secp256k1 pe calea HD Ethereum
m/44'/60'/0'/0/{index}, ale cărei 20 de bytes de adresă
(keccak256(pubkey)[12:]) reprezintă ACEEAȘI identitate redată în trei
moduri:
| Lane | Codificare |
|---|---|
| Native | bech32 cu prefixul qor (qor1…) |
| EVM | 0x + hex checksum mixed-case EIP-55 |
| SVM | base58 al celor 20 de bytes completați la dreapta cu 12 bytes zero (32 de bytes) |
Un depozit către oricare dintre cele trei ajunge într-un singur sold, iar cheia este semnatarul pe fiecare lane (pe lane-ul EVM, în interiorul unei ferestre de autorizare; lane-ul de tranzacții SVM este în prezent dezactivat la nivelul întregii rețele, deci SVM este astăzi doar în citire):
import {
deriveUnifiedAccount,
qoreAddresses,
addressesFrom20,
} from "@qorechain/sdk";
const account = await deriveUnifiedAccount(mnemonic);
account.cosmos; // "qor1…" bech32, Native lane
account.evm; // "0x…" EIP-55 hex, EVM lane
account.svm; // "<base58>" 32-byte SVM address (addr20 + 12 zero bytes)
account.addressBytes; // the raw 20 bytes shared by all three
account.publicKey; // "0x…" hex string of the 33-byte compressed secp256k1 key
account.privateKey; // "0x…" hex string (a secret: never log or commit it)
account.pqc; // { publicKey, secretKey } — ML-DSA-87, derived below
// Decode any ONE encoding into all three:
const all = qoreAddresses({ evm: account.evm });
all.cosmos; // qor1…
all.svm; // base58
// or straight from the raw 20 bytes:
const same = addressesFrom20(account.addressBytes);
unifiedAccountFromSeed(seed32) face același lucru pornind de la o cheie
privată secp256k1 brută de 32 de bytes.
Derivarea seed-ului PQC
Perechea de chei ML-DSA-87 a contului este derivată determinist și este legată de adresă:
pqcSeed = shake256("qorechain:pqc:v1|" + cosmosAddress + "|" + mnemonic, 32)
astfel încât poate fi recuperată din { address, mnemonic } și este
identică în toate SDK-urile QoreChain, indiferent de limbaj. (Pentru
unifiedAccountFromSeed, slotul mnemonicului este "seed:" + hex(seed32).)
Începând cu SDK 0.8.3, derivePqcCanonical(account, mnemonic) rezolvă mai
întâi account la forma sa qor1… cu litere mici (canonicalAccount), astfel
încât un bech32 cu litere mari sau forma 0x a aceluiași cont produce aceeași
cheie.
Trimiterea pe lane-ul Native cu cheia eth
Un cont unificat semnează tranzacțiile pe calea Native cu schema
eth_secp256k1: semnătura clasică este secp256k1 peste keccak256 al
bytes-ilor SignDoc (nu sha256), iar cheia publică din SignerInfo folosește
URL-ul de tip /cosmos.evm.crypto.v1.ethsecp256k1.PubKey. Calea hibridă
(signHybridEth, sau EthNativeSigner cu signMode: "hybrid" implicit)
atașează suplimentar extensia PQCHybridSignature ML-DSA-87, obligatorie pe
rețelele live.
Sunt necesare două lucruri înainte de primul transfer hibrid. Mai întâi, cheia
ML-DSA-87 a contului trebuie înregistrată (o singură dată). Înregistrarea
este singura tranzacție semnată clasic și scutită de regula hibridă: semneaz-o
cu signMode: "classical". publicKey al contului este un șir hex 0x, deci
decodează-l în bytes pentru ecdsaPubkey. În al doilea rând, transmite rest
semnatarului, ca să poată întreba rețeaua ce
formă a sign-bytes verifică (pe qorechain-vladi și
qorechain-diana aruncă o eroare în loc să ghicească). Necesită SDK 0.8.1 sau
mai nou și npm i @cosmjs/stargate:
import { StargateClient } from "@cosmjs/stargate";
import {
createClient,
deriveUnifiedAccount,
EthNativeSigner,
buildRegisterPqcKeyMsg,
toBase,
} from "@qorechain/sdk";
const REST = "https://api.qore.host";
const RPC = "https://rpc.qore.host";
const CHAIN_ID = "qorechain-vladi";
const client = createClient({ network: "mainnet", endpoints: { rest: REST, rpc: RPC } });
const transport = await StargateClient.connect(RPC);
const account = await deriveUnifiedAccount(mnemonic);
// The REST account record carries the number and sequence (the account must
// exist, that is, have received funds).
async function authInfo() {
const { account: base } = await client.rest.get<{
account: { account_number: string; sequence: string };
}>(`/cosmos/auth/v1beta1/accounts/${account.cosmos}`);
return { accountNumber: BigInt(base.account_number), sequence: BigInt(base.sequence) };
}
// 1) Register the ML-DSA-87 key once (classical-signed, exempt from the hybrid rule).
const classical = new EthNativeSigner(account, { signMode: "classical" });
await classical.signAndBroadcast(transport, {
chainId: CHAIN_ID,
...(await authInfo()),
messages: [
buildRegisterPqcKeyMsg(account.cosmos, {
pqcKeypair: account.pqc,
ecdsaPubkey: Uint8Array.from(Buffer.from(account.publicKey.slice(2), "hex")),
}),
],
fee: { amount: [{ denom: "uqor", amount: "60000" }], gas: "600000" }, // the 2,592-byte key needs about 600k gas
});
// 2) Send with the hybrid signature (the default signMode).
const signer = new EthNativeSigner(account, { rest: REST });
const res = await signer.bankSend(
transport,
"qor1recipient…",
[{ denom: "uqor", amount: toBase("1.5") }],
{
chainId: CHAIN_ID,
...(await authInfo()),
fee: { amount: [{ denom: "uqor", amount: "40000" }], gas: "400000" }, // 0.1 uqor per gas is the floor
},
);
console.log(res.transactionHash);
Pentru control la nivel inferior, signHybridEth(params) /
signClassicalEth(params) returnează bytes-ii TxRaw asamblați și
artefactele de semnare, iar accountAuthInfo(baseAccount) citește
account_number / sequence dintr-un BaseAccount protobuf a cărui cheie
publică on-chain folosește URL-ul de tip eth_secp256k1 (un cont REST JSON
are nevoie ca mai întâi câmpurile să-i fie mapate, așa cum face authInfo mai
sus).
SDK 0.6.1 a corectat o eroare de codificare critică pentru consens:
extensia de tx-body /qorechain.pqc.v1.PQCHybridSignature era serializată
JSON în Any.value, iar chain-ul respingea aceste tranzacții la CheckTx
(eroare de parsare a tranzacției). Acum este codificată protobuf (valoarea
extensiei începe cu 0x08) în toate cele cinci limbaje. Orice tranzacție
hibridă — inclusiv lane-ul eth-native — construită cu SDK ≤ 0.6.0 este
respinsă on-chain: actualizează la 0.6.1 sau o versiune ulterioară.
Phantom și alte portofele externe
SDK-urile 0.6.0 - 0.7.0 conțineau connectPhantomUnified() și unifiedAccountFromPhantomSignature() (eliminate în 0.8.0; în Python și Rust, unified_account_from_phantom_signature, în Go, unified.UnifiedAccountFromPhantomSignature, în Java, UnifiedAccounts.unifiedAccountFromPhantomSignature), care derivau cheia de cheltuire a unui cont dintr-o semnătură Phantom peste mesajul fix QoreChain unified account derivation v1 urmat de cheia publică base58 a portofelului.
O semnătură ed25519 este deterministă și orice site poate cere portofelului să semneze același mesaj, deci oricine a obținut vreodată semnătura poate reconstrui cheile secp256k1 și ML-DSA ale contului. Ambele funcții aruncă acum o eroare.
Dacă ați creat un cont astfel, considerați-l compromis de acum: creați un cont nou dintr-un mnemonic, mutați fondurile în el și apoi legați Phantom de noul cont ca autentificator (vezi mai jos). Contul vechi nu poate fi reparat, pentru că adresa lui este legată de cheia expusă, iar o rotire a cheii PQC înlocuiește doar jumătatea post-cuantică. Folosiți SDK 0.8.2 sau mai nou.
Ca o cheie Phantom (sau MetaMask) să cheltuiască dintr-un cont, folosiți benzile de autentificator: înregistrați cheia portofelului cu MsgRegisterAuthenticator, apoi cheltuiți din contul PQC canonic prin MsgExecuteCosmos / MsgExecuteEVM sub o permisiune și o SpendingRule. Contul își păstrează propriile chei derivate din mnemonic, iar cheia portofelului poate fi revocată oricând. Vezi Autentificatori și cheltuieli delegate. Rețineți că modulul abstractaccount care oferă aceste benzi este momentan oprit pe ambele rețele (GET /qorechain/abstractaccount/v1/config arată enabled: false), deci o cheie de portofel nu poate fi încă legată; până atunci folosiți direct noul cont din mnemonic.
Criptografie post-cuantică (PQC)
QoreChain suportă semnături ML-DSA-87 (Dilithium-5, FIPS 204). SDK-ul expune primitivele direct.
import {
generatePqcKeypair,
pqcSign,
pqcVerify,
ML_DSA_87_PUBLIC_KEY_LENGTH,
ML_DSA_87_SIGNATURE_LENGTH,
} from "@qorechain/sdk";
const keypair = generatePqcKeypair();
const message = new TextEncoder().encode("hello");
const signature = pqcSign(keypair.secretKey, message);
const ok = pqcVerify(keypair.publicKey, message, signature);
Constantele de lungime exportate (ML_DSA_87_PUBLIC_KEY_LENGTH,
ML_DSA_87_SECRET_KEY_LENGTH, ML_DSA_87_SIGNATURE_LENGTH,
ML_DSA_87_SEED_LENGTH) îți permit să validezi dimensiunile buffer-elor.
Dedesubt, primitivele PQC provin din qorechain-pqc — biblioteca open-source, bazată exclusiv pe standarde, care încapsulează implementări auditate FIPS-204/203/202 în spatele unui API unic și consistent, în șase limbaje (JavaScript/TypeScript, Rust, Go, C, Python, Java). Apelează-o direct atunci când ai nevoie de primitivele brute sau de formatarea
hybridSignBytesîn afara SDK-ului.
Semnatari pluggable
Pentru compoziție, SDK-ul oferă o abstracție Signer, plus implementările
PqcSigner și HybridSigner, și un tip SignatureMode ("classical" | "pqc" | "hybrid"). Folosește-le atunci când vrei să conectezi semnarea PQC în propriul
tău flux, în loc să apelezi direct primitivele.
Semnarea hibridă
O tranzacție hibridă poartă atât o semnătură clasică secp256k1, cât și o
semnătură ML-DSA-87, astfel încât rămâne validă sub verificare clasică, în
timp ce câștigă protecție post-cuantică. Partea post-cuantică călătorește ca
o extensie PQCHybridSignature pe tranzacție.
Începând cu versiunea de chain v3.2.0 (versiunea pe care rulează ambele
rețele), valoarea implicită a rețelei este hybrid_signature_mode = required,
cu allow_classical_fallback = false. Semnarea hibridă prin buildHybridTx /
signAndBroadcastHybrid (transmite rest), sau signHybridEth /
EthNativeSigner pentru conturile unificate eth-native, este obligatorie
pentru tranzacțiile pe calea Native; tranzacțiile Native doar-clasice sunt
respinse on-chain. Tranzacțiile EVM sunt un lane separat, dar începând cu
v3.2.0 sunt admise doar de la un cont cu o cheie ML-DSA-87 înregistrată și cu o
fereastră de autorizare deschisă.
Versiunea 0.6.1 a corectat codificarea extensiei PQCHybridSignature (JSON →
protobuf, critică pentru consens). Tranzacțiile hibride construite cu SDK
0.6.0 sau anterior eșuează la CheckTx cu o eroare de parsare a tranzacției —
actualizează la 0.6.1+.
Constructorul end-to-end pentru un cont coin-type-118 este
signAndBroadcastHybrid (sau buildHybridTx pentru a construi fără a
difuza). Are nevoie de semnatarul clasic, de perechea de chei ML-DSA-87 a
contului, de numărul contului și de sequence, precum și de rest.
SDK 0.8.3 sau mai nou, npm i @cosmjs/stargate:
import { StargateClient } from "@cosmjs/stargate";
import {
deriveNativeAccount,
directSignerFromPrivateKey,
derivePqcCanonical,
signAndBroadcastHybrid,
qorechainRegistry,
MSG_SEND_TYPE_URL,
toBase,
} from "@qorechain/sdk";
const REST = "https://api.qore.host";
const RPC = "https://rpc.qore.host";
const account = await deriveNativeAccount(mnemonic); // coin type 118
const signer = await directSignerFromPrivateKey(account.privateKey, "qor");
const pqcKeypair = derivePqcCanonical(account.address, mnemonic); // the key registered on-chain
const transport = await StargateClient.connect(RPC);
const info = await transport.getSequence(account.address); // { accountNumber, sequence }
const result = await signAndBroadcastHybrid({
registry: qorechainRegistry(),
signer,
pqcKeypair,
messages: [
{
typeUrl: MSG_SEND_TYPE_URL,
value: {
fromAddress: account.address,
toAddress: "qor1recipient…",
amount: [{ denom: "uqor", amount: toBase("1.5") }],
},
},
],
fee: { amount: [{ denom: "uqor", amount: "40000" }], gas: "400000" }, // 0.1 uqor per gas is the floor
chainId: "qorechain-vladi",
accountNumber: info.accountNumber,
sequence: info.sequence,
rest: REST, // lets the SDK pick the v1/v2 PQC sign-bytes form
transport,
});
Condiție prealabilă on-chain
Înainte ca o tranzacție hibridă să poată fi verificată PQC, cheia publică PQC a
semnatarului trebuie să fie înregistrată prin MsgRegisterPQCKeyV2
(semnată clasic, scutită de regula hibridă). O fac
ensurePqcRegistered / buildRegisterPqcKeyMsg din SDK,
qorechaind tx pqc register-key sau un portofel QoreChain.
includePqcPublicKey: true doar încapsulează cheia în extensie; nu o
înregistrează, iar chain-ul nu înregistrează o cheie la prima utilizare.
Ce formă a sign-bytes?
Jumătatea ML-DSA-87 a unei tranzacții hibride este semnată peste una dintre două forme de bytes, iar o rețea o verifică exact pe una la un anumit height:
v1: BE32(len B0) ‖ B0 ‖ BE32(len A) ‖ A
v2: "qorechain-pqc-hybrid-v2" ‖ BE64(len chainId) ‖ chainId ‖ BE32(len B0) ‖ B0 ‖ BE32(len A) ‖ A
B0 este corpul tranzacției fără extensia PQC, iar A sunt bytes-ii
AuthInfo. qorechain-vladi și qorechain-diana verifică ambele v2.
Trecerea a fost înregistrată sub numele de upgrade v3.2.0 pe mainnet și sub
v3.1.98 (apoi v3.2.0) pe testnet, deci un client trebuie să interogheze
GET /cosmos/upgrade/v1beta1/applied_plan/{name} pentru ambele nume și să
folosească v2 dacă oricare dintre height-uri este peste zero. SDK-ul face acest
lucru pentru tine: fiecare constructor hibrid primește signBytesVersion
("auto" implicit, sau "v1" / "v2"), iar "auto" are nevoie de URL-ul REST
al rețelei (rest) pe aceste două chain-uri: aruncă o eroare în loc să
ghicească. Căile de nivel înalt reapelează rezolvarea și reîncearcă o dată
când chain-ul refuză semnătura cu codul pqc 21. În testele unitare,
transmite signBytesVersion explicit, ca testul să nu depindă de un nod live.
Vezi și Depanarea codului 21.
Contractul tranzacției hibride (nivel general)
Tranzacția este semnată clasic peste bytes-ii standard de semnare (care
exclud extensia PQC), iar semnătura ML-DSA-87 este calculată și atașată
ca extensia PQCHybridSignature. Deoarece bytes-ii clasici de semnare exclud
extensia, semnătura clasică rămâne validă indiferent dacă un verificator
înțelege sau nu partea PQC. Funcțiile ajutătoare de nivel inferior
(encodeHybridExtension, attachHybridExtension,
buildHybridSignatureExtension, HYBRID_SIG_TYPE_URL) și constructorii
end-to-end (buildHybridTx, signAndBroadcastHybrid) sunt exportate pentru
utilizare avansată.
Trimiterea tranzacției hibride este calea obligatorie pe rețeaua live pentru tranzacțiile cosmos. Primitivele locale de semnare/verificare și funcțiile ajutătoare de construire a tranzacțiilor sunt disponibile deja astăzi.
Rotația cheii PQC
Începând cu SDK 0.7.0, un cont își poate roti cheia ML-DSA-87 către o cheie
nouă, de același algoritm — migrând canonic o cheie legacy
shake256(mnemonic) către cheia legată de adresă
shake256("qorechain:pqc:v1|addr|mnemonic") — prin
rotatePqcKeyMsgFromMnemonic (ambele chei semnează dual bytes-ii rotației).
Vezi Rotația cheii în ghidul
Autentificatori pentru un exemplu complet.
Identificatori de algoritm
SDK-ul exportă ID-uri de algoritm și funcții ajutătoare pentru lucrul la
nivel de protocol: AlgorithmUnspecified, AlgorithmDilithium5,
AlgorithmMLKEM1024, algorithmName(id) și isSignatureAlgorithm(id).