Rand Protocol
ドキュメント
基本概念

アカウントとアドレス

チェーン上の2種類の鍵、シールドされた rand1 アドレスの導出方法、viewing key が開示する内容、そして chain 11 のショートアドレスが元に戻された理由を説明します。

この文書は、用途に合った鍵を生成し、アドレスを解析・検証し、鍵を渡すと何が開示されるかを把握するためのものです。このチェーンにアカウントはありません。残高はあくまで鍵ファイルに関する事実であり、チェーンに関する事実ではありません。

2種類の鍵、2種類のアドレス

ノードキーウォレットキー
生成コマンドrand-node keygenrand keygen
秘密情報32バイトのシード256ビットのスペンドキー
公開される識別子32バイトのアドレス(base58、32〜44文字)シールドアドレス(rand1…、1,665文字または1,666文字)
署名の対象ブロック、投票、faucet のミント、Unbond、Withdraw、Registration、chain 20 では AdmitValidator の投票。トークンの Key ミント権限者としては TokenMint と SetAuthority。chain 20 では、これらのメッセージはいずれもジェネシスハッシュも対象に含めます(Transactions)なし。転送は証明によって承認されます
チェーン上での現れ方ブロックのプロポーザ、バリデータのレジスタ行、faucet ミントのミンター、トークンの key 権限者転送には現れず、エンベロープの内部で秘匿されます。公開額を伴って価値がプールに入る場面では公開されます。ブリッジのデposit、トークンのミント、chain 20 では invoke の支払いが受取人のアドレスを指定します。バリデータの支払先アドレスはレジスタに記録され、chain 20 のジェネシスにはブリッジの手数料トレジャリーが記されます。chain 14 以降は、faucet のミントとジェネシスの alloc ノートが所有者の pk(アドレスの先頭32バイト)を公開します
ファイル{ "seed", "address", "public_key" }、パーミッション 0600{ "version": 2, "spend_key": "<64 hex>" }、パーミッション 0600

この2つが混ざることはありません。バリデータは前者で識別されます。同じ Dilithium2 の鍵形式は、トークンの Key ミント権限者(rand token create --authority-key-out は rand-node keygen と同じ形式のファイルを書き出します)、ブリッジの停止キー、および6つのポスト量子ガーディアン鍵にも使われます。ガーディアンのアテステーション鍵は secp256k1 で、Ethereum 形式の20バイトアドレスを持ちます(Bridge)。価値は後者の鍵のもとで保持されます。

透過アドレス

ノードキーは32バイトのシードで、そこから Dilithium2 の鍵ペアが決定論的に導出されます。公開鍵は1,312バイト、署名は2,420バイトです。アドレスは公開鍵のドメイン分離された BLAKE3 ハッシュで、base58 で表現されます。

address = blake3("rand-address" || public_key)      // 32 bytes, shown as base58

署名されるオブジェクト(投票、ブロック、ミント、ステーキング操作)は公開鍵の全体を伴うため、検証者は生のアドレスをそのまま信頼するのではなく、blake3("rand-address" || public_key) == address を確認します。同じシードからは、ノードの libp2p アイデンティティも得られます。これは blake3("rand-p2p-identity" || seed) から作られる ed25519 鍵で、ブートストラップ multiaddr の /p2p/ の後ろに置かれるピア ID に相当します。

rand-node keygen --out node.key.json      # prints the address
rand-node address --key node.key.json     # address, public key (hex), libp2p peer id

例: EdrEC2QRQrQF64iRXxF9927jNj6RidiHHByzx5xD6F1e は chain 20 のバリデータの1つです。chain 15 から chain 20 までは、chain 14 で更新された18個の鍵を再利用するため、それらのアドレスとピア ID は変わりません。また、chain 13 のバリデータのアドレスやピア ID は、どれも使われていません。chain 15 でさらに8つのバリデータがボンドされ、chain 16 以降は26個すべてがジェネシスファイルに含まれています。

シールドアドレス

ウォレットは256ビットのスペンドキー sk というただ1つの秘密情報でできており、それ以外はすべてそこからの導出です。導出には Poseidon2 を用い、最初に吸収するワードとしてドメインタグを置きます(H(domain, msg))。これは zkVM が回路内で計算するのと同じ関数です。

spend key   sk                          256 bits, in wallet.key.json
   │
   ├─ viewing key   nk = H(NK, sk)      sees the whole history; cannot spend
   │     ├─ pk = H(PK, nk)              the field a note names its owner by
   │     ├─ nullifier(cm) = H(NF, nk, cm)   what a spend publishes
   │     ├─ ovk                         opens envelopes this wallet sent
   │     └─ ML-KEM-768 decapsulation key    opens envelopes sent to this wallet
   └─ address   rand1 + base58(pk || kem_ek)

アドレスは、pk の32バイトに1,184バイトの ML-KEM-768 カプセル化鍵が続いたもので、rand1 プレフィックスの後ろに base58 で表現されます。長さは1,665文字または1,666文字です(base58 での長さは先頭のバイトに依存し、14個に1個ほどが短いほうになります)。サイズは約1.6 KBで、チェックサムはありません。送信者はノートのエンベロープを封印するためにカプセル化鍵を必要とするので、それがアドレスに含まれています。このアドレスは設計上公開されています。誰でもこのアドレスに支払うことができ、アドレスを知っていても、そこに何が保持されているかは分かりません。

rand keygen                      # wallet.key.json; refuses to overwrite an existing file
rand address                     # rand1… on stdout, its fingerprint XXXX-XXXX-XXXX-XXXX on stderr

v0.5.10 以降、rand address は標準出力にアドレスと改行だけを出力するため、スクリプトでは $(rand address) が使えます。フィンガープリントは標準エラー出力に出ます。

解析は ShieldedAddress::parse と全く同じ手順です。rand1 を取り除き、base58 をデコードし、長さがちょうど 32 + 1,184 = 1,216 バイトであることを要求します。先頭の32バイトは8個のリトルエンディアン u32 ワードとしての pk、残りがカプセル化鍵です。RPC は、2,000文字より長いパラメータを解析する前に拒否します。

鍵ファイルの隣には、ウォレットが <key>.key.json.notes.json を保持します。これはこの鍵が開いたノートのキャッシュです。ノートの平文を含むため、鍵と同じくパーミッション 0600 で書き込まれます。その各行は、リーフ0からスキャンし直せば復元できます。

アドレスの共有: フィンガープリントと randpay: リンク

アドレスは長すぎて読み上げたり目で比較したりできず、チェックサムもありません。そのため v0.5.10 以降、すべてのウォレットはアドレスの横にフィンガープリントを表示します。フィンガープリントは表示専用で、チェーン上にもワイヤ上にも載りません。

fingerprint = first 80 bits of blake3("rand-address-fingerprint-1" || pk_bytes || kem_ek)
            = 16 Crockford base32 characters, shown as XXXX-XXXX-XXXX-XXXX

ハッシュは、アドレスがエンコードしている1,216バイトの生データに対して計算されます。文字集合は 0123456789ABCDEFGHJKMNPQRSTVWXYZ です。解析では大文字小文字とハイフンを無視し、O は 0 として、I と L は 1 として読みます。

randpay: リンクは、アドレスと、任意でその支払い内容を運びます。

randpay:<rand1…>[?amount=<decimal>][&asset=<index | 64 hex | rpl1…>][&memo=<percent-encoded UTF-8>]

amount はアセットの表示単位で指定し(RAND は小数点以下9桁の10進数、トークンはそのレジストリ行自身の decimals)、ゼロは認められません。memo は UTF-8 で最大510バイトです。スキームは大文字小文字を区別せずに照合されます。未知のパラメータ、重複したパラメータ、不正なパーセントエンコーディングは拒否されます。

rand address --uri --amount 1.5 --memo "invoice #42" --qr   # a randpay: link, as a terminal QR
rand contacts add alice rand1q9f…                            # shows the fingerprint, asks add? [y/N]
rand send alice 1.5 --memo "coffee"                          # to a contact, with a memo

rand send <TO> は、rand1… アドレス、randpay: リンク、保存済みの連絡先名のいずれかを受け取り、この順で試します。リンクの amount、asset、memo は、コマンドラインで省略された項目を補います。両方で指定されて値が食い違う場合は拒否されます。証明を作る前に、send は to <contact, if any> · fingerprint XXXX-XXXX-XXXX-XXXX · <amount> <asset> を表示して確認を求めます。スクリプトでは --yes を渡します。連絡先は鍵の隣に <key>.contacts.json として、パーミッション 0600 で保存されます。メモはノートのエンベロープの内部で運ばれます(Notes)。

rand balance <address> というコマンドも、残高を返す RPC もありません。チェーンは誰に対してもそれに答えられないからです。ウォレットは、ビューイングキーでチェーン上のすべてのエンベロープをスキャンして残高を計算します(Notes)。

他者に渡せる鍵

開示は鍵の性質であり、チェーンがそれを強制することはできません。

鍵開けるもの粒度
ビューイングキー nk(64 hex)ウォレットがこれまでに受け取ったすべてのノートと、ovk を通じて、これまでに送ったすべてのノート、および各支出のナリファイアすべてか無か、過去にも将来にも及ぶ
トランザクションキー TxKey(32バイト)それが封印したちょうど1つのエンベロープ。ノートと、chain 18 以降はそのメモ1回の支払い
コールキー K_call1回のコンフィデンシャルコールの秘匿入力トランスクリプト1回のコール

ビューイングキーでは支出できません。chain 17 以降(分割承認)は、バンドル証明そのものが nk から作られ、すべてのバンドルは、スペンドキーだけが作れて1つのトランザクションに束縛される、小さな2つ目の認可証明も伴います(Notes)。これにより、委任された prover に nk を渡して、つまりウォレットの履歴全体を見せても、支出はさせずに済みます。認可証明は、常にウォレット自身のマシンで作られます。この分割によって、アドレス、鍵、既存のノートは変わりません。ウォレットは実行のたびに nk を導出し、保存することはありません。TxKey はエンベロープごとに新しく生成され、封印後に破棄されます。ノードには rand_importViewingKey でビューイングキーを渡せます(メモリ上のみ、ノードあたり最大64個、再起動で消えます)。これにより、ノードが保有者に代わってスキャンします。これがエクスプローラの経路です。また、rand_checkTransaction(hash, key) は、ステートレスな1回の呼び出しで、1件の支払いを TxKey に照らして確認します。rand viewing-key は nk を出力し、rand tx-key <hash> は、このウォレットが開けるトランザクションの各出力のキーを出力します。

トークン ID はアドレスではない

RPL トークンには、3つ目の種類のテキスト識別子があります。これはトークンを指すもので、保有者を指すことはありません。トークンの32バイトのアセット ID は、64文字の16進数と rpl1… として表示されます。これは bech32m(BIP-350)で、human-readable part は rpl、32個の ID バイトに対して52文字のデータ部と6文字のチェックサムを持ち、62文字です。解析は厳格です。チェックサムの誤り、別の human-readable part、ちょうど32バイトではないペイロード、大文字小文字の混在は拒否されるため、タイプミスが別のトークンに解決されることはありません。ノートは ID を持たず、その asset ワードがトークンの密なレジストリインデックスです(RPL tokens)。

ブリッジの受取人

ブリッジのワイヤフォーマットには32バイトの受取人スロットがありますが、シールドアドレスはそこに収まりません。現在は、このスロットにアドレスのハッシュを入れています。

recipient_hash = blake3("rand-shielded-recipient" || pk_bytes || kem_ek)

ソースチェーンの預入者がハッシュを指定し、ガーディアンがそれに署名し、BridgeAttest トランザクションが、台帳がそのハッシュと照合できるよう、アドレスの全体を運びます(Bridge)。

短いシールドアドレス: chain 11 で試行し、元に戻した

Chain 11(fullnode タグ v0.2、2026-09-17)は、長いアドレスを 53〜55 文字のレシーバー ID、すなわち rand1 + base58(blake3(receiver signing key) ‖ checksum) に置き換え、ML-KEM カプセル化鍵を署名付きのレシーバーレコードに移しました。送信者はこのレコードをエクスプローラーのレジストリから取得するか、支払いリクエストに含まれる形で受け取りました。この仕組みは 1 日稼働し、同じ日に元に戻されました(fullnode 17db41d)。chain 12 は上で説明したとおりの長いアドレスに戻り、chain 20 を含むそれ以降のすべての chain がそれを維持しています。

撤回した理由。レシーバー ID はハッシュであり、送信者はハッシュに対して note を封印できません。一度も登録したことのないウォレットへの最初の支払いでは、受信者が支払いリクエストかレコードファイルを渡す必要がありました。しかも登録自体が自己送金で支払われるため、空の新規ウォレットには行えませんでした。さらに、どちらの受け渡し経路でも、これから誰が支払いを受けるのかがレジストリに知られてしまいました。短いアドレス、無条件の送金可能性、暗号学的なプライバシーを両立できる設計は、ハイブリッドアドレスです。32 バイトの X25519 鍵をアドレスに含め(約 93 文字)、ポスト量子鍵は検索で取得します。未登録のウォレットへの最初の支払いは古典的な攻撃者に対して秘匿され、それ以降の支払いはすべて完全にポスト量子耐性を持ちます。これについては次に仕様を示します。それが提供されるまでは、長いアドレスが正式なアドレスです。

chain 11 の仕様とその実装は、ハイブリッド設計が再利用する部分のために fullnode の履歴に残されています(docs/superpowers/specs/2026-09-17-short-shielded-address.md、タグ v0.2)。再利用するのは、導出されたレシーバー署名鍵、署名付きレコードとその検証器、そしてチェーン状態としてのレジストリです。

fullnode のコミット c0878c3f(チェーン 21)に対して 2026-10-12 に最終確認しました。 コードとこのページが食い違う場合は、コードが正です。お知らせください。