#!/usr/bin/env python3 # Copyright (c) 2022-present The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. ######################################################################## # Adapted from Bitcoin Core test framework psbt.py # for BIP-375 PSBT validation tests. ######################################################################## import base64 import struct from io import BytesIO from .messages import ( CTransaction, deser_string, deser_compact_size, from_binary, ser_compact_size, ) # global types PSBT_GLOBAL_UNSIGNED_TX = 0x00 PSBT_GLOBAL_XPUB = 0x01 PSBT_GLOBAL_TX_VERSION = 0x02 PSBT_GLOBAL_FALLBACK_LOCKTIME = 0x03 PSBT_GLOBAL_INPUT_COUNT = 0x04 PSBT_GLOBAL_OUTPUT_COUNT = 0x05 PSBT_GLOBAL_TX_MODIFIABLE = 0x06 PSBT_GLOBAL_VERSION = 0xfb PSBT_GLOBAL_PROPRIETARY = 0xfc # per-input types PSBT_IN_NON_WITNESS_UTXO = 0x00 PSBT_IN_WITNESS_UTXO = 0x01 PSBT_IN_PARTIAL_SIG = 0x02 PSBT_IN_SIGHASH_TYPE = 0x03 PSBT_IN_REDEEM_SCRIPT = 0x04 PSBT_IN_WITNESS_SCRIPT = 0x05 PSBT_IN_BIP32_DERIVATION = 0x06 PSBT_IN_FINAL_SCRIPTSIG = 0x07 PSBT_IN_FINAL_SCRIPTWITNESS = 0x08 PSBT_IN_POR_COMMITMENT = 0x09 PSBT_IN_RIPEMD160 = 0x0a PSBT_IN_SHA256 = 0x0b PSBT_IN_HASH160 = 0x0c PSBT_IN_HASH256 = 0x0d PSBT_IN_PREVIOUS_TXID = 0x0e PSBT_IN_OUTPUT_INDEX = 0x0f PSBT_IN_SEQUENCE = 0x10 PSBT_IN_REQUIRED_TIME_LOCKTIME = 0x11 PSBT_IN_REQUIRED_HEIGHT_LOCKTIME = 0x12 PSBT_IN_TAP_KEY_SIG = 0x13 PSBT_IN_TAP_SCRIPT_SIG = 0x14 PSBT_IN_TAP_LEAF_SCRIPT = 0x15 PSBT_IN_TAP_BIP32_DERIVATION = 0x16 PSBT_IN_TAP_INTERNAL_KEY = 0x17 PSBT_IN_TAP_MERKLE_ROOT = 0x18 PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS = 0x1a PSBT_IN_MUSIG2_PUB_NONCE = 0x1b PSBT_IN_MUSIG2_PARTIAL_SIG = 0x1c PSBT_IN_PROPRIETARY = 0xfc # per-output types PSBT_OUT_REDEEM_SCRIPT = 0x00 PSBT_OUT_WITNESS_SCRIPT = 0x01 PSBT_OUT_BIP32_DERIVATION = 0x02 PSBT_OUT_AMOUNT = 0x03 PSBT_OUT_SCRIPT = 0x04 PSBT_OUT_TAP_INTERNAL_KEY = 0x05 PSBT_OUT_TAP_TREE = 0x06 PSBT_OUT_TAP_BIP32_DERIVATION = 0x07 PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08 PSBT_OUT_PROPRIETARY = 0xfc class PSBTMap: """Class for serializing and deserializing PSBT maps""" def __init__(self, map=None): self.map = map if map is not None else {} def deserialize(self, f): m = {} while True: k = deser_string(f) if len(k) == 0: break v = deser_string(f) if len(k) == 1: k = k[0] assert k not in m m[k] = v self.map = m def serialize(self): m = b"" for k,v in self.map.items(): if isinstance(k, int) and 0 <= k and k <= 255: k = bytes([k]) if isinstance(v, list): assert all(type(elem) is bytes for elem in v) v = b"".join(v) # simply concatenate the byte-strings w/o size prefixes m += ser_compact_size(len(k)) + k m += ser_compact_size(len(v)) + v m += b"\x00" return m class PSBT: """Class for serializing and deserializing PSBTs""" def __init__(self, *, g=None, i=None, o=None): self.g = g if g is not None else PSBTMap() self.i = i if i is not None else [] self.o = o if o is not None else [] self.in_count = len(i) if i is not None else None self.out_count = len(o) if o is not None else None self.version = None def deserialize(self, f): assert f.read(5) == b"psbt\xff" self.g = from_binary(PSBTMap, f) self.version = 0 if PSBT_GLOBAL_VERSION in self.g.map: assert PSBT_GLOBAL_INPUT_COUNT in self.g.map assert PSBT_GLOBAL_OUTPUT_COUNT in self.g.map self.version = struct.unpack("