Key Management
zkCoins uses BIP32 Hierarchical Deterministic (HD) wallets for key derivation. All keys are generated and stored locally in the browser — they never touch the server.
Key derivation
Random 256-bit seed (from browser crypto.getRandomValues)
└── BIP32 Master Key (Xpriv)
│
├── Public Key [0] → blinded with random bytes → Account Address
├── Public Key [1] → used as sender_next_public_key in TX 1
├── Public Key [2] → used as sender_next_public_key in TX 2
└── ...
Each transaction uses the current public key and derives the next one. This provides:
- Forward secrecy — each transaction uses a fresh key
- Deterministic derivation — all keys can be re-derived from the master key
- Stealth-like addresses — the blinded address is unlinkable to the public keys
Key storage
Currently, keys are stored in the browser's localStorage:
{
"address": "a1b2c3...",
"numPubkeys": 3,
"xpriv": "xprv9s21ZrQH..."
}
localStorage is not encrypted. The master private key is stored in plaintext. This is acceptable for testnet but must be addressed before mainnet:
- Phase 2: IndexedDB with Web Crypto API encryption
- Phase 3: Hardware wallet integration or WebAuthn
Schnorr signatures
Transaction commitments are signed with Schnorr signatures over the secp256k1 curve — the same cryptography that powers Bitcoin's Taproot. The signing happens in WebAssembly, compiled from the Rust bitcoin crate:
// In the browser via WASM
const signature = wasm.signSchnorr(privateKeyHex, messageHashHex);
Account address
The account address is derived from the first public key, blinded with random bytes:
Address = hash(blind(PublicKey[0]))
This ensures that the on-chain commitment (which contains the public key hash) cannot be linked back to the account address without knowledge of the blinding factor.
Backup and recovery
Unlike regular Bitcoin wallets, recovering a seed phrase alone does not restore a zkCoins wallet. The coin proofs — the Zero-Knowledge proofs of each coin's validity — must also be preserved. Without them, the coins cannot be spent.
This is a fundamental property of Client-Side Validation: the blockchain only stores nullifiers, not transaction data. The wallet must keep its own records.
Planned backup approach:
- Export wallet state as encrypted file (master key + coin proofs)
- Import on another device
- Re-scan blockchain for nullifiers to rebuild accumulator state