> ## Documentation Index
> Fetch the complete documentation index at: https://docs.metalntwx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Key Derivation

> How MIP410 derives spending, viewing, proving, and address capabilities from one wallet seed

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.

| Notation | Meaning |
| - | - |
| $\mathrm{Poseidon2}(\ldots)$ | The hash function used throughout MIP410. Its inputs and output are field elements in the proof system's native arithmetic. |
| Grumpkin | The elliptic curve shielded keys live on. Its point arithmetic uses the proof system's native field, which keeps key operations cheap to verify inside a proof. |
| $G$ | Grumpkin's fixed generator point. |
| $\mathrm{DOMAIN\_NK}$, $\mathrm{DOMAIN\_VIEW}$, … | Domain tags that separate otherwise similar derivations. |

Lower-case symbols ($zsk$, $nk$, $ivk$, $d$) are field elements; $G$, $ak$,
$g_d$, and $pk_d$ are Grumpkin points. A field element written against a
point, as in $zsk \cdot G$, means scalar multiplication.

## The hierarchy

```text theme={null}
seed sk                     wallet seed
 └─ zsk                     spending key
     ├─ ak                  spend-verification key
     ├─ nk                  nullifier key
     ├─ ivk                 incoming viewing key
     │   └─ (d, pk_d)       diversifier and diversified address
     └─ ovk                 outgoing viewing key
```

| Key | Full name | Derivation | Role |
| - | - | - | - |
| `sk` | wallet seed | — | Root secret. Every other key derives from it. |
| `zsk` | spending key | $\mathrm{Poseidon2}(\mathrm{DOMAIN\_ZSK}, seed_{lo}, seed_{hi})$ | Authorizes spends. It does not leave the key holder. |
| `ak` | spend-verification key | $ak = zsk \cdot G$ | Verifies spend signatures inside a proof. |
| `nk` | nullifier key | $nk = \mathrm{Poseidon2}(zsk, \mathrm{DOMAIN\_NK})$ | Derives a note's [nullifier](https://docs.metalntwx.com/reference/privacy/shielded-note.md#spending-a-note), which records when the note is spent. |
| `ivk` | incoming viewing key | $ivk = \mathrm{Poseidon2}(ak_x, ak_y, nk, \mathrm{DOMAIN\_VIEW})$ | Recognizes incoming notes and generates addresses. |
| `ovk` | outgoing viewing key | $\mathrm{Poseidon2}(\mathrm{DOMAIN\_OVK}, zsk)$ | Derived separately from the incoming viewing key. |
| `pk_d` | diversified address point | $pk_d = ivk \cdot g_d$ — see [Addresses](#addresses) | Forms a diversified address with `d`. One `ivk` can generate multiple addresses. |

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 $zsk$ from $ak$ requires solving a discrete logarithm on Grumpkin.
Recovering $ak$ or $nk$ from $ivk$ requires inverting Poseidon2 over $ak_x$,
$ak_y$, and $nk$. A derived key therefore does not reveal its ancestors.

## Addresses

A diversified address is the pair $(d, pk_d)$. The wallet chooses a fresh
diversifier $d$ and derives the address point $pk_d$:

$$
x_d = \mathrm{Poseidon2}(\mathrm{DOMAIN\_GD}, d), \qquad
g_d = (x_d, y_d), \qquad pk_d = ivk \cdot g_d
$$

The wallet solves $y_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 = \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 $pk_d$ to $affilID$ 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.

| Bundle | Typical holder | See arrivals | See spends | Build shield or spend proofs | Authorize spends |
| - | - | :-: | :-: | :-: | :-: |
| $\{ivk\}$ | a watch-only scanner | ✓ | — | — | — |
| $\{ivk, nk\}$ | an authorized viewer | ✓ | ✓ | — | — |
| $\{ak, nk\}$ | a proving service | ✓ | ✓ | ✓ | — |
| $zsk$ | key holder: the customer wallet or VASP custodial system | ✓ | ✓ | ✓ | ✓ |

$\{ak, nk\}$ sees arrivals because $ivk$ derives from it. The reverse does not
hold: $\{ivk, nk\}$ cannot reconstruct $(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\}$ with its VASP for proving. Both arrangements produce the same
onchain proofs. A customer using custodial keys may also receive $\{ivk\}$ for
watch-only access.

## Spend authorization

Spending a note requires two checks inside the proof:

1. **Ownership.** The prover witnesses $(ak, nk)$ and re-derives the note's
   diversified address.
2. **Authorization.** The circuit verifies a Schnorr signature under $ak$
   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](https://docs.metalntwx.com/reference/privacy/shielded-note.md#spending-a-note) 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\}$ 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 $zsk$.
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 $zsk_i$ and sharing only its $\{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:

$$
\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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.