After reviewing a number of PRs that want to incorporate hypothesis testing (full disclosure: I did not look deeply into any of them), I used Claude to determine what we'd actually gain from this approach.
In the end, it seemed simpler to me to just create new targeted test cases that directly address what "property-based testing" (to the extent I understand it) would be aiming for.
Below is Claude's list of specific test case additions.
Recommended SeedSigner test additions
These all belong in tests/test_psbt_parser.py or tests/test_decodepsbtqr.py
and use the existing pytest harness — no new dependencies.
1. Multisig PSBT, no descriptor registered
Construct a multisig PSBT (any of the three existing fixture types).
Instantiate PSBTParser without loading a descriptor. Assert:
num_change_outputs == 0 (no output trusted as self/change)
spend_amount == sum of all outputs
verify_multisig_output(...) is not called or returns False on every
output
2. Lying-coordinator (single-sig): forged BIP_DERIVATION on output
For each of the four single-sig types, take a fixture and modify one
output's PSBT_OUT_BIP32_DERIVATION to claim a path that does NOT
derive the output's actual scriptPubKey from the on-device seed.
Assert that output is NOT classified as change/self.
3. Lying-coordinator (multisig): forged BIP_DERIVATION with correct
descriptor loaded
Same construction as #2 but for multisig with the correct descriptor
loaded. Assert verify_multisig_output(...) returns False.
4. Multi-output consolidation across both branches
Construct a PSBT with no external outputs — multiple outputs to the
device's own addresses, mixing the change branch (1/*) and receive
branch (0/*). Assert:
spend_amount == 0
change_amount == input_amount - fee_amount
- All outputs classified as self
5. Multisig descriptor that doesn't contain the device's xpub
Load a descriptor whose key set does not include the device's xpub at
all (different signing quorum entirely). Construct any multisig PSBT.
Assert verify_multisig_output(...) returns False on all outputs.
6. Self-output at non-standard derivation path
Construct PSBTs with self-outputs at:
- A high index (e.g.
0/9999)
- A non-standard branch (e.g.
m/.../2/*)
Assert correct self-classification (high index) and correct rejection
or non-classification (non-standard branch — pin whatever the policy is).
7. Regression test for the known duplicate-branch bug
Pin the current behavior of the bug referenced in PR #929 / the
proposal. Include a docstring explaining the bug, expected behavior
once fixed, and link to the issue.
8. QR type-detection boundary cases (DecodeQR)
Feed DecodeQR.add_data() inputs that could be ambiguous between
detected types:
- A base64 string that is also a valid prefix of another format
- A UR-prefixed payload with a non-PSBT type
- Inputs at the boundary between PSBT formats (UR2 vs Specter vs BBQR)
- An input that could parse as both seed-type and wallet-json-type
For each, assert the chosen qr_type matches the documented priority
order. Pins the detection policy against silent regressions.
And then a separate section for embit test gaps:
Recommended embit upstream contributions
The following gaps exist in embit's test_psbt.py negative coverage
and would be appropriate as upstream PRs to
github.com/diybitcoinhardware/embit, not as SeedSigner tests.
Byte-level robustness
- Length-prefix mismatch: declared length exceeds remaining bytes
- Length-prefix mismatch: declared length is zero where data is expected
- Truncated input mid-key, mid-value, mid-section
- Integer overflow on amount fields (values near 2^64 - 1)
Each should assert the parser raises a specific exception cleanly,
without leaking a generic Python exception or silently truncating.
embit already covers BIP-174 invalid-vector compliance, duplicate keys,
invalid typed keys at all levels, malformed descriptors, and
sortedmulti vs multi key ordering — so those don't need duplication
upstream or in SeedSigner.
After reviewing a number of PRs that want to incorporate
hypothesistesting (full disclosure: I did not look deeply into any of them), I used Claude to determine what we'd actually gain from this approach.In the end, it seemed simpler to me to just create new targeted test cases that directly address what "property-based testing" (to the extent I understand it) would be aiming for.
Below is Claude's list of specific test case additions.
Recommended SeedSigner test additions
These all belong in
tests/test_psbt_parser.pyortests/test_decodepsbtqr.pyand use the existing pytest harness — no new dependencies.
1. Multisig PSBT, no descriptor registered
Construct a multisig PSBT (any of the three existing fixture types).
Instantiate
PSBTParserwithout loading a descriptor. Assert:num_change_outputs == 0(no output trusted as self/change)spend_amount == sum of all outputsverify_multisig_output(...)is not called or returns False on everyoutput
2. Lying-coordinator (single-sig): forged BIP_DERIVATION on output
For each of the four single-sig types, take a fixture and modify one
output's
PSBT_OUT_BIP32_DERIVATIONto claim a path that does NOTderive the output's actual scriptPubKey from the on-device seed.
Assert that output is NOT classified as change/self.
3. Lying-coordinator (multisig): forged BIP_DERIVATION with correct
descriptor loaded
Same construction as #2 but for multisig with the correct descriptor
loaded. Assert
verify_multisig_output(...)returns False.4. Multi-output consolidation across both branches
Construct a PSBT with no external outputs — multiple outputs to the
device's own addresses, mixing the change branch (
1/*) and receivebranch (
0/*). Assert:spend_amount == 0change_amount == input_amount - fee_amount5. Multisig descriptor that doesn't contain the device's xpub
Load a descriptor whose key set does not include the device's xpub at
all (different signing quorum entirely). Construct any multisig PSBT.
Assert
verify_multisig_output(...)returns False on all outputs.6. Self-output at non-standard derivation path
Construct PSBTs with self-outputs at:
0/9999)m/.../2/*)Assert correct self-classification (high index) and correct rejection
or non-classification (non-standard branch — pin whatever the policy is).
7. Regression test for the known duplicate-branch bug
Pin the current behavior of the bug referenced in PR #929 / the
proposal. Include a docstring explaining the bug, expected behavior
once fixed, and link to the issue.
8. QR type-detection boundary cases (DecodeQR)
Feed
DecodeQR.add_data()inputs that could be ambiguous betweendetected types:
For each, assert the chosen
qr_typematches the documented priorityorder. Pins the detection policy against silent regressions.
And then a separate section for
embittest gaps:Recommended embit upstream contributions
The following gaps exist in embit's
test_psbt.pynegative coverageand would be appropriate as upstream PRs to
github.com/diybitcoinhardware/embit, not as SeedSigner tests.
Byte-level robustness
Each should assert the parser raises a specific exception cleanly,
without leaking a generic Python exception or silently truncating.
embit already covers BIP-174 invalid-vector compliance, duplicate keys,
invalid typed keys at all levels, malformed descriptors, and
sortedmulti vs multi key ordering — so those don't need duplication
upstream or in SeedSigner.