メインコンテンツまでスキップ

アカウントとPQC署名

QoreChainのアカウントは、単一のBIP-39ニーモニックから導出されます。アカウントモデルには2種類あり、どちらも完全にサポートされています。

  • レーンごとのHD導出(レガシー/デフォルト) — 同じニーモニックから、ネイティブ(コインタイプ118)、EVM(コインタイプ60)、SVM(コインタイプ501)の各アカウントが、それぞれ独立した導出パスを介して生成されます。3つの鍵、3つのアドレスです。
  • 統一eth-nativeアカウント(SDK 0.6.0、チェーンv3.1.83) — 1つのeth_secp256k1鍵が、1つの20バイトのアイデンティティとして3種類すべてのアドレスエンコーディングで表現され、残高も1つに共有されます。統一アカウントを参照してください。

HD導出(レガシー/デフォルト、コインタイプ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

ニーモニックは、鍵が導出される前に(単語とチェックサムの両方が)検証されるため、タイプミスがあった場合は誤ったアカウントを黙って生成するのではなくエラーになります。validateMnemonic(mnemonic)で明示的に検証することもできます。

導出方式​

タイプ曲線パスアドレス
nativesecp256k1m/44'/118'/0'/0/{i}ripemd160(sha256(pubkey))のbech32(qor)
evmsecp256k1m/44'/60'/0'/0/{i}0x + keccak256(pubkey)[-20:]、EIP-55
svmed25519m/44'/501'/{i}'/0'32バイト公開鍵のbase58

アカウントインデックスを渡すことで、追加のアカウントを導出できます。TypeScriptの場合。

const second = await deriveNativeAccount(mnemonic, { accountIndex: 1 });

Python/Go/Rustでは、インデックスは位置引数です (derive_native_account(mnemonic, 1) / DeriveNativeAccount(mnemonic, 1) / derive_native_account(&mnemonic, 1))。

既知回答テストに関する注記​

これらの導出方式は決定論的であり、4つすべてのSDKにわたる既知回答テスト(known-answer tests)でカバーされているため、同じニーモニックはTypeScript、Python、Go、Rustのいずれでも同一のアドレスを生成します。これにより、ある言語で導出し、別の言語で検証することができます。

このレーンごとの導出(コインタイプ118でのderiveNativeAccount、および deriveEvmAccount / deriveSvmAccount)はレガシー/デフォルトモデルであり、 引き続きサポートされ、変更はありません。以下の統一アカウントは追加の、 オプトインのアイデンティティモデルです。

統一アカウント(eth-native)​

SDK 0.6.0(チェーンv3.1.83)以降、deriveUnifiedAccount(mnemonic, index = 0)は、 EthereumのHDパスm/44'/60'/0'/0/{index}上に1つのeth_secp256k1鍵を導出し、 その20バイトのアドレス(keccak256(pubkey)[12:])は同一のアイデンティティを 3通りの方法で表現します。

レーンエンコーディング
Nativeqorプレフィックスのbech32(qor1…)
EVM0x + EIP-55混在ケースチェックサム16進
SVM20バイトを12個のゼロバイトで右パディングした(32バイト)base58

3つのレーンのいずれかへの入金は1つの残高に反映され、その1つの鍵がすべての レーンでの署名者になります(EVMレーンでは認可ウィンドウの 内側で。SVMのトランザクションレーンは現在ネットワーク全体で無効化されているため、 SVMは現時点では読み取り専用です)。

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)は、生の32バイトsecp256k1秘密鍵から同じ処理を行います。

PQCシード導出​

アカウントのML-DSA-87鍵ペアは決定論的に導出され、アドレスに紐付けられます。

pqcSeed = shake256("qorechain:pqc:v1|" + cosmosAddress + "|" + mnemonic, 32)

これにより{ address, mnemonic }から復元可能であり、QoreChainの全言語SDKで同一です。 (unifiedAccountFromSeedの場合、ニーモニックの位置には"seed:" + hex(seed32)が入ります。) SDK 0.8.3以降、derivePqcCanonical(account, mnemonic)は、まずaccountを小文字の qor1…形式(canonicalAccount)に解決するため、大文字のbech32や同じアカウントの 0x形式でも同じ鍵が得られます。

eth鍵によるNativeレーンでの送金​

統一アカウントは、eth_secp256k1方式でNativeパスのトランザクションに署名します。 古典的な署名は、SignDocバイト列の(sha256ではなく)keccak256に対するsecp256k1署名であり、 SignerInfoの公開鍵は型URL/cosmos.evm.crypto.v1.ethsecp256k1.PubKeyを使用します。 ハイブリッドパス(signHybridEth、またはデフォルトのsignMode: "hybrid"を持つEthNativeSigner)は さらに、ML-DSA-87のPQCHybridSignature拡張を付加します。これは稼働中のネットワークでは 必須です。

最初のハイブリッド送金の前に、2つのことが必要です。1つ目は、アカウントのML-DSA-87鍵を (1回だけ)登録しておくことです。登録は、古典署名で行われるハイブリッド免除の唯一の トランザクションです。signMode: "classical"で署名してください。アカウントのpublicKeyは 0xで始まる16進文字列なので、ecdsaPubkey用にバイト列へデコードします。2つ目は、署名者に restを渡して、ネットワークがどの署名対象バイト列の形式を検証するかを 問い合わせられるようにすることです(qorechain-vladiとqorechain-dianaでは、推測せずに 例外を投げます)。SDK 0.8.1以降と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);

より低レベルの制御が必要な場合、signHybridEth(params) / signClassicalEth(params)は 組み立て済みのTxRawバイト列と署名アーティファクトを返し、accountAuthInfo(baseAccount)は オンチェーンの公開鍵がeth_secp256k1型URLを使用しているprotobufのBaseAccountから account_number / sequenceを読み取ります(REST JSONのアカウントは、上のauthInfoのように、 先にフィールドをマッピングする必要があります)。

ハイブリッドトランザクションにはSDK 0.6.1以降が必要です

SDK 0.6.1は、コンセンサスに関わる重大なエンコーディングのバグを修正しました。 /qorechain.pqc.v1.PQCHybridSignatureのtx-body拡張がJSONとしてAny.valueに シリアライズされており、チェーンはCheckTxの段階でそれらのトランザクションを 拒否していました(txパースエラー)。現在は5つすべての言語でprotobufエンコーディング (拡張値は0x08で始まる)になっています。SDK 0.6.0以前でビルドされたハイブリッド トランザクション(eth-nativeレーンを含む)はすべてオンチェーンで拒否されます。 0.6.1以降にアップグレードしてください。

Phantom とその他の外部ウォレット​

SDK 0.8.0 で削除

SDK 0.6.0 から 0.7.0 には connectPhantomUnified() と unifiedAccountFromPhantomSignature()(0.8.0 で削除。Python と Rust: unified_account_from_phantom_signature、Go: unified.UnifiedAccountFromPhantomSignature、Java: UnifiedAccounts.unifiedAccountFromPhantomSignature)が含まれていました。これらは、固定メッセージ QoreChain unified account derivation v1 とウォレットの base58 公開鍵に対する Phantom の署名から、アカウントの支払い鍵を導出していました。 ed25519 署名は決定論的であり、どの Web サイトでもウォレットに同じメッセージへの署名を求めることができます。そのため、その署名を一度でも入手した者は、アカウントの secp256k1 鍵と ML-DSA 鍵を再構築できます。両関数は現在、例外を投げます。

この方法でアカウントを作成した場合は、ただちに侵害されたものとして扱ってください。新しいニーモニックのアカウントを作成して資金を移し、その後 Phantom を新しいアカウントにオーセンティケーターとしてリンクしてください(下記)。古いアカウントは修復できません。アドレスが漏洩した鍵に結び付いており、PQC 鍵のローテーションはポスト量子の部分しか置き換えないためです。SDK 0.8.2 以降を使用してください。

Phantom(または MetaMask)の鍵にアカウントから支払わせるには、オーセンティケーターレーンを使います。ウォレットの鍵を MsgRegisterAuthenticator で登録し、権限と SpendingRule の範囲内で MsgExecuteCosmos / MsgExecuteEVM を通じて正規の PQC アカウントから支払います。アカウントはニーモニックから導出した自身の鍵を保持し、ウォレットの鍵はいつでも取り消せます。オーセンティケーターと委任支払いを参照してください。 なお、これらのレーンを提供する abstractaccount モジュールは現在両ネットワークで無効になっているため(GET /qorechain/abstractaccount/v1/config は enabled: false を返します)、ウォレットの鍵はまだリンクできません。それまでは新しいニーモニックのアカウントを直接使用してください。

ポスト量子暗号(PQC)​

QoreChainはML-DSA-87(Dilithium-5、FIPS 204)署名をサポートしています。SDKは このプリミティブを直接公開しています。

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);

エクスポートされている長さ定数(ML_DSA_87_PUBLIC_KEY_LENGTH、 ML_DSA_87_SECRET_KEY_LENGTH、ML_DSA_87_SIGNATURE_LENGTH、 ML_DSA_87_SEED_LENGTH)を使うと、バッファサイズを検証できます。

内部的には、PQCプリミティブはqorechain-pqc — 監査済みのFIPS-204/203/202実装を6言語(JavaScript/TypeScript、Rust、Go、C、Python、Java)にわたり一貫したAPIでラップした、オープンソースかつ標準準拠のライブラリ — に由来します。SDKの外でプリミティブそのものやhybridSignBytesのフレーミングが必要な場合は、これを直接利用してください。

プラガブルな署名者(Signer)​

構成のために、SDKはSigner抽象化と、それを実装したPqcSignerおよび HybridSigner、そしてSignatureMode型("classical" | "pqc" | "hybrid")を 提供します。プリミティブを直接呼び出すのではなく、PQC署名を独自のフローに 組み込みたい場合はこれらを使用してください。

ハイブリッド署名​

ハイブリッドトランザクションは、古典的なsecp256k1署名とML-DSA-87署名の 両方を持つため、古典的な検証の下でも有効なまま、ポスト量子保護を得られます。 ポスト量子部分は、トランザクションのPQCHybridSignature拡張として運ばれます。

Nativeパスではハイブリッド署名が必須です

チェーンバージョンv3.2.0(両ネットワークが稼働しているバージョン)の時点で、 ネットワークのデフォルトはhybrid_signature_mode = required、 allow_classical_fallback = falseです。buildHybridTx / signAndBroadcastHybrid (restを渡す)によるハイブリッド署名 — または統一eth-nativeアカウント向けの signHybridEth / EthNativeSigner — は、Nativeパスのトランザクションで必須です。 古典署名のみのNativeトランザクションはオンチェーンで拒否されます。EVMトランザクションは 別のレーンですが、v3.2.0以降は、ML-DSA-87鍵が登録済みで、かつ 認可ウィンドウが開いているアカウントからのみ受け付けられます。

SDK 0.6.0以下のハイブリッドトランザクションは拒否されます

0.6.1リリースは、PQCHybridSignature拡張のエンコーディング(JSON→protobuf、 コンセンサスに関わる重大な変更)を修正しました。SDK 0.6.0以前でビルドされた ハイブリッドトランザクションは、CheckTxの段階でtxパースエラーとして失敗します — 0.6.1以降にアップグレードしてください。

コインタイプ118のアカウント向けのエンドツーエンドのビルダーはsignAndBroadcastHybrid (ブロードキャストせずに構築のみ行う場合はbuildHybridTx)です。これには、古典署名者、 アカウントのML-DSA-87鍵ペア、アカウント番号とシーケンス、そしてrestが必要です。 SDK 0.8.3以降、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,
});

オンチェーンの前提条件​

ハイブリッドトランザクションがPQC検証されるためには、あらかじめ署名者のPQC公開鍵を MsgRegisterPQCKeyV2(古典署名で行い、ハイブリッド免除)で登録しておく必要があります。 SDKのensurePqcRegistered / buildRegisterPqcKeyMsg、qorechaind tx pqc register-key、またはQoreChainウォレットがこれを行います。includePqcPublicKey: trueは 鍵を拡張に埋め込むだけで、鍵を登録するものではありません。チェーンも初回使用時に 鍵を登録することはありません。

どの署名対象バイト列形式か?​

ハイブリッドトランザクションのML-DSA-87部分は、2つのバイト列形式のいずれかに対して 署名され、ネットワークはどの高さでもちょうど1つだけを検証します。

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はPQC拡張を除いたトランザクションボディ、AはAuthInfoのバイト列です。 qorechain-vladiとqorechain-dianaはどちらもv2を検証します。この切り替えは、 メインネットではアップグレード名v3.2.0で、テストネットではv3.1.98(その後v3.2.0)で 記録されているため、クライアントは両方の名前について GET /cosmos/upgrade/v1beta1/applied_plan/{name}を問い合わせ、どちらかの高さが0より 大きければv2を使う必要があります。SDKはこれを自動で行います。すべてのハイブリッド ビルダーはsignBytesVersion(デフォルトは"auto"、または"v1" / "v2")を受け取り、 この2つのチェーンでは"auto"にネットワークのREST URL(rest)が必要で、推測せずに 例外を投げます。高レベルのパスは、チェーンがpqcコード21で署名を拒否した場合に、 再解決して1回だけ再試行します。ユニットテストでは、テストが稼働中のノードに依存しない よう、signBytesVersionを明示的に渡してください。 コード21のトラブルシューティングも 参照してください。

ハイブリッドtxの契約(概要)​

トランザクションは、標準の(PQC拡張を含まない)署名対象バイト列に対して古典的に 署名され、ML-DSA-87署名が計算されてPQCHybridSignature拡張として付加されます。 古典署名対象バイト列が拡張を含まないため、検証者がPQC部分を理解するかどうかに かかわらず、古典署名は有効なままです。低レベルのヘルパー(encodeHybridExtension、 attachHybridExtension、buildHybridSignatureExtension、HYBRID_SIG_TYPE_URL)と、 エンドツーエンドのビルダー(buildHybridTx、signAndBroadcastHybrid)がエクスポート されており、高度な用途に利用できます。

ハイブリッドトランザクションの送信は、cosmosトランザクションについて稼働中のネットワークで 必須のパスです。ローカルでの署名/検証プリミティブとtx構築ヘルパーは、現時点で 利用可能です。

PQC鍵ローテーション​

SDK 0.7.0以降、アカウントはML-DSA-87鍵を同一アルゴリズムの新しい鍵に ローテーションできます — レガシーなshake256(mnemonic)鍵から、アドレスに 紐付いたshake256("qorechain:pqc:v1|addr|mnemonic")鍵へと正規に移行するもので、 rotatePqcKeyMsgFromMnemonicを介して行います(両方の鍵がローテーションバイト列に 二重署名します)。完全な例については、Authenticatorsガイドの 鍵ローテーションを参照してください。

アルゴリズム識別子​

SDKは、プロトコルレベルの作業向けにアルゴリズムIDとヘルパーをエクスポートしています。 AlgorithmUnspecified、AlgorithmDilithium5、AlgorithmMLKEM1024、 algorithmName(id)、isSignatureAlgorithm(id)です。