The wallet on your phone
Your phone matches payments locally, keeps its keys in the device keychain, and connects to Bitcoin peers, directly or through your own Tor and I2P gateways, while the app is open and in short background checks iOS allows.
What this means for you
Unexpected payments may appear on the next sync. Fees are estimates, and a backup restores signing only when it holds this phone’s words, which the iCloud copy always does; see what a backup cannot restore. Direct connections expose your IP address to peers; your own Tor or I2P gateways can carry peer connections instead. The read guide, payment guide and recovery guide explain these tradeoffs.
2. What a phone actually is
Constraints that are load-bearing, not stylistic:
- Outbound TCP only. The app talks to full-node peers over Lean’s own TCP, on libuv. No public inbound listener. Backgrounding stops networking; a background check iOS starts builds it again for one bounded scan. Peers are a small outbound pool (default 3): manual endpoints first, then a persisted good-peers list, then the clearnet peers of the census list bundled with this build, then DNS-seed results — seeds resolve over DNS-over-HTTPS (Cloudflare dns-json at 1.1.1.1, configurable, fetched through the shell’s HTTPS capability because Lean has no TLS) and fall back to
getaddrinfoif DoH is empty or down; RFC1918/loopback answers are dropped on public networks. Dials race a batch of candidates at a time with a short per-attempt timeout, and report exhaustion instead of spinning forever. Peer routing (Automatic, Direct only or Manual) decides which networks are eligible; DNS seeds are used only when clearnet is, and Tor or I2P candidates only through their gateway. Each build ships the census list of its day; the app downloads no census and has no refresh for it. Saved-peer and DNS discovery continue independently. With DNS as the only source, the diversity policy permits two of the three default slots. No addr gossip, no scoring buckets — a misbehaving peer is dropped and replaced. - Foreground, plus short background checks. Winnow syncs while open, and asks iOS for background checks (
BGAppRefreshTaskandBGProcessingTask, at most every 15 minutes). Each is one scan of up to 2,000 blocks inside the time iOS grants, about 25 seconds for a refresh and up to two minutes for processing, with the foreground's routing, peers and checks; then networking stops. iOS decides when and whether they run; they do not run while the phone is off, after Winnow is force-quit, or with Background App Refresh off. Automatic routing that last reached peers through a gateway skips a check when no gateway answers rather than connect directly with no banner to say so. There is no standing connection and no push: push notifications would require a server that knows your addresses or your txids. A payment you are not looking at is found at the next check or the next open. - This-device keychain. Secrets live in the iOS Keychain as
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, with iCloud Keychain sync off. They are not in the app’s ordinary JSON state or device backup. Automatic iCloud backup creates a separate encrypted copy after device authentication unless disabled in Advanced; its wrapping key synchronizes through iCloud Keychain. The app asks for device-owner authentication (passcode, Face ID or Touch ID) before it reads the secret, and fails closed on a device with no passcode. Unlike the Swift app, the Keychain item carries no user-presence flag of its own, so this gate rests on the app’s code. Recovery words appear only behind an explicit Advanced control (Back up → Show recovery phrase). That screen offers an explicit copy action; its pasteboard item is local-only, expires after two minutes, and the screen says paper is more private. Signing loads the secret for the call. - Cryptography and transport dependencies. Curve math (key derivation, Taproot tweaks, BIP340 signing) is Winnow’s own secp256k1 in Lean,
Lean/Secp, with proofs that tie it to a specification curve. Hashes, AES-GCM, filters, headers, PSBT, descriptors and Bitcoin P2P are Lean too. The other code in the app is Lean’s runtime and standard library, libuv, GMP and LeanNative’s Objective-C shell. - App Store reality. iOS 17 or later. The app targets iPhone and iPad; Apple silicon Macs run the iPad app (“Designed for iPad”), and there is no Mac Catalyst build. Wallet and protocol behavior lives in the
WinnowLean library underLean/; the app’s model, update and view are inLeanApp/WinnowApp. Lean test executables check both, and recorded iPhone journeys check the interface against a regtest node.
Bandwidth and battery are real but secondary. Steady-state compact filters are a few megabytes a day (approx.; see read-side §2.5). The worse mobile cost is policy: an always-on mempool subscription, a historical back-scan, or a server that answers getbalance. Those are refused even when the radio would allow them.
3. The four jobs, and two more
| Job | Default on this phone | Paper |
|---|---|---|
| What’s mine? | BIP157/158 filters, matched on-device, from creation height forward | read-side |
| Where’s the tip? | 80-byte headers, PoW + chainwork | read-side |
| What fee? | User override, else median of our confirmed spends, else presets; raised to the BIP133 feefilter floor most peer seats report, with an automatic increase capped at 120 sat/vB |
write-side |
| Did my tx get out? | inv → getdata → tx to the peer pool, rebroadcast until a filter match, echo-watch while Send is open |
write-side |
| Who holds the keys? | This-device Keychain; Taproot key-path for the everyday wallet | this paper, write-side |
| Shared custody? | Raw k-of-n sortedmulti_a script-path accounts in Advanced mode, coordinated by PSBT, no server; a Ledger can sign over Bluetooth; no MuSig2 |
multisig accounts |
| Moving a wallet in? | A history bundle. There is no back-scan. | import |
One user-controlled escape hatch crosses several rows: a selected external explorer website. Opening an exact address or transaction requires a warning; its response never becomes wallet state.
4. What the device forces onto the wallet
4.1 Fresh, and forward-only
A new wallet and its recovery phrase are created immediately, offline. Header synchronization and automatic backup continue after onboarding; the wallet starts scanning from the last already-validated height so the interval cannot be missed. Recovering an old wallet privately from the chain would mean gigabytes of historical filters on a radio the user pays for. The product does not do that. Import requires the previous wallet’s answers to come along — descriptor, UTXOs, history, a last-known height — and the phone verifies them by scanning forward from that height. The argument and the bundle format are import.
This is the constraint that makes the rest cheap. It is not a v1 shortcut.
4.2 Taproot only
Receiving is BIP86 P2TR. There is no legacy / nested-segwit / P2WPKH path, and no ECDSA signing path. One output type means one watch-list shape, one sighash, one witness size for fee math (66 bytes for a key-path spend), and no “which address type did this contact use?” branch in the UI. The proposed P2WSH Safe is a deliberately scoped exception: an opt-in wsh(pk(KEY)) store with its own hardened keys, its own watch list and an ECDSA/BIP143 signing path, added beside the Taproot account rather than in place of it. Nothing about the account described here changes; see the Safe’s design and release criteria.
The phone can pay standard P2PKH, P2SH, P2WPKH, P2WSH and P2TR addresses. Recovery supports descriptor-derived coins; wallet files requiring unsupported signing data are refused. Keep the original wallet and backup for any unsupported coin types. See import and recovery.
4.3 Multisig accounts, same read path
A multisig account is another descriptor and another watch list, scanned with the same filters. The phone does not run a coordinator, a coinjoin server, or a notification service for co-signers. The PSBT is the coordinator, and anything that carries text carries it. A paired Ledger is reached over Bluetooth instead: it registers the account, shows addresses and payments on its own screen, and returns a signature the phone verifies before keeping it. Each signer adds an ordinary script-path signature, in any order. A backup that carries a MuSig2 account from the older Swift app is refused rather than restored without those funds. Multisig accounts.
4.4 Mainnet by default, signet for testing
The app opens on mainnet by default; signet is available in Advanced settings with its own wallet state. CI’s UI journeys and their video use a disposable regtest node. The numbers in screenshots/timings.json are historical signet measurements.
5. Trust on a phone
What the user is asked to trust, stated as mechanisms rather than intentions:
- The keychain and the device. The default backup file excludes recovery words. An export made with Include recovery phrase contains your phone’s signing secret. Protect that file like the words themselves. Anyone able to unlock your phone may be able to authorize signing or secret access.
- Winnow’s secp256k1. Curve math on secrets is Winnow’s own Lean code,
Lean/Secp. Its proofs are about the answers, not the timing: it computes with GMP-backed numbers and its scalar multiplication branches on each bit of a secret, so signing time depends on the key. That is acceptable for signing the owner approves on their own phone, where no one else can trigger or time it; the code is not for servers or signing services (security notes). Each BIP340 signature takes 32 fresh bytes of auxiliary randomness. - The peers you can reach. Compact filters are not consensus-committed. Multi-peer
cfcheckpt/cfheaderscomparison detects disagreement; it cannot prove which peer lied, and a fully eclipsed phone can be shown a consistent lie. Manual peers exist so a user who has a node they trust can skip DNS seeds. The residual risk is read-side §2.7.1 and §2.9. - No one, on the read path, by default. No server is handed an address, an xpub, or a scripthash. A network observer sees a compact-filter client syncing from a height — the same bytes every such client downloads.
What the user is not asked to trust: an indexer operator’s retention policy or a push-notification vendor. Automatic iCloud backup adds trust in the Apple Account and iCloud Keychain recovery path. The app asks for device authentication and keeps the backup preference. Wallet, protocol and crypto code is the Winnow library under Lean/, which does not depend on the app; what only iOS can do goes through the shell’s declared capabilities. The Objective-C shell and iOS are trusted, not proven.
6. Deliberate absences
These are product decisions, not missing tickets:
- Always-on mempool / background 0-conf / live fee histograms
- Historical filter back-scan
- BIP37, Electrum, or any “ask a node about my address”
- An embedded consensus engine or fraud-proof verifier
- A multisig coordinator server
The focused guides explain the consequences: delayed payment discovery, coarser fees, a required history backup, and transaction disclosure to relay peers.
7. How the implementation is checked
The validation appendix of the read side covers the stack. CI runs on every pull request and push to main. It builds the Lean once, checking every proof, then runs the Lean test executables, a regtest stage (io-test, backend-test and a differential check of multisig accounts against Bitcoin Core) and the Objective-C UI journeys in an iPhone simulator against a disposable regtest node: create, receive, send and confirm; pair a simulated Ledger, verify an address and spend; a 2-of-3 with two Bitcoin Core wallets, then its recovery path; the shipped census list; a silent payment. Each Debug, Release and device build is checked for Swift. Guide illustrations and screenshots/timings.json retain their historical provenance; they are not current-run evidence.
The journeys record a video and screenshots, which this site takes from the newest successful run on main; the recording keeps its source and run.
8. Status
The app defaults to mainnet and also supports signet. Current signing choices are ordinary Taproot and script-path k-of-n accounts. Silent payments can be sent, not received. Planned features remain on the roadmap.