Skip to main content

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

KeyCapabilityDisclosure risk
skEverything — full spend authorityNever share
fvk = (ak, nk, rivk, dk, ovk)View all incoming and outgoing notes; cannot spendCompliance auditor
ivkRecognize incoming notes to your addressIncoming-only auditor
ovkRecover notes you sent (via C_out)Outgoing-only auditor
Payment address (d, gd, pkd)Receive notes; unlinkable per-indexSafe 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.