Skip to main content
MIP410 derives each wallet’s keys from one wallet seed through one-way derivations. The resulting hierarchy separates the capabilities to recognize incoming notes, detect spends, build proofs, and authorize spends. A key holder can share a derived key with a scanner, proving service, or the MIP410 authority without revealing its ancestors.

Notation

The formulas on this page use four objects. Lower-case symbols (zskzsk, nknk, ivkivk, dd) are field elements; GG, akak, gdg_d, and pkdpk_d are Grumpkin points. A field element written against a point, as in zsk⋅Gzsk \cdot G, means scalar multiplication.

The hierarchy

The seed is 32 bytes. seed_lo is bytes 0–15 and seed_hi is bytes 16–31, each interpreted as a big-endian unsigned integer. Their order is significant. Recovering zskzsk from akak requires solving a discrete logarithm on Grumpkin. Recovering akak or nknk from ivkivk requires inverting Poseidon2 over akxak_x, akyak_y, and nknk. A derived key therefore does not reveal its ancestors.

Addresses

A diversified address is the pair (d,pkd)(d, pk_d). The wallet chooses a fresh diversifier dd and derives the address point pkdpk_d: xd=Poseidon2(DOMAIN_GD,d),gd=(xd,yd),pkd=ivk⋅gdx_d = \mathrm{Poseidon2}(\mathrm{DOMAIN\_GD}, d), \qquad g_d = (x_d, y_d), \qquad pk_d = ivk \cdot g_d The wallet solves yd2=xd3−17y_d^2 = x_d^3 - 17 in the Grumpkin base field and chooses the smaller of the two roots in canonical integer order. If there is no root, it chooses another diversifier. This exact derivation matters: a generic hash-to-curve function will produce different addresses. Three properties follow:
  • Unlinkable. Without the ivk behind them, two addresses with different diversifiers do not disclose a shared owner cryptographically. Reuse or offchain disclosure can still link them.
  • One scanner. A single ivk recognizes notes sent to every address it generates. Generating more addresses does not require more viewing keys.
  • Change ownership. The proof enforces that change belongs to the sender. Wallets should choose a fresh diversifier; the circuit does not check whether that diversifier has been used before.
Nullifiers are independent of diversified addresses. Publishing one does not reveal which address held the note.

Derived identifiers

The hierarchy also yields one stable identifier for the identity: affilID=Poseidon2(DOMAIN_AFFIL,ivk)affilID = \mathrm{Poseidon2}(\mathrm{DOMAIN\_AFFIL}, ivk). Every address generated from the same ivk shares this affilID. An ivk holder can compute affilID and the address alone does not reveal it. Linking pkdpk_d to affilIDaffilID requires the ivk that produced both. Sanctions screening binds to affilID, so generating a fresh address does not change the sanctions result. Customer-to-VASP affiliation is maintained offchain; affilID is not a customer registration in the pool’s VASP tree.

The capability lattice

Each key bundle grants a specific set of capabilities. {ak,nk}\{ak, nk\} sees arrivals because ivkivk derives from it. The reverse does not hold: {ivk,nk}\{ivk, nk\} cannot reconstruct (ak,nk)(ak, nk). A proving service still needs the key holder’s signature for each shield or spend. Recipient proofs are different: ivk is sufficient for their ownership check, but resolution also requires the recipient VASP’s policy-wrapper proof. Seeing arrivals requires access to the encrypted note memos or note openings. Detecting spends also requires each known note’s leaf index. The MIP410 authority has this visibility only if the relevant keys and data are shared with it; contract administration does not grant them. The institutional payload uses the recipient VASP’s separate messaging key.

Key custody

With custodial keys, the VASP’s custodial system holds the customer’s zsk and can generate addresses, scan notes, build proofs, and authorize spends. With self-custodied keys, the customer’s wallet holds zsk and may share {ak,nk}\{ak, nk\} with its VASP for proving. Both arrangements produce the same onchain proofs. A customer using custodial keys may also receive {ivk}\{ivk\} for watch-only access.

Spend authorization

Spending a note requires two checks inside the proof:
  1. Ownership. The prover witnesses (ak,nk)(ak, nk) and re-derives the note’s diversified address.
  2. Authorization. The circuit verifies a Schnorr signature under akak over a Poseidon2 digest of the action’s public values.
For payment initiation, the digest binds the payment effect hash, input lock IDs, nonce, and deadline. An unshield authorization binds its destination, amount, inputs, and change note. These bindings prevent a proving service or submitter from redirecting the action. Recording a note’s nullifier in the spent set prevents the note from being spent again.

Outsourced proving

Proof generation can be delegated without sharing the spending key. The following flow applies to self-custodied keys. With custodial keys, the VASP can sign and prove directly. Delegation hands the proving service {ak,nk}\{ak, nk\} and runs one round trip:
  1. The wallet builds the payment intent and output note commitments.
  2. The service prepares the witness, including tree paths and policy inputs, computes the nullifiers and input lock IDs, and returns the digest.
  3. The wallet checks the digest against its intent and signs with zskzsk.
  4. The service completes the proof and a submitter submits the finished transaction.
The service can build or withhold a proof. It cannot authorize or redirect a spend because each action requires a signature derived from zsk over the wallet-approved digest. A wallet may isolate proving services by deriving a separate zskizsk_i and sharing only its {aki,nki}\{ak_i, nk_i\} bundle.

The institution’s messaging key

One key on this page belongs to no wallet. Each VASP in the pool’s institution registry holds a messaging key: an independent Grumpkin Schnorr key pair, generated by the institution and not derived from any customer seed. Its verification key sits in the institution’s leaf of the pool’s VASP tree beside the hash of its policy verification key, so the leaf is the one place the chain learns which key speaks for the institution: leaf=Poseidon2(DOMAIN_FI_LEAF, fi_id, vk_hash, mpkx, mpky)\mathrm{leaf} = \mathrm{Poseidon2}(\mathrm{DOMAIN\_FI\_LEAF},\ fi\_id,\ vk\_hash,\ mpk_x,\ mpk_y) The key signs payment requests, institutional payload commitments, payment contexts inside policy wrappers, and the institution’s own leaf updates. Wallets verify requests offchain. The receiving VASP verifies the institutional payload signature offchain. The wrapper verifies its context signature inside the proof under the key opened from the VASP tree. updateFi replaces the policy verification-key hash and messaging key under a signature by the old messaging key, verified through the Grumpkin precompiles. The VASP can rotate its own key without an authority transaction. The pool accepts the previous registration root after an admission. A key rotation or revocation clears that previous root, so proofs using the old leaf must be rebuilt against the updated registry. Request validation must use an accepted registry snapshot and check the request’s expiry.

What the chain sees

Wallet keys remain private. Diversified addresses travel in payment requests and encrypted note memos, and wallet signatures are verified inside proofs. VASP messaging public keys are public in registry updates. The chain also carries note commitments, input lock IDs, payment and payload commitments, nullifiers when notes are spent, and proofs. Accepted payments retain their input lock IDs permanently; rejected or reclaimed holds release them.