Parse unknown yielded tags in python client for sign_psbt
What changed, and why it matters
This is a small defensive update to the Python companion library for Ledger's Bitcoin app. It changes how the client handles unexpected data returned by a future version of the hardware app, so that the data is labeled as 'unknown' rather than being misread as a transaction input number. It is a robustness/future-proofing fix, not a patch for an active security flaw.
No urgent action. Users of the Python client can update to benefit from cleaner handling of future app versions. Developers should ensure downstream callers handle UnknownSignPsbtYieldedObject gracefully.
Security signals we found
Forward-compatibility hardening for protocol parsing
Prevents misinterpretation of future yielded tags as input indices
No bounds/safety logic changes to device-side signing
Client-only change; no device firmware changes
Evidence from the diff
The commit adds an UnknownSignPsbtYieldedObject dataclass and updates _decode_signpsbt_yielded_value in the Python client. When the first value of a yielded response is 0x80000000 or greater, the code now treats it as a forward-compatible tag, reads the actual input index from the buffer, and returns the remaining bytes as an opaque unknown object. Previously, such a tag would have been interpreted as a raw input index, producing a nonsensical enormous index. The Rust client already used this approach.
Changed components
bitcoin_client/ledger_bitcoin/client.pybitcoin_client/ledger_bitcoin/client_base.pybitcoin_client/ledger_bitcoin/__init__.pyInspect captured patch +19 / −3
diff --git a/bitcoin_client/ledger_bitcoin/__init__.py b/bitcoin_client/ledger_bitcoin/__init__.py
index 9a2a278..5cf2f55 100644
--- a/bitcoin_client/ledger_bitcoin/__init__.py
+++ b/bitcoin_client/ledger_bitcoin/__init__.py
@@ -1,7 +1,7 @@
"""Ledger Nano Bitcoin app client"""
-from .client_base import Client, TransportClient, PartialSignature, MusigPubNonce, MusigPartialSignature, SignPsbtYieldedObject
+from .client_base import Client, TransportClient, PartialSignature, MusigPubNonce, MusigPartialSignature, SignPsbtYieldedObject, UnknownSignPsbtYieldedObject
from .client import createClient
from .common import Chain
@@ -16,6 +16,7 @@ __all__ = [
"MusigPubNonce",
"MusigPartialSignature",
"SignPsbtYieldedObject",
+ "UnknownSignPsbtYieldedObject",
"createClient",
"Chain",
"AddressType",
diff --git a/bitcoin_client/ledger_bitcoin/client.py b/bitcoin_client/ledger_bitcoin/client.py
index 2764dea..16fa04a 100644
--- a/bitcoin_client/ledger_bitcoin/client.py
+++ b/bitcoin_client/ledger_bitcoin/client.py
@@ -11,7 +11,7 @@ from .embit.networks import NETWORKS
from .command_builder import BitcoinCommandBuilder, BitcoinInsType
from .common import Chain, read_uint, read_varint
from .client_command import ClientCommandInterpreter, CCMD_YIELD_MUSIG_PARTIALSIGNATURE_TAG, CCMD_YIELD_MUSIG_PUBNONCE_TAG
-from .client_base import Client, MusigPartialSignature, MusigPubNonce, SignPsbtYieldedObject, TransportClient, PartialSignature
+from .client_base import Client, MusigPartialSignature, MusigPubNonce, SignPsbtYieldedObject, UnknownSignPsbtYieldedObject, TransportClient, PartialSignature
from .client_legacy import LegacyClient
from .exception import DeviceException
from .errors import UnknownDeviceError
@@ -145,6 +145,11 @@ def _decode_signpsbt_yielded_value(res: bytes) -> Tuple[int, SignPsbtYieldedObje
partial_signature=partial_signature
)
)
+ elif input_index_or_tag >= 0x80000000:
+ # this is certainly an unknown tag added by a future version of the app
+ # we expect the first element to be the input index, and the rest of the data is opaque
+ input_index = read_varint(res_buffer)
+ return input_index, UnknownSignPsbtYieldedObject(input_index_or_tag, res_buffer.read())
else:
# other values follow an encoding without an explicit tag, where the
# first element is the input index. All the signature types are implemented
diff --git a/bitcoin_client/ledger_bitcoin/client_base.py b/bitcoin_client/ledger_bitcoin/client_base.py
index d7b9461..dcc9527 100644
--- a/bitcoin_client/ledger_bitcoin/client_base.py
+++ b/bitcoin_client/ledger_bitcoin/client_base.py
@@ -120,8 +120,18 @@ class MusigPartialSignature:
partial_signature: bytes
+@dataclass(frozen=True)
+class UnknownSignPsbtYieldedObject:
+ """Represents an unknown object returned by sign_psbt, for forward compatibility.
+
+ It contains the tag and the opaque bytes returned by the device.
+ """
+ tag: int
+ data: bytes
+
+
SignPsbtYieldedObject = Union[PartialSignature,
- MusigPubNonce, MusigPartialSignature]
+ MusigPubNonce, MusigPartialSignature, UnknownSignPsbtYieldedObject]
class Client:
Why this scored 19/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.