Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/build-bitcoind/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ runs:
path: ${{ env.CCACHE_DIR }}
key: ${{ github.job }}-ccache-${{ github.run_id }}

- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: bitcoind
path: bitcoind.tar.gz
11 changes: 8 additions & 3 deletions .github/actions/build-sim/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ runs:
- name: Install dependencies
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y gcc-arm-linux-gnueabihf libsdl2-image-dev libslirp-dev libpcsclite-dev ninja-build libltdl-dev
apt_options=(
-o Acquire::Retries=3
-o Acquire::http::Timeout=30
-o Acquire::https::Timeout=30
)
timeout 5m sudo apt-get "${apt_options[@]}" update
timeout 30m sudo apt-get "${apt_options[@]}" install -y gcc-arm-linux-gnueabihf libsdl2-image-dev libslirp-dev libpcsclite-dev ninja-build libltdl-dev
pip install poetry uv
wget https://github.com/protocolbuffers/protobuf/releases/download/v22.0/protoc-22.0-linux-x86_64.zip
sudo unzip protoc-22.0-linux-x86_64.zip -d /usr/local
Expand Down Expand Up @@ -46,7 +51,7 @@ runs:
done
tar -czf "${{ inputs.archive }}.tar.gz" ${{ inputs.paths }}

- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: ${{ inputs.name }}-sim
path: ${{ inputs.archive }}.tar.gz
Expand Down
22 changes: 16 additions & 6 deletions .github/actions/install-sim/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ inputs:
runs:
using: composite
steps:
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
name: ${{ inputs.device == 'ledger-legacy' && 'ledger' || inputs.device }}-sim

Expand All @@ -18,18 +18,28 @@ runs:
apt-get install -y libsdl2-image-2.0-0 libusb-1.0-0
tar -xvf trezor-firmware.tar.gz

- if: inputs.device == 'coldcard'
- if: startsWith(inputs.device, 'coldcard')
shell: bash
run: |
apt-get update
apt-get install -y libpcsclite-dev libusb-1.0-0 swig

archive=coldcard-firmware
if [[ "${{ inputs.device }}" == "coldcard-edge" ]]; then
archive=coldcard-edge-firmware
fi
# Extract the archive - this includes the full firmware directory
tar -xvf coldcard-firmware.tar.gz
tar -xvf "${archive}.tar.gz"

poetry run pip install -r test/work/firmware/requirements.txt
pip install -r test/work/firmware/requirements.txt
requirements="test/work/firmware/requirements.txt"
if [[ "${{ inputs.device }}" == "coldcard-edge" ]]; then
# Edge's full firmware requirements pin legacy development tools that
# are not needed to run the simulator and do not build on modern Python.
requirements="test/work/firmware/unix/requirements.txt"
fi
poetry run pip install -r "${requirements}"
poetry run pip install pysdl2-dll
pip install -r "${requirements}"
pip install pysdl2-dll

- if: inputs.device == 'bitbox01'
Expand Down Expand Up @@ -63,7 +73,7 @@ runs:
pip install -e test/work/speculos

- if: startsWith(inputs.device, 'ledger')
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: ${{ inputs.device == 'ledger-legacy' && 'ledger_app_legacy' || 'ledger_app' }}

Expand Down
2 changes: 1 addition & 1 deletion .github/actions/test-device/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ runs:
pip install poetry
poetry install

- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
name: bitcoind

Expand Down
4 changes: 2 additions & 2 deletions .github/actions/test-dist/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ runs:
run: |
pip install poetry

- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
name: dist
path: dist/
Expand All @@ -30,7 +30,7 @@ runs:
run: |
poetry install; cd dist; tar -xvf hwi*linux*.tar.gz; cd ..

- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
name: bitcoind

Expand Down
3 changes: 3 additions & 0 deletions .github/sim-build-map.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"coldcard": [
{ "name": "coldcard", "archive": "coldcard-firmware", "paths": "test/work/firmware" }
],
"coldcard_edge": [
{ "name": "coldcard-edge", "archive": "coldcard-edge-firmware", "paths": "test/work/firmware" }
],
"bitbox": [
{ "name": "bitbox01", "archive": "mcu", "paths": "test/work/mcu" },
{ "name": "bitbox02", "archive": "bitbox02", "paths": "test/work/bitbox02-firmware/build-build-noasan/bin/simulator" }
Expand Down
29 changes: 23 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
container: python:3.12
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- run: |
pip install poetry
Expand All @@ -39,7 +39,7 @@ jobs:
runs-on: ubuntu-latest
container: python:3.12
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- run: |
pip install poetry
Expand All @@ -54,7 +54,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- uses: docker/build-push-action@v6
with:
Expand All @@ -78,7 +78,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- uses: docker/build-push-action@v6
with:
Expand All @@ -98,7 +98,7 @@ jobs:
find dist -type f -exec sha256sum {} \;
"

- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: dist
path: dist/
Expand All @@ -125,6 +125,15 @@ jobs:
include: ${{ needs.prepare-sim-matrices.outputs.coldcard }}
runs-on: ubuntu-latest

sim-builder-coldcard-edge:
name: Coldcard Edge sim builder
needs: prepare-sim-matrices
uses: ./.github/workflows/sim-builder.yml
with:
sim: coldcard-edge
include: ${{ needs.prepare-sim-matrices.outputs.coldcard_edge }}
runs-on: ubuntu-latest

sim-builder-bitbox:
name: Bitbox sim builder
needs: prepare-sim-matrices
Expand Down Expand Up @@ -177,7 +186,7 @@ jobs:
name: bitcoind builder
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: ./.github/actions/build-bitcoind

test-trezor-1:
Expand Down Expand Up @@ -215,6 +224,14 @@ jobs:
device: coldcard
runs-on: ubuntu-latest

test-coldcard-edge:
uses: ./.github/workflows/device-test.yml
needs: [sim-builder-coldcard-edge, bitcoind-builder, dist-builder]
with:
device: coldcard-edge
runs-on: ubuntu-latest
python_versions: '["3.14"]'

test-bitbox01:
uses: ./.github/workflows/device-test.yml
needs: [sim-builder-bitbox, bitcoind-builder, dist-builder]
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/device-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ on:
required: false
type: string
default: ubuntu-latest
python_versions:
required: false
type: string
default: '["3.10", "3.11", "3.12", "3.13", "3.14"]'

jobs:
test-device:
Expand All @@ -18,7 +22,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
python-version: ${{ fromJSON(inputs.python_versions) }}
device:
- ${{ inputs.device }}
test:
Expand All @@ -30,7 +34,7 @@ jobs:
- {script: 'Bindist'}
container: python:${{ matrix.python-version }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: run test
if: ${{ matrix.test.interface }}
uses: ./.github/actions/test-device
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ledger-app-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
git clone --branch 2.5.0 --depth 1 https://github.com/LedgerHQ/app-bitcoin.git
cd app-bitcoin
make DEBUG=1 COIN=bitcoin_testnet BOLOS_SDK=$NANOX_SDK
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: ledger_app
path: app-bitcoin/bin/app.elf
2 changes: 1 addition & 1 deletion .github/workflows/ledger-legacy-app-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
git clone --depth 1 https://github.com/LedgerHQ/app-bitcoin-legacy.git -b legacy-1.6.6
cd app-bitcoin-legacy
make DEBUG=1 BOLOS_SDK=$NANOSP_SDK
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: ledger_app_legacy
path: app-bitcoin-legacy/bin/app.elf
8 changes: 6 additions & 2 deletions .github/workflows/prepare-sim-matrices.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ on:
coldcard:
description: JSON include array for coldcard
value: ${{ jobs.prepare.outputs.coldcard }}
coldcard_edge:
description: JSON include array for Coldcard Edge
value: ${{ jobs.prepare.outputs.coldcard_edge }}
bitbox:
description: JSON include array for bitbox
value: ${{ jobs.prepare.outputs.bitbox }}
Expand All @@ -28,19 +31,20 @@ jobs:
outputs:
trezor: ${{ steps.gen.outputs.trezor }}
coldcard: ${{ steps.gen.outputs.coldcard }}
coldcard_edge: ${{ steps.gen.outputs.coldcard_edge }}
bitbox: ${{ steps.gen.outputs.bitbox }}
jade: ${{ steps.gen.outputs.jade }}
ledger: ${{ steps.gen.outputs.ledger }}
keepkey: ${{ steps.gen.outputs.keepkey }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- id: gen
shell: bash
run: |
set -euo pipefail
sudo apt-get install -y jq
map_file=".github/sim-build-map.json"
for sim in trezor coldcard bitbox jade ledger keepkey; do
for sim in trezor coldcard coldcard_edge bitbox jade ledger keepkey; do
include=$(jq -c --arg s "$sim" '.[$s]' "$map_file")
if [[ -z "$include" || "$include" == "null" ]]; then
echo "Missing entry for $sim in $map_file" >&2
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sim-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
matrix:
include: ${{ fromJSON(inputs.include) }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: ./.github/actions/build-sim
with:
name: ${{ matrix.name }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/type-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
container: python:3.12
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- run: |
pip install poetry
Expand Down
24 changes: 22 additions & 2 deletions hwilib/devices/coldcard.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
AF_P2WSH,
AF_P2SH,
AF_P2WSH_P2SH,
AF_P2TR,
)
from .._base58 import (
get_xpub_fingerprint,
Expand Down Expand Up @@ -79,6 +80,15 @@
# Using the simulator: https://github.com/Coldcard/firmware/blob/master/unix/README.md


def _firmware_version_supports_psbt_v2(version: str) -> bool:
if version.endswith(("Q", "X")):
return True
try:
return tuple(int(part) for part in version.split(".")) >= (5, 2, 0)
except ValueError:
return False


def coldcard_exception(f: Callable[..., Any]) -> Callable[..., Any]:
@wraps(f)
def func(*args: Any, **kwargs: Any) -> Any:
Expand Down Expand Up @@ -122,6 +132,12 @@ def is_edge(self):

return self._is_edge

def _supports_psbt_v2(self) -> bool:
if self.device.is_simulator or self.is_edge:
return True
version = self.device.firmware_version()[1]
return _firmware_version_supports_psbt_v2(version)

@coldcard_exception
def get_pubkey_at_path(self, path: str) -> ExtendedKey:
self.device.check_mitm()
Expand Down Expand Up @@ -164,9 +180,11 @@ def sign_tx(self, tx: PSBT) -> PSBT:
if our_keys > passes:
passes = our_keys

if tx.version == 2 and not self._supports_psbt_v2():
tx.convert_to_v0()

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)
Expand Down Expand Up @@ -267,7 +285,9 @@ def display_singlesig_address(
elif addr_type == AddressType.LEGACY:
addr_fmt = AF_CLASSIC
elif addr_type == AddressType.TAP:
raise UnavailableActionError("Coldcard does not support displaying Taproot addresses yet")
if not self.is_edge:
raise UnavailableActionError("Coldcard does not support displaying Taproot addresses yet. Use EDGE.")
addr_fmt = AF_P2TR
else:
raise BadArgumentError("Unknown address type")

Expand Down
10 changes: 10 additions & 0 deletions test/data/coldcard-edge-multisig.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
diff --git a/unix/variant/sim_settings.py b/unix/variant/sim_settings.py
--- a/unix/variant/sim_settings.py
+++ b/unix/variant/sim_settings.py
@@ -71,5 +71,5 @@ if '--ms' in sys.argv:
sim_defaults['miniscript'] = [['CC-2-of-4', 'sh(wsh(sortedmulti(2,@0/**,@1/**,@2/**,@3/**)))', ['[0f056943/48h/1h/0h/1h]tpubDF2rnouQaaYrUEy2JM1YD3RFzew4onawGM4X2Re67gguTf5CbHonBRiFGe3Xjz7DK88dxBFGf2i7K1hef3PM4cFKyUjcbJXddaY9F5tJBoP', '[6ba6cfd0/48h/1h/0h/1h]tpubDFcrvj5n7gyatVbr8dHCUfHT4CGvL8hREBjtxc4ge7HZgqNuPhFimPRtVg6fRRwfXiQthV9EBjNbwbpgV2VoQeL1ZNXoAWXxP2L9vMtRjax', '[747b698e/48h/1h/0h/1h]tpubDExj5FnaUnPAjjgzELoSiNRkuXJG8Cm1pbdiA4Hc5vkAZHphibeVcUp6mqH5LuNVKbtLVZxVSzyja5X26Cfmx6pzRH6gXBUJAH7MiqwNyuM', '[7bb026be/48h/1h/0h/1h]tpubDFiuHYSJhNbHaGtB5skiuDLg12tRboh2uVZ6KGXxr8WVr28pLcS7F3gv8SsHFa2tm1jtx3VAuw56YfgRkdo6DXyfp51oygTKY3nJFT5jBMt'], {'af': 26, 'm_n': (2, 4), 'b67': 1, 'ct': 'XTN'}]]
else:
# P2SH: 2of4 using BIP39 passwords: "Me", "Myself", "and I", and (empty string) on simulator
- sim_defaults['miniscript'] = [['MeMyself', 'sh(sortedmulti(2,@0/**,@1/**,@2/**,@3/**))', ['[6ba6cfd0/45h]tpubD9429UXFGCTKJ9NdiNK4rC5ygqSUkginycYHccqSg5gkmyQ7PZRHNjk99M6a6Y3NY8ctEUUJvCu6iCCui8Ju3xrHRu3Ez1CKB4ZFoRZDdP9', '[747b698e/45h]tpubD97nVL37v5tWyMf9ofh5rznwhh1593WMRg6FT4o6MRJkKWANtwAMHYLrcJFsFmPfYbY1TE1LLQ4KBb84LBPt1ubvFwoosvMkcWJtMwvXgSc', '[7bb026be/45h]tpubD9ArfXowvGHnuECKdGXVKDMfZVGdephVWg8fWGWStH3VKHzT4ph3A4ZcgXWqFu1F5xGTfxncmrnf3sLC86dup2a8Kx7z3xQ3AgeNTQeFxPa', '[0f056943/45h]tpubD8NXmKsmWp3a3DXhbihAYbYLGaRNVdTnr6JoSxxfXYQcmwVtW2hv8QoDwng6JtEonmJoL3cNEwfd2cLXMpGezwZ2vL2dQ7259bueNKj9C8n'], {'af': 8, 'm_n': (2, 4), 'b67': 1, 'ct': 'XTN'}]]
+ sim_defaults['miniscript'] = [['mstest', 'sh(sortedmulti(2,@0/**,@1/**,@2/**,@3/**))', ['[6ba6cfd0/45h]tpubD9429UXFGCTKJ9NdiNK4rC5ygqSUkginycYHccqSg5gkmyQ7PZRHNjk99M6a6Y3NY8ctEUUJvCu6iCCui8Ju3xrHRu3Ez1CKB4ZFoRZDdP9', '[747b698e/45h]tpubD97nVL37v5tWyMf9ofh5rznwhh1593WMRg6FT4o6MRJkKWANtwAMHYLrcJFsFmPfYbY1TE1LLQ4KBb84LBPt1ubvFwoosvMkcWJtMwvXgSc', '[7bb026be/45h]tpubD9ArfXowvGHnuECKdGXVKDMfZVGdephVWg8fWGWStH3VKHzT4ph3A4ZcgXWqFu1F5xGTfxncmrnf3sLC86dup2a8Kx7z3xQ3AgeNTQeFxPa', '[0f056943/45h]tpubD8NXmKsmWp3a3DXhbihAYbYLGaRNVdTnr6JoSxxfXYQcmwVtW2hv8QoDwng6JtEonmJoL3cNEwfd2cLXMpGezwZ2vL2dQ7259bueNKj9C8n'], {'af': 8, 'm_n': (2, 4), 'b67': 1, 'ct': 'XTN'}]]
sim_defaults['fee_limit'] = -1
Loading
Loading