coldcard: Implement register_descriptor
What changed, and why it matters
This commit adds a new feature to the Coldcard hardware wallet driver in the HWI library: the ability to register a Bitcoin output descriptor on the device. The code sends the descriptor to the Coldcard in chunks, verifies the upload with a SHA-256 checksum, and then asks the device to enroll/register it. There is no obvious security bug in the diff itself; it is a straightforward implementation of a missing driver method. The main security-relevant observation is that the new code path involves device communication and a checksum check, but the diff does not show any vulnerability.
Review the Coldcard protocol documentation to confirm that `multisig_enroll` is the correct command for descriptor registration and that the checksum/position assertions match the expected protocol. Consider adding bounds checks on descriptor/name size, validating the descriptor before transmission, and replacing `assert` statements with proper error handling for production robustness. No immediate security patch appears required based solely on this diff.
Security signals we found
New device communication path introduced (descriptor upload/enrollment)
SHA-256 checksum verification performed client-side against device-reported digest
Use of `assert` for protocol position checks (may crash on unexpected device behavior in non-optimized runs)
Empty `registration` bytes returned; reliance on device-side storage for registration proof
Simulator-specific keypress automation (`sim_keypress(b'y')`) included
Evidence from the diff
The patch implements register_descriptor in hwilib/devices/coldcard.py. It serializes a descriptor and name to JSON, uploads the bytes to the Coldcard in MAX_BLK_LEN chunks using CCProtocolPacker.upload, recomputes a SHA-256 digest over the sent bytes, compares it with the device’s digest via CCProtocolPacker.sha256, and finally calls CCProtocolPacker.multisig_enroll. For simulators, it also sends a keypress confirmation. The method returns a RegisteredDescriptor with an empty registration byte string. No input validation, length limits, or exception handling beyond the @coldcard_exception decorator are visible in the diff. The change is additive and does not modify existing signing or key-handling logic.
Changed components
hwilib/devices/coldcard.pyColdcard hardware wallet driverHWI descriptor registration APIInspect captured patch +42 / −1
### hwilib/devices/coldcard.py
@@ -9,7 +9,11 @@
Union,
)
-from ..descriptor import MultisigDescriptor
+from ..descriptor import (
+ Descriptor,
+ MultisigDescriptor,
+ RegisteredDescriptor,
+)
from ..hwwclient import HardwareWalletClient
from ..errors import (
ActionCanceledError,
@@ -59,6 +63,7 @@
import base64
import hid
import io
+import json
import sys
import time
import struct
@@ -415,6 +420,42 @@ def can_sign_taproot(self) -> bool:
"""
return self.is_edge
+ @coldcard_exception
+ def register_descriptor(self, name: str, descriptor: 'Descriptor') -> RegisteredDescriptor:
+ conf = {
+ "desc": descriptor.to_string(),
+ "name": name
+ }
+ conf_bytes = json.dumps(conf).encode()
+
+ stream = io.BytesIO(conf_bytes)
+ size = len(conf_bytes)
+
+ # Send the descriptor bytes
+ left = size
+ check = sha256()
+ for pos in range(0, size, MAX_BLK_LEN):
+ here = stream.read(min(MAX_BLK_LEN, left))
+ if not here:
+ break
+ left -= len(here)
+ result = self.device.send_recv(CCProtocolPacker.upload(pos, size, here))
+ assert result == pos
+ check.update(here)
+
+ # verify the send
+ expect = check.digest()
+ result = self.device.send_recv(CCProtocolPacker.sha256())
+ assert len(result) == 32
+ if result != expect:
+ raise DeviceFailureError(f"Wrong checksum, expected {expect.hex()}, got {result.hex()}")
+
+ # Register the descriptor
+ self.device.send_recv(CCProtocolPacker.multisig_enroll(size, expect), timeout=None)
+ if self.device.is_simulator:
+ self.device.send_recv(CCProtocolPacker.sim_keypress(b'y'))
+ return RegisteredDescriptor(name=name, descriptor=descriptor, device_type="coldcard", registration=b"")
+
def enumerate(password: Optional[str] = None, expert: bool = False, chain: Chain = Chain.MAIN, allow_emulators: bool = True) -> List[Dict[str, Any]]:
results = []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.