Overview
A Monero cold signer for Passport Prime, designed to pair with the public Cake Wallet mobile app as the online watch-only wallet. Passport holds the private spend key offline, receives Cake’s unsigned transactions over animated QR, reviews and signs locally with CLSAG + Bulletproof+, and returns a Cake-compatible signed transaction over animated QR for Cake to broadcast.
It deliberately mirrors Cake/CupCake’s existing offline-signing model (wallet2 payloads, bc-ur frames) rather than inventing a new protocol, so it interops with the public Cake build today. One real stagenet transaction has been signed on hardware, scanned back into Cake, and broadcast. Cake receives only the primary address, private view key, restore height, and synced key images. It never receives the spend key or device seed.
What it does
- Full CLSAG + Bulletproof+ RingCT signing via serai’s monero-wallet (monero-oxide), packaged as a wallet2 signed_tx_set.
- wallet2 decrypt → review → sign: CryptoNight-v0 key, Blake2b-HMAC auth, CBOR parse, then deterministic signing.
- Key-image sync (xmr-output → xmr-keyimage) computed on-device against official Monero test vectors.
- 16-word Polyseed recovery for Passport-derived wallets; import existing Polyseed or 24/25-word legacy seeds.
- Up to 16 wallet slots: device-derived (deterministic from app seed) or imported, sealed with XChaCha20-Poly1305.
- Animated-QR transport (bc-ur): xmr-txunsigned, xmr-txsigned, xmr-output, xmr-keyimage; static JSON pairing QR.
- Runs entirely on-device: vendored no_std cryptonight, deterministic ChaCha20Rng (no getrandom). Integrated-address and payment-ID destinations are rejected.
Technical breakdown
How the proof-of-concept is built, for developers evaluating the platform.
System model
Passport is the cold signer; Cake is the online watch-only wallet. Cake gets the primary address, private view key, restore height, and the key images Passport generates on sync. It never gets the spend key, the device seed, or any imported seed phrase. The flow is all animated QR: pairing JSON → Cake; xmr-output → Passport; xmr-keyimage → Cake; xmr-txunsigned → Passport; xmr-txsigned → Cake broadcasts.
The signing flow
src/monero/txset.rs decrypts the wallet2 unsigned_tx_set (chacha key = cn_slow_hash_v0(view_secret), Blake2b-HMAC verified) and parses sources, destinations, and fee for review. src/monero/signer.rs reconstructs serai OutputWithDecoys per input, seeds a deterministic ChaCha20Rng, calls SignableTransaction::sign() (RctType ClsagBulletproofPlus), and repackages the result as a wallet2 signed_tx_set, with an empty key_images vec, which Cake requires. It currently signs one transaction per set; multi-transaction wallet2 sets still need fixture coverage.
Wallets & seeds
Slot 0 matches the app’s core derivation; slots 1–15 are domain-separated children of the app seed, each recoverable as a 16-word Polyseed (birthday pinned to 2026-01-01). Legacy 24/25-word seeds import via the monero-seed crate and are sealed at rest with XChaCha20-Poly1305 under an app-seed-derived key.
Dig into the source
README, architecture notes, and the wire protocol live in the repo.

