Browse Source

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 5 years ago
parent
commit
d1b8aefde6
4 changed files with 48 additions and 14 deletions
  1. 9 6
      mmgen/main_addrgen.py
  2. 1 7
      mmgen/main_wallet.py
  3. 9 1
      mmgen/seed.py
  4. 29 0
      test/test_py_d/ts_ref.py

+ 9 - 6
mmgen/main_addrgen.py

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

+ 1 - 7
mmgen/main_wallet.py

@@ -155,13 +155,7 @@ if invoked_as in ('conv','passchg','subgen'):
 	gmsg('Processing output wallet')
 	gmsg('Processing output wallet')
 
 
 if invoked_as == 'subgen':
 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:
 else:
 	ss_out = SeedSource(ss=ss_in,passchg=invoked_as=='passchg')
 	ss_out = SeedSource(ss=ss_in,passchg=invoked_as=='passchg')
 
 

+ 9 - 1
mmgen/seed.py

@@ -71,12 +71,20 @@ class Seed(SeedBase):
 		self.subseeds = { 'long': OrderedDict(), 'short': OrderedDict() }
 		self.subseeds = { 'long': OrderedDict(), 'short': OrderedDict() }
 		SeedBase.__init__(self,seed_bin=seed_bin)
 		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)
 		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']):
 		if ss_idx.idx > len(self.subseeds['long']):
 			self.gen_subseeds(ss_idx.idx)
 			self.gen_subseeds(ss_idx.idx)
 		sid = list(self.subseeds[ss_idx.type].keys())[ss_idx.idx-1]
 		sid = list(self.subseeds[ss_idx.type].keys())[ss_idx.idx-1]
 		idx,nonce = self.subseeds[ss_idx.type][sid]
 		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)
 		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)
 		return SubSeed(self,idx,nonce,length=ss_idx.type)
 
 

+ 29 - 0
test/test_py_d/ts_ref.py

@@ -82,6 +82,10 @@ class TestSuiteRef(TestSuiteBase,TestSuiteShared):
 	cmd_group = ( # TODO: move to tooltest2
 	cmd_group = ( # TODO: move to tooltest2
 		('ref_words_to_subwallet_chk1','subwallet generation from reference words file (long subseed)'),
 		('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_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_addrfile_chk',   'saved reference address file'),
 		('ref_segwitaddrfile_chk','saved reference address file (segwit)'),
 		('ref_segwitaddrfile_chk','saved reference address file (segwit)'),
 		('ref_bech32addrfile_chk','saved reference address file (bech32)'),
 		('ref_bech32addrfile_chk','saved reference address file (bech32)'),
@@ -132,6 +136,31 @@ class TestSuiteRef(TestSuiteBase,TestSuiteShared):
 		t.read()
 		t.read()
 		return t
 		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=[]):
 	def ref_addrfile_chk(self,ftype='addr',coin=None,subdir=None,pfx=None,mmtype=None,add_args=[]):
 		af_key = 'ref_{}file'.format(ftype)
 		af_key = 'ref_{}file'.format(ftype)
 		af_fn = TestSuiteRef.sources[af_key].format(pfx or self.altcoin_pfx,'' if coin else self.tn_ext)
 		af_fn = TestSuiteRef.sources[af_key].format(pfx or self.altcoin_pfx,'' if coin else self.tn_ext)