mirror of
https://github.com/SeedSigner/seedsigner.git
synced 2026-10-05 15:08:25 +00:00
A p2sh-p2wpkh output's scriptPubKey is a bare p2sh hash. BIP-174 leaves the redeem script optional on an output, and without it _get_policy types the output as plain p2sh, which does not match a p2sh-p2wpkh wallet policy. The output then never reaches the ownership check at all. Two things follow. The user's own change is displayed as a payment out to a stranger. And an output that keeps a genuine claim on this seed while repointing its scriptPubKey escapes the ownership-contradiction refusal by dropping one optional field. Nested single sig is the only script type whose identity depends on an optional field its proof does not read: the rebuild is p2sh(p2wpkh(K)) from our own seed at the claimed path and the inputs' policy, so the missing field is a discriminator rather than evidence. For p2sh-p2wsh the discriminator and the proof material are the same field, so its absence is genuine silence and correctly out of reach. _policy_shape_matches becomes _is_change_candidate, an instance method, since it now reads the inputs' policy and the output's verified derivation paths. Beside the unchanged shape comparison it admits a bare p2sh output under a p2sh-p2wpkh wallet that carries no m-of-n, supplies exactly one derivation path entry, and holds one verified claim on this seed. Everything below is unchanged: the rebuild decides, honest change is counted as change, and a repointed output raises PSBTOutputOwnershipContradictionError. The single-entry conditions are what keep that refusal safe. A genuine multisig output carries one derivation path entry per cosigner, so it never reaches the contradiction check by this route. The shape that would is a bare p2sh output withholding both scripts while annotating a single key of a multisig, and no surveyed coordinator emits one. Every coordinator but Bitcoin Core annotates only its own change output. Core annotates an output because a descriptor solved its script, so it holds all n key origins and writes them.
Running Tests
The tests are designed to be run on non-Raspi hardware.
Setup
On your testing machine you'll have to install:
# general dependencies
pip3 install -r requirements.txt
# test suite dependencies
pip3 install -r tests/requirements.txt
Then make the seedsigner python module visible/importable to the tests by installing it:
pip3 install -e .
Running all tests, calculating overall test coverage
tldr: just run the convenience script from the project root:
./tests/run_full_coverage.sh
Running tests manually
Run the whole test suite:
pytest
Run a specific test file:
pytest tests/test_this_file.py
Run a specific test:
pytest tests/test_this_file.py::test_this_specific_test
Force pytest to show logging output:
pytest tests/test_this_file.py::test_this_specific_test -o log_cli=1
# or (same result)
pytest tests/test_this_file.py::test_this_specific_test --log-cli-level=DEBUG
Annoying complications:
- If you want to see
print()statements that are in a test file, add-s - Better idea: use a proper logger in the test file and use one of the above options to display logs
Screenshot generator
The screenshot generator is meant to mostly be a utility and not really part of the test suite. However,
it is actually implemented to be run by pytest.
see: Screenshot generator README
Generate coverage manually
Run tests and generate test coverage
coverage run -m pytest
The screenshots can generate their own separate coverage report:
coverage run -m pytest tests/screenshot_generator/generator.py --locale es
Show the resulting test coverage details:
coverage report
Generate the interactive html report:
coverage html