Most of what we write about here lives on the chain: training epochs, runtime upgrades, contract deploys, finality incidents. This post is about something that lives on your desk instead. It is the QV desktop wallet, a cold-storage wallet for Bitcoin, Lightning and QBC, and it is the piece of the network a person actually holds in their hands.
Before anything else, the honest framing: this is a private build, not a public release. It runs, it moves real value on paths we have tested end to end, and the people building it use it. It is not something we are handing to the public today, and this post is not a download link. It is an engineering account of what the wallet is, how it is put together, and the discipline we run it through before it earns anyone's trust. We would rather describe a private build accurately than announce a public one prematurely.
Why a desktop cold wallet at all
A cold wallet is one whose signing keys never touch a live, internet-facing service. The keys are generated on your machine, sealed on your disk, and used only to sign a transaction you have already inspected. The network can carry that signed transaction; it never sees the key.
For a post-quantum chain that matters twice over. QBC signs with lattice cryptography rather than the elliptic-curve signatures a large quantum computer is expected to break, so the wallet has to speak that language natively. And because the wallet holds Bitcoin and Lightning alongside QBC, it has to be a real multi-asset wallet, not a single-chain toy with two extra tabs bolted on.
The build is a Tauri v2 application: a Rust core that does all the cryptography, key handling and network work, with a thin Svelte interface on top. It is cross-compiled to Windows from Linux, ships as a single static binary with no external runtime to install, and stores everything under one encrypted vault.
One phrase, everything under it
The whole wallet derives from a single 24-word recovery phrase. That is the only secret a user has to keep, and it is enough to reconstruct every signing key the wallet uses, across every asset and every privacy tier, on a fresh machine. One thing a phrase cannot rebuild on its own is Lightning channel state, the live balances and revocation secrets inside an open channel; that state lives in the wallet's on-disk data, which is part of why we treat Lightning as the hot compartment described below.
- The transparent QBC signing key is an ML-DSA-87 lattice key derived deterministically from the phrase. Its address is a hash of the public key, and every transparent send is a lattice signature the live chain verifies with the exact same verifier the validators run.
- The Bitcoin keys come from the same phrase through the standard derivation paths, one per address type (more on that below).
- The confidential blinding factors, the values that hide amounts in shielded transactions, are derived from a phrase-based confidential seed rather than drawn from a random pool. That is a deliberate choice: a browser wallet that draws blindings randomly and stores them in local storage can lose them; a wallet that derives them from your phrase can always rebuild them.
Everything the wallet caches beyond the phrase, including its notebook of confidential outputs, is sealed at rest with Argon2id key stretching and XChaCha20-Poly1305 authenticated encryption. The raw key is re-derived in memory when you unlock, and it is wiped from memory when you lock. On Windows the sealed vault is additionally bound to the device, so a copied vault file alone is not enough.
Four tiers, from transparent to unlinkable
QBC has more than one privacy model, and the wallet supports the whole ladder, from fully transparent to fully unlinkable. Each tier has a different maturity, and we label them precisely rather than flattening them into one claim.
- Transparent, post-quantum. Amounts and parties are public, signatures are lattice-based. This is the plain path and it is live.
- Amount-hiding. Values are hidden behind Pedersen commitments and range proofs while parties stay visible. The wallet can shield a coin, split a note into a private payment plus private change, and unshield back to a transparent output. Amount-hiding is live and has been exercised with real value on the chain.
- Post-quantum note delivery. Each shielded output can carry an encrypted memo to its recipient using ML-KEM-768 key encapsulation, so a non-interactive private send can reach someone without a round trip, with the note itself authenticated by a lattice spend key. This tier is built into the wallet.
- Unlinkable pool. The most private tier uses a Plonky3 STARK circuit over a note-commitment tree with nullifiers, so a spend proves membership without revealing which note it spends. This is the frontier of the design. It is built and has been run with value on the chain inside a contained pool, and it is exactly the sort of deep cryptographic work that stays private and under review until it has earned a wider trust than a single team's word.
The wallet presents these as tiers a user can climb, not as a single button that overpromises. A transparent send is honest about being transparent. A shielded send is honest about what it hides and what it does not.
A real Bitcoin engine
The largest single piece of this build is the Bitcoin side, and it is where "not a toy with extra tabs" gets earned. The wallet uses a modern descriptor-based engine and supports the full spread of address types, each as its own sub-wallet with its own descriptor pair and its own derivation path:
- Native SegWit (BIP-84, the
bc1qaddresses), - Taproot (BIP-86, the
bc1paddresses), - Nested SegWit (BIP-49, the
3...addresses), - Legacy (BIP-44, the
1...addresses).
Balances aggregate across all of them; change always returns to an address the wallet itself owns, on an internal keychain, never to a recipient and never burned. Address parsing is bound to the configured network, so a testnet address cannot be paid from a mainnet wallet by accident.
On top of that sits the transaction machinery a serious Bitcoin user expects:
- Full Bitcoin Core integration, so you can point the wallet at your own node rather than a third-party server. All of that traffic runs through a transport that can go over Tor or a SOCKS proxy, and every response is bounded in size and in time, designed so that a hostile or broken node cannot wedge the wallet, a property the red-team waves exist to keep true.
- Replace-by-fee and child-pays-for-parent, so a stuck transaction can be bumped or unstuck without losing its outputs.
- Coin control and coin freezing, so you choose which coins fund a spend and can set specific coins aside so they are never selected automatically.
- Batch sends, many recipients in one transaction with a single change output.
- Full PSBT support, including combining partially signed transactions, so the wallet interoperates with hardware signers and air-gapped flows.
The engine is also mempool and chain aware, so fees and confirmations reflect the real state of the network rather than a guess.
Lightning, on your terms
Lightning is the one asset that cannot be cold. A payment channel has to stay online to route, so the wallet does not pretend otherwise; it treats Lightning as a bounded hot compartment sitting beside the cold paths. The wallet runs a Lightning node in-process, and you can open and close channels, receive against invoices, and pay invoices, all gated by a spend policy you control. There is a rolling treasury cap on how much can leave over Lightning in a window, the routing fee a payment can spend is bounded to exactly what the confirmation screen discloses, and every fund-moving action (opening a channel, closing one, paying an invoice) passes through the same confirmation gate as everything else.
Air-gapped, by QR
For people who want the signing machine to never touch a network at all, the wallet speaks animated QR. An unsigned Bitcoin transaction can be exported as a sequence of QR frames, carried to an offline machine, signed there, and carried back as another sequence, using the same standard format hardware wallets and Sparrow use. Receiving is a QR too. The post-quantum shielded address is large enough that it exceeds what a single QR can carry, so for that one case the wallet is deliberately copy-only rather than pretending a QR can hold it.
The security model: the screen you confirm is the transaction you sign
The most important idea in the wallet is small and strict. The interface is a web view, and we treat a web view as something that could be fully compromised. So no interface code is ever trusted to authorise a spend.
Every spend is built in the Rust core, and its exact details (destination, amount and fee) are frozen under a one-time token. The final confirmation is a native operating-system dialog, drawn outside the web view, showing the amount and destination recomputed from the very bytes that are about to be signed. Only after you approve that native dialog does the core sign the transaction it froze. A compromised interface can ask to spend; by design it cannot make the wallet sign something other than what the native dialog showed you, and cannot sign anything without that human gesture. Keeping that property true, on every path, is exactly what the red-team waves described below exist to verify.
Around that core idea sit the ordinary but essential hygiene properties: secret material lives in memory that is wiped on drop, keys are never written to logs or databases, an unattended wallet locks itself on an idle timeout and on the machine going to sleep, and a hard session cap closes it regardless of what the interface claims. The cold-path signing keys exist in memory only for the instant a signature is produced.
How we decide it is good enough: red-team until it stops finding things
None of the above is worth much if we only tested the happy path. So the way we harden this wallet is adversarial, and it is the part we are most willing to describe.
We freeze a build, then run wave after wave of focused attack reviews against it, each wave a fresh set of reviewers, each hunting a different way the wallet could betray its user: a transaction that displays one thing and signs another, an arithmetic edge that a hostile server could trigger, a crash or a hang reachable from the network, a secret that lingers where it should not, a way to move value that skips the confirmation. A finding does not just get patched. It resets the clock. The rule is that a frozen build has to survive several consecutive clean waves, with no genuine exploitable defect found, before it is allowed to ship at all. One real defect and the count goes back to zero on a new frozen build.
That discipline is unglamorous and it is the point. Two examples from the most recent cycle, both found by our own reviews and both fixed before this build was blessed:
- A remote hang in the Bitcoin Core reader. The code that reads a response from your Bitcoin node bounded the total time it would wait, but a lower-level read loop could still be fed one byte at a time, just inside each window, with no end-of-line, and spin far longer than the outer limit intended. A hostile node or a machine-in-the-middle on the connection could have used it to freeze the Bitcoin part of the wallet. The fix reads the response in a way that checks the deadline on every byte, so a trickling attacker now hits the wall in seconds.
- An arithmetic overflow on a hostile chain tip. A confirmation count was computed in a way that could overflow if a server reported an absurd, impossible block height. Harmless in this build, but exactly the kind of edge we would rather remove than reason about. It is now saturating arithmetic that cannot overflow at all.
Neither was catastrophic. That is the nature of a build that has already been through many rounds: what the later rounds find is narrow, and each round finds something narrower than the last, until the reviews converge on nothing. We keep going until they do.
What this is, and what it is not
To be exact about maturity, because we care about that here: the wallet is built. The transparent and amount-hiding paths are live and have moved real value through the exact code this build ships. The post-quantum delivery and unlinkable-pool tiers are built, and the deepest of them stays in private testing where deep cryptography belongs until it has earned more than an internal endorsement.
And it is private. We are not publishing it today. A wallet that holds keys is the least forgiving software there is, and we would rather run it hard in private, keep breaking our own build until the breaks stop coming, and describe the work honestly, than ship a public release a day before it deserves to be one.
When it is ready for your keys, we will say so plainly, the same way we are saying plainly today that it is not there yet. Until then, this is what a post-quantum cold wallet looks like while it is still being made worthy of the name.