From d1b8aefde6d3a13337cbe3147d9913eb09b6765b Mon Sep 17 00:00:00 2001 From: MMGen Date: Tue, 14 May 2019 10:45:53 +0000 Subject: [PATCH] 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' --- mmgen/main_addrgen.py | 15 +++++++++------ mmgen/main_wallet.py | 8 +------- mmgen/seed.py | 10 +++++++++- test/test_py_d/ts_ref.py | 29 +++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 14 deletions(-) diff --git a/mmgen/main_addrgen.py b/mmgen/main_addrgen.py index 0d67e8f6..74ffbdb0 100755 --- a/mmgen/main_addrgen.py +++ b/mmgen/main_addrgen.py @@ -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: diff --git a/mmgen/main_wallet.py b/mmgen/main_wallet.py index c99173ed..0369bea7 100755 --- a/mmgen/main_wallet.py +++ b/mmgen/main_wallet.py @@ -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') diff --git a/mmgen/seed.py b/mmgen/seed.py index caf491a4..d248927c 100755 --- a/mmgen/seed.py +++ b/mmgen/seed.py @@ -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) diff --git a/test/test_py_d/ts_ref.py b/test/test_py_d/ts_ref.py index 79d9fd63..bb4826f5 100755 --- a/test/test_py_d/ts_ref.py +++ b/test/test_py_d/ts_ref.py @@ -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)