Subwallets, Part 2: key/address generation using the parent wallet

See commit 7538a94 for Part 1

Examples:

    # Create a bogus wallet in mnemonic format for testing purposes:
    $ echo $(yes bee | head -n24) > bogus.mmwords

    # Get the Seed ID of the wallet's 5th short (128-bit) subwallet:
    $ mmgen-tool get_subseed 5S wallet=bogus.mmwords
    30D66FF5

    # Generate ten bech32 addresses from that subwallet:
    $ mmgen-addrgen --type=bech32 --subwallet=5S bogus.mmwords 1-10
    ...
    Addresses written to file '30D66FF5-B[1-10].addrs'

    # Same as above, but generate secret keys too:
    $ mmgen-keygen --type=bech32 --subwallet=5S bogus.mmwords 1-10
    ...
    Secret keys written to file '30D66FF5-B[1-10].akeys'
This commit is contained in:
The MMGen Project 2019-05-14 10:45:53 +00:00
commit d1b8aefde6
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
4 changed files with 48 additions and 14 deletions

View file

@ -35,7 +35,7 @@ if sys.argv[0].split('-')[-1] == 'keygen':
else:
gen_what = 'addresses'
gen_desc = 'addresses'
opt_filter = 'hbcdeEiHOkKlpzPqrStv-'
opt_filter = 'hbcdeEiHOkKlpzPqrStUv-'
note_addrkey = ''
note_secp256k1 = """
If available, the secp256k1 library will be used for address generation.
@ -78,6 +78,8 @@ opts_data = {
-S, --stdout Print {what} to stdout
-t, --type=t Choose address type. Options: see ADDRESS TYPES below
(default: {dmat})
-U, --subwallet= U Generate {what} for subwallet 'U' (see SUBWALLETS
below)
-v, --verbose Produce more verbose output
-x, --b16 Print secret keys in hexadecimal too
""",
@ -96,9 +98,7 @@ ADDRESS TYPES:
NOTES FOR ALL GENERATOR COMMANDS
{n_pw}
{n_bw}
{n_sw}{n_pw}{n_bw}
FMT CODES:
@ -118,7 +118,8 @@ FMT CODES:
'notes': lambda s: s.format(
n_secp=note_secp256k1,
n_addrkey=note_addrkey,
n_pw=help_notes('passwd'),
n_sw=help_notes('subwallet')+'\n\n',
n_pw=help_notes('passwd')+'\n\n',
n_bw=help_notes('brainwallet'),
n_fmt='\n '.join(SeedSource.format_fmt_codes().splitlines()),
n_at='\n '.join(["'{}','{:<12} - {}".format(
@ -145,8 +146,10 @@ do_license_msg()
ss = SeedSource(sf)
ss_seed = ss.seed if opt.subwallet is None else ss.seed.subseed(opt.subwallet,print_msg=True)
i = (gen_what=='addresses') or bool(opt.no_addresses)*2
al = (KeyAddrList,AddrList,KeyList)[i](seed=ss.seed,addr_idxs=idxs,mmtype=addr_type)
al = (KeyAddrList,AddrList,KeyList)[i](seed=ss_seed,addr_idxs=idxs,mmtype=addr_type)
al.format()
if al.gen_addrs and opt.print_checksum:

View file

@ -155,13 +155,7 @@ if invoked_as in ('conv','passchg','subgen'):
gmsg('Processing output wallet')
if invoked_as == 'subgen':
msg_r('{} {} of {}...'.format(
green('Generating subseed'),
ss_idx.hl(),
ss_in.seed.sid.hl(),
))
msg('\b\b\b => {}'.format(ss_in.seed.subseed(ss_idx).sid.hl()))
ss_out = SeedSource(seed=ss_in.seed.subseed(ss_idx).data)
ss_out = SeedSource(seed=ss_in.seed.subseed(ss_idx,print_msg=True).data)
else:
ss_out = SeedSource(ss=ss_in,passchg=invoked_as=='passchg')

View file

@ -71,12 +71,20 @@ class Seed(SeedBase):
self.subseeds = { 'long': OrderedDict(), 'short': OrderedDict() }
SeedBase.__init__(self,seed_bin=seed_bin)
def subseed(self,ss_idx_in):
def subseed(self,ss_idx_in,print_msg=False):
ss_idx = SubSeedIdx(ss_idx_in)
if print_msg:
msg_r('{} {} of {}...'.format(
green('Generating subseed'),
ss_idx.hl(),
self.sid.hl(),
))
if ss_idx.idx > len(self.subseeds['long']):
self.gen_subseeds(ss_idx.idx)
sid = list(self.subseeds[ss_idx.type].keys())[ss_idx.idx-1]
idx,nonce = self.subseeds[ss_idx.type][sid]
if print_msg:
msg('\b\b\b => {}'.format(SeedID.hlc(sid)))
assert idx == ss_idx.idx, "{} != {}: subseed list idx does not match subseed idx!".format(idx,ss_idx.idx)
return SubSeed(self,idx,nonce,length=ss_idx.type)

View file

@ -82,6 +82,10 @@ class TestSuiteRef(TestSuiteBase,TestSuiteShared):
cmd_group = ( # TODO: move to tooltest2
('ref_words_to_subwallet_chk1','subwallet generation from reference words file (long subseed)'),
('ref_words_to_subwallet_chk2','subwallet generation from reference words file (short subseed)'),
('ref_subwallet_addrgen1','subwallet address file generation (long subseed)'),
('ref_subwallet_addrgen2','subwallet address file generation (short subseed)'),
('ref_subwallet_keygen1','subwallet key-address file generation (long subseed)'),
('ref_subwallet_keygen2','subwallet key-address file generation (short subseed)'),
('ref_addrfile_chk', 'saved reference address file'),
('ref_segwitaddrfile_chk','saved reference address file (segwit)'),
('ref_bech32addrfile_chk','saved reference address file (bech32)'),
@ -132,6 +136,31 @@ class TestSuiteRef(TestSuiteBase,TestSuiteShared):
t.read()
return t
def ref_subwallet_addrgen(self,ss_idx,target='addr'):
wf = dfl_words_file
args = ['-d',self.tr.trash_dir,'--subwallet='+ss_idx,wf,'1-10']
t = self.spawn('mmgen-{}gen'.format(target),args)
t.expect('Generating subseed {}'.format(ss_idx))
chk_sid = self.chk_data['ref_subwallet_sid']['98831F3A:{}'.format(ss_idx)]
assert chk_sid == t.expect_getend('Checksum for .* data ',regex=True)[:8]
if target == 'key':
t.expect('Encrypt key list? (y/N): ','n')
fn = t.written_to_file(('Addresses','Secret keys')[target=='key'])
assert chk_sid in fn,'incorrect filename: {} (does not contain {})'.format(fn,chk_sid)
return t
def ref_subwallet_addrgen1(self):
return self.ref_subwallet_addrgen('32L')
def ref_subwallet_addrgen2(self):
return self.ref_subwallet_addrgen('1S')
def ref_subwallet_keygen1(self):
return self.ref_subwallet_addrgen('32L',target='key')
def ref_subwallet_keygen2(self):
return self.ref_subwallet_addrgen('1S',target='key')
def ref_addrfile_chk(self,ftype='addr',coin=None,subdir=None,pfx=None,mmtype=None,add_args=[]):
af_key = 'ref_{}file'.format(ftype)
af_fn = TestSuiteRef.sources[af_key].format(pfx or self.altcoin_pfx,'' if coin else self.tn_ext)