Cuentas y firma PQC
Las cuentas de QoreChain se derivan de un único mnemónico BIP-39. Existen dos modelos de cuenta, ambos totalmente soportados:
- Derivación HD por carril (legado/por defecto) — el mismo mnemónico produce una cuenta nativa (coin type 118), una EVM (coin type 60) y una SVM (coin type 501) mediante rutas de derivación independientes. Tres claves, tres direcciones.
- Cuentas unificadas eth-native (SDK 0.6.0, cadena v3.1.83) — UNA clave
eth_secp256k1es UNA identidad de 20 bytes representada como las tres codificaciones de dirección, con un único saldo compartido. Consulta Cuentas unificadas.
Derivación HD (legado/por defecto, 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
El mnemónico se valida (palabras y checksum) antes de derivar cualquier
clave, de modo que un error tipográfico lanza una excepción en lugar de producir
silenciosamente una cuenta incorrecta. Puedes validar explícitamente con
validateMnemonic(mnemonic).
Esquemas de derivación
| Tipo | Curva | Ruta | Dirección |
|---|---|---|---|
| native | secp256k1 | m/44'/118'/0'/0/{i} | bech32 qor de 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 de la clave pública de 32 bytes |
Pasa un índice de cuenta para derivar cuentas adicionales. En TypeScript:
const second = await deriveNativeAccount(mnemonic, { accountIndex: 1 });
En Python/Go/Rust el índice es un argumento posicional
(derive_native_account(mnemonic, 1) / DeriveNativeAccount(mnemonic, 1) /
derive_native_account(&mnemonic, 1)).
Nota sobre pruebas de respuesta conocida
Los esquemas de derivación son deterministas y están cubiertos por pruebas de respuesta conocida (known-answer tests) en los cuatro SDK, de modo que el mismo mnemónico produce direcciones idénticas en TypeScript, Python, Go y Rust. Esto te permite derivar en un lenguaje y verificar en otro.
Esta derivación por carril (
deriveNativeAccountcon coin type 118, másderiveEvmAccount/deriveSvmAccount) es el modelo legado/por defecto y sigue soportada sin cambios. Las cuentas unificadas descritas a continuación son un modelo de identidad adicional y opcional (opt-in).
Cuentas unificadas (eth-native)
Desde el SDK 0.6.0 (cadena v3.1.83), deriveUnifiedAccount(mnemonic, index = 0)
deriva UNA clave eth_secp256k1 en la ruta HD de Ethereum m/44'/60'/0'/0/{index}
cuyos 20 bytes de dirección (keccak256(pubkey)[12:]) son la MISMA identidad
representada de tres maneras:
| Carril | Codificación |
|---|---|
| Native | bech32 con el prefijo qor (qor1…) |
| EVM | 0x + hexadecimal con checksum de mayúsculas/minúsculas EIP-55 |
| SVM | base58 de los 20 bytes rellenados a la derecha con 12 bytes cero (32 bytes) |
Un depósito a cualquiera de las tres llega a un solo saldo, y la clave gasta en todos los carriles:
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; // 33-byte compressed secp256k1 public key
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) hace lo mismo a partir de una clave privada
secp256k1 en bruto de 32 bytes.
La derivación de la semilla PQC
El par de claves ML-DSA-87 de la cuenta se deriva de forma determinista y vinculada a la dirección:
pqcSeed = shake256("qorechain:pqc:v1|" + cosmosAddress + "|" + mnemonic, 32)
de modo que es recuperable a partir de { address, mnemonic } e idéntico en
todos los SDK de lenguaje de QoreChain. (Para unifiedAccountFromSeed, el
espacio del mnemónico es "seed:" + hex(seed32).)
Enviar por el carril Native con la clave eth
Una cuenta unificada firma las transacciones de la ruta Native con el esquema
eth_secp256k1: la firma clásica es secp256k1 sobre keccak256 de los bytes
del SignDoc (no sha256), y la clave pública del SignerInfo usa la URL de tipo
/cosmos.evm.crypto.v1.ethsecp256k1.PubKey. La ruta híbrida
(signHybridEth) adjunta adicionalmente la extensión PQCHybridSignature
ML-DSA-87 — obligatoria en las redes en producción:
import { EthNativeSigner, deriveUnifiedAccount } from "@qorechain/sdk";
const account = await deriveUnifiedAccount(mnemonic);
const signer = new EthNativeSigner(account); // signMode: "hybrid" by default
// `transport` is anything with broadcastTx (e.g. a connected client).
await signer.bankSend(
transport,
"qor1recipient…",
[{ denom: "uqor", amount: "1000000" }], // 1 QOR
{ chainId: "qorechain-vladi", accountNumber, sequence, fee },
);
Para un control de más bajo nivel, signHybridEth(params) /
signClassicalEth(params) devuelven los bytes TxRaw ensamblados y los
artefactos de firma, y accountAuthInfo(baseAccount) lee account_number /
sequence de una cuenta cuya clave pública on-chain usa la URL de tipo
eth_secp256k1. La ruta solo clásica es para el mensaje único
MsgRegisterPQCKeyV2, exento del arranque (bootstrap); usa la híbrida para todo
lo demás.
El SDK 0.6.1 corrigió un error de codificación crítico para el consenso: la
extensión del cuerpo de transacción /qorechain.pqc.v1.PQCHybridSignature se
serializaba en JSON dentro de Any.value, y la cadena rechazaba esas
transacciones en CheckTx (un error de análisis de la tx). Ahora se codifica en
protobuf (el valor de la extensión comienza con 0x08) en los cinco lenguajes.
Cualquier transacción híbrida — incluido el carril eth-native — construida con
el SDK ≤ 0.6.0 es rechazada on-chain: actualiza a 0.6.1 o posterior.
Phantom (P1a): una cuenta unificada sin exportar una clave
connectPhantomUnified() (TypeScript) deriva una cuenta unificada canónica y
no custodial a partir de una firma determinista de Phantom: el usuario firma
un mensaje fijo y separado por dominio con la clave ed25519 de Phantom, y
shake256(signature, 32) siembra la cuenta.
import {
connectPhantomUnified,
unifiedAccountFromPhantomSignature,
} from "@qorechain/sdk";
// In the browser (uses window.solana):
const account = await connectPhantomUnified();
// Or, given a raw signature you already have:
const same = unifiedAccountFromPhantomSignature(signatureBytes);
La cuenta derivada es una clave canónica separada de la clave ed25519 de Phantom — Phantom nunca ve los secretos secp256k1/PQC derivados. Para permitir que la propia clave de Phantom gaste desde la cuenta bajo límites, consulta Autenticadores y gasto delegado.
Criptografía post-cuántica (PQC)
QoreChain soporta firmas ML-DSA-87 (Dilithium-5, FIPS 204). El SDK expone las primitivas directamente.
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);
Las constantes de longitud exportadas (ML_DSA_87_PUBLIC_KEY_LENGTH,
ML_DSA_87_SECRET_KEY_LENGTH, ML_DSA_87_SIGNATURE_LENGTH,
ML_DSA_87_SEED_LENGTH) te permiten validar los tamaños de los búferes.
Por debajo, las primitivas PQC provienen de qorechain-pqc — la biblioteca de código abierto y basada únicamente en estándares que envuelve implementaciones auditadas de FIPS-204/203/202 tras una API consistente en seis lenguajes (JavaScript/TypeScript, Rust, Go, C, Python, Java). Recurre a ella directamente cuando necesites las primitivas en bruto o el framing
hybridSignBytesfuera del SDK.
Firmantes conectables (pluggable)
Para la composición, el SDK proporciona una abstracción Signer junto con las
implementaciones PqcSigner y HybridSigner, y un enum SignatureMode. Úsalos
cuando quieras integrar la firma PQC en tu propio flujo en lugar de llamar a las
primitivas directamente.
Firma híbrida
Una transacción híbrida lleva tanto una firma clásica secp256k1 como una
firma ML-DSA-87, de modo que sigue siendo válida bajo verificación clásica a la
vez que gana protección post-cuántica. La parte post-cuántica viaja como una
extensión PQCHybridSignature en la transacción.
A partir de la versión actual de la cadena (v3.1.85), el valor por defecto
de la red es hybrid_signature_mode = required con
allow_classical_fallback = false. La firma híbrida mediante buildHybridTx
(con includePqcPublicKey) — o signHybridEth para cuentas unificadas
eth-native — es obligatoria para las transacciones de la ruta Native; las
transacciones Native solo clásicas se rechazan on-chain. Las transacciones EVM
usan una ruta eth_secp256k1 separada y no se ven afectadas.
La versión 0.6.1 corrigió la codificación de la extensión PQCHybridSignature
(JSON → protobuf, crítico para el consenso). Las transacciones híbridas
construidas con el SDK 0.6.0 o anterior fallan en CheckTx con un error de
análisis de la tx — actualiza a 0.6.1+.
import {
buildHybridTx,
deriveNativeAccount,
directSignerFromPrivateKey,
} from "@qorechain/sdk";
const account = await deriveNativeAccount(mnemonic);
const signer = await directSignerFromPrivateKey(account.privateKey, "qor");
// buildHybridTx assembles a tx with BOTH a classical signature and an
// ML-DSA-87 signature attached as a PQCHybridSignature extension.
// (See packages/ts and the pqc-hybrid-sign example for the full call.)
Requisito previo on-chain
Antes de que una transacción híbrida pase la verificación PQC on-chain, la clave
pública PQC del firmante debe estar registrada mediante el mensaje
MsgRegisterPQCKey de la cadena — a menos que establezcas
includePqcPublicKey: true, que incrusta la clave en la extensión para que la
cadena pueda registrarla automáticamente en el primer uso.
Contrato de la tx híbrida (alto nivel)
La transacción se firma de forma clásica sobre los bytes de firma estándar (que
excluyen la extensión PQC), y la firma ML-DSA-87 se calcula y se adjunta
como la extensión PQCHybridSignature. Como los bytes de firma clásicos
excluyen la extensión, la firma clásica sigue siendo válida tanto si un
verificador entiende la parte PQC como si no. Los helpers de más bajo nivel
(encodeHybridExtension, attachHybridExtension,
buildHybridSignatureExtension, HYBRID_SIG_TYPE_URL) y los constructores de
extremo a extremo (buildHybridTx, signAndBroadcastHybrid) se exportan para
uso avanzado.
El envío de transacciones híbridas es la ruta obligatoria en la red en producción para las transacciones cosmos. Las primitivas locales de firma/verificación y los helpers de construcción de tx están disponibles hoy.
Rotación de claves PQC
Desde el SDK 0.7.0, una cuenta puede rotar su clave ML-DSA-87 a una nueva clave
del mismo algoritmo — migrando canónicamente una clave legada
shake256(mnemonic) a la clave vinculada a la dirección
shake256("qorechain:pqc:v1|addr|mnemonic") — mediante
rotatePqcKeyMsgFromMnemonic (ambas claves firman conjuntamente los bytes de
rotación). Consulta
Rotación de claves en la guía de
Autenticadores para un ejemplo completo.
Identificadores de algoritmo
El SDK exporta IDs de algoritmo y helpers para trabajo a nivel de protocolo:
AlgorithmUnspecified, AlgorithmDilithium5, AlgorithmMLKEM1024,
algorithmName(id) e isSignatureAlgorithm(id).