From 180d853480d304f1058cf16159e549263f7e3955 Mon Sep 17 00:00:00 2001 From: scgbckbone Date: Mon, 25 Aug 2025 17:10:14 +0200 Subject: [PATCH 1/3] fix miniscript/multisig --- hwilib/devices/coldcard.py | 106 ++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 61 deletions(-) diff --git a/hwilib/devices/coldcard.py b/hwilib/devices/coldcard.py index e64f3619c..c6951ed42 100644 --- a/hwilib/devices/coldcard.py +++ b/hwilib/devices/coldcard.py @@ -126,72 +126,56 @@ def sign_tx(self, tx: PSBT) -> PSBT: """ self.device.check_mitm() - # Get this devices master key fingerprint - xpub = self.device.send_recv(CCProtocolPacker.get_xpub('m/0\''), timeout=None) - master_fp = get_xpub_fingerprint(xpub) - - # For multisigs, we may need to do multiple passes if we appear in an input multiple times - passes = 1 - for psbt_in in tx.inputs: - our_keys = 0 - for key in psbt_in.hd_keypaths.keys(): - keypath = psbt_in.hd_keypaths[key] - if keypath.fingerprint == master_fp and key not in psbt_in.partial_sigs: - our_keys += 1 - if our_keys > passes: - passes = our_keys - - for _ in range(passes): - # Get psbt in hex and then make binary - tx.convert_to_v0() - fd = io.BytesIO(base64.b64decode(tx.serialize())) - - # learn size (portable way) - sz = fd.seek(0, 2) - fd.seek(0) - - left = sz - chk = sha256() - for pos in range(0, sz, MAX_BLK_LEN): - here = fd.read(min(MAX_BLK_LEN, left)) - if not here: - break - left -= len(here) - result = self.device.send_recv(CCProtocolPacker.upload(pos, sz, here)) - assert result == pos - chk.update(here) - - # do a verify - expect = chk.digest() - result = self.device.send_recv(CCProtocolPacker.sha256()) - assert len(result) == 32 - if result != expect: - raise DeviceFailureError("Wrong checksum:\nexpect: %s\n got: %s" % (b2a_hex(expect).decode('ascii'), b2a_hex(result).decode('ascii'))) - - # start the signing process - ok = self.device.send_recv(CCProtocolPacker.sign_transaction(sz, expect), timeout=None) - assert ok is None - if self.device.is_simulator: - self.device.send_recv(CCProtocolPacker.sim_keypress(b'y')) - - print("Waiting for OK on the Coldcard...", file=sys.stderr) - - while 1: - time.sleep(0.250) - done = self.device.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None) - if done is None: - continue + # Get psbt in hex and then make binary + tx.convert_to_v0() + fd = io.BytesIO(base64.b64decode(tx.serialize())) + + # learn size (portable way) + sz = fd.seek(0, 2) + fd.seek(0) + + left = sz + chk = sha256() + for pos in range(0, sz, MAX_BLK_LEN): + here = fd.read(min(MAX_BLK_LEN, left)) + if not here: break + left -= len(here) + result = self.device.send_recv(CCProtocolPacker.upload(pos, sz, here)) + assert result == pos + chk.update(here) + + # do a verify + expect = chk.digest() + result = self.device.send_recv(CCProtocolPacker.sha256()) + assert len(result) == 32 + if result != expect: + raise DeviceFailureError("Wrong checksum:\nexpect: %s\n got: %s" % (b2a_hex(expect).decode('ascii'), b2a_hex(result).decode('ascii'))) + + # start the signing process + ok = self.device.send_recv(CCProtocolPacker.sign_transaction(sz, expect), timeout=None) + assert ok is None + if self.device.is_simulator: + self.device.send_recv(CCProtocolPacker.sim_keypress(b'y')) + + print("Waiting for OK on the Coldcard...", file=sys.stderr) + + while 1: + time.sleep(0.250) + done = self.device.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None) + if done is None: + continue + break - if len(done) != 2: - raise DeviceFailureError('Failed: %r' % done) + if len(done) != 2: + raise DeviceFailureError('Failed: %r' % done) - result_len, result_sha = done + result_len, result_sha = done - result = self.device.download_file(result_len, result_sha, file_number=1) + result = self.device.download_file(result_len, result_sha, file_number=1) - tx = PSBT() - tx.deserialize(base64.b64encode(result).decode()) + tx = PSBT() + tx.deserialize(base64.b64encode(result).decode()) return tx From 4217b1e06e8173269c2e523cfcc2e8c0897d5abc Mon Sep 17 00:00:00 2001 From: scgbckbone Date: Mon, 25 Aug 2025 17:50:09 +0200 Subject: [PATCH 2/3] remove coldcard multisig patch --- .github/actions/install-sim/action.yml | 1 - ci/cirrus.Dockerfile | 1 - test/setup_environment.sh | 2 -- 3 files changed, 4 deletions(-) diff --git a/.github/actions/install-sim/action.yml b/.github/actions/install-sim/action.yml index abfbded13..443a0b74e 100644 --- a/.github/actions/install-sim/action.yml +++ b/.github/actions/install-sim/action.yml @@ -27,7 +27,6 @@ runs: git config --global user.name "ci" pushd test/work; git clone --recursive https://github.com/Coldcard/firmware.git; popd tar -xvf coldcard-mpy.tar.gz - pushd test/work/firmware; git am ../../data/coldcard-multisig.patch; popd poetry run pip install -r test/work/firmware/requirements.txt pip install -r test/work/firmware/requirements.txt diff --git a/ci/cirrus.Dockerfile b/ci/cirrus.Dockerfile index 17d6752d8..77a7b1f24 100644 --- a/ci/cirrus.Dockerfile +++ b/ci/cirrus.Dockerfile @@ -74,7 +74,6 @@ RUN protoc --version ## Set up environments first to take advantage of layer caching #RUN mkdir test #COPY test/setup_environment.sh test/setup_environment.sh -#COPY test/data/coldcard-multisig.patch test/data/coldcard-multisig.patch ## One by one to allow for intermediate caching of successful builds #RUN cd test; ./setup_environment.sh --trezor-1 #RUN cd test; ./setup_environment.sh --trezor-t diff --git a/test/setup_environment.sh b/test/setup_environment.sh index cb4600fe8..89e2874bc 100755 --- a/test/setup_environment.sh +++ b/test/setup_environment.sh @@ -148,8 +148,6 @@ if [[ -n ${build_coldcard} ]]; then coldcard_setup_needed=true fi fi - # Apply patch to make simulator work in linux environments - git am ../../data/coldcard-multisig.patch # Build the simulator. This is cached, but it is also fast poetry run pip install -r requirements.txt From 8036afed3a77fe0df9c948a6c80cd5f17a6fd25b Mon Sep 17 00:00:00 2001 From: scgbckbone Date: Mon, 25 Aug 2025 18:11:06 +0200 Subject: [PATCH 3/3] fdfd --- hwilib/devices/coldcard.py | 113 ++++++++++++++++++++++--------------- 1 file changed, 69 insertions(+), 44 deletions(-) diff --git a/hwilib/devices/coldcard.py b/hwilib/devices/coldcard.py index c6951ed42..05384415d 100644 --- a/hwilib/devices/coldcard.py +++ b/hwilib/devices/coldcard.py @@ -126,56 +126,81 @@ def sign_tx(self, tx: PSBT) -> PSBT: """ self.device.check_mitm() - # Get psbt in hex and then make binary - tx.convert_to_v0() - fd = io.BytesIO(base64.b64decode(tx.serialize())) - - # learn size (portable way) - sz = fd.seek(0, 2) - fd.seek(0) - - left = sz - chk = sha256() - for pos in range(0, sz, MAX_BLK_LEN): - here = fd.read(min(MAX_BLK_LEN, left)) - if not here: - break - left -= len(here) - result = self.device.send_recv(CCProtocolPacker.upload(pos, sz, here)) - assert result == pos - chk.update(here) - - # do a verify - expect = chk.digest() - result = self.device.send_recv(CCProtocolPacker.sha256()) - assert len(result) == 32 - if result != expect: - raise DeviceFailureError("Wrong checksum:\nexpect: %s\n got: %s" % (b2a_hex(expect).decode('ascii'), b2a_hex(result).decode('ascii'))) - - # start the signing process - ok = self.device.send_recv(CCProtocolPacker.sign_transaction(sz, expect), timeout=None) - assert ok is None - if self.device.is_simulator: - self.device.send_recv(CCProtocolPacker.sim_keypress(b'y')) + # Get this devices master key fingerprint + xpub = self.device.send_recv(CCProtocolPacker.get_xpub('m/0\''), timeout=None) + master_fp = get_xpub_fingerprint(xpub) - print("Waiting for OK on the Coldcard...", file=sys.stderr) + # For multisigs, we may need to do multiple passes if we appear in an input multiple times + passes = 1 - while 1: - time.sleep(0.250) - done = self.device.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None) - if done is None: - continue - break + ver = None + try: + ver = self.device.send_recv(CCProtocolPacker.version()) + except: pass + if not ver or ("X" not in ver.split("\n")[1]): + # is not EDGE firmware, to sign multiple keys + # from same origin, signing must be repeated + # EDGE can sign all in one sitting + for psbt_in in tx.inputs: + our_keys = 0 + for key in psbt_in.hd_keypaths.keys(): + keypath = psbt_in.hd_keypaths[key] + if keypath.fingerprint == master_fp and key not in psbt_in.partial_sigs: + our_keys += 1 + if our_keys > passes: + passes = our_keys + + for _ in range(passes): + # Get psbt in hex and then make binary + tx.convert_to_v0() + fd = io.BytesIO(base64.b64decode(tx.serialize())) + + # learn size (portable way) + sz = fd.seek(0, 2) + fd.seek(0) + + left = sz + chk = sha256() + for pos in range(0, sz, MAX_BLK_LEN): + here = fd.read(min(MAX_BLK_LEN, left)) + if not here: + break + left -= len(here) + result = self.device.send_recv(CCProtocolPacker.upload(pos, sz, here)) + assert result == pos + chk.update(here) + + # do a verify + expect = chk.digest() + result = self.device.send_recv(CCProtocolPacker.sha256()) + assert len(result) == 32 + if result != expect: + raise DeviceFailureError("Wrong checksum:\nexpect: %s\n got: %s" % (b2a_hex(expect).decode('ascii'), b2a_hex(result).decode('ascii'))) + + # start the signing process + ok = self.device.send_recv(CCProtocolPacker.sign_transaction(sz, expect), timeout=None) + assert ok is None + if self.device.is_simulator: + self.device.send_recv(CCProtocolPacker.sim_keypress(b'y')) + + print("Waiting for OK on the Coldcard...", file=sys.stderr) + + while 1: + time.sleep(0.250) + done = self.device.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None) + if done is None: + continue + break - if len(done) != 2: - raise DeviceFailureError('Failed: %r' % done) + if len(done) != 2: + raise DeviceFailureError('Failed: %r' % done) - result_len, result_sha = done + result_len, result_sha = done - result = self.device.download_file(result_len, result_sha, file_number=1) + result = self.device.download_file(result_len, result_sha, file_number=1) - tx = PSBT() - tx.deserialize(base64.b64encode(result).decode()) + tx = PSBT() + tx.deserialize(base64.b64encode(result).decode()) return tx