Key Hierarchy
The design goal is to let a single seed phrase control everything, while allowing selective disclosure: you can hand an auditor your viewing key without giving them spend authority.
The full tree
sk (32-byte master spending key)
│
├─ ask = toBjjScalar( SHA512(sk ‖ 0x06) ) spend-auth signing key (BJJ scalar)
│ └─ ak = [ask] · G_spend spend-auth verifying key (BJJ point, public)
│
├─ nk = toBn254( SHA512(sk ‖ 0x07) ) nullifier-deriving key (BN254 field element)
│
├─ rivk = toBn254( SHA512(sk ‖ 0x08) ) IVK commitment trapdoor
│
├─ dk = truncate32( SHA512(sk ‖ 0x09) ) diversifier key (32 bytes)
│ └─ d[i] = lower88bits( Poseidon2([DIVERSIFY_DOMAIN, dk_field, i]) )
│ └─ gd[i] = DiversifyHash(d[i]) diversified base point on BJJ
│
├─ ovk = truncate32( SHA512(sk ‖ 0x0a) ) outgoing viewing key (32 bytes)
│
└─ pqSeed = SHA512(sk ‖ 0x0c) ML-KEM-768 seed (64 bytes)
└─ (mlkemPub, mlkemPriv) = ml_kem768.keygen(pqSeed)
Derived:
fvk = (ak, nk, rivk, dk, ovk) full viewing key
ivk = Poseidon2([IVK_DOMAIN, ak_x, ak_y, nk, rivk]) mod 2^251 incoming viewing key
pkd[i] = [ivk] · gd[i] diversified transmission key
PaymentAddress[i] = (d[i], gd[i], pkd[i])
Incoming viewing key (ivk) derivation
The incoming viewing key ivk is derived via a collision-resistant hash (crh_ivk):
ivk = Poseidon2([IVK_DOMAIN, ak_x, ak_y, nk, rivk]) & (2^251 - 1)
Diversified addresses
Every user has an unbounded family of payment addresses, all controlled by one sk. Each addr[i] = (d[i], gd[i], pkd[i]) has a different on-chain footprint, so multiple payments to the same user are unlinkable even to a payer who knows the recipient's sk.
d[i] = lower 11 bytes of Poseidon2([DIVERSIFY_DOMAIN, dk_field, i])
gd[i] = DiversifyHash(d[i])
pkd[i] = [ivk] · gd[i]
The full construction, the hash-to-curve loop, the post-quantum key that travels with an address, and the mandatory subgroup checks are covered in Stealth Addresses.
Key disclosure levels
| Key | Capability | Disclosure risk |
|---|---|---|
sk | Everything — full spend authority | Never share |
fvk = (ak, nk, rivk, dk, ovk) | View all incoming and outgoing notes; cannot spend | Compliance auditor |
ivk | Recognize incoming notes to your address | Incoming-only auditor |
ovk | Recover notes you sent (via C_out) | Outgoing-only auditor |
Payment address (d, gd, pkd) | Receive notes; unlinkable per-index | Safe to share publicly |
This hierarchy means a Platus wallet can implement delegated scanning: hand ivk to a scanning service, which notifies you when notes arrive, without the service learning your spend keys or outgoing history.