PSBTv2 (BIP-370) Support#87
Conversation
Add a check for `p2sh-p2wsh`, `p2sh-p2wpkh` and raise an exception for unknow types. fix diybitcoinhardware#93 Co-authored-by: edilmedeiros <jose.edil@gmail.com> Co-authored-by: moisespompilio <93723302+moisesPompilio@users.noreply.github.com>
This reverts commit e1d7339.
In order to test the replace assertions with conditional error raising, specifically, in bip85 code, was added a test for `derive_entropy` method as well the failure cases described in `derive_mnemonic` and `derive_hex`.
…spaces should be invalid
…rsing - Add LOCKTIME_THRESHOLD constant (500000000) for locktime type discrimination - Add required_time_locktime and required_height_locktime fields to InputScope - Propagate those fields in InputScope.update() - Parse PSBT_IN_REQUIRED_TIME_LOCKTIME (0x11) and PSBT_IN_REQUIRED_HEIGHT_LOCKTIME (0x12) in InputScope.read_value(), with range validation - Serialize 0x11 and 0x12 in InputScope.write_to() under the version==2 block - Add version=None parameter to InputScope.read_value() and OutputScope.read_value() so callers can signal the PSBT version being parsed - Add InputScope.read_from() and OutputScope.read_from() classmethods that accept and forward the version parameter (supersede PSBTScope base classmethod for v2) - Propagate version in LInputScope.read_value() and LOutputScope.read_value() (pset.py) so the Liquid subclasses stay compatible - Pass version=self.version when PSBTView calls read_from() for input/output scopes so PSBTv2 scopes are parsed with the correct version context
01b834d to
c001bb1
Compare
- Add TxModifiable class with INPUTS/OUTPUTS/SIGHASH_SINGLE bit flags - Add PSBT._validate_v2_output() classmethod to check required PSBTv2 output fields; PSET overrides it to allow value_commitment in place of value - Add tx_modifiable_flags field to PSBT.__init__() (None for v0/unset) - Update comment on self.version to note '2 for v2' - Add PSBT.determine_locktime(): implements BIP-370 locktime selection algorithm (height vs time preference, max-of-minimums) - Update PSBT.tx property to call determine_locktime() for PSBTv2 - Update PSBT.write_to(): serialize PSBT_GLOBAL_TX_MODIFIABLE (0x06) when set - Rewrite PSBT.read_from(): collect all global KVs into OrderedDict before branching on PSBTv2 vs PSBTv0; detect version from 0xfb; reject unsigned tx in PSBTv2 and reject v2-only keys in PSBTv0; validate required fields after parsing; call read_from with version=version on all scopes - Rewrite PSBT.parse_unknowns(): strip 0xfb/0x06; guard 0x02/0x03/0x04/0x05 behind version==2; track _raw_input_count_from_global and _raw_output_count_from_global for PSBTv2 scope parsing - Add version guards to InputScope.read_value() for 0x0e/0x0f/0x10: PSBT_IN_PREVIOUS_TXID, PSBT_IN_OUTPUT_INDEX, and PSBT_IN_SEQUENCE are rejected when version != 2 - Add version guards to OutputScope.read_value() for 0x03/0x04: PSBT_OUT_AMOUNT and PSBT_OUT_SCRIPT are rejected when version != 2
…_with - Update sign_with(): after each legacy/segwit signature, update tx_modifiable_flags per BIP-370: clear INPUTS bit if not ANYONECANPAY, clear OUTPUTS bit if not NONE, set SIGHASH_SINGLE bit if sighash type is SINGLE - Add get_tx_modifiable() / set_tx_modifiable(flags): getter/setter for PSBT_GLOBAL_TX_MODIFIABLE; setter raises PSBTError on PSBTv0 - Add is_inputs_modifiable(): True if version!=2, tx_modifiable_flags is None, or INPUTS bit is set - Add is_outputs_modifiable(): True if version!=2, tx_modifiable_flags is None, or OUTPUTS bit is set - Add has_sighash_single(): True only if version==2, tx_modifiable_flags is not None, and SIGHASH_SINGLE bit is set - Add add_input(input_scope): appends an InputScope, gated on is_inputs_modifiable(); updates _raw_input_count_from_global for PSBTv2 - Add add_output(output_scope): appends an OutputScope, gated on is_outputs_modifiable(); updates _raw_output_count_from_global for PSBTv2
Add test_psbtV2.py with 37 tests covering PSBTv2 parsing, serialization, locktime selection, TxModifiable flags, and add_input/add_output API.
There was a problem hiding this comment.
Please rebase with master (we plan to stop using develop branch). There shouldn't be conflicts rebasing it locally with git.
Later, there are a few things to take a look at:
-
PSBTView does not fully enforce or apply PSBTv2 rules. It can accept a v2 PSBT with PSBT_GLOBAL_UNSIGNED_TX if that key appears before PSBT_GLOBAL_VERSION, and PSBTView.locktime uses only fallback locktime instead of deriving locktime from per-input requirements. Sighashes computed through PSBTView can therefore commit to the wrong locktime.
-
add_input() / add_output() only check modifiable flags. BIP370 Constructor rules also require validating required fields, locktime compatibility, preserving existing signed locktime, and handling SIGHASH_SINGLE input/output pairing.
-
Several PSBTv2 fixed-width input fields lack exact length validation: previous txid, output index, sequence, required time locktime, and required height locktime. Malformed values can be accepted and later serialize incorrectly or fail unexpectedly.
-
compress mode can lose its OOM-protection benefit for PSBTv2 non-witness UTXOs when PSBT_IN_NON_WITNESS_UTXO appears before PSBT_IN_PREVIOUS_TXID / PSBT_IN_OUTPUT_INDEX in the input map. Since key order is not guaranteed, this should be pre-scanned, deferred, or documented.
-
Signedness should be made consistent with BIP370: PSBT_OUT_AMOUNT is read as signed but written unsigned, and PSBT_GLOBAL_TX_VERSION is specified as signed int32 but read/written unsigned.
-
Explicit PSBTv0 version handling is too strict. BIP174 allows v0 PSBTs to either omit
PSBT_GLOBAL_VERSIONor set it to0, but this parser rejects version0. -
PSBTv2 global input/output counts are used to allocate placeholder scope objects before parsing actual scopes. Since those counts are attacker-controlled and the placeholders are later discarded, this creates an avoidable memory-exhaustion risk. Store counts only and allocate scopes as they are parsed.
Recommended test additions: PSBTv2 parse/serialize roundtrips, PSBTView locktime/sighash behavior, constructor API behavior, signer TX_MODIFIABLE updates, malformed fixed-width field lengths, and the PSET v2 output validation path.
This PR improves PSBT (Partially Signed Bitcoin Transaction) v2 support, especially strict validation and compatibility with BIP 370 test vectors. It also adds a comprehensive set of tests for PSBTv2 edge cases and locktime determination.
Details
1. PSBTv2 Compliance and Parsing Improvements
src/embit/liquid/pset.pyto allow passing theversionparameter down toread_valueand its parent implementations. This ensures correct handling of versioned PSBT fields.super().read_valueto pass along theversionargument, improving extensibility and correctness for PSBTv2.2. PSBT View Updates
src/embit/psbtview.py:input()andoutput()methods now pass theversionparameter when reading from the stream, allowing PSBT-in and out scopes to parse versioned fields correctly.3. Comprehensive PSBTv2 Test Vectors
tests/tests/test_psbtV2.pycontaining:Motivation
Testing
pytest tests/tests/test_psbtV2.pyto verify all BIP 370 test vectors and locktime cases.embitto confirm correct parsing — everything worked as expected.References