이 페이지를 사용하면 용도에 맞는 키를 생성하고, 주소를 파싱하거나 검증하며, 키를 다른 사람에게 건넬 때 무엇이 공개되는지 알 수 있습니다. 이 체인에는 계정이 없습니다. 잔액은 키 파일에 관한 사실이며, 체인에 관한 사실이 아닙니다.
두 가지 키, 두 가지 주소
| 노드 키 | 지갑 키 | |
|---|---|---|
| 생성 명령 | rand-node keygen | rand keygen |
| 비밀 값 | 32바이트 시드 | 256비트 지출 키 |
| 공개 식별자 | 32바이트 주소, base58, 32~44자 | 차폐 주소, rand1…, 1,665자 또는 1,666자 |
| 서명 대상 | 블록, 투표, faucet 민트, Unbond, Withdraw, Registration, 체인 20에서는 AdmitValidator 투표. 토큰의 Key 민트 권한으로서는 TokenMint와 SetAuthority. 체인 20에서는 이 메시지 모두가 제네시스 해시도 포함합니다 (Transactions) | 없음: 전송은 증명으로 승인됩니다 |
| 체인에 나타나는 방식 | 블록 제안자, 밸리데이터 레지스터 행, faucet 민트의 민터, 토큰의 key 권한 | 전송에는 나타나지 않으며, 봉투 안에 봉인되어 있습니다. 공개 금액과 함께 가치가 풀에 들어오는 지점에서는 공개됩니다. 브리지 입금, 토큰 민트, 체인 20에서는 invoke의 지급이 수취인의 주소를 명시하고, 밸리데이터의 지급 주소는 레지스터에 있으며, 체인 20의 제네시스는 브리지의 수수료 금고를 명시합니다. 또한 체인 14부터 faucet 민트와 제네시스 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바이트 주소를 가집니다 (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는 체인 20의 밸리데이터 중 하나입니다. 체인 15~20은 체인 14를 위해 교체된 열여덟 개의 키를 재사용하므로 이들의 주소와 피어 ID는 바뀌지 않았고, 체인 13의 밸리데이터 주소나 피어 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으로 기록되며, 모든 행은 leaf 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)이며 0이어서는 안 됩니다. 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바이트) | 그 키가 봉인한 정확히 하나의 봉투: 노트와, 체인 18부터는 그 메모 | 하나의 지급 |
호출 키 K_call | 하나의 기밀 호출의 비공개 입력 트랜스크립트 | 하나의 호출 |
조회 키로는 지출할 수 없습니다. 체인 17(분할 승인)부터 번들 증명 자체는 nk로 만들어지며, 모든 번들은 지출 키만 만들 수 있고 하나의 트랜잭션에 묶인 두 번째의 작은 승인 증명도 담습니다 (Notes). 덕분에 위임된 증명자에게 nk, 즉 지갑의 전체 이력을 건네더라도 증명자는 지출할 수 없으며, 승인 증명은 항상 지갑이 자신의 기기에서 만듭니다. 주소, 키, 기존 노트는 이 분할로 달라지지 않습니다. 지갑은 실행할 때마다 nk를 도출하고 저장하지 않으며, 각 TxKey는 봉투마다 새로 생성되어 봉인 후 폐기됩니다. 노드에는 rand_importViewingKey로 조회 키를 건넬 수 있으며(메모리 내, 노드당 최대 64개, 재시작 시 사라짐), 그러면 노드가 소유자를 대신해 스캔합니다. 이것이 탐색기의 방식입니다. rand_checkTransaction(hash, key)는 상태 없는 한 번의 호출로 하나의 지급을 TxKey에 대해 확인합니다. rand viewing-key는 nk를 출력하고, rand tx-key <hash>는 이 지갑이 열 수 있는 트랜잭션의 각 출력에 대한 키를 출력합니다.
토큰 ID는 주소가 아닙니다
RPL 토큰에는 세 번째 종류의 텍스트 식별자가 있으며, 이는 토큰을 가리킬 뿐 보유자를 가리키지 않습니다. 토큰의 32바이트 자산 ID는 64개의 hex 문자와 rpl1…로 표시됩니다. 후자는 32개의 ID 바이트에 대해 사람이 읽을 수 있는 부분을 rpl로 하는 bech32m(BIP-350)이며, 52개의 데이터 문자와 6자의 체크섬을 합쳐 62자입니다. 파싱은 엄격합니다. 잘못된 체크섬, 다른 사람이 읽을 수 있는 부분, 정확히 32바이트가 아닌 페이로드, 혼합된 대소문자는 거부되므로 오타가 다른 토큰으로 해석되는 일은 없습니다. 노트는 ID를 담지 않으며, 노트의 asset 워드는 토큰의 조밀한 레지스트리 인덱스입니다 (RPL tokens).
브리지 수취인
브리지의 와이어 형식에는 32바이트 수취인 슬롯이 있으며, 차폐 주소는 여기에 들어가지 않습니다. 현재 이 슬롯에는 주소의 해시가 담깁니다.
recipient_hash = blake3("rand-shielded-recipient" || pk_bytes || kem_ek)
소스 체인의 입금자가 해시를 지정하고, 가디언이 이에 서명하며, BridgeAttest 트랜잭션은 원장이 해시와 대조해 확인할 수 있도록 전체 주소를 담습니다 (Bridge).
짧은 차폐 주소: 체인 11에서 시도했으나 되돌림
Chain 11(fullnode 태그 v0.2, 2026-09-17)은 긴 주소를 53~55자의 수신자 id, 즉 rand1 + base58(blake3(수신자 서명 키) ‖ 체크섬)로 대체했고, ML-KEM 캡슐화 키는 서명된 수신자 레코드로 옮겼습니다. 송신자는 이 레코드를 익스플로러의 레지스트리에서 가져오거나 결제 요청에 인라인으로 포함된 형태로 받았습니다. 이 체계는 하루 동안 운영되었고 같은 날 되돌려졌습니다(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). 재사용되는 부분은 파생된 수신자 서명 키, 서명된 레코드와 그 검증기, 그리고 체인 상태로서의 레지스트리입니다.