LEE’s HD wallet derives all account keys from a single mnemonic phrase ([BIP-039](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki)). The mnemonic generates a `seed` that roots two independent key subtrees: one for public state accounts (based on [BIP-032](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)) and one for private state accounts (based on [ZIP-032](https://zips.z.cash/zip-0032)).
Each parent-to-child edge is parametrized by an index. A key is uniquely addressed by its path from the root. E.g. `m_pub/0/4/12` is the 12th child of the 4th child of the 0th child of the public master key; `m_priv/0/4/12` is the analogous path for private accounts.
The seed is the single secret from which all wallet keys are derived. It is 64 bytes, generated from a BIP-039 mnemonic phrase via PBKDF2-HMAC-SHA512:
```rust
// SeedHolder::new_mnemonic / new_os_random
let mnemonic = Mnemonic::from_entropy(&entropy_bytes); // 24-word phrase from 32 random bytes
let seed: [u8; 64] = mnemonic.to_seed(passphrase); // PBKDF2, 2048 iterations
```
The seed is the only value derived directly from the mnemonic. Both key subtrees are rooted from it, distinguished by their HMAC key string:
```rust
// Public root
let hash_value = hmac_sha512::HMAC::mac(seed, b"LEE_master_pub");
// Private root
let hash_value = hmac_sha512::HMAC::mac(seed, b"LEE_master_priv");
```
In both cases the 64-byte HMAC output is split the same way: the first 32 bytes become the subtree's root secret key and the last 32 bytes become its root chain code.
Public accounts consist of three keys (secret key `sk`, Schnorr secret key `ssk` and public key `pk`). Additionally, auxillary values chain index `ci` and chain code `cc`. A public account keys are generated using its chain index and its parent's chain code.
The secret key (`sk`) is used for generating the Schnorr secret key. Currently, the secret key is not directly used in LEE. Rather, the Schnorr secret key (`ssk`) is used for managing the public account. At some point in the future, Schnorr signatures (and `ssk`) will be phased out. At that point, `sk` will be used for authorization in a PQ signature scheme.
The secret key `sk` is computed in one using one of two different methods. The method used depeneds on whether the account keys is the root key or a child key.
The Schnorr secret key (`ssk`) is used for managing public ownership and authorization Authorization for spending funds and, in some cases, modifying account data is handled by signing the transaction with the account’s Schnorr secret key. The Schnorr secret key serves as protective layer between the account's secret key and quantum attackers.
The Schnorr secret key (`ssk`) is computed from the secret key (`sk`) as follows:
1.`private_key` is the scalar associated in the basefield for `secp256k1` generated from `sk`.
2.`public_key` is the corresponding (compressed) public key in `secp256k1`.
3.`ssk = private_key + Sha256(public_key)` where the hash of `public_key` is parsed as a scalar.
A public account’s public key (`pk`) is used to verify account ownership. Specifically, the public key is used to validate (Schnorr) signatures purporting to authorizing an account’s usage. The public key is the compressed Schnorr public key (32-bytes).
Private accounts consist of three types of keys: secret spending key `ssk`, nullifier keys (`nsk`, `npk`), and viewing keys (`vsk`, `vpk`). Additionally, auxiliary values chain index `ci` and chain code `cc`. Private account keys are generated using their chain index and their parent's chain code.
Each private account's key data are stored in `ChildKeysPrivate`.
Unlike public account keys, private account keys can be used for multiple accounts. A list (`BTreeMap`) is used to store all private accounts that use the same set of private account keys. `KeyChain` bundle all of the secret keys.
The `SecretSpendingKey` (`ssk`) is the root secret for a private account node. It is never transmitted or stored on-chain; all other keys are derived from it.
For both the root and each child, the `ssk` is a 32-byte value taken from the first half of an HMAC-SHA512 output.
#### Secret key generation for root
Root private keys are derived from the BIP-039 seed via HMAC-SHA512 keyed with `"LEE_master_priv"`. The 64-byte output is split: the first 32 bytes become the `SecretSpendingKey` (`ssk`) and the last 32 bytes become the child chain code (`ccc`).
Child keys are derived from the parent's `PrivateKeyHolder` (via a SHA-256 "parent point" commitment) and the parent's `ccc`, mixed with the child index `cci`. The scheme follows ZIP-032 in spirit.
let parent_pt = parent_hash.finalize(); // 32 bytes
// 2. Derive child ssk and ccc
let mut input = vec![];
input.extend_from_slice(b"LEE_seed_priv");
input.extend_from_slice(&parent_pt);
input.extend_from_slice(&cci.to_be_bytes()); // big-endian per BIP-032
let hash_value = hmac_sha512::HMAC::mac(input, parent.ccc);
let ssk = SecretSpendingKey(*hash_value.first_chunk::<32>().expect("64-byte output"));
let ccc = *hash_value.last_chunk::<32>().expect("64-byte output");
```
### Nullifier keys
The nullifier secret key (`nsk`) and nullifier public key (`npk`) are derived from the `ssk` and child index. The `npk` is used to compute the `AccountId` for a private account and to generate nullifiers when an account is spent.
**`nsk` derivation** — SHA-256 keyed with domain-separation bytes and the child index:
The nullifier secret key and nullifier public key include constants (`1_u8` and `7_u8`). These constants are used to avoid collision of inputs between nullifier secret keys and nullifier public keys.
The viewing secret key (`vsk`) is a 64-byte ML-KEM 768 seed split into two 32-byte halves `d` and `z`. The viewing public key (`vpk`) is the corresponding ML-KEM 768 encapsulation key (1184 bytes). The `vsk` allows the holder to decrypt ciphertexts sent to the account; the `vpk` is the address component senders use to encrypt.