diff --git a/releases/EdgeChangeLog.md b/releases/EdgeChangeLog.md index 82bb530a..1474938b 100644 --- a/releases/EdgeChangeLog.md +++ b/releases/EdgeChangeLog.md @@ -13,6 +13,7 @@ This lists the changes in the most recent EDGE firmware, for each hardware platf # Shared Improvements - Both Mk4 and Q +- Enhancement: Support per-input required height and time locktimes in PSBTv2 transactions. - New Feature: Added USB ncry v3 authenticated encryption with direction-separated keys and replay protection - Enhancement: Warn when a transaction's block-height `nLockTime` is more than ten years beyond the Bitcoin block height known to the firmware. @@ -47,6 +48,10 @@ This lists the changes in the most recent EDGE firmware, for each hardware platf and mappings from a compromised USB host. - Bugfix: Fix device crash when message-signing input is valid JSON but not an object (NFC / QR / SD `.json` file). Thanks to [@Amiga500](https://github.com/Amiga500). +- Bugfix: Harden PSBTv2 parsing by rejecting key data on singleton fields, + malformed global input/output count encodings, and files missing the required + global version. +- Bugfix: Preserve Taproot context while deriving Miniscript policies. # Mk4 Specific Changes diff --git a/releases/Next-ChangeLog.md b/releases/Next-ChangeLog.md index ec686f8a..36c73e57 100644 --- a/releases/Next-ChangeLog.md +++ b/releases/Next-ChangeLog.md @@ -49,6 +49,10 @@ This lists the new changes that have not yet been published in a normal release. filesystems with more than one sector per cluster), fixing an integer underflow in `psram_copy_file`/`psram_mmap_file` that allowed out-of-bounds PSRAM writes, reads, and mappings from a compromised USB host. +- Bugfix: Harden PSBTv2 parsing by rejecting key data on singleton fields, + malformed global input/output count encodings, and files missing the required + global version. +- Bugfix: Preserve Taproot context while deriving Miniscript policies. # Mk Specific Changes diff --git a/shared/miniscript.py b/shared/miniscript.py index 115993ad..a04c2569 100644 --- a/shared/miniscript.py +++ b/shared/miniscript.py @@ -160,7 +160,7 @@ def derive(self, idx, key_map=None, change=False): arg = arg.derive(idx, key_map, change) args.append(arg) - return type(self)(*args) + return type(self)(*args, taproot=self.taproot) @property def properties(self): diff --git a/shared/psbt.py b/shared/psbt.py index 9c5ec272..8a5cf644 100644 --- a/shared/psbt.py +++ b/shared/psbt.py @@ -448,7 +448,8 @@ def parse_subpaths(self, my_xfp, parent, cosign_xfp=None): # Track details of each output of PSBT # class psbtOutputProxy(psbtProxy): - no_keys = { PSBT_OUT_REDEEM_SCRIPT, PSBT_OUT_WITNESS_SCRIPT, PSBT_OUT_TAP_INTERNAL_KEY, PSBT_OUT_TAP_TREE } + no_keys = { PSBT_OUT_REDEEM_SCRIPT, PSBT_OUT_WITNESS_SCRIPT, PSBT_OUT_AMOUNT, + PSBT_OUT_SCRIPT, PSBT_OUT_TAP_INTERNAL_KEY, PSBT_OUT_TAP_TREE } blank_flds = ('unknown', 'subpaths', 'redeem_script', 'witness_script', 'sp_idxs', 'is_change', 'amount', 'script', 'attestation', 'proprietary', @@ -693,7 +694,9 @@ class psbtInputProxy(psbtProxy): # only part-sigs have a key to be stored. no_keys = {PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_WITNESS_UTXO, PSBT_IN_SIGHASH_TYPE, PSBT_IN_REDEEM_SCRIPT, PSBT_IN_WITNESS_SCRIPT, PSBT_IN_FINAL_SCRIPTSIG, - PSBT_IN_FINAL_SCRIPTWITNESS,PSBT_IN_TAP_KEY_SIG, + PSBT_IN_FINAL_SCRIPTWITNESS, PSBT_IN_PREVIOUS_TXID, PSBT_IN_OUTPUT_INDEX, + PSBT_IN_SEQUENCE, PSBT_IN_REQUIRED_TIME_LOCKTIME, + PSBT_IN_REQUIRED_HEIGHT_LOCKTIME, PSBT_IN_TAP_KEY_SIG, PSBT_IN_TAP_INTERNAL_KEY, PSBT_IN_TAP_MERKLE_ROOT} blank_flds = ( @@ -1383,7 +1386,10 @@ def serialize(self, out_fd, is_v2): class psbtObject(psbtProxy): "Just? parse and store" short_values = { PSBT_GLOBAL_TX_MODIFIABLE } - no_keys = { PSBT_GLOBAL_UNSIGNED_TX, PSBT_GLOBAL_GENERIC_SIGNED_MESSAGE } + no_keys = { PSBT_GLOBAL_UNSIGNED_TX, PSBT_GLOBAL_TX_VERSION, + PSBT_GLOBAL_FALLBACK_LOCKTIME, PSBT_GLOBAL_INPUT_COUNT, + PSBT_GLOBAL_OUTPUT_COUNT, PSBT_GLOBAL_TX_MODIFIABLE, + PSBT_GLOBAL_VERSION, PSBT_GLOBAL_GENERIC_SIGNED_MESSAGE } blank_flds = ("hashPrevouts", "hashSequence", "hashOutputs", "hashValues", "hashScriptPubKeys", "tr_hashPrevouts", "tr_hashSequence", "tr_hashOutputs", "my_tr_in", "unknown") @@ -1499,10 +1505,14 @@ def store(self, kt, key, val): elif kt == PSBT_GLOBAL_FALLBACK_LOCKTIME: self.fallback_locktime = unpack("= 2 diff --git a/testing/test_sign.py b/testing/test_sign.py index 8310b7aa..8e05141d 100644 --- a/testing/test_sign.py +++ b/testing/test_sign.py @@ -7,7 +7,13 @@ from ckcc_protocol.protocol import CCProtocolPacker, CCProtoError from binascii import b2a_hex, a2b_hex from psbt import BasicPSBT, BasicPSBTInput, BasicPSBTOutput, PSBT_IN_REDEEM_SCRIPT -from psbt import PSBT_GLOBAL_VERSION, PSBT_IN_WITNESS_UTXO, PSBT_OUT_AMOUNT +from psbt import (PSBT_GLOBAL_VERSION, PSBT_GLOBAL_TX_VERSION, + PSBT_GLOBAL_FALLBACK_LOCKTIME, PSBT_GLOBAL_INPUT_COUNT, + PSBT_GLOBAL_OUTPUT_COUNT, PSBT_GLOBAL_TX_MODIFIABLE, + PSBT_IN_WITNESS_UTXO, PSBT_IN_PREVIOUS_TXID, + PSBT_IN_OUTPUT_INDEX, PSBT_IN_SEQUENCE, + PSBT_IN_REQUIRED_TIME_LOCKTIME, PSBT_IN_REQUIRED_HEIGHT_LOCKTIME, + PSBT_OUT_AMOUNT, PSBT_OUT_SCRIPT) from io import BytesIO from pprint import pprint from decimal import Decimal @@ -83,6 +89,65 @@ def add_duplicate(psbt): assert 'PSBT parse failed' in ee.value.args[0] + +@pytest.mark.parametrize('scope, ktype, value', [ + ('global', PSBT_GLOBAL_TX_VERSION, struct.pack('