# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC. # # psbt.py - yet another PSBT parser/serializer but used only for test cases. # import io, struct from binascii import b2a_hex as _b2a_hex from binascii import a2b_hex from base64 import b64decode, b64encode from serialize import ser_compact_size, deser_compact_size from ctransaction import CTransaction, CTxOut, CTxIn, COutPoint, uint256_from_str, ser_uint256 b2a_hex = lambda a: str(_b2a_hex(a), 'ascii') # BIP-174 aka PSBT defined values # # GLOBAL === PSBT_GLOBAL_UNSIGNED_TX = 0x00 PSBT_GLOBAL_XPUB = 0x01 PSBT_GLOBAL_VERSION = 0xfb PSBT_GLOBAL_PROPRIETARY = 0xfc # BIP-370 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_GENERIC_SIGNED_MESSAGE = 0x09 # INPUTS === 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 # Proof of Reserves PSBT_IN_RIPEMD160 = 0x0a PSBT_IN_SHA256 = 0x0b PSBT_IN_HASH160 = 0x0c PSBT_IN_HASH256 = 0x0d # BIP-370 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 # BIP-371 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 # OUTPUTS === PSBT_OUT_REDEEM_SCRIPT = 0x00 PSBT_OUT_WITNESS_SCRIPT = 0x01 PSBT_OUT_BIP32_DERIVATION = 0x02 # BIP-370 PSBT_OUT_AMOUNT = 0x03 PSBT_OUT_SCRIPT = 0x04 # BIP-371 PSBT_OUT_TAP_INTERNAL_KEY = 0x05 PSBT_OUT_TAP_TREE = 0x06 PSBT_OUT_TAP_BIP32_DERIVATION = 0x07 PSBT_PROP_CK_ID = b"COINKITE" def ser_prop_key(identifier, subtype, keydata=b''): # arg types are: bytes, int (< 256), bytes key = b"" key += ser_compact_size(len(identifier)) key += identifier key += ser_compact_size(subtype) key += keydata return key class PSBTSection: def __init__(self, fd=None, idx=None): self.defaults() self.my_index = idx if not fd: return while 1: ks = deser_compact_size(fd) if ks is None: break if ks == 0: break key = fd.read(ks) vs = deser_compact_size(fd) val = fd.read(vs) kt = key[0] self.parse_kv(kt, key[1:], val) def serialize(self, fd, v2): def wr(ktype, val, key=b''): fd.write(ser_compact_size(1 + len(key))) fd.write(bytes([ktype]) + key) fd.write(ser_compact_size(len(val))) fd.write(val) self.serialize_kvs(wr, v2) fd.write(b'\0') class BasicPSBTInput(PSBTSection): def defaults(self): self.utxo = None self.witness_utxo = None self.part_sigs = {} self.sighash = None self.bip32_paths = {} self.taproot_bip32_paths = {} self.taproot_internal_key = None self.taproot_key_sig = None self.redeem_script = None self.witness_script = None self.previous_txid = None # v2 self.prevout_idx = None # v2 self.sequence = None # v2 self.req_time_locktime = None # v2 self.req_height_locktime = None # v2 self.others = {} self.unknown = {} def __eq__(a, b): if a.sighash != b.sighash: # no sighash == SIGHASH_ALL if {a.sighash, b.sighash} != {None, 1}: return False rv = a.utxo == b.utxo and \ a.witness_utxo == b.witness_utxo and \ a.redeem_script == b.redeem_script and \ a.witness_script == b.witness_script and \ a.my_index == b.my_index and \ a.bip32_paths == b.bip32_paths and \ a.taproot_key_sig == b.taproot_key_sig and \ a.taproot_bip32_paths == b.taproot_bip32_paths and \ a.taproot_internal_key == b.taproot_internal_key and \ sorted(a.part_sigs.keys()) == sorted(b.part_sigs.keys()) and \ a.previous_txid == b.previous_txid and \ a.prevout_idx == b.prevout_idx and \ a.sequence == b.sequence and \ a.req_time_locktime == b.req_time_locktime and \ a.req_height_locktime == b.req_height_locktime and \ a.unknown == b.unknown if rv: # NOTE: equality test on signatures requires parsing DER stupidness # and some maybe understanding of R/S values on curve that I don't have. assert all(a.part_sigs[k] == b.part_sigs[k] for k in a.part_sigs) return rv def parse_kv(self, kt, key, val): if kt == PSBT_IN_NON_WITNESS_UTXO: self.utxo = val assert not key elif kt == PSBT_IN_WITNESS_UTXO: self.witness_utxo = val assert not key elif kt == PSBT_IN_PARTIAL_SIG: self.part_sigs[key] = val elif kt == PSBT_IN_SIGHASH_TYPE: assert len(val) == 4 self.sighash = struct.unpack(" val=(path) # ignore PSBT_GLOBAL_XPUB on 0th index (should not be part of parsed key) self.xpubs.append((key[1:], val)) elif kt == PSBT_GLOBAL_VERSION: self.version = struct.unpack("