unsplit MS support, many TODOs still
This commit is contained in:
parent
d22631028a
commit
3d0ec47333
@ -24,6 +24,7 @@ rehash
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
!!
|
||||
$ psbt_faker --help
|
||||
|
||||
Usage: psbt_faker [OPTIONS] OUTPUT.PSBT [XPUB]
|
||||
|
||||
14
ms-example.txt
Normal file
14
ms-example.txt
Normal file
@ -0,0 +1,14 @@
|
||||
# Example Coldcard Multisig setup file
|
||||
#
|
||||
# This includes the simulator's default key, and BIP-39 derived keys using passwords
|
||||
# "Me", "Myself", "And I". It is a 2 of 4 (but that could be edited here)
|
||||
#
|
||||
Name: MeMyselfAndI
|
||||
Policy: 2 of 4
|
||||
|
||||
Derivation: m/45h
|
||||
|
||||
6BA6CFD0: tpubD9429UXFGCTKJ9NdiNK4rC5ygqSUkginycYHccqSg5gkmyQ7PZRHNjk99M6a6Y3NY8ctEUUJvCu6iCCui8Ju3xrHRu3Ez1CKB4ZFoRZDdP9
|
||||
747B698E: tpubD97nVL37v5tWyMf9ofh5rznwhh1593WMRg6FT4o6MRJkKWANtwAMHYLrcJFsFmPfYbY1TE1LLQ4KBb84LBPt1ubvFwoosvMkcWJtMwvXgSc
|
||||
7BB026BE: tpubD9ArfXowvGHnuECKdGXVKDMfZVGdephVWg8fWGWStH3VKHzT4ph3A4ZcgXWqFu1F5xGTfxncmrnf3sLC86dup2a8Kx7z3xQ3AgeNTQeFxPa
|
||||
0F056943: tpubD8NXmKsmWp3a3DXhbihAYbYLGaRNVdTnr6JoSxxfXYQcmwVtW2hv8QoDwng6JtEonmJoL3cNEwfd2cLXMpGezwZ2vL2dQ7259bueNKj9C8n
|
||||
@ -20,11 +20,7 @@ b2a_hex = lambda a: str(_b2a_hex(a), 'ascii')
|
||||
SIM_XPUB = 'tpubD6NzVbkrYhZ4XzL5Dhayo67Gorv1YMS7j8pRUvVMd5odC2LBPLAygka9p7748JtSq82FNGPppFEz5xxZUdasBRCqJqXvUHq6xpnsMcYJzeh'
|
||||
|
||||
|
||||
@click.group()
|
||||
def main():
|
||||
pass
|
||||
|
||||
@main.command()
|
||||
@click.command()
|
||||
@click.argument('out_psbt', type=click.File('wb'), metavar="OUTPUT.PSBT")
|
||||
@click.argument('xpub', type=str, default=SIM_XPUB)
|
||||
@click.option('--num-outs', '-n', help="Number of outputs (default 1)", default=1)
|
||||
@ -37,67 +33,80 @@ def main():
|
||||
@click.option('--testnet', '-t', help="Assume testnet3 addresses (default mainnet)", is_flag=True, default=False)
|
||||
@click.option('--partial', '-p', help="Change first input so its different XPUB and result cannot be finalized", is_flag=True, default=False)
|
||||
@click.option('--zero-xfp', '-z', help="Provide zero XFP and junk XPUB (cannot be signed, but should be decodable)", is_flag=True, default=False)
|
||||
def faker(num_change, num_outs, out_psbt, value, testnet, xpub, segwit, fee, styles, base64, partial, zero_xfp):
|
||||
@click.option('--multisig', '-m', type=click.File('rt'), metavar="config.txt", help="[MS] CC Multisig config file (text)", default=None)
|
||||
@click.option('--locktime', '-l', help="[MS] nLocktime value (default current block height)", default=None)
|
||||
@click.option('--input-amount', '-n', help="[MS] Size of each input in sats (default 100k sats each input)", default=100000)
|
||||
@click.option('--legacy', help="[MS] Make inputs be legacy p2sh style", is_flag=True, default=False)
|
||||
def main(num_change, num_outs, out_psbt, value, testnet, xpub, segwit, fee, styles, base64, partial, zero_xfp, multisig, locktime, input_amount, legacy):
|
||||
'''Construct a valid PSBT which spends non-existant BTC to random addresses!'''
|
||||
|
||||
num_ins = int(value)
|
||||
total_outs = num_outs + num_change
|
||||
|
||||
# TODO: PSBTv2 if flag set
|
||||
|
||||
if zero_xfp:
|
||||
xpub = None
|
||||
|
||||
chg_style = 'p2pkh' if not segwit else 'p2wpkh'
|
||||
|
||||
if not styles:
|
||||
styles = [chg_style]
|
||||
if multisig:
|
||||
# TODO: flag to include xpubs in header
|
||||
|
||||
psbt, outs = fake_txn(num_ins, total_outs, master_xpub=xpub, fee=fee,
|
||||
segwit_in=segwit, outstyles=styles, change_style=chg_style,
|
||||
partial=partial,
|
||||
is_testnet=testnet, change_outputs=list(range(num_outs, num_outs+num_change)))
|
||||
chg_style = 'p2sh' if not segwit else 'p2wsh'
|
||||
if not styles:
|
||||
styles = [chg_style]
|
||||
|
||||
# TODO: slow getting this, better to estimate unless they want real value
|
||||
# TODO: for single-sig too
|
||||
# TODO: modulate value (today + 2 days, etc) for CCC testing/validation
|
||||
if locktime is None:
|
||||
try:
|
||||
import urllib.request
|
||||
u = urllib.request.urlopen("https://mempool.space/api/blocks/tip/height")
|
||||
locktime = int(u.read().decode())
|
||||
except:
|
||||
locktime = 0
|
||||
|
||||
ms_config = multisig.read()
|
||||
name, af, keys, M, N = from_simple_text(ms_config.split("\n"))
|
||||
psbt, outs = fake_ms_txn(num_ins, num_outs, M, keys, fee=fee, locktime=locktime,
|
||||
change_outputs=list(range(num_change)), outstyles=styles,
|
||||
segwit_in=not legacy, input_amount=input_amount)
|
||||
|
||||
else:
|
||||
chg_style = 'p2pkh' if not segwit else 'p2wpkh'
|
||||
if not styles:
|
||||
styles = [chg_style]
|
||||
|
||||
psbt, outs = fake_txn(num_ins, total_outs, master_xpub=xpub, fee=fee,
|
||||
segwit_in=segwit, outstyles=styles, change_style=chg_style,
|
||||
partial=partial,
|
||||
is_testnet=testnet, change_outputs=list(range(num_outs, num_outs+num_change)))
|
||||
|
||||
|
||||
out_psbt.write(psbt if not base64 else b64encode(psbt))
|
||||
|
||||
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))
|
||||
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)))
|
||||
|
||||
#print("\nPSBT to be signed: " + out_psbt.name, end='\n\n')
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument('ms_conf', type=click.File('r'), metavar="CC ms export")
|
||||
'''
|
||||
@main.command('ms')
|
||||
@click.argument('out_psbt', type=click.File('wb'), metavar="OUTPUT.PSBT")
|
||||
@click.option('--input-amount', '-n', help="Size of each input in sats (default 100k sats each input)", default=100000)
|
||||
@click.option('--num-ins', help="Number of inputs (default 1)", default=1)
|
||||
@click.option('--num-outs', help="Number of outputs (default 1)", default=1)
|
||||
@click.option('--num-change', '-c', help="Number of change outputs (default 1)", default=1)
|
||||
@click.option('--fee', '-f', help="Miner's fee in Satoshis", default=1000)
|
||||
@click.option('--locktime', '-l', help="nLocktime value (default current block height)", default=None)
|
||||
@click.option('--legacy', help="Make inputs be legacy p2sh style", is_flag=True, default=False)
|
||||
@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 ms_faker(ms_conf, out_psbt, num_ins, num_change, num_outs, legacy, fee, styles, base64,
|
||||
locktime, input_amount):
|
||||
'''Construct a valid multisig PSBT which spends non-existant BTC to random addresses!'''
|
||||
|
||||
if locktime is None:
|
||||
try:
|
||||
import urllib.request
|
||||
u = urllib.request.urlopen("https://mempool.space/api/blocks/tip/height")
|
||||
locktime = int(u.read().decode())
|
||||
except:
|
||||
locktime = 0
|
||||
|
||||
ms_config = ms_conf.read()
|
||||
name, af, keys, M, N = from_simple_text(ms_config.split("\n"))
|
||||
psbt = fake_ms_txn(num_ins, num_outs, M, keys, fee=fee, locktime=locktime,
|
||||
change_outputs=list(range(num_change)), outstyles=styles,
|
||||
segwit_in=not legacy, input_amount=input_amount)
|
||||
|
||||
out_psbt.write(psbt if not base64 else b64encode(psbt))
|
||||
''Construct a valid multisig PSBT which spends non-existant BTC to random addresses!'''
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@ -326,6 +326,7 @@ def fake_ms_txn(num_ins, num_outs, M, keys, fee=10000, outvals=None, segwit_in=T
|
||||
spendable = CTxIn(COutPoint(supply.sha256, 0), nSequence=seq)
|
||||
txn.vin.append(spendable)
|
||||
|
||||
outputs = []
|
||||
for i in range(num_outs):
|
||||
if not outstyles:
|
||||
style = ADDR_STYLES_MULTI[i % len(ADDR_STYLES_MULTI)]
|
||||
@ -367,12 +368,15 @@ def fake_ms_txn(num_ins, num_outs, M, keys, fee=10000, outvals=None, segwit_in=T
|
||||
|
||||
txn.vout.append(h)
|
||||
|
||||
# TODO FIXME #
|
||||
#XXX#outputs.append( (Decimal(h.nValue)/Decimal(1E8), scr, (i not in change_outputs)) )
|
||||
|
||||
psbt.txn = txn.serialize_with_witness()
|
||||
|
||||
rv = BytesIO()
|
||||
psbt.serialize(rv)
|
||||
|
||||
return rv.getvalue()
|
||||
return rv.getvalue(), [(n, render_address(s, 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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user