Subwallets, Part 3: transaction signing using the parent wallet
See commit7538a94for Part 1 See commitd1b8aeffor Part 2 NOTE: to guard against Seed ID collisions, only the default or first wallet specified in `mmgen-txsign` will be used for signing subwallet transactions. Live example using MMGen regtest (Bob and Alice) mode: $ mmgen-regtest setup # Create a bogus wallet for Bob in mnemonic format: $ echo $(yes bee | head -n24) > bogus.mmwords # Convert the wallet and make it Bob's default: $ mkdir -p $HOME/.mmgen/regtest/btc/bob $ mmgen-walletconv -d $HOME/.mmgen/regtest/btc/bob bogus.mmwords ... MMGen wallet written to file /home/user/.mmgen/regtest/btc/bob/DF449DA4-*.mmdat # Choose two subwallets, 1S and 2L, and get their Seed IDs: $ mmgen-tool --bob get_subseed 1S # ==> 930E1AD5 $ mmgen-tool --bob get_subseed 2L # ==> 62B02F54 # Generate five bech32 addresses each from default wallet and subwallets: $ mmgen-addrgen --bob --type=bech32 1-5 $ mmgen-addrgen --bob --type=bech32 --subwallet=1S 1-5 $ mmgen-addrgen --bob --type=bech32 --subwallet=2L 1-5 # Import the addresses into Bob's tracking wallet: $ mmgen-regtest bob $ mmgen-addrimport --bob DF449DA4*addrs $ mmgen-addrimport --bob 930E1AD5*addrs $ mmgen-addrimport --bob 62B02F54*addrs # Fund addresses from each of the wallets: $ mmgen-regtest send bcrt1q0v8eczv37ynl9zn8w3rh53xrkyuddrunuz74rd 10 # DF449DA4:B:1 $ mmgen-regtest send bcrt1qtzxlnng6jd7yakzp7r69y6nmh5wp0wx7xg6e9w 10 # 930E1AD5:B:1 $ mmgen-regtest send bcrt1qxnj0wgusq357qj62hq88thrw9cwanxc7926vrz 10 # 62B02F54:B:1 # Create a transaction spending to and from each of the wallets: $ mmgen-txcreate --bob --tx-fee=3s DF449DA4:B:2,1.11 930E1AD5:B:2,1.23 62B02F54:B:2 ... (choose inputs 1-3) ... Transaction written to file '<MMGen txid>[2.34].testnet.rawtx' # Sign the transaction: $ mmgen-txsign --bob *\[2.34\].testnet.rawtx ... Found subseed 930E1AD5 (DF449DA4:1S) ... Found subseed 62B02F54 (DF449DA4:2L) ... Signed transaction written to file '<MMGen txid>[2.34].testnet.sigtx' # Send the transaction: $ mmgen-txsend -q --bob *\[2.34\].testnet.sigtx Transaction sent: <BTC txid> # Mine a block and view the result: $ mmgen-regtest generate $ mmgen-tool --bob twview
This commit is contained in:
parent
83e54ccb6e
commit
82086c9936
4 changed files with 115 additions and 8 deletions
|
|
@ -21,6 +21,7 @@ mmgen-txdo: Create, sign and broadcast an online MMGen transaction
|
|||
"""
|
||||
|
||||
from mmgen.common import *
|
||||
from mmgen.obj import SubSeedIdxRange
|
||||
|
||||
opts_data = {
|
||||
'sets': [('yes', True, 'quiet', True)],
|
||||
|
|
@ -71,6 +72,9 @@ opts_data = {
|
|||
-P, --passwd-file= f Get {pnm} wallet passphrase from file 'f'
|
||||
-r, --rbf Make transaction BIP 125 (replace-by-fee) replaceable
|
||||
-q, --quiet Suppress warnings; overwrite files without prompting
|
||||
-u, --subseeds= n The number of subseed pairs to scan for (default: {ss},
|
||||
maximum: {ss_max}). Only the default or first supplied
|
||||
wallet is scanned for subseeds.
|
||||
-v, --verbose Produce more verbose output
|
||||
-V, --vsize-adj= f Adjust transaction's estimated vsize by factor 'f'
|
||||
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
||||
|
|
@ -84,6 +88,7 @@ opts_data = {
|
|||
kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
|
||||
fu=help_notes('rel_fee_desc'),
|
||||
fl=help_notes('fee_spec_letters'),
|
||||
ss=g.subseeds,ss_max=SubSeedIdxRange.max_idx,
|
||||
kg=g.key_generator,
|
||||
cu=g.coin),
|
||||
'notes': lambda s: s.format(
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ mmgen-txsign: Sign a transaction generated by 'mmgen-txcreate'
|
|||
"""
|
||||
|
||||
from mmgen.common import *
|
||||
from mmgen.obj import SubSeedIdxRange
|
||||
from mmgen.seed import SeedSource
|
||||
|
||||
# -w, --use-wallet-dat (keys from running coin daemon) removed: use walletdump rpc instead
|
||||
|
|
@ -60,6 +61,9 @@ opts_data = {
|
|||
-q, --quiet Suppress warnings; overwrite files without prompting
|
||||
-I, --info Display information about the transaction and exit
|
||||
-t, --terse-info Like '--info', but produce more concise output
|
||||
-u, --subseeds= n The number of subseed pairs to scan for (default: {ss},
|
||||
maximum: {ss_max}). Only the default or first supplied
|
||||
wallet is scanned for subseeds.
|
||||
-v, --verbose Produce more verbose output
|
||||
-V, --vsize-adj= f Adjust transaction's estimated vsize by factor 'f'
|
||||
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
||||
|
|
@ -77,6 +81,7 @@ column below:
|
|||
g=g,pnm=g.proj_name,pnl=g.proj_name.lower(),dn=g.proto.daemon_name,
|
||||
kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
|
||||
kg=g.key_generator,
|
||||
ss=g.subseeds,ss_max=SubSeedIdxRange.max_idx,
|
||||
cu=g.coin),
|
||||
'notes': lambda s: s.format(
|
||||
help_notes('txsign'),
|
||||
|
|
|
|||
|
|
@ -38,26 +38,35 @@ ERROR: a key file must be supplied for the following non-{pnm} address{{}}:\n
|
|||
""".format(pnm=pnm).strip()
|
||||
}
|
||||
|
||||
saved_seeds = {}
|
||||
from collections import OrderedDict
|
||||
saved_seeds = OrderedDict()
|
||||
|
||||
def get_seed_for_seed_id(sid,infiles,saved_seeds):
|
||||
|
||||
if sid in saved_seeds:
|
||||
return saved_seeds[sid]
|
||||
|
||||
subseeds_checked = False
|
||||
while True:
|
||||
if infiles:
|
||||
seed = SeedSource(infiles.pop(0),ignore_in_fmt=True).seed
|
||||
elif subseeds_checked == False:
|
||||
seed = saved_seeds[list(saved_seeds)[0]].subseed_by_seed_id(sid,print_msg=True)
|
||||
subseeds_checked = True
|
||||
if not seed: continue
|
||||
elif opt.in_fmt:
|
||||
qmsg('Need seed data for Seed ID {}'.format(sid))
|
||||
seed = SeedSource().seed
|
||||
msg('User input produced Seed ID {}'.format(seed.sid))
|
||||
if not seed.sid == sid: # TODO: add test
|
||||
seed = seed.subseed_by_seed_id(sid,print_msg=True)
|
||||
|
||||
if seed:
|
||||
saved_seeds[seed.sid] = seed
|
||||
if seed.sid == sid: return seed
|
||||
else:
|
||||
die(2,'ERROR: No seed source found for Seed ID: {}'.format(sid))
|
||||
|
||||
saved_seeds[seed.sid] = seed
|
||||
if seed.sid == sid: return seed
|
||||
|
||||
def generate_kals_for_mmgen_addrs(need_keys,infiles,saved_seeds):
|
||||
mmids = [e.mmid for e in need_keys]
|
||||
sids = {i.sid for i in mmids}
|
||||
|
|
|
|||
|
|
@ -41,13 +41,13 @@ rt_data = {
|
|||
},
|
||||
'rtBals': {
|
||||
'btc': ('499.9999488','399.9998282','399.9998147','399.9996877',
|
||||
'52.99990000','946.99933647','999.99923647','52.9999',
|
||||
'52.99980410','946.99933647','999.99914057','52.9999',
|
||||
'946.99933647'),
|
||||
'bch': ('499.9999484','399.9999194','399.9998972','399.9997692',
|
||||
'46.78900000','953.20966920','999.99866920','46.789',
|
||||
'46.78890380','953.20966920','999.99857300','46.789',
|
||||
'953.2096692'),
|
||||
'ltc': ('5499.99744','5399.994425','5399.993885','5399.987535',
|
||||
'52.99000000','10946.93753500','10999.92753500','52.99',
|
||||
'52.98520500','10946.93753500','10999.92274000','52.99',
|
||||
'10946.937535'),
|
||||
},
|
||||
'rtBals_gb': {
|
||||
|
|
@ -114,6 +114,21 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
|
|||
('alice_send_estimatefee', 'tx creation with no fee on command line'),
|
||||
('generate', 'mining a block'),
|
||||
('bob_bal6', "Bob's balance"),
|
||||
|
||||
('bob_subwallet_addrgen1', "generating Bob's addrs from subwallet 29L"),
|
||||
('bob_subwallet_addrgen2', "generating Bob's addrs from subwallet 127S"),
|
||||
('bob_subwallet_addrimport1', "importing Bob's addrs from subwallet 29L"),
|
||||
('bob_subwallet_addrimport2', "importing Bob's addrs from subwallet 127S"),
|
||||
('bob_subwallet_fund', "funding Bob's subwallet addrs"),
|
||||
('generate', 'mining a block'),
|
||||
('bob_twview2', "viewing Bob's tracking wallet"),
|
||||
('bob_twview3', "viewing Bob's tracking wallet"),
|
||||
('bob_subwallet_txcreate', 'creating a transaction with subwallet inputs'),
|
||||
('bob_subwallet_txsign', 'signing a transaction with subwallet inputs'),
|
||||
('bob_subwallet_txdo', "sending from Bob's subwallet addrs"),
|
||||
('generate', 'mining a block'),
|
||||
('bob_twview4', "viewing Bob's tracking wallet"),
|
||||
|
||||
('bob_alice_bal', "Bob and Alice's balances"),
|
||||
('alice_bal2', "Alice's balance"),
|
||||
|
||||
|
|
@ -134,6 +149,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
|
|||
|
||||
('stop', 'stopping regtest daemon'),
|
||||
)
|
||||
usr_subsids = { 'bob': {}, 'alice': {} }
|
||||
|
||||
def __init__(self,trunner,cfgs,spawn):
|
||||
coin = g.coin.lower()
|
||||
|
|
@ -184,12 +200,25 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
|
|||
def _user_sid(self,user):
|
||||
return os.path.basename(get_file_with_ext(self._user_dir(user),'mmdat'))[:8]
|
||||
|
||||
def addrgen(self,user,wf=None,addr_range='1-5',mmtypes=[]):
|
||||
def _get_user_subsid(self,user,subseed_idx):
|
||||
|
||||
if subseed_idx in self.usr_subsids[user]:
|
||||
return self.usr_subsids[user][subseed_idx]
|
||||
|
||||
fn = get_file_with_ext(self._user_dir(user),'mmdat')
|
||||
t = self.spawn('mmgen-tool',['get_subseed',subseed_idx,'wallet='+fn],no_msg=True)
|
||||
t.passphrase('MMGen wallet',rt_pw)
|
||||
sid = t.read().strip()[:8]
|
||||
self.usr_subsids[user][subseed_idx] = sid
|
||||
return sid
|
||||
|
||||
def addrgen(self,user,wf=None,addr_range='1-5',subseed_idx=None,mmtypes=[]):
|
||||
from mmgen.addr import MMGenAddrType
|
||||
for mmtype in mmtypes or g.proto.mmtypes:
|
||||
t = self.spawn('mmgen-addrgen',
|
||||
['--quiet','--'+user,'--type='+mmtype,'--outdir={}'.format(self._user_dir(user))] +
|
||||
([wf] if wf else []) +
|
||||
(['--subwallet='+subseed_idx] if subseed_idx else []) +
|
||||
[addr_range],
|
||||
extra_desc='({})'.format(MMGenAddrType.mmtypes[mmtype]['name']))
|
||||
t.passphrase('MMGen wallet',rt_pw)
|
||||
|
|
@ -302,6 +331,65 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
|
|||
def bob_bal6(self):
|
||||
return self.user_bal('bob',rtBals[7])
|
||||
|
||||
def bob_subwallet_addrgen1(self):
|
||||
return self.addrgen('bob',subseed_idx='29L',mmtypes=['C']) # 29L: 2FA7BBA8
|
||||
|
||||
def bob_subwallet_addrgen2(self):
|
||||
return self.addrgen('bob',subseed_idx='127S',mmtypes=['C']) # 127S: '09E8E286'
|
||||
|
||||
def subwallet_addrimport(self,user,subseed_idx):
|
||||
sid = self._get_user_subsid(user,subseed_idx)
|
||||
return self.addrimport(user,sid=sid,mmtypes=['C'])
|
||||
|
||||
def bob_subwallet_addrimport1(self): return self.subwallet_addrimport('bob','29L')
|
||||
def bob_subwallet_addrimport2(self): return self.subwallet_addrimport('bob','127S')
|
||||
|
||||
def bob_subwallet_fund(self):
|
||||
sid1 = self._get_user_subsid('bob','29L')
|
||||
sid2 = self._get_user_subsid('bob','127S')
|
||||
chg_addr = self._user_sid('bob') + (':B:1',':L:1')[g.coin=='BCH']
|
||||
outputs_cl = [sid1+':C:2,0.29',sid2+':C:3,0.127',chg_addr]
|
||||
inputs = ('3','1')[g.coin=='BCH']
|
||||
return self.user_txdo('bob',rtFee[1],outputs_cl,inputs,extra_args=['--subseeds=127'])
|
||||
|
||||
def bob_twview2(self):
|
||||
sid1 = self._get_user_subsid('bob','29L')
|
||||
return self.user_twview('bob',chk=r'\b{}:C:2\b\s+{}'.format(sid1,'0.29'),sort='twmmid')
|
||||
|
||||
def bob_twview3(self):
|
||||
sid2 = self._get_user_subsid('bob','127S')
|
||||
return self.user_twview('bob',chk=r'\b{}:C:3\b\s+{}'.format(sid2,'0.127'),sort='amt')
|
||||
|
||||
def bob_subwallet_txcreate(self):
|
||||
sid1 = self._get_user_subsid('bob','29L')
|
||||
sid2 = self._get_user_subsid('bob','127S')
|
||||
outputs_cl = [sid1+':C:5,0.0159',sid2+':C:5']
|
||||
t = self.spawn('mmgen-txcreate',['-d',self.tmpdir,'-B','--bob'] + outputs_cl)
|
||||
return self.txcreate_ui_common(t,
|
||||
menu = ['a'],
|
||||
inputs = ('1,2','2,3')[g.coin=='BCH'],
|
||||
interactive_fee = '0.00001')
|
||||
|
||||
def bob_subwallet_txsign(self):
|
||||
fn = get_file_with_ext(self.tmpdir,'rawtx')
|
||||
t = self.spawn('mmgen-txsign',['-d',self.tmpdir,'--bob','--subseeds=127',fn])
|
||||
t.view_tx('t')
|
||||
t.passphrase('MMGen wallet',rt_pw)
|
||||
t.do_comment(None)
|
||||
t.expect('(Y/n): ','y')
|
||||
t.written_to_file('Signed transaction')
|
||||
return t
|
||||
|
||||
def bob_subwallet_txdo(self):
|
||||
outputs_cl = [self._user_sid('bob')+':L:5']
|
||||
inputs = ('1,2','2,3')[g.coin=='BCH']
|
||||
return self.user_txdo('bob',rtFee[5],outputs_cl,inputs,menu=['a'],extra_args=['--subseeds=127']) # sort: amt
|
||||
|
||||
def bob_twview4(self):
|
||||
sid = self._user_sid('bob')
|
||||
amt = ('0.4169328','0.41364')[g.coin=='LTC']
|
||||
return self.user_twview('bob',chk=r'\b{}:L:5\b\s+.*\s+\b{}\b'.format(sid,amt),sort='twmmid')
|
||||
|
||||
def bob_bal5_getbalance(self):
|
||||
t_ext,t_mmgen = rtBals_gb[0],rtBals_gb[1]
|
||||
assert Decimal(t_ext) + Decimal(t_mmgen) == Decimal(rtBals[3])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue