Rand Protocol
文档
核心概念

账户与地址

链上的两类密钥、屏蔽 rand1 地址的派生方式、查看密钥会披露哪些信息,以及链 11 的短地址为何被回退。

使用本页可以为具体用途生成正确的密钥,解析或校验地址,并了解把一个密钥交给他人会披露什么。本链上没有账户:余额是关于密钥文件的事实,而不是关于链的事实。

两种密钥,两类地址

节点密钥钱包密钥
生成命令rand-node keygenrand keygen
秘密32 字节种子256 位花费密钥
公开身份32 字节地址,base58 编码,32 至 44 个字符屏蔽地址,rand1…,1,665 或 1,666 个字符
签名对象区块、投票、水龙头铸造、Unbond、Withdraw、Registration,自链 20 起还有 AdmitValidator 投票,在链 21 上还有多签账户的支付与轮换以及 SetRandPrice 投票;作为代币 Key 铸造权限时,签署 TokenMint 与 SetAuthority。自链 20 起,上述每一种消息的签名还覆盖创世哈希(交易)无:转账由其证明授权
在链上的呈现区块提议者、验证节点登记簿中的一行、水龙头铸造的铸造者、代币的 key 权限从不出现在转账中,它在转账里被封入信封。凡有公开金额的价值进入资金池之处,它会被公布:桥接存款、代币铸造以及自链 20 起的调用(invoke)支付都会写明收款方地址;验证节点的收款地址在登记簿中;链 20 的创世写明了桥的手续费金库;自链 14 起,水龙头铸造与创世 alloc 票据会公布所有者的 pk,即地址的前 32 字节
文件{ "seed", "address", "public_key" },权限 0600{ "version": 2, "spend_key": "<64 hex>" },权限 0600

两者从不混用。验证节点由第一类密钥标识,同样的 Dilithium2 密钥形态也用于代币的 Key 铸造权限(rand token create --authority-key-out 写出的文件与 rand-node keygen 的形态相同)、桥的暂停密钥及其六个后量子守护者密钥。守护者的证明密钥为 secp256k1,使用 Ethereum 风格的 20 字节地址(桥)。价值由第二类密钥持有。

透明地址

节点密钥是一个 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/ 之后的 peer 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 是链 21 的验证节点之一。链 15 至 21 沿用了为链 14 轮换的十八个密钥,因此它们的地址和 peer id 不变,链 13 的验证节点地址和 peer id 已无一仍在使用;链 15 上又有八个验证节点加入质押,自链 16 起,全部 26 个都在创世文件中。

屏蔽地址

一个钱包只有一个秘密,即 256 位花费密钥 sk,其余一切都由它派生。派生使用 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 长度取决于开头的字节;约每十四个地址中有一个是较短的),约 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 在 stdout 上只输出地址和一个换行符,因此脚本中可以使用 $(rand address)。指纹输出到 stderr。

解析方式与 ShieldedAddress::parse 完全一致:去掉 rand1,做 base58 解码,并要求恰好为 32 + 1,184 = 1,216 字节;前 32 字节是以八个小端序 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 为九位小数的十进制数,代币则按其登记行自身的 decimals),且不得为零。memo 最多 510 字节的 UTF-8。协议方案的匹配不区分大小写;未知参数、重复参数或错误的百分号编码都会被拒绝。

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。备注随票据信封一同传递(票据)。

没有 rand balance <address>,也没有余额 RPC:链无法为任何人回答这个问题。钱包通过用其查看密钥扫描链上的每一个信封来计算自己的余额(票据)。

可以交给他人的密钥

披露是密钥的属性,链无法强制其中任何一项。

密钥可打开粒度
查看密钥 nk(64 hex)钱包曾经收到的每一张票据,以及通过 ovk 打开它曾经发出的每一张票据,并附每次花费的 nullifier全部或全无,既追溯过去也涵盖未来
交易密钥 TxKey(32 字节)恰好它所封装的那一个信封:票据,以及自链 18 起的备注一笔付款
调用密钥 K_call一次保密调用的私有输入转录一次调用

查看密钥不能花费。自链 17 起(拆分授权),捆绑包证明本身由 nk 生成,而每个捆绑包还携带第二个很小的授权证明,只有花费密钥才能生成,并且绑定到这一笔交易(票据)。这使得受托的证明者可以被交予 nk,从而获知钱包的全部历史,却无法花费;钱包总是在自己的机器上生成授权证明。地址、密钥和既有票据不受此拆分影响。钱包在每次运行时派生 nk,从不存储它;每个 TxKey 在每个信封中全新抽取,并在封装后丢弃。可以用 rand_importViewingKey 把查看密钥交给节点(保存在内存中,每个节点最多 64 个,重启后消失),使其代持有者扫描,这是浏览器的路径;rand_checkTransaction(hash, key) 则用一次无状态调用,依据 TxKey 检查一笔付款。rand viewing-key 输出 nk,rand tx-key <hash> 输出本钱包可打开的、该交易每个输出的密钥。

代币 id 不是地址

RPL 代币有第三类文本标识符,它指代的是代币,而绝不是持有者。代币的 32 字节资产 id 以 64 个十六进制字符和 rpl1… 两种形式显示:后者是 bech32m(BIP-350),人类可读部分为 rpl,承载 32 个 id 字节,52 个数据字符加 6 个字符的校验和,共 62 个字符。解析是严格的:校验和错误、人类可读部分不同、载荷不是恰好 32 字节,或大小写混用,均被拒绝,因此笔误绝不会解析为另一个代币。票据不携带该 id;其 asset 字是代币在登记簿中的稠密索引(RPL 代币)。

桥的收款方

桥的线路格式有一个 32 字节的收款方槽位,而屏蔽地址放不进去。目前该槽位携带的是地址的哈希:

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

源链上的存款人写明该哈希,守护者对其签名,而 BridgeAttest 交易携带完整地址,供账本据此核对(桥)。

短屏蔽地址:在链 11 上试用,已回退

链 11(fullnode 标签 v0.2,2026-09-17)用一个 53 至 55 个字符的接收方标识取代了长地址,其形式为 rand1 + base58(blake3(receiver signing key) ‖ checksum),并将 ML-KEM 封装密钥移入一份经签名的接收方记录中;发送方从浏览器的注册表获取该记录,或在支付请求中以内联方式收到它。该设计运行了一天,并于当天被回退(fullnode 17db41d);链 12 恢复为长地址,与上文所述完全一致,此后的每条链(包括链 20)均沿用长地址。

为何撤下。接收方标识是一个哈希值,而发送方无法将票据封装给一个哈希值。向从未注册过的钱包发出的第一笔付款,需要接收方交出一份支付请求或一个记录文件,而注册本身要通过一笔自转账来支付,新建的空钱包无法完成这笔转账。两种递送途径还都会让注册表得知谁即将收款。既能保持短地址,又能保证无条件可发送和密码学隐私的设计,是一种混合地址:内联一个 32 字节的 X25519 密钥(约 93 个字符),后量子密钥则通过查询获取;向未注册钱包的首笔付款可抵御经典对手而保持机密,此后每一笔均为完全后量子安全。该方案将在下一节说明;在其发布之前,长地址就是地址。

链 11 的规范及其实现保留在 fullnode 的历史记录中(docs/superpowers/specs/2026-09-17-short-shielded-address.md,标签 v0.2),供混合设计复用其中的部分:派生的接收方签名密钥、签名记录及其验证器,以及作为链上状态的注册表。

最近一次核对所依据的 fullnode 提交为 c0878c3f(链 21),日期为 2026-10-12。 若代码与本页不一致,以代码为准;请告知我们。