psbt.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2022 The Limenka developers
   3  # Distributed under the MIT software license, see the accompanying
   4  # file COPYING or http://www.opensource.org/licenses/mit-license.php.
   5  
   6  import base64
   7  
   8  from .messages import (
   9      CTransaction,
  10      deser_string,
  11      from_binary,
  12      ser_compact_size,
  13  )
  14  
  15  
  16  # global types
  17  PSBT_GLOBAL_UNSIGNED_TX = 0x00
  18  PSBT_GLOBAL_XPUB = 0x01
  19  PSBT_GLOBAL_TX_VERSION = 0x02
  20  PSBT_GLOBAL_FALLBACK_LOCKTIME = 0x03
  21  PSBT_GLOBAL_INPUT_COUNT = 0x04
  22  PSBT_GLOBAL_OUTPUT_COUNT = 0x05
  23  PSBT_GLOBAL_TX_MODIFIABLE = 0x06
  24  PSBT_GLOBAL_VERSION = 0xfb
  25  PSBT_GLOBAL_PROPRIETARY = 0xfc
  26  
  27  # per-input types
  28  PSBT_IN_NON_WITNESS_UTXO = 0x00
  29  PSBT_IN_WITNESS_UTXO = 0x01
  30  PSBT_IN_PARTIAL_SIG = 0x02
  31  PSBT_IN_SIGHASH_TYPE = 0x03
  32  PSBT_IN_REDEEM_SCRIPT = 0x04
  33  PSBT_IN_WITNESS_SCRIPT = 0x05
  34  PSBT_IN_BIP32_DERIVATION = 0x06
  35  PSBT_IN_FINAL_SCRIPTSIG = 0x07
  36  PSBT_IN_FINAL_SCRIPTWITNESS = 0x08
  37  PSBT_IN_POR_COMMITMENT = 0x09
  38  PSBT_IN_RIPEMD160 = 0x0a
  39  PSBT_IN_SHA256 = 0x0b
  40  PSBT_IN_HASH160 = 0x0c
  41  PSBT_IN_HASH256 = 0x0d
  42  PSBT_IN_PREVIOUS_TXID = 0x0e
  43  PSBT_IN_OUTPUT_INDEX = 0x0f
  44  PSBT_IN_SEQUENCE = 0x10
  45  PSBT_IN_REQUIRED_TIME_LOCKTIME = 0x11
  46  PSBT_IN_REQUIRED_HEIGHT_LOCKTIME = 0x12
  47  PSBT_IN_TAP_KEY_SIG = 0x13
  48  PSBT_IN_TAP_SCRIPT_SIG = 0x14
  49  PSBT_IN_TAP_LEAF_SCRIPT = 0x15
  50  PSBT_IN_TAP_BIP32_DERIVATION = 0x16
  51  PSBT_IN_TAP_INTERNAL_KEY = 0x17
  52  PSBT_IN_TAP_MERKLE_ROOT = 0x18
  53  PSBT_IN_PROPRIETARY = 0xfc
  54  
  55  # per-output types
  56  PSBT_OUT_REDEEM_SCRIPT = 0x00
  57  PSBT_OUT_WITNESS_SCRIPT = 0x01
  58  PSBT_OUT_BIP32_DERIVATION = 0x02
  59  PSBT_OUT_AMOUNT = 0x03
  60  PSBT_OUT_SCRIPT = 0x04
  61  PSBT_OUT_TAP_INTERNAL_KEY = 0x05
  62  PSBT_OUT_TAP_TREE = 0x06
  63  PSBT_OUT_TAP_BIP32_DERIVATION = 0x07
  64  PSBT_OUT_PROPRIETARY = 0xfc
  65  
  66  
  67  class PSBTMap:
  68      """Class for serializing and deserializing PSBT maps"""
  69  
  70      def __init__(self, map=None):
  71          self.map = map if map is not None else {}
  72  
  73      def deserialize(self, f):
  74          m = {}
  75          while True:
  76              k = deser_string(f)
  77              if len(k) == 0:
  78                  break
  79              v = deser_string(f)
  80              if len(k) == 1:
  81                  k = k[0]
  82              assert k not in m
  83              m[k] = v
  84          self.map = m
  85  
  86      def serialize(self):
  87          m = b""
  88          for k,v in self.map.items():
  89              if isinstance(k, int) and 0 <= k and k <= 255:
  90                  k = bytes([k])
  91              m += ser_compact_size(len(k)) + k
  92              m += ser_compact_size(len(v)) + v
  93          m += b"\x00"
  94          return m
  95  
  96  class PSBT:
  97      """Class for serializing and deserializing PSBTs"""
  98  
  99      def __init__(self, *, g=None, i=None, o=None):
 100          self.g = g if g is not None else PSBTMap()
 101          self.i = i if i is not None else []
 102          self.o = o if o is not None else []
 103          self.tx = None
 104  
 105      def deserialize(self, f):
 106          assert f.read(5) == b"psbt\xff"
 107          self.g = from_binary(PSBTMap, f)
 108          assert PSBT_GLOBAL_UNSIGNED_TX in self.g.map
 109          self.tx = from_binary(CTransaction, self.g.map[PSBT_GLOBAL_UNSIGNED_TX])
 110          self.i = [from_binary(PSBTMap, f) for _ in self.tx.vin]
 111          self.o = [from_binary(PSBTMap, f) for _ in self.tx.vout]
 112          return self
 113  
 114      def serialize(self):
 115          assert isinstance(self.g, PSBTMap)
 116          assert isinstance(self.i, list) and all(isinstance(x, PSBTMap) for x in self.i)
 117          assert isinstance(self.o, list) and all(isinstance(x, PSBTMap) for x in self.o)
 118          assert PSBT_GLOBAL_UNSIGNED_TX in self.g.map
 119          tx = from_binary(CTransaction, self.g.map[PSBT_GLOBAL_UNSIGNED_TX])
 120          assert len(tx.vin) == len(self.i)
 121          assert len(tx.vout) == len(self.o)
 122  
 123          psbt = [x.serialize() for x in [self.g] + self.i + self.o]
 124          return b"psbt\xff" + b"".join(psbt)
 125  
 126      def make_blank(self):
 127          """
 128          Remove all fields except for PSBT_GLOBAL_UNSIGNED_TX
 129          """
 130          for m in self.i + self.o:
 131              m.map.clear()
 132  
 133          self.g = PSBTMap(map={PSBT_GLOBAL_UNSIGNED_TX: self.g.map[PSBT_GLOBAL_UNSIGNED_TX]})
 134  
 135      def to_base64(self):
 136          return base64.b64encode(self.serialize()).decode("utf8")
 137  
 138      @classmethod
 139      def from_base64(cls, b64psbt):
 140          return from_binary(cls, base64.b64decode(b64psbt))
 141