[Policy] Support raw transaction versions == 3

Co-authored-by: scgbckbone <scgbckbone@proton.me>
This commit is contained in:
russeree 2025-12-29 14:34:04 -08:00 committed by doc-hex
parent f7c7275f12
commit 7b8a533265
5 changed files with 86 additions and 3 deletions

View File

@ -6,6 +6,7 @@ This lists the new changes that have not yet been published in a normal release.
- New Feature: Export [BIP-380](https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki) extended key expression.
Navigate to `Advanced/Tools -> Export Wallet -> Key Expression`
- New Feature: Support for v3 transactions
# Mk4 Specific Changes

View File

@ -1289,7 +1289,7 @@ class psbtObject(psbtProxy):
self.txn_version, marker, flags = unpack("<iBB", fd.read(6))
self.had_witness = (marker == 0 and flags != 0x0)
assert self.txn_version in {1,2}, "bad txn version"
assert self.txn_version in {1,2,3}, "bad txn version"
if not self.had_witness:
# rewind back over marker+flags

View File

@ -1494,7 +1494,7 @@ def end_sign(dev, need_keypress, press_cancel):
assert res[0:4] != b'psbt', 'still a PSBT, but asked for finalize'
t = CTransaction()
t.deserialize(io.BytesIO(res))
assert t.nVersion in [1, 2]
assert t.nVersion in [1, 2, 3]
# TODO: pull out signatures from signed txn

View File

@ -482,7 +482,7 @@ class BasicPSBT:
assert self.txn, 'v0: missing reqd section - PSBT_GLOBAL_UNSIGNED_TX'
elif self.version == 2:
# tx version needs to be at least 2 because locktimes
assert self.txn_version == 2, 'v2: missing reqd section - PSBT_GLOBAL_TX_VERSION'
assert self.txn_version in {2, 3}, 'v2: missing reqd section - PSBT_GLOBAL_TX_VERSION'
assert self.input_count is not None, 'v2: missing reqd section - PSBT_GLOBAL_INPUT_COUNT'
assert self.output_count is not None, 'v2: missing reqd section - PSBT_GLOBAL_OUTPUT_COUNT'

View File

@ -3401,6 +3401,88 @@ def test_finalize_with_foreign_inputs(bitcoind, bitcoind_d_sim_watch, start_sign
assert title == "PSBT Signed"
assert "Finalized transaction (ready for broadcast)" in story
@pytest.mark.parametrize("psbt_v2", [False, True])
def test_txn_v3(psbt_v2, fake_txn, start_sign, end_sign, cap_story):
psbt = fake_txn([["p2wpkh"],["p2tr"]], [["p2tr", None, True],["p2wpkh"]], psbt_v2=psbt_v2)
po = BasicPSBT().parse(psbt)
if psbt_v2:
po.txn_version = 3
else:
txo = CTransaction()
txo.deserialize(BytesIO(po.txn))
txo.nVersion = 3
po.txn = txo.serialize()
start_sign(po.as_bytes())
title, story = cap_story()
assert title == "OK TO SEND?"
assert "Consolidating" not in story
assert "Change back" in story
assert "to script" not in story
signed = end_sign(accept=True, finalize=False)
assert signed
po_signed = BasicPSBT().parse(signed)
if psbt_v2:
assert po_signed.version == 2
assert po_signed.txn is None
assert po_signed.txn_version == 3
else:
assert po_signed.txn
assert po_signed.version == 0
assert po_signed.txn_version is None
txo0 = CTransaction()
txo0.deserialize(BytesIO(po_signed.txn))
assert txo0.nVersion == 3
@pytest.mark.parametrize("finalize", [False, True])
def test_txn_v3_eph_anchor(finalize, set_seed_words, start_sign, end_sign, cap_story):
# https://github.com/portlandhodl/jade-docker-web-server/blob/main/example_tools/test_vectors.py
TEST_PSBT_V3 = "cHNidP8BAFIDAAAAAdqopVcNAsr10z7dkzqG6J+3/vd10vriIDDe4O7u8GwfAQAAAAD9////AYOtdgAAAAAAFgAUlOv8+jtC6QYYNWgtA5ekaOGSfHH6WAQATwEENYfPA2elg8CAAAAA+dpuPB69NWUcEs/VfnCtrdxTyQG8xphTtTK6VDbgWHICWD2jKQxipNxf2bJk+0aHxEjn0N6nZD7MFrR/4o5QKZIQgLjDfFQAAIABAACAAAAAgAABAHECAAAAAU4xPHhslj2Nik1d9YVWuCSWLCjsI7ZTbACg4uOEtiJTAAAAAAD9////AqCKBgAAAAAAFgAUIzpZyckZ7FOvLecQynCBNbFwTf3xrXYAAAAAABYAFDpfSBYMJlSC2MY0FF4NUiMpxeZI8VgEAAEBH/GtdgAAAAAAFgAUOl9IFgwmVILYxjQUXg1SIynF5kgBAwQBAAAAIgYDz3o9vHG3Vkh9MjjDMaWr+SiQQZDs8AgzFTBs+xUnQkEYgLjDfFQAAIABAACAAAAAgAAAAAAAAAAAACICApGbj0iZaA6IAmws63XtQbV6hfJAxMKojI6QwWSIRflDGIC4w3xUAACAAQAAgAAAAIAAAAAAAQAAAAA="
TEST_PSBT_V3_EPHEMERAL_ANCHOR = "cHNidP8BAHEDAAAAAdqopVcNAsr10z7dkzqG6J+3/vd10vriIDDe4O7u8GwfAQAAAAD9////AoOtdgAAAAAAFgAUlOv8+jtC6QYYNWgtA5ekaOGSfHEAAAAAAAAAABYAFJTr/Po7QukGGDVoLQOXpGjhknxx+lgEAE8BBDWHzwNnpYPAgAAAAPnabjwevTVlHBLP1X5wra3cU8kBvMaYU7UyulQ24FhyAlg9oykMYqTcX9myZPtGh8RI59Dep2Q+zBa0f+KOUCmSEIC4w3xUAACAAQAAgAAAAIAAAQBxAgAAAAFOMTx4bJY9jYpNXfWFVrgkliwo7CO2U2wAoOLjhLYiUwAAAAAA/f///wKgigYAAAAAABYAFCM6WcnJGexTry3nEMpwgTWxcE398a12AAAAAAAWABQ6X0gWDCZUgtjGNBReDVIjKcXmSPFYBAABAR/xrXYAAAAAABYAFDpfSBYMJlSC2MY0FF4NUiMpxeZIAQMEAQAAACIGA896Pbxxt1ZIfTI4wzGlq/kokEGQ7PAIMxUwbPsVJ0JBGIC4w3xUAACAAQAAgAAAAIAAAAAAAAAAAAAiAgKRm49ImWgOiAJsLOt17UG1eoXyQMTCqIyOkMFkiEX5QxiAuMN8VAAAgAEAAIAAAACAAAAAAAEAAAAAIgICkZuPSJloDogCbCzrde1BtXqF8kDEwqiMjpDBZIhF+UMYgLjDfFQAAIABAACAAAAAgAAAAAABAAAAAA=="
TEST_MNEMONIC = "tobacco tobacco tobacco tobacco tobacco tobacco tobacco tobacco tobacco tobacco tobacco tobacco"
set_seed_words(TEST_MNEMONIC)
v3 = base64.b64decode(TEST_PSBT_V3)
po = BasicPSBT().parse(v3)
assert po.version == 0
start_sign(v3, finalize=finalize)
title, story = cap_story()
assert title == "OK TO SEND?"
assert "warning" not in story
res = end_sign(finalize=finalize)
if not finalize:
po_signed = BasicPSBT().parse(res)
assert po_signed.txn_version is None
res = po_signed.txn
txo = CTransaction()
txo.deserialize(BytesIO(res))
assert txo.nVersion == 3
v3_eph_anchor = base64.b64decode(TEST_PSBT_V3_EPHEMERAL_ANCHOR)
po = BasicPSBT().parse(v3)
assert po.version == 0
start_sign(v3_eph_anchor, finalize=finalize)
title, story = cap_story()
assert title == "OK TO SEND?"
assert "1 warning below" in story
assert "Non-standard zero value output(s)" in story
res = end_sign(finalize=finalize)
if not finalize:
po_signed = BasicPSBT().parse(res)
assert po_signed.txn_version is None
res = po_signed.txn
txo = CTransaction()
txo.deserialize(BytesIO(res))
assert txo.nVersion == 3
# EOF
@pytest.mark.bitcoind