Exemple #1
0
def parse_tx(arg, parser, tx_db, network):
    # hex transaction id
    tx = None

    if TX_ID_RE.match(arg):
        if tx_db is None:
            tx_db = create_tx_db(network)
        tx = tx_db.get(h2b_rev(arg))
        if not tx:
            parser.error("can't find Tx with id %s" % arg)
        return tx, tx_db

    # hex transaction data
    try:
        return Tx.from_hex(arg), tx_db
    except Exception:
        pass

    if os.path.exists(arg):
        try:
            with open(arg, "rb") as f:
                if f.name.endswith("hex"):
                    f = io.BytesIO(codecs.getreader("hex_codec")(f).read())
                tx = Tx.parse(f)
                tx.parse_unspents(f)
        except Exception:
            pass

    return tx, tx_db
Exemple #2
0
 def tx_for_tx_hash(self, tx_hash):
     """
     returns the pycoin.tx object for tx_hash
     """
     try:
         url_append = "?token=%s&includeHex=true" % self.api_key
         url = self.base_url("txs/%s%s" % (b2h_rev(tx_hash), url_append))
         result = json.loads(urlopen(url).read().decode("utf8"))
         tx = Tx.parse(io.BytesIO(h2b(result.get("hex"))))
         return tx
     except:
         raise Exception
Exemple #3
0
 def get(self, key):
     for path in self.paths_for_hash(key):
         try:
             tx = Tx.parse(open(path, "rb"))
             if tx and tx.hash() == key:
                 return tx
         except IOError:
             pass
     for method in self.lookup_methods:
         try:
             tx = method(key)
             if tx and tx.hash() == key:
                 self.put(tx)
                 return tx
         except Exception:
             pass
     return None
Exemple #4
0
def test_change_troublesome(start_sign, cap_story, try_path, expect):
    # NOTE: out#1 is change:
    # addr = 'mvBGHpVtTyjmcfSsy6f715nbTGvwgbgbwo'
    # path = (m=4369050F)/44'/1'/0'/1/5
    # pubkey = 03c80814536f8e801859fc7c2e5129895b261153f519d4f3418ffb322884a7d7e1

    psbt = open('data/example-change.psbt', 'rb').read()
    b4 = BasicPSBT().parse(psbt)

    if 0:
        #from pycoin.tx.Tx import Tx
        #from pycoin.tx.TxOut import TxOut
        # tweak output addr to garbage
        t = Tx.parse(BytesIO(b4.txn))
        chg = t.txs_out[1]          # pycoin.tx.TxOut.TxOut
        b = bytearray(chg.script)
        b[-5] ^= 0x55
        chg.script = bytes(b)

        b4.txn = t.as_bin()

    pubkey = a2b_hex('03c80814536f8e801859fc7c2e5129895b261153f519d4f3418ffb322884a7d7e1')
    path = [int(p) if ("'" not in p) else 0x80000000+int(p[:-1]) 
                        for p in try_path.split('/')]
    bin_path = b4.outputs[1].bip32_paths[pubkey][0:4] \
                + b''.join(struct.pack('<I', i) for i in path)
    b4.outputs[1].bip32_paths[pubkey] = bin_path

    with BytesIO() as fd:
        b4.serialize(fd)
        mod_psbt = fd.getvalue()

    open('debug/troublesome.psbt', 'wb').write(mod_psbt)

    start_sign(mod_psbt)
    time.sleep(0.1)
    title, story = cap_story()
    assert 'OK TO SEND' in title
    assert '(1 warning below)' in story, "no warning shown"

    assert expect in story, story

    assert parse_change_back(story) == (Decimal('1.09997082'), ['mvBGHpVtTyjmcfSsy6f715nbTGvwgbgbwo'])
Exemple #5
0
 def test_validate_multisig(self):
     # this is a transaction in the block chain
     # the unspents are included too, so it can be validated
     f = io.BytesIO(h2b(
         "01000000025718fb915fb8b3a802bb699ddf04dd91261ef6715f5f2820a2b1b9b7e38b"
         "4f27000000004a004830450221008c2107ed4e026ab4319a591e8d9ec37719cdea0539"
         "51c660566e3a3399428af502202ecd823d5f74a77cc2159d8af2d3ea5d36a702fef9a7"
         "edaaf562aef22ac35da401ffffffff038f52231b994efb980382e4d804efeadaee13cf"
         "e01abe0d969038ccb45ec17000000000490047304402200487cd787fde9b337ab87f9f"
         "e54b9fd46d5d1692aa58e97147a4fe757f6f944202203cbcfb9c0fc4e3c453938bbea9"
         "e5ae64030cf7a97fafaf460ea2cb54ed5651b501ffffffff0100093d00000000001976"
         "a9144dc39248253538b93d3a0eb122d16882b998145888ac0000000002000000000000"
         "004751210351efb6e91a31221652105d032a2508275f374cea63939ad72f1b1e02f477"
         "da782100f2b7816db49d55d24df7bdffdbc1e203b424e8cd39f5651ab938e5e4a19356"
         "9e52ae404b4c00000000004751210351efb6e91a31221652105d032a2508275f374cea"
         "63939ad72f1b1e02f477da7821004f0331742bbc917ba2056a3b8a857ea47ec088dd10"
         "475ea311302112c9d24a7152ae"))
     tx = Tx.parse(f)
     tx.parse_unspents(f)
     self.assertEqual(tx.id(), "70c4e749f2b8b907875d1483ae43e8a6790b0c8397bbb33682e3602617f9a77a")
     self.assertEqual(tx.bad_signature_count(), 0)
Exemple #6
0
 def test_recognize_multisig(self):
     h = (
         "010000000139c92b102879eb95f14e7344e4dd7d481e1238b1bfb1fa0f735068d2927b"
         "231400000000910047304402208fc06d216ebb4b6a3a3e0f906e1512c372fa8a9c2a92"
         "505d04e9b451ea7acd0c0220764303bb7e514ddd77855949d941c934e9cbda8e3c3827"
         "bfdb5777477e73885b014730440220569ec6d2e81625dd18c73920e0079cdb4c1d67d3"
         "d7616759eb0c18cf566b3d3402201c60318f0a62e3ba85ca0f158d4dfe63c0779269eb"
         "6765b6fc939fc51e7a8ea901ffffffff0140787d01000000001976a914641ad5051edd"
         "97029a003fe9efb29359fcee409d88ac0000000040787d0100000000c952410496ec45"
         "f878b62c46c4be8e336dff7cc58df9b502178cc240eb3d31b1266f69f5767071aa3e01"
         "7d1b82a0bb28dab5e27d4d8e9725b3e68ed5f8a2d45c730621e34104cc71eb30d653c0"
         "c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b8"
         "7bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4410461cbdcc5409fb4b4d42b51"
         "d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8"
         "a540911abe3e7854a26f39f58b25c15342af53ae")
     f = io.BytesIO(h2b(h))
     tx = Tx.parse(f)
     tx.parse_unspents(f)
     self.assertEqual(tx.id(), "10c61e258e0a2b19b245a96a2d0a1538fe81cd4ecd547e0a3df7ed6fd3761ada")
     the_script = tx.unspents[0].script
     s = script_obj_from_script(tx.unspents[0].script)
     self.assertEqual(s.script(), the_script)
Exemple #7
0
    def parse(self, raw):
        if raw[0:10] == b'70736274ff':
            raw = a2b_hex(raw.strip())
        assert raw[0:5] == b'psbt\xff', "bad magic"

        with io.BytesIO(raw[5:]) as fd:

            # globals
            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]
                if kt == PSBT_GLOBAL_UNSIGNED_TX:
                    self.txn = val

                    t = Tx.parse(io.BytesIO(val))
                    num_ins = len(t.txs_in)
                    num_outs = len(t.txs_out)
                else:
                    raise ValueError('unknown global key type: 0x%02x' % kt)

            assert self.txn, 'missing reqd section'

            self.inputs = [BasicPSBTInput(fd, idx) for idx in range(num_ins)]
            self.outputs = [
                BasicPSBTOutput(fd, idx) for idx in range(num_outs)
            ]

            sep = fd.read(1)
            assert sep == b''

        return self
Exemple #8
0
 def test_validate_multisig(self):
     # this is a transaction in the block chain
     # the unspents are included too, so it can be validated
     f = io.BytesIO(
         h2b("01000000025718fb915fb8b3a802bb699ddf04dd91261ef6715f5f2820a2b1b9b7e38b"
             "4f27000000004a004830450221008c2107ed4e026ab4319a591e8d9ec37719cdea0539"
             "51c660566e3a3399428af502202ecd823d5f74a77cc2159d8af2d3ea5d36a702fef9a7"
             "edaaf562aef22ac35da401ffffffff038f52231b994efb980382e4d804efeadaee13cf"
             "e01abe0d969038ccb45ec17000000000490047304402200487cd787fde9b337ab87f9f"
             "e54b9fd46d5d1692aa58e97147a4fe757f6f944202203cbcfb9c0fc4e3c453938bbea9"
             "e5ae64030cf7a97fafaf460ea2cb54ed5651b501ffffffff0100093d00000000001976"
             "a9144dc39248253538b93d3a0eb122d16882b998145888ac0000000002000000000000"
             "004751210351efb6e91a31221652105d032a2508275f374cea63939ad72f1b1e02f477"
             "da782100f2b7816db49d55d24df7bdffdbc1e203b424e8cd39f5651ab938e5e4a19356"
             "9e52ae404b4c00000000004751210351efb6e91a31221652105d032a2508275f374cea"
             "63939ad72f1b1e02f477da7821004f0331742bbc917ba2056a3b8a857ea47ec088dd10"
             "475ea311302112c9d24a7152ae"))
     tx = Tx.parse(f)
     tx.parse_unspents(f)
     self.assertEqual(
         tx.id(),
         "70c4e749f2b8b907875d1483ae43e8a6790b0c8397bbb33682e3602617f9a77a")
     self.assertEqual(tx.bad_signature_count(), 0)
Exemple #9
0
 def test_recognize_multisig(self):
     h = (
         "010000000139c92b102879eb95f14e7344e4dd7d481e1238b1bfb1fa0f735068d2927b"
         "231400000000910047304402208fc06d216ebb4b6a3a3e0f906e1512c372fa8a9c2a92"
         "505d04e9b451ea7acd0c0220764303bb7e514ddd77855949d941c934e9cbda8e3c3827"
         "bfdb5777477e73885b014730440220569ec6d2e81625dd18c73920e0079cdb4c1d67d3"
         "d7616759eb0c18cf566b3d3402201c60318f0a62e3ba85ca0f158d4dfe63c0779269eb"
         "6765b6fc939fc51e7a8ea901ffffffff0140787d01000000001976a914641ad5051edd"
         "97029a003fe9efb29359fcee409d88ac0000000040787d0100000000c952410496ec45"
         "f878b62c46c4be8e336dff7cc58df9b502178cc240eb3d31b1266f69f5767071aa3e01"
         "7d1b82a0bb28dab5e27d4d8e9725b3e68ed5f8a2d45c730621e34104cc71eb30d653c0"
         "c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b8"
         "7bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4410461cbdcc5409fb4b4d42b51"
         "d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8"
         "a540911abe3e7854a26f39f58b25c15342af53ae")
     f = io.BytesIO(h2b(h))
     tx = Tx.parse(f)
     tx.parse_unspents(f)
     self.assertEqual(tx.id(), "10c61e258e0a2b19b245a96a2d0a1538fe81cd4ecd547e0a3df7ed6fd3761ada")
     script = tx.unspents[0].script
     multisig_info = script_info_for_script(script)
     del multisig_info["type"]
     s = script_for_multisig(**multisig_info)
     self.assertEqual(s, script)
Exemple #10
0
 def tx_for_tx_hash(self, tx_hash):
     "Get a Tx by its hash."
     URL = "%s/tx/raw/%s" % (self.url, b2h_rev(tx_hash))
     r = json.loads(urlopen(URL).read().decode("utf8"))
     tx = Tx.parse(io.BytesIO(h2b(r.get("data").get("tx").get("hex"))))
     return tx
Exemple #11
0
def parse_context(args, parser):
    # defaults

    txs = []
    spendables = []
    payables = []

    key_iters = []

    TX_ID_RE = re.compile(r"^[0-9a-fA-F]{64}$")

    # there are a few warnings we might optionally print out, but only if
    # they are relevant. We don't want to print them out multiple times, so we
    # collect them here and print them at the end if they ever kick in.

    warning_tx_cache = None
    warning_tx_for_tx_hash = None
    warning_spendables = None

    if args.private_key_file:
        wif_re = re.compile(r"[1-9a-km-zA-LMNP-Z]{51,111}")
        # address_re = re.compile(r"[1-9a-kmnp-zA-KMNP-Z]{27-31}")
        for f in args.private_key_file:
            if f.name.endswith(".gpg"):
                gpg_args = ["gpg", "-d"]
                if args.gpg_argument:
                    gpg_args.extend(args.gpg_argument.split())
                gpg_args.append(f.name)
                popen = subprocess.Popen(gpg_args, stdout=subprocess.PIPE)
                f = popen.stdout
            for line in f.readlines():
                # decode
                if isinstance(line, bytes):
                    line = line.decode("utf8")
                # look for WIFs
                possible_keys = wif_re.findall(line)

                def make_key(x):
                    try:
                        return Key.from_text(x)
                    except Exception:
                        return None

                keys = [make_key(x) for x in possible_keys]
                for key in keys:
                    if key:
                        key_iters.append((k.wif() for k in key.subkeys("")))

                # if len(keys) == 1 and key.hierarchical_wallet() is None:
                #    # we have exactly 1 WIF. Let's look for an address
                #   potential_addresses = address_re.findall(line)

    # update p2sh_lookup
    p2sh_lookup = {}
    if args.pay_to_script:
        for p2s in args.pay_to_script:
            try:
                script = h2b(p2s)
                p2sh_lookup[hash160(script)] = script
            except Exception:
                print("warning: error parsing pay-to-script value %s" % p2s)

    if args.pay_to_script_file:
        hex_re = re.compile(r"[0-9a-fA-F]+")
        for f in args.pay_to_script_file:
            count = 0
            for l in f:
                try:
                    m = hex_re.search(l)
                    if m:
                        p2s = m.group(0)
                        script = h2b(p2s)
                        p2sh_lookup[hash160(script)] = script
                        count += 1
                except Exception:
                    print("warning: error parsing pay-to-script file %s" %
                          f.name)
            if count == 0:
                print("warning: no scripts found in %s" % f.name)

    # we create the tx_db lazily
    tx_db = None

    if args.db:
        the_ram_tx_db = dict((tx.hash(), tx) for tx in args.db)
        if tx_db is None:
            tx_db = get_tx_db(args.network)
        tx_db.lookup_methods.append(the_ram_tx_db.get)

    for arg in args.argument:

        # hex transaction id
        if TX_ID_RE.match(arg):
            if tx_db is None:
                warning_tx_cache = message_about_tx_cache_env()
                warning_tx_for_tx_hash = message_about_tx_for_tx_hash_env(
                    args.network)
                tx_db = get_tx_db(args.network)
            tx = tx_db.get(h2b_rev(arg))
            if not tx:
                for m in [
                        warning_tx_cache, warning_tx_for_tx_hash,
                        warning_spendables
                ]:
                    if m:
                        print("warning: %s" % m, file=sys.stderr)
                parser.error("can't find Tx with id %s" % arg)
            txs.append(tx)
            continue

        # hex transaction data
        try:
            tx = Tx.from_hex(arg)
            txs.append(tx)
            continue
        except Exception:
            pass

        is_valid = is_address_valid(arg, allowable_netcodes=[args.network])
        if is_valid:
            payables.append((arg, 0))
            continue

        try:
            key = Key.from_text(arg)
            # TODO: check network
            if key.wif() is None:
                payables.append((key.address(), 0))
                continue
            # TODO: support paths to subkeys
            key_iters.append((k.wif() for k in key.subkeys("")))
            continue
        except Exception:
            pass

        if os.path.exists(arg):
            try:
                with open(arg, "rb") as f:
                    if f.name.endswith("hex"):
                        f = io.BytesIO(codecs.getreader("hex_codec")(f).read())
                    tx = Tx.parse(f)
                    txs.append(tx)
                    try:
                        tx.parse_unspents(f)
                    except Exception as ex:
                        pass
                    continue
            except Exception:
                pass

        parts = arg.split("/")
        if len(parts) == 4:
            # spendable
            try:
                spendables.append(Spendable.from_text(arg))
                continue
            except Exception:
                pass

        if len(parts) == 2 and is_address_valid(
                parts[0], allowable_netcodes=[args.network]):
            try:
                payables.append(parts)
                continue
            except ValueError:
                pass

        parser.error("can't parse %s" % arg)

    if args.fetch_spendables:
        warning_spendables = message_about_spendables_for_address_env(
            args.network)
        for address in args.fetch_spendables:
            spendables.extend(spendables_for_address(address))

    for tx in txs:
        if tx.missing_unspents() and (args.augment or tx_db):
            if tx_db is None:
                warning_tx_cache = message_about_tx_cache_env()
                warning_tx_for_tx_hash = message_about_tx_for_tx_hash_env(
                    args.network)
                tx_db = get_tx_db(args.network)
            tx.unspents_from_db(tx_db, ignore_missing=True)

    return (txs, spendables, payables, key_iters, p2sh_lookup, tx_db,
            warning_tx_cache, warning_tx_for_tx_hash, warning_spendables)
Exemple #12
0
def deserialize(tx_data):
    return Tx.parse(BytesIO(tx_data))
Exemple #13
0
def deserialize(tx_data):
    return Tx.parse(BytesIO(tx_data))
Exemple #14
0
 def tx_from_b64(h):
     f = io.BytesIO(binascii.a2b_base64(h.encode("utf8")))
     return Tx.parse(f)
Exemple #15
0
 def tx_for_tx_hash(self, tx_hash):
     "Get a Tx by its hash."
     url = self.base_url("get_tx", b2h_rev(tx_hash))
     r = json.loads(urlopen(url).read().decode("utf8"))
     tx = Tx.parse(io.BytesIO(h2b(r.get("data").get("tx_hex"))))
     return tx
Exemple #16
0
 def tx_for_tx_hash(self, tx_hash):
     "Get a Tx by its hash."
     url = self.base_url("get_tx", b2h_rev(tx_hash))
     r = json.loads(urlopen(url).read().decode("utf8"))
     tx = Tx.parse(io.BytesIO(h2b(r.get("data").get("tx_hex"))))
     return tx
Exemple #17
0
def dump(psbt, hex_output, bin_output, testnet, base64, show_addrs):

    raw = psbt.read()
    if raw[0:10] == b'70736274ff':
        raw = _a2b_hex(raw.strip())
    if raw[0:6] == b'cHNidP':
        raw = b64decode(raw)

    #assert raw[0:5] == b'psbt\xff'

    if hex_output:
        print(b2a_hex(raw))
        sys.exit(0)

    if base64:
        print(str(b64encode(raw), 'ascii'))
        sys.exit(0)

    print("%d bytes in PSBT: %s" % (len(raw), psbt.name))

    if bin_output:
        bin_output.write(raw)
        sys.exit(0)

    with io.BytesIO(raw) as fd:
        hdr = fd.read(4)
        sep1 = fd.read(1)

        print("-- HEADER --\n\n%s 0x%02x\n" % (str(hdr, 'ascii'), sep1[0]))

        print("-- GLOBALS --")
        num_ins = None
        num_outs = None
        section = 'globals'
        section_idx = 0
        expect_outs = set()

        while 1:
            first = fd.read(1)
            if first == b'':
                print("-- ACTUAL EOF --")
                break

            try:
                ks = deser_compact_size(fd, first[0])
            except:
                print("? confused at %d=0x%x" % (fd.tell(), fd.tell()))
                break

            if ks == 0:
                section_idx += 1

                if section == 'globals':
                    section_idx = 0
                    section = 'inputs'
                    print("-- INPUT #0 --")
                elif section == 'inputs':
                    if section_idx == num_ins:
                        print("-- OUTPUT #0 --")
                        section = 'outputs'
                        section_idx = 0
                    else:
                        print("-- INPUT #%d --" % section_idx)
                elif section == 'outputs':
                    if section_idx == num_outs:
                        print("-- EXPECT EOF --")
                        section = 'past eof'
                        section_idx = 0
                    else:
                        print("-- OUTPUT #%d --" % section_idx)
                else:
                    print("-- ?? %s ??  --" % section.upper())
                continue

            try:
                assert ks
                key = fd.read(ks)

                vs = deser_compact_size(fd)
                assert vs
                val = fd.read(vs)

            except:
                print("? confused at %d=0x%x" % (fd.tell(), fd.tell()))
                break

            try:
                if section == 'globals':
                    purpose = ['GLOBAL_UNSIGNED_TX', 'GLOBAL_XPUB'][key[0]]
                elif section == 'inputs':
                    purpose = [
                        'IN_NON_WITNESS_UTXO', 'IN_WITNESS_UTXO',
                        'IN_PARTIAL_SIG', 'IN_SIGHASH_TYPE',
                        'IN_REDEEM_SCRIPT', 'IN_WITNESS_SCRIPT',
                        'IN_BIP32_DERIVATION', 'IN_FINAL_SCRIPTSIG',
                        'IN_FINAL_SCRIPTWITNESS'
                    ][key[0]]
                elif section == 'outputs':
                    purpose = [
                        'OUT_REDEEM_SCRIPT', 'OUT_WITNESS_SCRIPT',
                        'OUT_BIP32_DERIVATION'
                    ][key[0]]

            except IndexError:
                purpose = 'Unknown type=0x%0x' % key[0]

            print('\n  key: %02x ' % key[0], end='')
            if len(key) <= 1:
                print("(%s)" % purpose)
            else:
                print('%s\n       (%s + %d bytes)' %
                      (b2a_hex(key[1:]), purpose, ks - 1))

            print('value: ', end='')

            if len(val) == 4 and key[0] != PSBT_GLOBAL_XPUB:
                nn, = struct.unpack("<I", val)
                print("'%s' = 0x%x = %d\n" % (b2a_hex(val), nn, nn))
                continue

            print('%s  (%d bytes)\n' % (b2a_hex(val), vs))

            # prefix byte for addresses in current network
            ADP = b'\x6f' if testnet else b'\0'

            if (section, key[0]) in [('globals', PSBT_GLOBAL_UNSIGNED_TX),
                                     ('inputs', PSBT_IN_NON_WITNESS_UTXO)]:
                # Parse and sumarize the bitcoin transaction.
                # - also works for non-witness UTXO

                try:
                    t = Tx.parse(io.BytesIO(val))
                    print(" Transaction: (%d inputs, %d outputs, %d witness)" %
                          (len(t.txs_in), len(t.txs_out),
                           sum(1 for i in t.txs_in if i.witness)))
                    print("            : txid %s" % t.hash())
                    for n, i in enumerate(t.txs_in):
                        print("   [in #%d] %s" %
                              (n,
                               i.address(ADP) if i.script else '(not signed)'))
                        if section == 'globals':
                            print("    from %s : %d" %
                                  (b2h_rev(i.previous_hash), i.previous_index))
                    for n, o in enumerate(t.txs_out):
                        out_addr = render_address(o.script, testnet)
                        print("  [out #%d] %.8f => %s" %
                              (n, o.coin_value / 1E8, out_addr))
                        expect_outs.add(out_addr)
                    print("\n")

                    if num_ins is None:
                        num_ins = len(t.txs_in)
                        num_outs = len(t.txs_out)
                except:
                    print("(unable to parse txn)")
                    raise

            if (section, key[0]) == ('globals', PSBT_GLOBAL_XPUB):
                # key is: binary BIP32 serialization (not base58)
                # value is: master key fingerprint catenated with the derivation path of public key

                fingerprint = val[0:4]
                if len(val) > 4:
                    path = [
                        struct.unpack_from('<I', val, offset=i)[0]
                        for i in range(4, len(val), 4)
                    ]
                    path = [
                        str(i & 0x7fffffff) + ("'" if i & 0x80000000 else "")
                        for i in path
                    ]
                else:
                    # valid: no subpath, just xfp.
                    path = []

                print("       XPUB: %s" % b2a_hashed_base58(key[1:]))
                print("    HD Path: (m=%s)/%s\n" %
                      (xfp2hex(fingerprint), '/'.join(path)))

            if (section, key[0]) in [('inputs', PSBT_IN_BIP32_DERIVATION),
                                     ('outputs', PSBT_OUT_BIP32_DERIVATION)]:
                # HD key paths
                try:
                    pubkey = key[1:]
                    fingerprint = val[0:4]
                    path = [
                        struct.unpack_from('<I', val, offset=i)[0]
                        for i in range(4, len(val), 4)
                    ]
                    path = [
                        str(i & 0x7fffffff) + ("'" if i & 0x80000000 else "")
                        for i in path
                    ]

                    # conservative: render them all, pick one found if expected
                    # - but not helpful for multisig, need to look around to know if thats the case
                    addrs = []
                    if show_addrs:
                        if len(pubkey) in {33, 65}:
                            # assume old skool b58 p2pkh, bitcoin mainnet, etc.
                            h20 = hash160(pubkey)
                            for prefix in [0, 111]:
                                addrs.append(
                                    b2a_hashed_base58(bytes([prefix]) + h20))
                            for hrp in ['bc', 'tb']:
                                addrs.append(bech32_encode(hrp, 0,
                                                           h20))  # really?

                        match = set(addrs).intersection(expect_outs)
                        if match:
                            addrs = list(match)

                    print("     Pubkey: %s (%d bytes)" %
                          (b2a_hex(pubkey), len(pubkey)))
                    print("    HD Path: (m=%s)/%s" %
                          (xfp2hex(fingerprint), '/'.join(path)))
                    for addr in addrs:
                        print("             = %s" % addr)
                    print("\n")
                except:
                    print("(unable to parse hdpath)")

            if (section, key[0]) in [('inputs', PSBT_IN_REDEEM_SCRIPT),
                                     ('inputs', PSBT_IN_WITNESS_SCRIPT),
                                     ('outputs', PSBT_OUT_REDEEM_SCRIPT),
                                     ('outputs', PSBT_OUT_WITNESS_SCRIPT)]:

                try:
                    if val[-1] == 0xAE and len(val) > 22:
                        M, N = (val[0] - 80, val[-2] - 80)
                        print("     P2SH Multisig: %d of %d" % (M, N))
                        print("     Pubkeys: ")
                        for idx in range(N):
                            pk = val[1 + (34 * idx):]
                            assert pk[0] == 0x21
                            print("        #%d: %s" %
                                  (idx + 1, b2a_hex(pk[1:1 + 33])))

                        exp = ((N * 34) + 3)
                        if len(val) > exp:
                            print("        (plus JUNK: %d bytes)" %
                                  (len(val) - exp))
                        print("\n")

                        # XXX decode implied p2sh addresses here too?
                except:
                    print("(unable to parse POSSIBLE multisig redeem script)")