ledger: support BIP388 policy signing
What changed, and why it matters
This commit updates the Ledger hardware wallet support in HWI so that newer Ledger devices can sign Bitcoin transactions using registered BIP388 wallet policies. Previously, any attempt to use registered descriptors with a Ledger was blocked with an error. The change removes that blanket block for modern Ledger apps and adds logic to reconstruct each registered wallet policy and include its registration proof when signing. Inferred policies still work as before. This is a feature addition, not a fix for an active vulnerability, but it touches security-sensitive signing code.
Review the WalletPolicy reconstruction logic for correctness, ensure the registration HMAC is validated before signing, and confirm that descriptor-derived address types cannot be manipulated to downgrade security. Consider adding tests covering registered-policy signing on both legacy and modern Ledger clients.
Security signals we found
Change removes an explicit error path for registered descriptors, increasing supported functionality
Adds reconstruction of registered wallet policies and propagation of registration HMACs during PSBT signing
Touches hardware-wallet signing path where incorrect policy handling could lead to signing unintended transactions
No explicit bug fix or vulnerability disclosure in commit message or diff
Evidence from the diff
The patch modifies hwilib/devices/ledger.py. It replaces a blanket rejection of registered_descriptors on Ledger with a narrower check that only rejects them on LegacyClient. It then iterates over registered descriptors, builds WalletPolicy objects from their BIP388 templates and key info, determines their address type, and stores them in a wallets dictionary keyed by wallet policy ID, carrying the registration HMAC. This enables BIP388 policy signing on current Ledger apps while keeping legacy behavior unchanged.
Changed components
hwilib/devices/ledger.pyLedger hardware wallet signing flowBIP388 registered descriptor handlingInspect captured patch +21 / −2
### hwilib/devices/ledger.py
@@ -207,8 +207,8 @@ def sign_tx(
- Only keys derived with standard BIP 44, 49, 84, and 86 derivation paths are supported for single signature addresses.
"""
- if registered_descriptors:
- raise UnavailableActionError("The Ledger does not support BIP388 policy signing")
+ if registered_descriptors and isinstance(self.client, LegacyClient):
+ raise UnavailableActionError("Legacy Ledger app does not support BIP388 policy signing")
master_fp = self.get_master_fingerprint()
def legacy_sign_tx() -> PSBT:
@@ -234,6 +234,25 @@ def legacy_sign_tx() -> PSBT:
# Figure out which wallets are signing
wallets: Dict[bytes, Tuple[int, AddressType, WalletPolicy, Optional[bytes]]] = {}
+ for registered_descriptor in sorted(
+ registered_descriptors or set(),
+ key=lambda registration: registration.serialize(),
+ ):
+ descriptor = registered_descriptor.descriptor
+ registered_wallet = WalletPolicy(
+ registered_descriptor.name,
+ descriptor.get_bip388_template(),
+ [p.get_bip388_key_info() for p in descriptor.get_pubkey_providers()],
+ )
+ registered_addrtype = descriptor.get_address_type()
+ if registered_addrtype is None:
+ raise BadArgumentError("Registered descriptor does not have an address type")
+ wallets[registered_wallet.id] = (
+ signing_priority[registered_addrtype],
+ registered_addrtype,
+ registered_wallet,
+ registered_descriptor.registration,
+ )
pubkeys: Dict[int, bytes] = {}
for input_num, psbt_in in builtins.enumerate(psbt2.inputs):
utxo = NoneWhy this scored 31/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.