diff --git a/.containerignore b/.containerignore new file mode 100644 index 000000000..895b58026 --- /dev/null +++ b/.containerignore @@ -0,0 +1,11 @@ +# Exclude local build artifacts and any symlinks outside the context +build* + +# Local documentation files (can be symlinks outside the context) +*.md + +# Common Python / tooling artifacts +__pycache__/ +*.pyc +.venv/ +dist/ diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..895b58026 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +# Exclude local build artifacts and any symlinks outside the context +build* + +# Local documentation files (can be symlinks outside the context) +*.md + +# Common Python / tooling artifacts +__pycache__/ +*.pyc +.venv/ +dist/ diff --git a/.github/workflows/device-test.yml b/.github/workflows/device-test.yml index 5232314e7..5ec9ede76 100644 --- a/.github/workflows/device-test.yml +++ b/.github/workflows/device-test.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.9', '3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12'] device: - ${{ inputs.device }} test: diff --git a/.github/workflows/ledger-app-builder.yml b/.github/workflows/ledger-app-builder.yml index d873a2411..6bfc73e4e 100644 --- a/.github/workflows/ledger-app-builder.yml +++ b/.github/workflows/ledger-app-builder.yml @@ -11,14 +11,17 @@ jobs: build: name: Build Bitcoin App runs-on: ${{ inputs.runs-on }} - # Pin to 4.23.0 for SDK v25.9.0 compatibility with Speculos v0.25.10 - container: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:4.23.0 + # Pin to 5.3.2 for SDK compatibility with app-bitcoin-new v2.4.6 + container: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:5.3.2 steps: - run: | - # Pin to v2.4.1 - last version that worked with HWI CI (PR #795 merged Sept 2025) - git clone --branch 2.4.1 --depth 1 https://github.com/LedgerHQ/app-bitcoin-new.git + # Pin to v2.4.6 + git clone --branch 2.4.6 --depth 1 https://github.com/LedgerHQ/app-bitcoin-new.git cd app-bitcoin-new - make DEBUG=1 BOLOS_SDK=$NANOX_SDK + # Work around register-wallet policy parsing/display bugs fixed after v2.4.6. + git fetch --depth 2 origin 24bd597ee9c60fe58b522360011437d6a8679d33 a2ba8560c9306c53bd30840bd2333cecd7f47ae4 + git -c user.name="HWI CI" -c user.email="hwi-ci@example.invalid" cherry-pick 24bd597ee9c60fe58b522360011437d6a8679d33 a2ba8560c9306c53bd30840bd2333cecd7f47ae4 + make DEBUG=1 COIN=bitcoin_testnet BOLOS_SDK=$NANOX_SDK - uses: actions/upload-artifact@v4 with: name: ledger_app diff --git a/.github/workflows/ledger-legacy-app-builder.yml b/.github/workflows/ledger-legacy-app-builder.yml index aac5afb1a..bc4ae0ffb 100644 --- a/.github/workflows/ledger-legacy-app-builder.yml +++ b/.github/workflows/ledger-legacy-app-builder.yml @@ -11,7 +11,7 @@ jobs: build: name: Build Bitcoin Legacy App runs-on: ${{ inputs.runs-on }} - # Pin to 4.23.0 for SDK v25.9.0 compatibility with Speculos v0.25.10 + # Pin to 4.23.0 for SDK v25.9.0 compatibility with Speculos v0.26.9 container: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:4.23.0 steps: - run: | diff --git a/.python-version b/.python-version index bd28b9c5c..44677e5cc 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.9 +3.10.20 diff --git a/README.md b/README.md index c80374b18..ec1f8fab2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Bitcoin Hardware Wallet Interface -[![Build Status](https://api.cirrus-ci.com/github/bitcoin-core/HWI.svg)](https://cirrus-ci.com/github/bitcoin-core/HWI) +[![CI](https://github.com/bitcoin-core/HWI/actions/workflows/ci.yml/badge.svg)](https://github.com/bitcoin-core/HWI/actions/workflows/ci.yml) [![Documentation Status](https://readthedocs.org/projects/hwi/badge/?version=latest)](https://hwi.readthedocs.io/en/latest/?badge=latest) The Bitcoin Hardware Wallet Interface is a Python library and command line tool for interacting with hardware wallets. diff --git a/ci/cirrus.Dockerfile b/ci/cirrus.Dockerfile deleted file mode 100644 index 07c201c6e..000000000 --- a/ci/cirrus.Dockerfile +++ /dev/null @@ -1,99 +0,0 @@ -# Cache break (modify this line to break cirrus' dockerfile build cache) 1 - -FROM python:3.9 - -ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update -RUN apt-get install -y \ - autotools-dev \ - automake \ - bsdmainutils \ - build-essential \ - ccache \ - clang \ - cmake \ - curl \ - cython3 \ - gcc-arm-none-eabi \ - gcc-arm-linux-gnueabihf \ - git \ - libboost-system-dev \ - libboost-filesystem-dev \ - libboost-chrono-dev \ - libboost-test-dev \ - libboost-thread-dev \ - libc6-dev-armhf-cross \ - libdb-dev \ - libdb++-dev \ - libevent-dev \ - libgcrypt20-dev \ - libnewlib-arm-none-eabi \ - libpcsclite-dev \ - libsdl2-dev \ - libsdl2-image-dev \ - libssl-dev \ - libslirp-dev \ - libtool \ - libudev-dev \ - libusb-1.0-0-dev \ - ninja-build \ - pkg-config \ - qemu-user-static \ - swig - -RUN pip install poetry flake8 -RUN wget https://static.rust-lang.org/rustup/dist/x86_64-unknown-linux-gnu/rustup-init -RUN chmod +x rustup-init && ./rustup-init -y -ENV PATH="/root/.cargo/bin:$PATH" -RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v22.0/protoc-22.0-linux-x86_64.zip -RUN unzip protoc-22.0-linux-x86_64.zip -d /usr/local -RUN protoc --version - -#################### -# Local build/test steps -# ----------------- -# To install all simulators/tests locally, uncomment the block below, -# then build the docker image and interactively run the tests -# as needed. -# e.g., -# docker build -f ci/cirrus.Dockerfile -t hwi_test . -# docker run -it --entrypoint /bin/bash hwi_test -# cd test; poetry run ./run_tests.py --ledger --coldcard --interface=cli --device-only -# For BitBox02: -# docker build -f ci/cirrus.Dockerfile -t hwi_test . -# ./ci/build_bitbox02.sh -# docker run -it -v bitbox02_volume:/test/work/bitbox02-firmware --name hwi --entrypoint /bin/bash hwi_test -# cd test; poetry run ./run_tests.py --bitbox02 --interface=cli --device-only -#################### - -#################### -#ENV EMAIL=email -#COPY pyproject.toml pyproject.toml -#RUN poetry run pip install construct pyelftools mnemonic jsonschema -# -## 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 -#RUN cd test; ./setup_environment.sh --coldcard -#RUN cd test; ./setup_environment.sh --bitbox01 -#RUN cd test; ./setup_environment.sh --ledger -#RUN cd test; ./setup_environment.sh --keepkey -#RUN cd test; ./setup_environment.sh --jade -#RUN cd test; ./setup_environment.sh --bitcoind -# -## Once everything has been built, put rest of files in place -## which have higher turn-over. -#COPY test/ test/ -#COPY hwi.py hwi-qt.py README.md / -#COPY hwilib/ /hwilib/ -#RUN poetry install -# -#################### - -ENV LC_ALL=C.UTF-8 -ENV LANG=C.UTF-8 -ENV LANGUAGE=C.UTF-8 diff --git a/ci/py310.Dockerfile b/ci/py310.Dockerfile index 299b44333..20106aa24 100644 --- a/ci/py310.Dockerfile +++ b/ci/py310.Dockerfile @@ -1,4 +1,4 @@ -# Cache break (modify this line to break cirrus' dockerfile build cache) 1 +# Cache break (modify this line to break the dockerfile build cache) 1 FROM python:3.10 diff --git a/ci/py311.Dockerfile b/ci/py311.Dockerfile index ab80e889a..cf97b42cf 100644 --- a/ci/py311.Dockerfile +++ b/ci/py311.Dockerfile @@ -1,4 +1,4 @@ -# Cache break (modify this line to break cirrus' dockerfile build cache) +# Cache break (modify this line to break the dockerfile build cache) FROM python:3.11 diff --git a/ci/py37.Dockerfile b/ci/py37.Dockerfile deleted file mode 100644 index fc8b67781..000000000 --- a/ci/py37.Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -# Cache break (modify this line to break cirrus' dockerfile build cache) 1 - -FROM python:3.7 - -ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update -RUN apt-get install -y \ - cython3 \ - git \ - libpcsclite-dev \ - libsdl2-dev \ - libsdl2-image-dev \ - libslirp-dev \ - libudev-dev \ - libusb-1.0-0-dev \ - qemu-user-static \ - swig - -RUN pip install poetry flake8 - -ENV LC_ALL=C.UTF-8 -ENV LANG=C.UTF-8 -ENV LANGUAGE=C.UTF-8 diff --git a/ci/py39.Dockerfile b/ci/py39.Dockerfile deleted file mode 100644 index 9c6f5eeb8..000000000 --- a/ci/py39.Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -# Cache break (modify this line to break cirrus' dockerfile build cache) 1 - -FROM python:3.9 - -ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update -RUN apt-get install -y \ - cython3 \ - git \ - libpcsclite-dev \ - libsdl2-dev \ - libsdl2-image-dev \ - libslirp-dev \ - libudev-dev \ - libusb-1.0-0-dev \ - qemu-user-static \ - swig - -RUN pip install poetry flake8 - -ENV LC_ALL=C.UTF-8 -ENV LANG=C.UTF-8 -ENV LANGUAGE=C.UTF-8 diff --git a/contrib/build-wine.Dockerfile b/contrib/build-wine.Dockerfile index 335b580bb..d781dfdcd 100644 --- a/contrib/build-wine.Dockerfile +++ b/contrib/build-wine.Dockerfile @@ -1,7 +1,5 @@ FROM debian:bookworm-slim -SHELL ["/bin/bash", "-c"] - ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update RUN apt-get install -y \ diff --git a/contrib/build.Dockerfile b/contrib/build.Dockerfile index 4e1025352..d1d23861c 100644 --- a/contrib/build.Dockerfile +++ b/contrib/build.Dockerfile @@ -1,7 +1,5 @@ FROM debian:bookworm-slim -SHELL ["/bin/bash", "-c"] - ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update RUN apt-get install -y \ @@ -36,9 +34,9 @@ ENV PATH="$PYENV_ROOT/bin:$PATH" COPY contrib/reproducible-python.diff /opt/reproducible-python.diff ENV PYTHON_CONFIGURE_OPTS="--enable-shared" -ENV BUILD_DATE="Jan 1 2019" +ENV BUILD_DATE="Jan 1 2026" ENV BUILD_TIME="00:00:00" -RUN eval "$(pyenv init --path)" && eval "$(pyenv virtualenv-init -)" && cat /opt/reproducible-python.diff | pyenv install -kp 3.9.19 +RUN /bin/bash -c 'eval "$(pyenv init --path)" && eval "$(pyenv virtualenv-init -)" && cat /opt/reproducible-python.diff | pyenv install -kp 3.10.20' ENV LC_ALL=C.UTF-8 ENV LANG=C.UTF-8 diff --git a/contrib/build_bin.sh b/contrib/build_bin.sh index 2db94f439..af8c233dc 100755 --- a/contrib/build_bin.sh +++ b/contrib/build_bin.sh @@ -6,8 +6,11 @@ set -ex ARCH=$(uname -m | tr '[:upper:]' '[:lower:]') +PYTHON_VERSION=3.10.20 + eval "$(pyenv init --path)" eval "$(pyenv virtualenv-init -)" +export PYENV_VERSION="$PYTHON_VERSION" pip install -U pip pip install poetry @@ -21,8 +24,8 @@ else fi # We also need to change the timestamps of all of the base library files -lib_dir=$(pyenv prefix)/lib/python3.9 -TZ=UTC find ${lib_dir} -name '*.py' -type f -execdir touch -t "201901010000.00" '{}' \; +lib_dir=$(pyenv prefix)/lib/python3.10 +TZ=UTC find ${lib_dir} -name '*.py' -type f -execdir touch -t "202601010000.00" '{}' \; # Make the standalone binary export PYTHONHASHSEED=42 @@ -45,7 +48,7 @@ fi target_tarfile="hwi-${VERSION}-${OS}-${ARCH}.tar.gz" -if [[ $gui_support == "--with-gui" ]]; then +if [[ $gui_support == "--with-gui" && $ARCH == "x86_64" ]]; then tar -czf $target_tarfile hwi hwi-qt else tar -czf $target_tarfile hwi diff --git a/contrib/build_dist.sh b/contrib/build_dist.sh index e15b9e5d9..c4fec9a3e 100755 --- a/contrib/build_dist.sh +++ b/contrib/build_dist.sh @@ -4,8 +4,11 @@ set -ex +PYTHON_VERSION=3.10.20 + eval "$(pyenv init --path)" eval "$(pyenv virtualenv-init -)" +export PYENV_VERSION="$PYTHON_VERSION" pip install -U pip pip install poetry @@ -20,5 +23,5 @@ fi # Make the distribution archives for pypi poetry build -f wheel -# faketime is needed to make sdist detereministic -TZ=UTC faketime -f "2019-01-01 00:00:00" poetry build -f sdist +# faketime is needed to make sdist deterministic +TZ=UTC faketime -f "2026-01-01 00:00:00" poetry build -f sdist diff --git a/contrib/build_wine.sh b/contrib/build_wine.sh index 64d211f97..30efca207 100755 --- a/contrib/build_wine.sh +++ b/contrib/build_wine.sh @@ -3,7 +3,11 @@ set -ex -PYTHON_VERSION=3.9.13 +# Note: Python MSIs/EXEs are no longer hosted for 3.10.x on python.org. +# The NuGet python package currently only goes up to 3.10.11, so Windows builds use that. +PYTHON_VERSION=3.10.11 +PYTHON_NUGET_URL="https://api.nuget.org/v3-flatcontainer/python/${PYTHON_VERSION}/python.${PYTHON_VERSION}.nupkg" +PYTHON_NUGET_HASH="7c6f99b160a36a7e09492dfcff2b0a3a60bb5229ca44cdcc3ecb32871a6144d0" PYTHON_FOLDER="python3" PYHOME="c:/$PYTHON_FOLDER" @@ -19,20 +23,15 @@ WINDOWS_SDK_VERSION=10.0.17763.0 wine 'wineboot' -# Install Python -# Get the PGP keys -wget -O pubkeys.txt -N -c "https://keybase.io/stevedower/pgp_keys.asc?fingerprint=7ed10b6531d7c8e1bc296021fc624643487034e5" -gpg --import pubkeys.txt -rm pubkeys.txt - -# Install python components -for msifile in core dev exe lib pip tools; do - wget -N -c "https://www.python.org/ftp/python/$PYTHON_VERSION/amd64/${msifile}.msi" - wget -N -c "https://www.python.org/ftp/python/$PYTHON_VERSION/amd64/${msifile}.msi.asc" - gpg --verify "${msifile}.msi.asc" "${msifile}.msi" - wine msiexec /i "${msifile}.msi" /qb TARGETDIR=$PYHOME - rm $msifile.msi* -done +# Install Python from NuGet package +wget -O python.nupkg -N -c "$PYTHON_NUGET_URL" +echo "$PYTHON_NUGET_HASH python.nupkg" | sha256sum -c +rm -rf python-nupkg +7z x python.nupkg -opython-nupkg >/dev/null +rm -rf ~/.wine/drive_c/python3 +mkdir -p ~/.wine/drive_c/python3 +cp -a python-nupkg/tools/* ~/.wine/drive_c/python3/ +rm -rf python.nupkg python-nupkg # Get and build libusb wget -N -c -O libusb.tar.bz2 "$LIBUSB_URL" @@ -40,7 +39,7 @@ echo "$LIBUSB_HASH libusb.tar.bz2" | sha256sum -c tar -xf libusb.tar.bz2 pushd "libusb-$LIBUSB_VERSION" ./configure --host=x86_64-w64-mingw32 -faketime -f "2019-01-01 00:00:00" make +faketime -f "2026-01-01 00:00:00" make cp libusb/.libs/libusb-1.0.dll ~/.wine/drive_c/python3/ popd rm -r libusb* @@ -62,7 +61,7 @@ $PYTHON -m pip install poetry # We also need to change the timestamps of all of the base library files lib_dir=~/.wine/drive_c/python3/Lib -TZ=UTC find ${lib_dir} -name '*.py' -type f -execdir touch -t "201901010000.00" '{}' \; +TZ=UTC find ${lib_dir} -name '*.py' -type f -execdir touch -t "202601010000.00" '{}' \; # Install python dependencies POETRY="wine $PYHOME/Scripts/poetry.exe" diff --git a/docs/development/release-process.rst b/docs/development/release-process.rst index fb739ffdc..16cf86081 100644 --- a/docs/development/release-process.rst +++ b/docs/development/release-process.rst @@ -4,7 +4,7 @@ Release Process 1. Bump version number in ``pyproject.toml`` and ``hwilib/__init__.py``, generate the setup.py file, and git tag release 2. Build distribution archives for PyPi with ``contrib/build_dist.sh`` 3. For MacOS and Linux, use ``contrib/build_bin.sh``. This needs to be run on a macOS machine for the macOS binary and on a Linux machine for the linux one. -4. For Windows, use ``contrib/build_wine.sh`` to build the Windows binary using wine +4. For Windows, use ``contrib/build_wine.sh`` to build the Windows binary using wine. Note that this uses Python 3.10.11 via the NuGet package because python.org no longer hosts the MSIs for newer 3.10.x releases. 5. Make ``SHA256SUMS.txt`` using ``contrib/make_shasums.sh``. 6. Make ``SHA256SUMS.txt.asc`` using ``gpg --clearsign SHA256SUMS.txt`` 7. Upload distribution archives to PyPi @@ -26,7 +26,7 @@ Build everything:: docker run -it --name hwi-builder -v $PWD:/opt/hwi --rm --workdir /opt/hwi hwi-builder /bin/bash -c "contrib/build_bin.sh && contrib/build_dist.sh" docker run -it --name hwi-wine-builder -v $PWD:/opt/hwi --rm --workdir /opt/hwi hwi-wine-builder /bin/bash -c "contrib/build_wine.sh" - docker run --platform linux/arm64 -it --rm --name hwi-builder-arm64 -v $PWD:/opt/hwi --workdir /opt/hwi hwi-builder-arm64 /bin/bash -c "contrib/build_bin.sh --without-gui && contrib/build_dist.sh --without-gui" + docker run --platform linux/arm64 -it --rm --name hwi-builder-arm64 -v $PWD:/opt/hwi --workdir /opt/hwi hwi-builder-arm64 /bin/bash -c "contrib/build_bin.sh --without-gui && contrib/build_dist.sh --without-gui" Building macOS binary ===================== @@ -35,14 +35,14 @@ Note that the macOS build is non-deterministic. First install `pyenv `_ using whichever method you prefer. -Then a deterministic build of Python 3.9.19 needs to be installed. This can be done with the patch in ``contrib/reproducible-python.diff``. First ``cd`` into HWI's source tree. Then use:: +Then a deterministic build of Python 3.10.20 needs to be installed. This can be done with the patch in ``contrib/reproducible-python.diff``. First ``cd`` into HWI's source tree. Then use:: - cat contrib/reproducible-python.diff | PYTHON_CONFIGURE_OPTS="--enable-framework" BUILD_DATE="Jan 1 2019" BUILD_TIME="00:00:00" pyenv install -kp 3.9.19 + cat contrib/reproducible-python.diff | PYTHON_CONFIGURE_OPTS="--enable-framework" BUILD_DATE="Jan 1 2026" BUILD_TIME="00:00:00" pyenv install -kp 3.10.20 -Make sure that python 3.9.19 is active:: +Make sure that python 3.10.20 is active:: $ python --version - Python 3.9.19 + Python 3.10.20 Now install `Poetry `_ with ``pip install poetry`` diff --git a/hwilib/psbt.py b/hwilib/psbt.py index 28b5e1888..9600ae59d 100644 --- a/hwilib/psbt.py +++ b/hwilib/psbt.py @@ -1046,8 +1046,11 @@ def cache_unsigned_tx_pieces(self) -> None: Does nothing if the PSBT is already v2. """ # To make things easier, we split up the global transaction - # and use the PSBTv2 fields for PSBTv0 - if self.tx is not None: + # and use the PSBTv2 fields for PSBTv0. + # Don't check self.tx instead of self.version: for PSBTv2 it is + # merely an empty placeholder, and its default version and locktime + # would overwrite the deserialized values. + if self.version == 0: self.setup_from_tx(self.tx) def setup_from_tx(self, tx: CTransaction): diff --git a/poetry.lock b/poetry.lock index 88af47e0e..7637723d7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -507,27 +507,6 @@ files = [ {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] -[[package]] -name = "importlib-metadata" -version = "7.0.1" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version == \"3.9\"" -files = [ - {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, - {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf (>=0.9.2)", "pytest-ruff"] - [[package]] name = "jinja2" version = "3.1.3" @@ -882,7 +861,6 @@ files = [ [package.dependencies] altgraph = "*" -importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""} packaging = ">=22.0" pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""} @@ -907,7 +885,6 @@ files = [ ] [package.dependencies] -importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} packaging = ">=22.0" setuptools = ">=42.0.0" @@ -933,7 +910,7 @@ description = "Python bindings for the Qt cross-platform application and UI fram optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <3.11" groups = ["main"] -markers = "python_version == \"3.9\" and extra == \"qt\"" +markers = "python_version == \"3.10\" and extra == \"qt\"" files = [ {file = "PySide2-5.15.2.1-5.15.2-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:b5e1d92f26b0bbaefff67727ccbb2e1b577f2c0164b349b3d6e80febb4c5bde2"}, {file = "PySide2-5.15.2.1-5.15.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:235240b6ec8206d9fdf0232472c6ef3241783d480425e5b54796f06e39ed23da"}, @@ -1017,7 +994,7 @@ description = "Python / C++ bindings helper module" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <3.11" groups = ["main"] -markers = "python_version == \"3.9\" and extra == \"qt\"" +markers = "python_version == \"3.10\" and extra == \"qt\"" files = [ {file = "shiboken2-5.15.2.1-5.15.2-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:f890f5611ab8f48b88cfecb716da2ac55aef99e2923198cefcf781842888ea65"}, {file = "shiboken2-5.15.2.1-5.15.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87079c07587859a525b9800d60b1be971338ce9b371d6ead81f15ee5a46d448b"}, @@ -1069,7 +1046,6 @@ babel = ">=2.9" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} docutils = ">=0.18.1,<0.21" imagesize = ">=1.3" -importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} Jinja2 = ">=3.0" packaging = ">=21.0" Pygments = ">=2.13" @@ -1252,7 +1228,7 @@ description = "A lil' TOML parser" optional = false python-versions = ">=3.7" groups = ["dev"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, @@ -1288,27 +1264,10 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] -[[package]] -name = "zipp" -version = "3.17.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version == \"3.9\"" -files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-ruff"] - [extras] qt = ["pyside2"] [metadata] lock-version = "2.1" -python-versions = "^3.9,<3.13" -content-hash = "ffa2aebd594ec1a5db15d7325c7bb26036b593faccf363163379e276bff1c191" +python-versions = "^3.10,<3.13" +content-hash = "304d5c9033aed625eb1f8cbbc2f23019fb708171bec6852c28ea8626221c0df0" diff --git a/pyproject.toml b/pyproject.toml index ab98299ba..4ed817acc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,17 +16,16 @@ packages = [ ] [tool.poetry.dependencies] -python = "^3.9,<3.13" +python = "^3.10,<3.13" hidapi = ">=0.14.0" ecdsa = "~0" pyaes = "^1.6" mnemonic = "~0" typing-extensions = "^4.4" libusb1 = ">=1.7,<4" -pyside2 = { version = "^5.14.0", optional = true, python = "<3.10" } +pyside2 = { version = "^5.15.2.1", optional = true, python = "<3.11" } cbor2 = ">=5.4.6,<5.8" pyserial = "^3.5" -dataclasses = {version = "^0.8", python = ">=3.6,<3.7"} semver = "^3.0.1" noiseprotocol = "^0.3.1" protobuf = "^4.23.3" diff --git a/setup.py b/setup.py index e821a4ca2..60a2a2783 100644 --- a/setup.py +++ b/setup.py @@ -17,11 +17,10 @@ 'hwilib.devices.ledger_bitcoin.ledgercomm', 'hwilib.devices.ledger_bitcoin.ledgercomm.interfaces', 'hwilib.devices.trezorlib', - 'hwilib.devices.trezorlib.transport', - 'hwilib.ui'] + 'hwilib.devices.trezorlib.transport'] package_data = \ -{'': ['*'], 'hwilib': ['udev/*']} +{'': ['*'], 'hwilib': ['udev/*', 'ui/*']} modules = \ ['hwi', 'hwi-qt'] @@ -39,8 +38,7 @@ 'typing-extensions>=4.4,<5.0'] extras_require = \ -{':python_version == "3.6"': ['dataclasses>=0.8,<0.9'], - 'qt:python_version < "3.10"': ['pyside2>=5.14.0,<6.0.0']} +{'qt:python_version < "3.11"': ['pyside2>=5.15.2.1,<6.0.0.0']} entry_points = \ {'console_scripts': ['hwi = hwilib._cli:main', 'hwi-qt = hwilib._gui:main']} @@ -49,7 +47,7 @@ 'name': 'hwi', 'version': '3.2.0', 'description': 'A library for working with Bitcoin hardware wallets', - 'long_description': "# Bitcoin Hardware Wallet Interface\n\n[![Build Status](https://api.cirrus-ci.com/github/bitcoin-core/HWI.svg)](https://cirrus-ci.com/github/bitcoin-core/HWI)\n[![Documentation Status](https://readthedocs.org/projects/hwi/badge/?version=latest)](https://hwi.readthedocs.io/en/latest/?badge=latest)\n\nThe Bitcoin Hardware Wallet Interface is a Python library and command line tool for interacting with hardware wallets.\nIt provides a standard way for software to work with hardware wallets without needing to implement device specific drivers.\nPython software can use the provided library (`hwilib`). Software in other languages can execute the `hwi` tool.\n\nCaveat emptor: Inclusion of a specific hardware wallet vendor does not imply any endorsement of quality or security.\n\n## Prerequisites\n\nPython 3 is required. The libraries and [udev rules](hwilib/udev/README.md) for each device must also be installed. Some libraries will need to be installed\n\nFor Ubuntu/Debian:\n```\nsudo apt install libusb-1.0-0-dev libudev-dev python3-dev\n```\n\nFor Centos:\n```\nsudo yum -y install python3-devel libusbx-devel systemd-devel\n```\n\nFor macOS:\n```\nbrew install libusb\n```\n\n## Install\n\n```\ngit clone https://github.com/bitcoin-core/HWI.git\ncd HWI\npoetry install # or 'pip3 install .' or 'python3 setup.py install'\n```\n\nThis project uses the [Poetry](https://github.com/sdispater/poetry) dependency manager. HWI and its dependencies can be installed via poetry by executing the following in the root source directory:\n\n```\npoetry install\n```\n\nPip can also be used to automatically install HWI and its dependencies using the `setup.py` file (which is usually in sync with `pyproject.toml`):\n\n```\npip3 install .\n```\n\nThe `setup.py` file can be used to install HWI and its dependencies so long as `setuptools` is also installed:\n\n```\npip3 install -U setuptools\npython3 setup.py install\n```\n\n## Dependencies\n\nSee `pyproject.toml` for all dependencies. Dependencies under `[tool.poetry.dependencies]` are user dependencies, and `[tool.poetry.dev-dependencies]` for development based dependencies. These dependencies will be installed with any of the three above installation methods.\n\n## Usage\n\nTo use, first enumerate all devices and find the one that you want to use with\n\n```\n./hwi.py enumerate\n```\n\nOnce the device type and device path are known, issue commands to it like so:\n\n```\n./hwi.py -t -d \n```\n\nAll output will be in JSON form and sent to `stdout`.\nAdditional information or prompts will be sent to `stderr` and will not necessarily be in JSON.\nThis additional information is for debugging purposes.\n\nTo see a complete list of available commands and global parameters, run\n`./hwi.py --help`. To see options specific to a particular command,\npass the `--help` parameter after the command name; for example:\n\n```\n./hwi.py getdescriptors --help\n```\n\n## Documentation\n\nDocumentation for HWI can be found on [readthedocs.io](https://hwi.readthedocs.io/).\n\n### Device Support\n\nFor documentation on devices supported and how they are supported, please check the [device support page](https://hwi.readthedocs.io/en/latest/devices/index.html#support-matrix)\n\n### Using with Bitcoin Core\n\nSee [Using Bitcoin Core with Hardware Wallets](https://hwi.readthedocs.io/en/latest/examples/bitcoin-core-usage.html).\n\n## License\n\nThis project is available under the MIT License, Copyright Andrew Chow.\n", + 'long_description': "# Bitcoin Hardware Wallet Interface\n\n[![CI](https://github.com/bitcoin-core/HWI/actions/workflows/ci.yml/badge.svg)](https://github.com/bitcoin-core/HWI/actions/workflows/ci.yml)\n[![Documentation Status](https://readthedocs.org/projects/hwi/badge/?version=latest)](https://hwi.readthedocs.io/en/latest/?badge=latest)\n\nThe Bitcoin Hardware Wallet Interface is a Python library and command line tool for interacting with hardware wallets.\nIt provides a standard way for software to work with hardware wallets without needing to implement device specific drivers.\nPython software can use the provided library (`hwilib`). Software in other languages can execute the `hwi` tool.\n\nCaveat emptor: Inclusion of a specific hardware wallet vendor does not imply any endorsement of quality or security.\n\n## Prerequisites\n\nPython 3 is required. The libraries and [udev rules](hwilib/udev/README.md) for each device must also be installed. Some libraries will need to be installed\n\nFor Ubuntu/Debian:\n```\nsudo apt install libusb-1.0-0-dev libudev-dev python3-dev\n```\n\nFor Centos:\n```\nsudo yum -y install python3-devel libusbx-devel systemd-devel\n```\n\nFor macOS:\n```\nbrew install libusb\n```\n\n## Install\n\n```\ngit clone https://github.com/bitcoin-core/HWI.git\ncd HWI\npoetry install # or 'pip3 install .' or 'python3 setup.py install'\n```\n\nThis project uses the [Poetry](https://github.com/sdispater/poetry) dependency manager. HWI and its dependencies can be installed via poetry by executing the following in the root source directory:\n\n```\npoetry install\n```\n\nPip can also be used to automatically install HWI and its dependencies using the `setup.py` file (which is usually in sync with `pyproject.toml`):\n\n```\npip3 install .\n```\n\nThe `setup.py` file can be used to install HWI and its dependencies so long as `setuptools` is also installed:\n\n```\npip3 install -U setuptools\npython3 setup.py install\n```\n\n## Dependencies\n\nSee `pyproject.toml` for all dependencies. Dependencies under `[tool.poetry.dependencies]` are user dependencies, and `[tool.poetry.dev-dependencies]` for development based dependencies. These dependencies will be installed with any of the three above installation methods.\n\n## Usage\n\nTo use, first enumerate all devices and find the one that you want to use with\n\n```\n./hwi.py enumerate\n```\n\nOnce the device type and device path are known, issue commands to it like so:\n\n```\n./hwi.py -t -d \n```\n\nAll output will be in JSON form and sent to `stdout`.\nAdditional information or prompts will be sent to `stderr` and will not necessarily be in JSON.\nThis additional information is for debugging purposes.\n\nTo see a complete list of available commands and global parameters, run\n`./hwi.py --help`. To see options specific to a particular command,\npass the `--help` parameter after the command name; for example:\n\n```\n./hwi.py getdescriptors --help\n```\n\n## Documentation\n\nDocumentation for HWI can be found on [readthedocs.io](https://hwi.readthedocs.io/).\n\n### Device Support\n\nFor documentation on devices supported and how they are supported, please check the [device support page](https://hwi.readthedocs.io/en/latest/devices/index.html#support-matrix)\n\n### Using with Bitcoin Core\n\nSee [Using Bitcoin Core with Hardware Wallets](https://hwi.readthedocs.io/en/latest/examples/bitcoin-core-usage.html).\n\n## License\n\nThis project is available under the MIT License, Copyright Andrew Chow.\n", 'author': 'Ava Chow', 'author_email': 'me@achow101.com', 'maintainer': 'None', @@ -61,7 +59,7 @@ 'install_requires': install_requires, 'extras_require': extras_require, 'entry_points': entry_points, - 'python_requires': '>=3.9,<3.13', + 'python_requires': '>=3.10,<3.13', } diff --git a/test/README.md b/test/README.md index 1e1264ec7..099f9d027 100644 --- a/test/README.md +++ b/test/README.md @@ -311,6 +311,18 @@ $ cmake -Bbuild -H. $ make -C build/ ``` +### Automation rules + +The Ledger tests drive the emulator's buttons with +`data/speculos-automation.json`. Speculos matches each rule against every +text fragment drawn on the screen, so beware of this pitfall when editing +it: screens can contain randomly generated base58 addresses, wrapped over +several lines, and a rule matching a short word can accidentally match a +wrapped address line. The resulting extra button press derails the rest of +the flow. This happened with a rule for the "To" screen title when an +address ended in "To". Keep rules multi-word where possible, or pin them to +the title row with `"y": 3` like the "To" rule. + ## Coldcard emulator Clone the repository: diff --git a/test/data/speculos-automation.json b/test/data/speculos-automation.json index f17a9c668..e46c4240b 100644 --- a/test/data/speculos-automation.json +++ b/test/data/speculos-automation.json @@ -1,6 +1,13 @@ { "version": 1, "rules": [ + { + "regexp": "^(Security risk|High fees warning).*", + "actions": [ + [ "button", 2, true ], + [ "button", 2, false ] + ] + }, { "text": "Confirm", "x": 43, "y": 37, @@ -11,6 +18,16 @@ [ "button", 2, false ] ] }, + { + "text": "Confirm", + "x": 41, "y": 34, + "actions": [ + [ "button", 1, true ], + [ "button", 2, true ], + [ "button", 1, false ], + [ "button", 2, false ] + ] + }, { "text": "Confirm account ", "actions": [ @@ -28,7 +45,15 @@ ] }, { - "regexp": "^(Address|Review|Amount|Fee|Confirm|The derivation|Derivation path|Reject if|The change path|Change path|Register wallet|Policy map|Key|Path|Public key|Spend from|Wallet name|Wallet policy|Descriptor template|Verify Bitcoin|To|Output|Warning).*", + "regexp": "^(Address|Review|Amount|Fee|Confirm|The derivation|Derivation path|The change path|Change path|Register wallet|Policy map|Key|Path|Public key|Our key|Their key|Unspendable key|Spend from|Transaction output|Wallet name|Wallet policy|Descriptor template|Verify [Bb]itcoin|Output|Warning).*", + "actions": [ + [ "button", 2, true ], + [ "button", 2, false ] + ] + }, + { + "regexp": "^To.*", + "y": 3, "actions": [ [ "button", 2, true ], [ "button", 2, false ] @@ -89,13 +114,6 @@ [ "button", 2, false ], [ "setbool", "seen_msg_hash", false ] ] - }, - { - "regexp": "^(Cancel|Reject).*", - "actions": [ - [ "button", 1, true ], - [ "button", 1, false ] - ] } ] } diff --git a/test/setup_environment.sh b/test/setup_environment.sh index 7d602a5f1..b00506e63 100755 --- a/test/setup_environment.sh +++ b/test/setup_environment.sh @@ -69,7 +69,7 @@ TREZOR_VERSION="core/v2.9.6" BITBOX01_VERSION="v7.1.0" BITBOX02_VERSION="firmware/v9.24.0" KEEPKEY_VERSION="v7.10.0" -SPECULOS_VERSION="v0.25.10" # Last version supporting Python 3.9 (v0.25.11+ requires >=3.10) +SPECULOS_VERSION="v0.26.9" # Requires Python >=3.10 (v0.25.11+) JADE_VERSION="1.0.36" # Keep COLDCARD_VERSION in sync with .github/actions/install-sim/action.yml @@ -115,12 +115,15 @@ if [[ -n ${build_trezor_1} || -n ${build_trezor_t} ]]; then fi if [[ -n ${build_trezor_t} ]]; then - rustup update - rustup toolchain uninstall nightly - rustup toolchain install nightly - rustup default nightly - rustup component add rustfmt - rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu + # Pin the Rust nightly to the one trezor-firmware ${TREZOR_VERSION} expects + # (see its shell.nix: rust-bin.nightly."2025-04-15"). The rolling "nightly" + # channel has drifted and newer nightlies reject the firmware's + # reexport_test_harness_main attribute with error E0658. + TREZOR_RUST_NIGHTLY="nightly-2025-04-15" + rustup toolchain install ${TREZOR_RUST_NIGHTLY} + rustup default ${TREZOR_RUST_NIGHTLY} + rustup component add rustfmt --toolchain ${TREZOR_RUST_NIGHTLY} + rustup component add rust-src --toolchain ${TREZOR_RUST_NIGHTLY}-x86_64-unknown-linux-gnu # Build trezor t emulator. This is pretty fast, so rebuilding every time is ok # But there should be some caching that makes this faster git am ../../data/trezor-t-build.patch @@ -292,9 +295,19 @@ if [[ -n ${build_ledger} ]]; then cd speculos + # Work around -Werror build failures in Speculos' bundled deps. + # GCC < 15 errors out on unknown "-Wno-error=..." options, so only add the + # unterminated-string-initialization suppression when the compiler supports it. + CFLAGS="-O -fno-builtin -fPIC -Wall -Wextra -Werror -Wno-error=maybe-uninitialized -Wno-error=array-parameter -Wno-error=array-bounds -Wno-error=discarded-qualifiers" + cc_major=$(cc -dumpversion | cut -d. -f1) + if [ "${cc_major:-0}" -ge 15 ]; then + CFLAGS="$CFLAGS -Wno-error=unterminated-string-initialization" + fi + export CFLAGS + # Build the simulator. This is cached, but it is also fast mkdir -p build - cmake -Bbuild -S . + cmake -Bbuild -S . -DCMAKE_C_FLAGS="${CFLAGS}" make -C build/ cd .. diff --git a/test/test_bitbox02.py b/test/test_bitbox02.py index a9d64b999..400da4afa 100644 --- a/test/test_bitbox02.py +++ b/test/test_bitbox02.py @@ -43,6 +43,7 @@ def __init__(self, simulator): self.include_xpubs = True self.supports_device_multiple_multisig = True self.supports_legacy = False + self.supports_arbitrary_keypool_paths = True def start(self): super().start() diff --git a/test/test_coldcard.py b/test/test_coldcard.py index c30742b3d..4259d1efd 100755 --- a/test/test_coldcard.py +++ b/test/test_coldcard.py @@ -45,6 +45,7 @@ def __init__(self, simulator): self.include_xpubs = False self.supports_device_multiple_multisig = True self.supports_legacy = True + self.supports_arbitrary_keypool_paths = True def start(self): super().start() diff --git a/test/test_device.py b/test/test_device.py index d22fdc386..2520f972a 100644 --- a/test/test_device.py +++ b/test/test_device.py @@ -36,6 +36,7 @@ def __init__(self): self.include_xpubs = None self.supports_device_multiple_multisig = None self.supports_legacy = None + self.supports_arbitrary_keypool_paths = True def start(self): assert self.type is not None @@ -313,13 +314,14 @@ def test_getkeypool(self): addr_info = self.wrpc.getaddressinfo(self.wrpc.getrawchangeaddress('bech32')) self.assertTrue(addr_info['hdkeypath'].startswith("m/84h/1h/3h/1/")) - keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--path', 'm/0h/0h/4h/*', '0', '20']) - self.assertIsInstance(keypool_desc, list, f"getkeypool returned error: {keypool_desc}") - import_result = self.wrpc.importdescriptors(keypool_desc) - self.assertTrue(import_result[0]['success']) - for _ in range(0, 21): - addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress('', 'bech32')) - self.assertTrue(addr_info['hdkeypath'].startswith("m/0h/0h/4h/")) + if self.emulator.supports_arbitrary_keypool_paths: + keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--path', 'm/0h/0h/4h/*', '0', '20']) + self.assertIsInstance(keypool_desc, list, f"getkeypool returned error: {keypool_desc}") + import_result = self.wrpc.importdescriptors(keypool_desc) + self.assertTrue(import_result[0]['success']) + for _ in range(0, 21): + addr_info = self.wrpc.getaddressinfo(self.wrpc.getnewaddress('', 'bech32')) + self.assertTrue(addr_info['hdkeypath'].startswith("m/0h/0h/4h/")) keypool_desc = self.do_command(self.dev_args + ['getkeypool', '--path', '/0h/0h/4h/*', '0', '20']) self.assertEqual(keypool_desc['error'], 'Path must start with m/') @@ -498,10 +500,12 @@ def _test_signtx(self, input_types, multisig_types, external, op_return: bool): in_amt = 1 number_inputs = 0 # Single-sig - if "segwit" in input_types: + if "segwit" in input_types or "sh_wit" in input_types: self.wpk_rpc.sendtoaddress(sh_wpkh_addr, in_amt) + number_inputs += 1 + if "segwit" in input_types or "wit" in input_types: self.wpk_rpc.sendtoaddress(wpkh_addr, in_amt) - number_inputs += 2 + number_inputs += 1 if "legacy" in input_types: self.wpk_rpc.sendtoaddress(pkh_addr, in_amt) number_inputs += 1 @@ -591,20 +595,12 @@ def test_signtx(self): # https://github.com/bitcoin-core/HWI/pull/795#issuecomment-3112271927 raise unittest.SkipTest("Coldcard sign test temporarily disabled") - if self.emulator.type == "ledger" and not self.emulator.legacy: - # https://github.com/bitcoin-core/HWI/pull/795#issuecomment-3112271927 - raise unittest.SkipTest("Test temporarily disabled for NanoX") - for addrtypes, multisig_types, external, op_return in self.signtx_cases: with self.subTest(addrtypes=addrtypes, multisig_types=multisig_types, external=external, op_return=op_return): self._test_signtx(addrtypes, multisig_types, external, op_return) # Make a huge transaction which might cause some problems with different interfaces def test_big_tx(self): - if self.emulator.type == "ledger" and not self.emulator.legacy: - # https://github.com/bitcoin-core/HWI/pull/795#issuecomment-3112271927 - raise unittest.SkipTest("Test temporarily disabled for NanoX") - # make a huge transaction keypool_desc = self.do_command(self.dev_args + ["getkeypool", "--account", "10", "--addr-type", "sh_wit", "0", "100"]) self.assertIsInstance(keypool_desc, list, f"getkeypool returned error: {keypool_desc}") @@ -636,10 +632,6 @@ def test_big_tx(self): class TestDisplayAddress(DeviceTestCase): def test_display_address_path(self): - if self.emulator.type == "ledger" and not self.emulator.legacy: - # https://github.com/bitcoin-core/HWI/pull/795#issuecomment-3112271927 - raise unittest.SkipTest("Test temporarily disabled for NanoX") - result = self.do_command(self.dev_args + ['displayaddress', "--addr-type", "legacy", '--path', 'm/44h/1h/0h/0/0']) if self.emulator.supports_legacy: self.assertNotIn('error', result) @@ -665,10 +657,6 @@ def test_display_address_bad_path(self): self.assertEqual(result['code'], -7) def test_display_address_descriptor(self): - if self.emulator.type == "ledger" and not self.emulator.legacy: - # https://github.com/bitcoin-core/HWI/pull/795#issuecomment-3112271927 - raise unittest.SkipTest("Test temporarily disabled for NanoX") - account_xpub = self.do_command(self.dev_args + ['getxpub', 'm/84h/1h/0h'])['xpub'] p2sh_segwit_account_xpub = self.do_command(self.dev_args + ['getxpub', 'm/49h/1h/0h'])['xpub'] legacy_account_xpub = self.do_command(self.dev_args + ['getxpub', 'm/44h/1h/0h'])['xpub'] @@ -790,10 +778,6 @@ def _check_sign_msg(self, msg): self.assertTrue(self.rpc.verifymessage(addr, sig, msg)) def test_sign_msg(self): - if self.emulator.type == "ledger" and not self.emulator.legacy: - # https://github.com/bitcoin-core/HWI/pull/795#issuecomment-3112271927 - raise unittest.SkipTest("Test temporarily disabled for NanoX") - self._check_sign_msg("Message signing test") self._check_sign_msg("285") # Specific test case for Ledger shorter S diff --git a/test/test_digitalbitbox.py b/test/test_digitalbitbox.py index 1a98c9562..9fc41ed3f 100755 --- a/test/test_digitalbitbox.py +++ b/test/test_digitalbitbox.py @@ -43,6 +43,7 @@ def __init__(self, simulator): self.include_xpubs = False self.supports_device_multiple_multisig = True self.supports_legacy = True + self.supports_arbitrary_keypool_paths = True def start(self): super().start() diff --git a/test/test_jade.py b/test/test_jade.py index 238c88f37..f5fef76dd 100755 --- a/test/test_jade.py +++ b/test/test_jade.py @@ -53,6 +53,7 @@ def __init__(self, jade_qemu_emulator_path): self.include_xpubs = False self.supports_device_multiple_multisig = True self.supports_legacy = True + self.supports_arbitrary_keypool_paths = True def start(self): super().start() diff --git a/test/test_keepkey.py b/test/test_keepkey.py index 845a0976a..ff9cf84c0 100755 --- a/test/test_keepkey.py +++ b/test/test_keepkey.py @@ -65,6 +65,7 @@ def __init__(self, path): self.include_xpubs = False self.supports_device_multiple_multisig = True self.supports_legacy = True + self.supports_arbitrary_keypool_paths = True def start(self): super().start() diff --git a/test/test_ledger.py b/test/test_ledger.py index 6ab162e74..5265deeba 100755 --- a/test/test_ledger.py +++ b/test/test_ledger.py @@ -47,12 +47,14 @@ def __init__(self, path, legacy=False): self.include_xpubs = True self.supports_device_multiple_multisig = True self.supports_legacy = True + # Bitcoin app 2.4.6 enforces standard Ledger paths for xpub derivation. + self.supports_arbitrary_keypool_paths = legacy def start(self): super().start() automation_path = os.path.abspath("data/speculos-automation.json") app_path = f"./apps/btc-test{'-legacy' if self.legacy else ''}.elf" - os.environ["SPECULOS_APPNAME"] = "Bitcoin Test:1.6.6" if self.legacy else "Bitcoin Test:2.4.1" + os.environ["SPECULOS_APPNAME"] = "Bitcoin Test:1.6.6" if self.legacy else "Bitcoin Test:2.4.6" self.emulator_stderr = open('ledger-emulator.stderr', 'a') # Start the emulator diff --git a/test/test_psbt.py b/test/test_psbt.py index 3a585b060..face4631a 100755 --- a/test/test_psbt.py +++ b/test/test_psbt.py @@ -1,6 +1,6 @@ #! /usr/bin/env python3 -from hwilib.psbt import PSBT +from hwilib.psbt import PSBT, PartiallySignedInput, PartiallySignedOutput from hwilib.errors import PSBTSerializationError import json import os @@ -28,5 +28,29 @@ def test_valid_psbt(self): serd = psbt.serialize() self.assertEqual(valid, serd) + def test_v2_tx_version_and_fallback_locktime_roundtrip(self): + psbt = PSBT() + psbt.version = 2 + psbt.tx_version = 2 + psbt.fallback_locktime = 123456 + + psbt_in = PartiallySignedInput(psbt.version) + psbt_in.prev_txid = bytes.fromhex("11" * 32) + psbt_in.prev_out = 0 + psbt.inputs.append(psbt_in) + + psbt_out = PartiallySignedOutput(psbt.version) + psbt_out.amount = 1000 + psbt_out.script = bytes.fromhex("51") + psbt.outputs.append(psbt_out) + + serialized = psbt.serialize() + parsed = PSBT() + parsed.deserialize(serialized) + + self.assertEqual(parsed.tx_version, 2) + self.assertEqual(parsed.fallback_locktime, 123456) + self.assertEqual(parsed.serialize(), serialized) + if __name__ == "__main__": unittest.main() diff --git a/test/test_trezor.py b/test/test_trezor.py index 335fa571f..b61c1f00a 100755 --- a/test/test_trezor.py +++ b/test/test_trezor.py @@ -64,6 +64,7 @@ def __init__(self, path, model): self.include_xpubs = False self.supports_device_multiple_multisig = True self.supports_legacy = True + self.supports_arbitrary_keypool_paths = True def start(self): super().start()