Working
This commit is contained in:
parent
7f5852ec17
commit
2b9cc32f8c
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,6 +2,7 @@ public.txt
|
||||
output.psbt
|
||||
foo*.psbt
|
||||
foo*.txt
|
||||
pp
|
||||
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
|
||||
88
README.md
88
README.md
@ -1,8 +1,8 @@
|
||||
# PSBT Faker
|
||||
|
||||
A simple program to create test PSBT files, that are plausible and self-consistent so
|
||||
the PSBT-signing tools will sign them. Does not involve any blockchains... completely
|
||||
made up inputs.
|
||||
that PSBT-signing tools will sign them. Does not involve any blockchains... completely
|
||||
made up inputs, output addresses choosen at random.
|
||||
|
||||
|
||||
## Usage
|
||||
@ -10,7 +10,25 @@ made up inputs.
|
||||
```
|
||||
# python3 -m pip install --editable .
|
||||
# rehash
|
||||
# pbst_faker test.psbt 1destaddr
|
||||
# pbst_faker --help
|
||||
|
||||
Usage: psbt_faker [OPTIONS] OUTPUT.PSBT XPUB
|
||||
|
||||
Construct a valid PSBT which spends non-existant BTC to random addresses!
|
||||
|
||||
Options:
|
||||
-t, --testnet Assume testnet3 addresses (default mainet)
|
||||
-s, --segwit Make ins/outs be segwit style
|
||||
-n, --num-outs INTEGER Number of outputs (default 1)
|
||||
-c, --num-change INTEGER Number of change outputs (default 1)
|
||||
-v, --value INTEGER Total BTC value of inputs (integer, default
|
||||
3)
|
||||
-f, --fee INTEGER Miner's fee in Satoshis
|
||||
-a, --styles [p2wpkh|p2wsh|p2sh|p2pkh|p2wsh-p2sh|p2wpkh-p2sh]
|
||||
Output address style (multiple ok)
|
||||
-6, --base64 Output base64 (default binary)
|
||||
--help Show this message and exit.
|
||||
|
||||
```
|
||||
|
||||
## Requirements
|
||||
@ -21,3 +39,67 @@ made up inputs.
|
||||
|
||||
(See `requirements.txt`)
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
```
|
||||
$ psbt_faker foo.psbt tpubD6NzVbkrYhZ4Xp6tGusznF6KMdYHy1JSCdDk3XVLDuAA7EgJKghA5J1FP4pDXb4sCypJjAYPB4uTTXkVo2iWzK8BsMaccXTNyShDx3gxagi -s -a p2wsh --fee 15000000 -c 0
|
||||
|
||||
Fake PSBT would send 3 BTC to:
|
||||
2.85000000 => bc1qqalzjffzy9nwcd35t0phdyugdmmqpskldgcw3xd40qxh32z908msf5alem
|
||||
0.15000000 => miners fee
|
||||
|
||||
$ psbt_faker foo.psbt tpubD6NzVbkrYhZ4Xp6tGusznF6KMdYHy1JSCdDk3XVLDuAA7EgJKghA5J1FP4pDXb4sCypJjAYPB4uTTXkVo2iWzK8BsMaccXTNyShDx3gxagi -n 10
|
||||
|
||||
Fake PSBT would send 3 BTC to:
|
||||
0.27272636 => 17VardgvHiYjDEtpBRWpqQLgrvKDUiGGaW
|
||||
0.27272636 => 1A1FDLRD1caNjbwpr4odqpcB2sGgZSgGqZ
|
||||
0.27272636 => 1P3Zr4zQko2CDbDDiqrkMduSppNB3Pb1Aq
|
||||
0.27272636 => 1LcDusCVB6KjjAcrk5NvscV4AQ3cRJTR8j
|
||||
0.27272636 => 15oy1fAxnbYr6Vgz7eNwjBQfujdvssdRaG
|
||||
0.27272636 => 1EkYuiLo9Tt3cYCJwMfDvX38MddTBMqPc1
|
||||
0.27272636 => 185VxgHqCEYudH6XXwdDiQtqfEUXGMxSXJ
|
||||
0.27272636 => 19dR12aRSj8nyUaJLM11ruExa7N6jdAmUJ
|
||||
0.27272636 => 1Ppj73d7z6cQvKhzezmaBywbJRSUnrymPE
|
||||
0.27272636 => 1CPCdAWTrVqgS8cHVTbDQwkCvASjTfcaTe
|
||||
0.27272636 => 1F2WTuA3BRpYmM82gsLuAdyAiLPYoUYijP (change back)
|
||||
0.00001000 => miners fee
|
||||
|
||||
|
||||
$ psbt_faker foo.psbt tpubD6NzVbkrYhZ4Xp6tGusznF6KMdYHy1JSCdDk3XVLDuAA7EgJKghA5J1FP4pDXb4sCypJjAYPB4uTTXkVo2iWzK8BsMaccXTNyShDx3gxagi -n 3 -v 100 -c 10
|
||||
|
||||
Fake PSBT would send 100 BTC to:
|
||||
7.69230692 => 13mRoGiQHzmPhaCgQZbjw42njWhV3ymqDw
|
||||
7.69230692 => 1MMbuGuuaJ9GnRXh4ixa6xiKER3xzg52TJ
|
||||
7.69230692 => 1NjnUBrWSSx8iK5TC3XJXqQ7grC23kpZX2
|
||||
7.69230692 => 1Aq96VVsd2nocTqAYQ4PnD6XhotKqmrBNn (change back)
|
||||
7.69230692 => 1Bj7KprFDJ1d1F1se3DKedASFYvjWNaZMT (change back)
|
||||
7.69230692 => 1HVTgLgZF95tF4B1CJk4BEvkLmT3hYDrmA (change back)
|
||||
7.69230692 => 17Uz3tHeG1Zf8W4hmst2kQtbH17tHe3UTN (change back)
|
||||
7.69230692 => 1LyLjaPcXbo5TxJYMYyUT9HzCgkJnKef1j (change back)
|
||||
7.69230692 => 19DasuH8grQGc4MrPPR5abYUZAKF9UbbwZ (change back)
|
||||
7.69230692 => 1JpymwTGWfXpcurLnsFbLcPRnkkzvRiKsy (change back)
|
||||
7.69230692 => 1PoFUSStjmogrv2eEtRjbpz8N5reETVVZn (change back)
|
||||
7.69230692 => 1Q8yrQsHotMNkrsAyEynDAuqC8rDs7nG41 (change back)
|
||||
7.69230692 => 1AbiaE64hjUygVoqkedaLvneHht8bbvPgo (change back)
|
||||
0.00001000 => miners fee
|
||||
|
||||
psbt_faker foo.psbt tpubD6NzVbkrYhZ4Xp6tGusznF6KMdYHy1JSCdDk3XVLDuAA7EgJKghA5J1FP4pDXb4sCypJjAYPB4uTTXkVo2iWzK8BsMaccXTNyShDx3gxagi -n 10 -a p2wpkh -a p2wsh -a p2sh -a p2pkh -a p2wsh-p2sh -a p2wpkh-p2sh
|
||||
|
||||
Fake PSBT would send 3 BTC to:
|
||||
0.27272636 => bc1q2l0zgfksxacs8hdxwmq56ftpzagcyvq8z237qf
|
||||
0.27272636 => bc1q4ru6vpngexl348we0fkydheat3azcvr96uc975tmvcy0z8kjaz6qz30498
|
||||
0.27272636 => 37Axq8rmQGjEHVoCb877RiNfWnnMtFCZ6H
|
||||
0.27272636 => 16JDSqRVvYdWV4KntQ5wjUK5es6CaiTyBc
|
||||
0.27272636 => 3HXq92K1xvx6QMNmQTHPWPLNiEReez595d
|
||||
0.27272636 => 3LyBpZ2aaTs1Qj1NFmGGttL8PyhEzB9iDW
|
||||
0.27272636 => bc1qsplnzq8n500q4zg6a8m2nj4c8ygvlp8p8zuppc
|
||||
0.27272636 => bc1qw9hery5rjcujuf3f09djlxepepx6luen7jq9t0hfsu44dv3t6x3s4k4aw5
|
||||
0.27272636 => 3Ld1TUaWQouRRGGAc8PSzvqtgjfyxdM3Vr
|
||||
0.27272636 => 1ABmPHMdqK4MqF9BkACv8PHHYL7McmbYAq
|
||||
0.27272636 => 15mkVohf2A1g9nVo9tn2KtN2f4eBHQCche (change back)
|
||||
0.00001000 => miners fee
|
||||
|
||||
|
||||
```
|
||||
|
||||
137
main.py
137
main.py
@ -26,11 +26,11 @@ from pycoin.key.BIP32Node import BIP32Node
|
||||
from pycoin.convention import tx_fee
|
||||
import urllib.request
|
||||
|
||||
from txn import *
|
||||
|
||||
b2a_hex = lambda a: str(_b2a_hex(a), 'ascii')
|
||||
#xfp2hex = lambda a: b2a_hex(a[::-1]).upper()
|
||||
|
||||
TESTNET = False
|
||||
|
||||
def str2ipath(s):
|
||||
# convert text to numeric path for BIP174
|
||||
for i in s.split('/'):
|
||||
@ -56,122 +56,43 @@ def str2path(xfp, s):
|
||||
p = list(str2ipath(s))
|
||||
return struct.pack('<%dI' % (1 + len(p)), xfp, *p)
|
||||
|
||||
def calc_pubkey(xpubs, path):
|
||||
# given a map of paths to xpubs, and a single path, calculate the pubkey
|
||||
assert path[0:2] == 'm/'
|
||||
|
||||
hard_prefix = '/'.join(s for s in path.split('/') if s[-1] == "'")
|
||||
hard_depth = hard_prefix.count('/') + 1
|
||||
|
||||
want = ('m/'+hard_prefix) if hard_prefix else 'm'
|
||||
assert want in xpubs, f"Need: {want} to build pubkey of {path}"
|
||||
|
||||
node = BIP32Node.from_hwif(xpubs[want])
|
||||
parts = [s for s in path.split('/') if s != 'm'][hard_depth:]
|
||||
|
||||
# node = node.subkey_for_path(path[2:])
|
||||
if not parts:
|
||||
assert want == path
|
||||
else:
|
||||
for sk in parts:
|
||||
node = node.subkey_for_path(sk)
|
||||
|
||||
return node.sec()
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument('out_psbt', type=click.File('wb'))
|
||||
@click.argument('payout_addresses', type=str, nargs='*')
|
||||
@click.option('--testnet', '-t', help="Assume testnet3 addresses", is_flag=True, default=False)
|
||||
@click.option('--xpub', help="Provide XPUB value", default=None)
|
||||
@click.option('--num-change', '-c', help="Number of change outputs", default=1)
|
||||
@click.option('--xfp', '--fingerprint', help="Provide XFP value, otherwise discovered from xpub", default=None)
|
||||
def faker(num_change, payout_addresses, out_psbt, testnet, xfp=None, xpub=None):
|
||||
@click.argument('out_psbt', type=click.File('wb'), metavar="OUTPUT.PSBT")
|
||||
@click.argument('xpub', type=str)
|
||||
@click.option('--testnet', '-t', help="Assume testnet3 addresses (default mainet)", is_flag=True, default=False)
|
||||
@click.option('--segwit', '-s', help="Make ins/outs be segwit style", is_flag=True, default=False)
|
||||
@click.option('--num-outs', '-n', help="Number of outputs (default 1)", default=1)
|
||||
@click.option('--num-change', '-c', help="Number of change outputs (default 1)", default=1)
|
||||
@click.option('--value', '-v', help="Total BTC value of inputs (integer, default 3)", default=3)
|
||||
@click.option('--fee', '-f', help="Miner's fee in Satoshis", default=1000)
|
||||
@click.option('--styles', '-a', help="Output address style (multiple ok)", multiple=True, default=None, type=click.Choice(ADDR_STYLES))
|
||||
@click.option('--base64', '-6', help="Output base64 (default binary)", is_flag=True, default=False)
|
||||
def faker(num_change, num_outs, out_psbt, value, testnet, xpub, segwit, fee, styles, base64):
|
||||
'''Construct a valid PSBT which spends non-existant BTC to random addresses!'''
|
||||
|
||||
global TESTNET
|
||||
TESTNET = testnet
|
||||
num_ins = int(value)
|
||||
total_outs = num_outs + num_change
|
||||
|
||||
''' Match lines like:
|
||||
m/0'/0'/0' => n3ieqYKgVR8oB2zsHVX1Pr7Zc31pP3C7ZJ
|
||||
m/0/2 => mh7finD8ctq159hbRzAeevSuFBJ1NQjoH2
|
||||
and also
|
||||
m => tpubD6NzVbkrYhZ4XzL5Dhayo67Gorv1YMS7j8pRUvVMd5odC2LBPLAygka9p7748JtSq82FNGPppFEz5xxZUdasBRCqJqXvUHq6xpnsMcYJzeh
|
||||
'''
|
||||
chg_style = 'p2pkh' if not segwit else 'p2wpkh'
|
||||
|
||||
psbt = BasicPSBT()
|
||||
if not styles:
|
||||
styles = [chg_style]
|
||||
|
||||
for path, addr in addrs:
|
||||
print(f"addr: {addr} ... ", end='')
|
||||
|
||||
rr = explora('address', addr, 'utxo')
|
||||
|
||||
if not rr:
|
||||
print('nada')
|
||||
continue
|
||||
|
||||
here = 0
|
||||
for u in rr:
|
||||
here += u['value']
|
||||
|
||||
tt = TxIn(h2b_rev(u['txid']), u['vout'])
|
||||
spending.append(tt)
|
||||
#print(rr)
|
||||
|
||||
pin = BasicPSBTInput(idx=len(psbt.inputs))
|
||||
psbt.inputs.append(pin)
|
||||
|
||||
pubkey = calc_pubkey(xpubs, path)
|
||||
|
||||
pin.bip32_paths[pubkey] = str2path(xfp, path)
|
||||
|
||||
# fetch the UTXO for witness signging
|
||||
td = explora('tx', u['txid'], 'hex', is_json=False)
|
||||
outpt = Tx.from_hex(td.decode('ascii')).txs_out[u['vout']]
|
||||
|
||||
with BytesIO() as b:
|
||||
outpt.stream(b)
|
||||
pin.witness_utxo = b.getvalue()
|
||||
psbt, outs = fake_txn(num_ins, total_outs, master_xpub=xpub, fee=fee,
|
||||
segwit_in=segwit, outstyles=styles, change_style=chg_style,
|
||||
is_testnet=testnet, change_outputs=list(range(num_outs, num_outs+num_change)))
|
||||
|
||||
|
||||
print('%.8f BTC' % (here / 1E8))
|
||||
total += here
|
||||
out_psbt.write(psbt if not base64 else b64encode(psbt))
|
||||
|
||||
if len(spending) > 15:
|
||||
print("Reached practical limit on # of inputs. "
|
||||
"You'll need to repeat this process again later.")
|
||||
break
|
||||
print(f"\nFake PSBT would send {num_ins} BTC to: ")
|
||||
print('\n'.join(" %.8f => %s %s" % (amt,dest, ' (change back)' if chg else '') for amt,dest,chg in outs))
|
||||
if fee:
|
||||
print(" %.8f => miners fee" % (Decimal(fee)/Decimal(1E8)))
|
||||
|
||||
assert total
|
||||
|
||||
print("Found total: %.8f BTC" % (total / 1E8))
|
||||
|
||||
print("Planning to send to: %s" % payout_address)
|
||||
|
||||
dest_scr = standard_tx_out_script(payout_address)
|
||||
|
||||
txn = Tx(2,spending,[TxOut(total, dest_scr)])
|
||||
|
||||
fee = tx_fee.recommended_fee_for_tx(txn)
|
||||
|
||||
# placeholder, single output that isn't change
|
||||
pout = BasicPSBTOutput(idx=0)
|
||||
psbt.outputs.append(pout)
|
||||
|
||||
print("Guestimate fee: %.8f BTC" % (fee / 1E8))
|
||||
|
||||
txn.txs_out[0].coin_value -= fee
|
||||
|
||||
# write txn into PSBT
|
||||
with BytesIO() as b:
|
||||
txn.stream(b)
|
||||
psbt.txn = b.getvalue()
|
||||
|
||||
out_psbt.write(psbt.as_bytes())
|
||||
|
||||
print("PSBT to be signed:\n\n\t" + out_psbt.name, end='\n\n')
|
||||
|
||||
#print("\nPSBT to be signed: " + out_psbt.name, end='\n\n')
|
||||
|
||||
if __name__ == '__main__':
|
||||
recovery()
|
||||
faker()
|
||||
|
||||
# EOF
|
||||
|
||||
211
txn.py
Normal file
211
txn.py
Normal file
@ -0,0 +1,211 @@
|
||||
#
|
||||
# Creating fake transactions. Not simple... but only for testing purposes, so ....
|
||||
#
|
||||
import time, os, random
|
||||
from binascii import b2a_hex, a2b_hex
|
||||
from psbt import BasicPSBT, BasicPSBTInput, BasicPSBTOutput, PSBT_IN_REDEEM_SCRIPT
|
||||
from io import BytesIO
|
||||
from pprint import pprint, pformat
|
||||
from decimal import Decimal
|
||||
from pycoin.key.BIP32Node import BIP32Node
|
||||
|
||||
# all possible addr types, including multisig/scripts
|
||||
ADDR_STYLES = ['p2wpkh', 'p2wsh', 'p2sh', 'p2pkh', 'p2wsh-p2sh', 'p2wpkh-p2sh']
|
||||
|
||||
# single-signer
|
||||
ADDR_STYLES_SINGLE = ['p2wpkh', 'p2pkh', 'p2wpkh-p2sh']
|
||||
|
||||
def prandom(count):
|
||||
# make some bytes, randomly, but not: deterministic
|
||||
return bytes(random.randint(0, 255) for i in range(count))
|
||||
|
||||
def fake_dest_addr(style='p2pkh'):
|
||||
# Make a plausible output address, but it's random garbage. Cant use for change outs
|
||||
|
||||
# See CTxOut.get_address() in ../shared/serializations
|
||||
|
||||
if style == 'p2wpkh':
|
||||
return bytes([0, 20]) + prandom(20)
|
||||
|
||||
if style == 'p2wsh':
|
||||
return bytes([0, 32]) + prandom(32)
|
||||
|
||||
if style in ['p2sh', 'p2wsh-p2sh', 'p2wpkh-p2sh']:
|
||||
# all equally bogus P2SH outputs
|
||||
return bytes([0xa9, 0x14]) + prandom(20) + bytes([0x87])
|
||||
|
||||
if style == 'p2pkh':
|
||||
return bytes([0x76, 0xa9, 0x14]) + prandom(20) + bytes([0x88, 0xac])
|
||||
|
||||
# missing: if style == 'p2pk' => pay to pubkey, considered obsolete
|
||||
|
||||
raise ValueError('not supported: ' + style)
|
||||
|
||||
def make_change_addr(wallet, style):
|
||||
# provide script, pubkey and xpath for a legit-looking change output
|
||||
import struct, random
|
||||
from pycoin.encoding import hash160
|
||||
|
||||
redeem_scr, actual_scr = None, None
|
||||
deriv = [12, 34, random.randint(0, 1000)]
|
||||
|
||||
xfp, = struct.unpack('I', wallet.fingerprint())
|
||||
|
||||
dest = wallet.subkey_for_path('/'.join(str(i) for i in deriv))
|
||||
|
||||
target = dest.hash160()
|
||||
assert len(target) == 20
|
||||
|
||||
is_segwit = False
|
||||
if style == 'p2pkh':
|
||||
redeem_scr = bytes([0x76, 0xa9, 0x14]) + target + bytes([0x88, 0xac])
|
||||
elif style == 'p2wpkh':
|
||||
redeem_scr = bytes([0, 20]) + target
|
||||
is_segwit = True
|
||||
elif style == 'p2wpkh-p2sh':
|
||||
redeem_scr = bytes([0, 20]) + target
|
||||
actual_scr = bytes([0xa9, 0x14]) + hash160(redeem_scr) + bytes([0x87])
|
||||
else:
|
||||
raise ValueError('cant make fake change output of type: ' + style)
|
||||
|
||||
return redeem_scr, actual_scr, is_segwit, dest.sec(), struct.pack('4I', xfp, *deriv)
|
||||
|
||||
def fake_txn(num_ins, num_outs, master_xpub=None, subpath="0/%d", fee=10000,
|
||||
outvals=None, segwit_in=False, outstyles=['p2pkh'], is_testnet=False,
|
||||
change_style='p2pkh',
|
||||
change_outputs=[]):
|
||||
|
||||
# make various size txn's ... completely fake and pointless values
|
||||
# - but has UTXO's to match needs
|
||||
# - input total = num_inputs * 1BTC
|
||||
from pycoin.tx.Tx import Tx
|
||||
from pycoin.tx.TxIn import TxIn
|
||||
from pycoin.tx.TxOut import TxOut
|
||||
from pycoin.serialize import h2b_rev
|
||||
from struct import pack
|
||||
|
||||
psbt = BasicPSBT()
|
||||
txn = Tx(2,[],[])
|
||||
|
||||
# we have a key; use it to provide "plausible" value inputs
|
||||
mk = BIP32Node.from_wallet_key(master_xpub)
|
||||
xfp = mk.fingerprint()
|
||||
|
||||
psbt.inputs = [BasicPSBTInput(idx=i) for i in range(num_ins)]
|
||||
psbt.outputs = [BasicPSBTOutput(idx=i) for i in range(num_outs)]
|
||||
|
||||
outputs = []
|
||||
|
||||
for i in range(num_ins):
|
||||
# make a fake txn to supply each of the inputs
|
||||
# - each input is 1BTC
|
||||
|
||||
# addr where the fake money will be stored.
|
||||
subkey = mk.subkey_for_path(subpath % i)
|
||||
sec = subkey.sec()
|
||||
assert len(sec) == 33, "expect compressed"
|
||||
assert subpath[0:2] == '0/'
|
||||
|
||||
psbt.inputs[i].bip32_paths[sec] = xfp + pack('<II', 0, i)
|
||||
|
||||
# UTXO that provides the funding for to-be-signed txn
|
||||
supply = Tx(2,[TxIn(pack('4Q', 0xdead, 0xbeef, 0, 0), 73)],[])
|
||||
|
||||
scr = bytes([0x76, 0xa9, 0x14]) + subkey.hash160() + bytes([0x88, 0xac])
|
||||
|
||||
supply.txs_out.append(TxOut(1E8, scr))
|
||||
|
||||
with BytesIO() as fd:
|
||||
if not segwit_in:
|
||||
supply.stream(fd)
|
||||
psbt.inputs[i].utxo = fd.getvalue()
|
||||
else:
|
||||
supply.txs_out[-1].stream(fd)
|
||||
psbt.inputs[i].witness_utxo = fd.getvalue()
|
||||
|
||||
spendable = TxIn(supply.hash(), 0)
|
||||
txn.txs_in.append(spendable)
|
||||
|
||||
|
||||
for i in range(num_outs):
|
||||
is_change = False
|
||||
|
||||
# random P2PKH
|
||||
if not outstyles:
|
||||
style = ADDR_STYLES[i % len(ADDR_STYLES)]
|
||||
else:
|
||||
style = outstyles[i % len(outstyles)]
|
||||
|
||||
if i in change_outputs:
|
||||
scr, act_scr, isw, pubkey, sp = make_change_addr(mk, change_style)
|
||||
psbt.outputs[i].bip32_paths[pubkey] = sp
|
||||
is_change = True
|
||||
else:
|
||||
scr = act_scr = fake_dest_addr(style)
|
||||
isw = ('w' in style)
|
||||
|
||||
assert scr
|
||||
act_scr = act_scr or scr
|
||||
|
||||
if isw:
|
||||
psbt.outputs[i].witness_script = scr
|
||||
elif style.endswith('sh'):
|
||||
psbt.outputs[i].redeem_script = scr
|
||||
|
||||
if not outvals:
|
||||
h = TxOut(round(((1E8*num_ins)-fee) / num_outs, 4), act_scr)
|
||||
else:
|
||||
h = TxOut(outvals[i], act_scr)
|
||||
|
||||
outputs.append( (Decimal(h.coin_value)/Decimal(1E8), act_scr, is_change) )
|
||||
|
||||
txn.txs_out.append(h)
|
||||
|
||||
with BytesIO() as b:
|
||||
txn.stream(b)
|
||||
psbt.txn = b.getvalue()
|
||||
|
||||
rv = BytesIO()
|
||||
psbt.serialize(rv)
|
||||
|
||||
return rv.getvalue(), [(n, render_address(s, is_testnet), ic) for n,s,ic in outputs]
|
||||
|
||||
|
||||
def render_address(script, testnet=True):
|
||||
# take a scriptPubKey (part of the TxOut) and convert into conventional human-readable
|
||||
# string... aka: the "payment address"
|
||||
from pycoin.encoding import b2a_hashed_base58
|
||||
from pycoin.contrib.segwit_addr import encode as bech32_encode
|
||||
|
||||
ll = len(script)
|
||||
|
||||
if not testnet:
|
||||
bech32_hrp = 'bc'
|
||||
b58_addr = bytes([0])
|
||||
b58_script = bytes([5])
|
||||
b58_privkey = bytes([128])
|
||||
else:
|
||||
bech32_hrp = 'tb'
|
||||
b58_addr = bytes([111])
|
||||
b58_script = bytes([196])
|
||||
b58_privkey = bytes([239])
|
||||
|
||||
# P2PKH
|
||||
if ll == 25 and script[0:3] == b'\x76\xA9\x14' and script[23:26] == b'\x88\xAC':
|
||||
return b2a_hashed_base58(b58_addr + script[3:3+20])
|
||||
|
||||
# P2SH
|
||||
if ll == 23 and script[0:2] == b'\xA9\x14' and script[22] == 0x87:
|
||||
return b2a_hashed_base58(b58_script + script[2:2+20])
|
||||
|
||||
# P2WPKH
|
||||
if ll == 22 and script[0:2] == b'\x00\x14':
|
||||
return bech32_encode(bech32_hrp, 0, script[2:])
|
||||
|
||||
# P2WSH
|
||||
if ll == 34 and script[0:2] == b'\x00\x20':
|
||||
return bech32_encode(bech32_hrp, 0, script[2:])
|
||||
|
||||
raise ValueError('Unknown payment script', repr(script))
|
||||
|
||||
# EOF
|
||||
Loading…
Reference in New Issue
Block a user