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
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
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
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'])
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)
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)
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
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)
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)
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
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)
def deserialize(tx_data): return Tx.parse(BytesIO(tx_data))
def tx_from_b64(h): f = io.BytesIO(binascii.a2b_base64(h.encode("utf8"))) return Tx.parse(f)
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
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)")