Rand Protocol
Documentation
Core concepts

Accounts and addresses

The two kinds of keys on the chain, how a shielded rand1 address is derived, what a viewing key discloses, and why the short address of chain 11 was reverted.

Use this page to generate the right key for the job, to parse or validate an address, and to know what handing someone a key discloses. There are no accounts on this chain: a balance is a fact about a key file, never about the chain.

Two keys, two kinds of address

node keywallet key
written byrand-node keygenrand keygen
secreta 32-byte seeda 256-bit spend key
public identitya 32-byte address, base58, 32 to 44 charactersa shielded address, rand1…, 1,665 or 1,666 characters
signsblocks, votes, faucet mints, Unbond, Withdraw, a Registration, since chain 20 an AdmitValidator vote, and on chain 21 a multisig account’s payments and rotations and a SetRandPrice vote; as a token’s Key mint authority, TokenMint and SetAuthority. Since chain 20 every one of these messages also covers the genesis hash (Transactions)nothing: a transfer is authorised by its proofs
appears on chain asa block proposer, a validator register row, the minter of a faucet mint, a token’s key authoritynever in a transfer, where it is sealed inside envelopes. It is published where value enters the pool with a public amount: a bridge deposit, a token mint and, since chain 20, an invoke’s payout name the recipient’s address, a validator’s payout address is in the register, chain 20’s genesis names the bridge’s fee treasury, and since chain 14 a faucet mint and a genesis alloc note publish the owner’s pk, the first 32 bytes of the address
file{ "seed", "address", "public_key" }, mode 0600{ "version": 2, "spend_key": "<64 hex>" }, mode 0600

The two never mix. Validators are identified by the first kind, and the same Dilithium2 key shape serves a token’s Key mint authority (rand token create --authority-key-out writes a file of rand-node keygen’s shape), the bridge’s pause key and its six post-quantum guardian keys. The guardians’ attestation keys are secp256k1, with Ethereum-style 20-byte addresses (Bridge). Value is held under the second kind.

The transparent address

A node key is a 32-byte seed from which a Dilithium2 key pair is derived deterministically: a 1,312-byte public key and 2,420-byte signatures. The address is a domain-separated BLAKE3 hash of the public key, rendered in base58:

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

Signed objects (votes, blocks, mints, staking actions) carry the full public key, so a verifier checks blake3("rand-address" || public_key) == address rather than trusting a bare address. The same seed also yields the node’s libp2p identity, an ed25519 key from blake3("rand-p2p-identity" || seed), which is the peer id after /p2p/ in a bootstrap multiaddr.

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

Example: EdrEC2QRQrQF64iRXxF9927jNj6RidiHHByzx5xD6F1e is one of chain 21’s validators. Chains 15 to 21 reuse the eighteen keys rotated for chain 14, so their addresses and peer ids are unchanged and no chain 13 validator address or peer id is still in use; eight more validators bonded in on chain 15, and all 26 are in the genesis file from chain 16 on.

The shielded address

A wallet is one secret, the 256-bit spend key sk, and everything else is a derivation of it. The derivation is Poseidon2 with a domain tag as the first absorbed word (H(domain, msg)), the same function the zkVM computes in circuit:

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)

The address is the 32 bytes of pk followed by the 1,184-byte ML-KEM-768 encapsulation key, base58 after the rand1 prefix: 1,665 or 1,666 characters (the base58 length depends on the leading bytes; about one address in fourteen is the shorter), about 1.6 KB, and no checksum. A sender needs the encapsulation key to seal the note envelope, which is why it is in the address. It is public by design: anyone may pay it, and holding it says nothing about what it holds.

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

Since v0.5.10 rand address prints exactly the address and a newline on stdout, so $(rand address) works in a script. The fingerprint goes to stderr.

Parsing, exactly as ShieldedAddress::parse does it: strip rand1, base58-decode, and require exactly 32 + 1,184 = 1,216 bytes; the first 32 are pk as eight little-endian u32 words, the rest the encapsulation key. The RPC refuses any parameter longer than 2,000 characters before parsing it.

Beside the key file the wallet keeps <key>.key.json.notes.json, a cache of the notes this key has opened. It holds note plaintexts, so it is written mode 0600 like the key; every row of it is recoverable by rescanning from leaf 0.

An address is too long to read aloud or compare by eye and carries no checksum, so since v0.5.10 every wallet shows a fingerprint beside it. The fingerprint is display only: nothing on chain or on the wire carries one.

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

The hash is over the 1,216 raw bytes the address encodes. The alphabet is 0123456789ABCDEFGHJKMNPQRSTVWXYZ; parsing ignores case and hyphens and reads O as 0 and I or L as 1.

A randpay: link carries an address and, optionally, what to pay it:

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

amount is in the asset’s display units (decimal RAND at nine decimals, a token at its registry row’s own decimals) and must not be zero. memo is at most 510 bytes of UTF-8. The scheme is matched without regard to case; an unknown parameter, a repeated one, or a bad percent-encoding is refused.

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> takes a rand1… address, a randpay: link or a saved contact’s name, tried in that order. A link’s amount, asset and memo fill in what the command line leaves out, and a value given both ways that disagrees is refused. Before proving, send prints to <contact, if any> · fingerprint XXXX-XXXX-XXXX-XXXX · <amount> <asset> and asks; a script passes --yes. Contacts are stored beside the key at <key>.contacts.json, mode 0600. The memo travels inside the note envelope (Notes).

There is no rand balance <address> and no balance RPC: the chain cannot answer it for anyone. A wallet computes its balance by scanning every envelope on the chain with its viewing key (Notes).

Keys you can hand to someone else

Disclosure is a property of keys, and the chain cannot compel any of it.

keyopensgrain
the viewing key nk (64 hex)every note the wallet ever received and, through ovk, every note it ever sent, with the nullifier of each spendall or nothing, retroactive and forward-looking
a transaction key TxKey (32 bytes)exactly the one envelope it sealed: the note and, since chain 18, its memoone payment
a call key K_callone confidential call’s private-input transcriptone call

A viewing key cannot spend. Since chain 17 (split authorisation) the bundle proof itself is made from nk, and every bundle also carries a second, small auth proof that only the spend key can make and that is bound to the one transaction (Notes). That is what lets a delegated prover be handed nk, and so the wallet’s whole history, without being able to spend; the wallet always makes the auth proof on its own machine. Addresses, keys and existing notes are unchanged by the split. The wallet derives nk on every run and never stores it; each TxKey is drawn fresh per envelope and dropped after sealing. A node may be handed a viewing key with rand_importViewingKey (in memory, at most 64 per node, gone at restart) so that it scans on the holder’s behalf — the explorer’s path — and rand_checkTransaction(hash, key) checks one payment against a TxKey in one stateless call. rand viewing-key prints nk, and rand tx-key <hash> prints the key of each output of a transaction this wallet can open.

Token ids are not addresses

An RPL token has a third kind of text identifier, and it names a token, never a holder. The token’s 32-byte asset id is shown as 64 hex characters and as rpl1…: bech32m (BIP-350) with the human-readable part rpl over the 32 id bytes, 52 data characters plus a 6-character checksum, 62 characters. Parsing is strict: a bad checksum, another human-readable part, a payload that is not exactly 32 bytes, or mixed case is refused, so a typo never resolves to a different token. A note does not carry the id; its asset word is the token’s dense registry index (RPL tokens).

The bridge recipient

The bridge’s wire format has a 32-byte recipient slot, and a shielded address does not fit in it. Today the slot carries a hash of the address:

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

The source-chain depositor names the hash, the guardians sign it, and the BridgeAttest transaction carries the full address for the ledger to check against it (Bridge).

The short shielded address: tried on chain 11, reverted

Chain 11 (fullnode tag v0.2, 2026-09-17) replaced the long address with a 53 to 55 character receiver id, rand1 + base58(blake3(receiver signing key) ‖ checksum), and moved the ML-KEM encapsulation key into a signed receiver record that a sender fetched from the explorer’s registry or received inline in a payment request. It ran for a day and was reverted the same day (fullnode 17db41d); chain 12 went back to the long address, exactly as described above, and every chain since, chain 20 included, keeps it.

Why it came out. A receiver id is a hash, and a sender cannot seal a note to a hash. The first payment to a wallet that had never registered needed the receiver to hand over a payment request or a record file, and registration itself was paid with a self-transfer, which a new, empty wallet cannot make. Both delivery paths also told the registry who was about to be paid. The design that keeps a short address, unconditional sendability and cryptographic privacy is a hybrid address: a 32-byte X25519 key inline (about 93 characters), the post-quantum key by lookup, with the first payment to an unregistered wallet confidential against classical adversaries and every later one fully post-quantum. That is specified next; until it ships, the long address is the address.

The chain 11 specification and its implementation stay in the fullnode history (docs/superpowers/specs/2026-09-17-short-shielded-address.md, tag v0.2) for the parts the hybrid design reuses: the derived receiver signing key, the signed record and its verifier, and the registry as chain state.

Last verified against fullnode commit c0878c3f (chain 21) on 2026-10-12. Where the code and this page disagree, the code is right; tell us.