Keys & Identity
Every Orbinum user operates two independent key pairs. Understanding why is most of understanding the privacy model: the key that signs your transactions is not the key that owns your notes.
Two key systems
| EVM keypair | Baby JubJub keypair | |
|---|---|---|
| Curve | secp256k1 | Baby JubJub (twisted Edwards) |
| Where it lives | Ethereum wallet (MetaMask, etc.) | Derived inside the wallet |
| Public identifier | EVM address (0xf24ff3a9…) | BJJ point x-coordinate (0x1d4a09a1…) |
| Purpose | Signs transactions, pays gas | Owns shielded notes, used in ZK circuits |
The reason for the second curve is cost. A scalar multiplication on Baby JubJub costs ~3,000–4,000 R1CS constraints inside a Groth16 circuit; the same operation on secp256k1 costs hundreds of thousands. Notes use BJJ keys because the circuit has to do that arithmetic on every spend.
The derivation tree
You never manage the second keypair. One signature from your EVM wallet produces a master secret, and every shielded-pool key branches off it:
EVM wallet
└─ EIP-712 typed data (domain "Orbinum Shielded Pool", version 2,
│ verifyingContract = shielded pool precompile)
│ └─ fallback for signers without EIP-712:
│ signMessage("orbinum-spending-key-v2\n<chainId>\n<address>")
└─ HKDF-SHA256(ikm=signature, info="orbinum-sk-v2:<chainId>:<address>")
└─ master (32 bytes, never used directly)
├─ spending_key = HKDF(master, "orbinum-spend-v3") → owner_pk = spending_key · G
├─ ivsk = HKDF(master, "orbinum-ivk-v3") → ivk (packed BJJ point)
├─ ovk = HKDF(master, "orbinum-ovk-v3")
└─ vault_key = HKDF(master, "orbinum-vault-key-v1")
The keys never leave the wallet, and the derivation is deterministic — the same wallet on the same chain always reproduces it. That is what makes vault recovery possible without a separate seed phrase.
v2orbinum-sk-v2 is deliberate, not a leftover. The identity scheme is v3, but only
the branches below the master changed — the same signature still produces the
same master. Bumping the digest would have forced every user to re-sign for an
identical result.
What each branch does
| Key | Capability |
|---|---|
spending_key | Spends notes. Derives owner_pk, your public receiving identity. |
ivsk → ivk | Incoming viewing key. Decrypts notes sent to you. |
ovk | Outgoing viewing key. Recovers what you sent. |
vault_key | Encrypts the local note database in your browser. |
Siblings, not a chain
The branches are siblings: no one of them derives another. This is the design decision the rest of the system leans on.
Because a viewing key cannot be walked back to the spending key, it can be handed out on its own — which is exactly what a watch-only wallet is. A derivation chain could not express that; anyone holding the parent would hold everything below it.
Two consequences follow:
ovkis the most sensitive of the three. The incoming viewing key reveals what arrived. The outgoing one reveals who you paid — the payment graph, not just a list of amounts.- Spending needs two branches. The scalar that spends a received note is derived from the shared secret, which comes from the viewing key. A spending key alone decrypts nothing and spends nothing. Back up the root, not one key.
Your published key is not what appears in your notes
owner_pk above is what you publish as your receiving identity. It is not
what appears in the commitment of a note somebody sends you.
Each received note carries a distinct stealth owner key, derived from the sender's ECDH shared secret. Two notes sent to you by the same person show two unrelated owner keys on-chain, and neither can be linked to your published one.
This is why a disclosure key proves one note's contents rather than "these notes belong to the same person" — see Note Discovery → Stealth addresses.
Related
- Notes & the Merkle Forest — what
owner_pkis committed into - Note Discovery — how the per-note ECDH keys are derived and scanned
- Watch-only Wallets — handing out a viewing key in practice