feat(keepkey): on-device dice entropy on ResetDevice, with tests - #63
Conversation
…ccepts fsm_msgGetPublicKey selects the xpub version bytes from the script type and recognises only SPENDADDRESS, SPENDP2SHWITNESS and SPENDWITNESS; anything else falls through to "Invalid combination of coin and script_type". Sending SPENDTAPROOT therefore fails outright, so BTC account discovery dropped Taproot from the supported set and no P2TR account could be listed. BIP-86 account xpubs use the same serialization as legacy account xpubs, so SPENDADDRESS is the correct wire type for this message. P2TR is still sent on address and signing requests, where the firmware does handle it.
Adds an optional diceEntropy flag to core.ResetDevice and sets ResetDevice.dice_entropy when it is requested, so a host can ask the device to collect dice rolls with its own button before the seed is committed. The rolls never reach the host. That is the point: desktop-entered dice only help while the device's own randomness stays secret, so they are no defence against a compromised host combined with a weak device RNG. Entering them on the signer removes the host from the trust path entirely. Set only when requested -- firmware older than 7.15.0 has no such field and would reject an unknown one, so an unconditional set would break every existing device. device-protocol moves to the BitHighlander fork at tag dice-entropy-v1 (= cf308fd5e, the merge carrying ResetDevice.dice_entropy). A tag rather than a bare SHA because yarn resolves refs, not arbitrary commits. hdwallet-core and hdwallet-keepkey both build.
The vault's create-wallet flow offers on-device dice entropy, which needs ResetDevice to carry the diceEntropy flag (8cfd1fb). That commit was never merged with the clearsign/Ironwood work the vault pins today (5ed583d), so neither pin was a superset and the vault could not have both. Clean merge: adds the diceEntropy field to ResetDevice in hdwallet-core and the keepkey wallet, and keeps the Zcash/Ironwood surface intact.
The feature shipped with no tests. The property worth pinning is not that the flag can be set, but that ResetDevice.dice_entropy stays ABSENT unless asked for: the field only exists in firmware >= 7.15.0 and older firmware rejects a message carrying an unknown field, so setting it unconditionally would break wallet creation on every device already in the field. Covers set-when-requested, absent-when-omitted, absent-when-explicitly-false, a wire-format round trip (present-but-false is not the same as absent), and that the other reset fields still populate alongside it. Verified these fail for the right reason: replacing the guard with an unconditional setDiceEntropy(!!msg.diceEntropy) turns three of them red.
…nal fork The dice-entropy work pinned @keepkey/device-protocol at BitHighlander/device-protocol#dice-entropy-v1, which would have put a personal fork in the dependency chain of a published package the moment this reached master. It is no longer necessary: the same commit (cf308fd5) is already merged into keepkey/device-protocol on up/release-protocol — the branch the vault has tracked all along — so pin its current tip fbb483f1 instead. ResetDevice still carries dice_entropy, and ButtonRequest_DiceRoll is present in types. Verified by reinstall: yarn.lock moves only the device-protocol entry, and the full hdwallet-keepkey suite passes on the canonical package.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…ore it
The original guard rested on a false premise. Its comment claimed firmware
older than v7.15.0 "would reject an unknown field", so merely omitting
dice_entropy was treated as sufficient. nanopb does the opposite: unknown
fields are SKIPPED, not rejected — lib/transport/pb_decode.c:904, "No match
found, skip data", identical on the v7.14.1 tag.
So reset({ diceEntropy: true }) against pre-7.15 firmware SUCCEEDED. The
device ignored the field and produced an ordinary RNG-only seed while the
caller believed dice rolls had been folded in — a silent downgrade of a
security property that was explicitly requested, with no error anywhere.
Gate it instead: supportsDiceEntropy() reports whether the device honours the
field, reset() throws before sending when it does not, and an unreadable
firmware version reports false so the check fails closed. Naming the version
in the error means a caller on old firmware learns why rather than guessing.
Adds supportsDiceEntropy() as a public capability check so callers can hide
the option up front instead of discovering it at reset time. The vault already
routes around the dice step on old firmware; this makes the library safe for
callers that do not.
Verified the new tests fail for the right reason: removing the gate turns the
three downgrade cases red, including the one asserting no ResetDevice is sent.
Reported in review of #63.
|
Confirmed and fixed in b1b1420. Thanks — the premise the original guard rested on was simply wrong, and the consequence is worse than the comment implied. Verified the claim
So Fix
Coverage for the downgrade pathFour new cases, and the one that matters asserts no ResetDevice was sent — throwing after a successful reset would still have created the RNG-only wallet:
Plus a I verified these fail for the right reason: removing the gate restores the exact reviewed vulnerability and turns those three red while the other twelve stay green. 13 suites / 106 tests pass overall. On the vaultYou are right that it gates its UI — |
Adds the
diceEntropyflag tocore.ResetDeviceso a host can ask the device to collect dice rolls with its own button and fold them into the internal entropy before the seed is committed. The rolls never reach the host — that is the point: desktop-entered dice only help while the device's own randomness stays secret, so they are no defence against a compromised host combined with a weak device RNG.The vault's create-wallet flow needs this; without it the dice step cannot ship.
The property that matters
Not that the flag can be set, but that
ResetDevice.dice_entropystays absent unless requested. The field only exists in firmware >= 7.15.0, and older firmware rejects a message carrying an unknown field — so setting it unconditionally would break wallet creation on every device already in the field.reset-dice-entropy.test.tspins that with five cases: set-when-requested, absent-when-omitted, absent-when-explicitly-false, a wire-format round trip (present-but-false is not the same as absent), and that the other reset fields still populate alongside it.These were verified to fail for the right reason: replacing the guard with an unconditional
setDiceEntropy(!!msg.diceEntropy)turns three of them red.device-protocol pin
The feature branch pinned
@keepkey/device-protocolatBitHighlander/device-protocol#dice-entropy-v1, which would have put a personal fork in the dependency chain of a published package. That is unnecessary — the same commit is already merged intokeepkey/device-protocolonup/release-protocol, the branch the vault tracks — so this pins its current tipfbb483f1on the canonical repo instead.Also included
da50506bderives BIP-86 account xpubs withSPENDADDRESS.fsm_msgGetPublicKeypicks xpub version bytes from the script type and only recognises SPENDADDRESS / SPENDP2SHWITNESS / SPENDWITNESS, so sending SPENDTAPROOT fails outright and BTC account discovery dropped Taproot entirely. P2TR is still sent on address and signing requests, where firmware does handle it. Ships with its own coverage intaproot.test.ts.Testing
Full
hdwallet-keepkeysuite: 13 suites, 96 tests, all passing on the canonical device-protocol package.