V1 Mainnet · One signature, every key
Key derivation
Menoid never asks for a new seed phrase. Your wallet signs one readable message, and every private key is derived from that signature, inside the browser tab. Sign again on another device and the same keys come back — nothing is stored on a server, nothing needs a backup.
The message you sign
Menoid: create my private account keys. Only sign this on https://app.menoid.xyz Anyone holding this signature can spend your private balance. Network: mainnet Key index: 0
On testnet.menoid.xyz the origin line and the network say testnet — and every key comes out different.
The derivation
Spending
sk = H(spend, seed) mod l
↓
spendPk = sk · Base8
↓
nk = Poseidon(2, sk)
Spends notes. sk only ever enters a proof.
Incoming viewing
incomingSecret = H(incoming, seed)
↓
ivk(e) = H(ivk, incomingSecret, e)
↓
ivkPub(e) = ivk(e) · G
Opens the notes people send you.
Outgoing viewing
outgoingSecret = H(outgoing, seed)
↓
ovk(e) = H(ovk, outgoingSecret, e)
Opens your own record of who you paid.
userCommitment = Poseidon(1, address, spendPk.x, spendPk.y)
Your private identity — registered on-chain together with ivkPub.
seed = keccak256("menoid/v1m/seed" ‖ network ‖ signature)
sk = keccak256("menoid/v1m/spend" ‖ seed) mod l // BabyJubJub scalar
spendPk = sk · Base8 // BabyJubJub point (x, y)
nk = Poseidon(2, sk) // nullifier key
incomingSecret = keccak256("menoid/v1m/incoming" ‖ seed)
outgoingSecret = keccak256("menoid/v1m/outgoing" ‖ seed)
ivk(e) = keccak256("menoid/v1m/ivk" ‖ incomingSecret ‖ e) mod (n − 1) + 1
ivkPub(e) = ivk(e) · G // secp256k1 point, 65 bytes
ovk(e) = keccak256("menoid/v1m/ovk" ‖ outgoingSecret ‖ e)
userCommitment = Poseidon(1, address, spendPk.x, spendPk.y)‖ is Solidity’s packed encoding (solidityPackedKeccak256). l is BabyJubJub’s subgroup order, n is secp256k1’s order, e is the viewing epoch (a uint32 starting at 0). In the tree above, H(tag, …) is the same keccak256 with the tag menoid/v1m/….
Each field, and why
Field
What it is
Why it is there
message
Plain text naming the network, the one site that should ask for it, and a key index.
WhyA phishing site’s request looks wrong at a glance; the network and the index choose which set of keys you get.
signature
Your wallet’s 65-byte signature over the message. The same wallet signs it the same way every time.
WhyIt seeds everything: no new seed phrase, and recovering on another device is just one more signature.
seed
keccak256 of a tag, the network name and the signature.
WhyMixes the network in a second time, so a mainnet signature handed to testnet code still gives unrelated keys.
sk
The spending key: a number below l, BabyJubJub’s subgroup order.
WhyBabyJubJub lives in the BN254 field the proofs use, so a circuit can rebuild spendPk from sk cheaply. sk is only ever a private input of a proof.
spendPk
sk · Base8 — a point (x, y) on BabyJubJub.
WhyThe public half of sk. It goes into your userCommitment; it never appears on-chain by itself.
nk
Poseidon(2, sk): the nullifier key.
WhyNullifiers use nk, not sk — so an auditor can be shown what you spent without getting the power to spend.
incomingSecret
A keccak256 of a tag and the seed.
WhyThe root of every incoming viewing key, one per epoch.
ivk · ivkPub
A secp256k1 key pair for epoch e. ivkPub is registered on-chain.
WhyNotes are encrypted to ivkPub (ECIES); only ivk opens them. “mod (n − 1) + 1” keeps ivk a valid private key.
outgoingSecret · ovk
A tag and the seed, then one 32-byte key per epoch.
WhyEncrypts your own record of each payment, so your history also shows who you paid.
userCommitment
Poseidon(1, address, spendPk.x, spendPk.y).
WhyYour private identity: it binds your public address to your spending key. Senders lock notes to it; proofs rebuild it to show ownership.
key index
A number in the message, 0 to start.
WhyrotateKeys moves you to the next index — a whole new identity, if your signature ever leaked.
viewing epoch
A number mixed into ivk and ovk, 0 to start.
WhyupdateViewingKey moves you to the next epoch: fresh viewing keys, same identity, same balance.
A small example: what changes what
| You… | sk · nk · userCommitment | ivk · ovk |
|---|---|---|
| Sign again on another device | Same | Same |
| Sign on testnet instead of mainnet | All different | All different |
| Move to key index 1 (rotateKeys) | All new | All new |
| Move to viewing epoch 1 (updateViewingKey) | Same | New, for epoch 1 on |
The signature is the master key
