feat(core): add human-assisted wallet entropy - #237
Draft
BullishNode wants to merge 8 commits into
Draft
Conversation
- SHA-512 mix/extract pool modelled on Bitcoin Core's RNGState::MixExtract: every source is concatenated and hashed together with the previous state (strictly additive - no source can ever reduce pool entropy), the first half of each digest is the output and the second half becomes the next secret state, with a 10ms strengthening pass on extraction - Mandatory floor: extraction refuses unless both the OS RNG (Random.secure) and a bdk RNG draw were mixed since the last extract, so seeds are never weaker than the platform CSPRNG. The two mandatory sources reach the kernel through independent bindings (Dart vs Rust getrandom) - Best-effort additive sources: CPU clock jitter (jitterentropy-style busy loop on a worker isolate), process/system stats, and IMU sensor noise via sensors_plus - New onboarding entropy ceremony: the user drags a finger on screen (VeraCrypt-style) and every raw pointer sample is mixed into the pool, with a minimalist trail, hint animation, hairline progress bar and milestone messages every 10% - New wallets are 24 words (256-bit entropy) via Mnemonic.fromEntropy; import, recovery and BIP85 paths are unchanged - Tests: known-answer vectors against an independent spec implementation, additivity property with adversarial sources, mandatory-source gating, collector failure policy, ceremony cubit pacing
- iOS: add NSMotionUsageDescription (sensors_plus accesses motion data; missing key crashes the app on first sensor read) - Bump sensors_plus 6.1.2 -> 7.1.0: 6.x never stopped the iOS magnetometer on stream cancellation (its cancel handler called stopDeviceMotionUpdates instead of stopMagnetometerUpdates) - Pause sensor sampling while the app is backgrounded via a lifecycle observer on the ceremony screen - Make the mandatory entropy gate unspoofable: only the new pool.mixMandatory (validated source identity + >=32-byte minimum) can satisfy the security floor; the UI-facing supplemental mix path never can, whatever source name it passes - Serialize mnemonic generation: collect -> mandatory mix -> extract is one queued transaction, so concurrent calls cannot interleave pool state - Memory hygiene: zeroize entropy and the bdk draw on every exit path (try/finally), dispose FFI mnemonic handles explicitly, wipe mixer input buffers and strengthening intermediates, wipe collector output after mixing, build OS RNG bytes without an intermediate list - Neutralize hardcoded '12 words' user copy in backup/recovery screens: new wallets are 24 words and the old copy instructed users to write down half their mnemonic - Accessibility: ceremony canvas gets a semantic label; taps count toward progress; after 20s a 'Continue without drawing' fallback appears so users who cannot perform gestures can still create a wallet (ceremony input is supplemental; the RNG floor is enforced at extraction) - Known-answer tests now pin extraction outputs to vectors computed by an independent Python hashlib implementation of the specification, plus gate-spoofing and short-read regression tests - Correct overclaiming doc comments: the two mandatory sources share the OS entropy root (thread_rng is userspace, OS-reseeded); they provide binding-diversity, not independent roots, and additivity is computational under SHA-512 assumptions
Source-level invariants that fail loudly instead of silently when a refactor introduces the RNG failure classes seen in the Coldcard firmware disclosure (predictable fallback binding, narrow reseed pipe, call-site drift): - fresh mnemonic generation has exactly one call site - only the generator and locator may reach the entropy pool from outside the entropy module - only the collector and generator may feed the mandatory gate - no non-secure Random anywhere in entropy or seed modules - production wiring cannot override the pool's strengthening budget
Dart's Random.secure is implemented by the Flutter engine registering dart::bin::GetEntropy, which reads /dev/urandom directly on both Android and iOS (runtime/bin/crypto_linux.cc and crypto_macos.cc in the Dart SDK) - not getrandom(2) or SecRandomCopyBytes as previously stated. Failure of the open/read throws with no fallback, so the fail-closed property is unchanged; only the mechanism description was wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Disclaimer
We have no reason to believe that BDK has any RNG or entropy-generation defect. This is an experimental hardening project intended to reduce reliance on any single entropy path and to reduce attack surface. It is not a response to an ongoing incident, known vulnerability, or suspected defect in BDK.
Scope boundary
This is the production finger-entropy implementation only. It ends at
3523d5fdcand deliberately excludes theBB_ENTROPY_CAPTUREresearch harness, logcat gesture-transcript capture, direct debug routing, debug APK, and draft debug release. Those remain isolated onresearch/entropy-capture-debugin this fork.The branch is based directly on upstream
SatoshiPortal/developatad6c1b559after fast-forwarding this fork'sdevelopto the same commit. It mirrors the production code in upstream draft PR SatoshiPortal/bullbitcoin-mobile#2547.Summary
Mnemonic.fromEntropyencoding; do not invoke BDK's random mnemonic constructor.Why
The goal is defense in depth against a hypothetical defect or supply-chain compromise affecting one randomness path. The human transcript is a physically distinct, deliberately uncredited input: it may preserve meaningful unpredictability if the OS RNG becomes predictable, but the implementation does not claim a fixed number of entropy bits from finger movement.
Five hundred is a conservative pacing threshold, not an assertion of 500 bits or any fixed per-event contribution. Pointer observations are correlated and device pipelines differ, so the gate rejects known low-value callbacks while the cryptographic pool mixes the complete accepted transcript.
Motion sensors were deliberately excluded after considering privacy and complexity. Their incremental entropy is difficult to quantify, they expand permissions and lifecycle behavior, and prior plugin defects demonstrate the reliability surface they add. The explicit finger ceremony supplies the independently motivated physical input without introducing another permission or dependency.
This is not designed to protect against a malicious application or runtime that can observe or replace both the OS draw and the pointer transcript.
Verification
make checksflutter analyze --fatal-warnings --fatal-infos— no issuesdart fix --dry-run—Nothing to fix!bull_uitests passedhashlibknown-answer vector for the 500-sample SHA-512 pool constructionDraft checklist