From 02256664fbd6c3ec03f424e8146ab504be810123 Mon Sep 17 00:00:00 2001 From: philemon Date: Thu, 15 Dec 2016 20:53:29 +0300 Subject: [PATCH] [seed]: export seed to hexadecimal (mmhex) format --- ...d-Its-Dependencies-on-Microsoft-Windows.md | 5 +- .../using-mmgen/Getting-Started-with-MMGen.md | 27 ++++++-- mmgen/seed.py | 62 ++++++++++++++++--- mmgen/util.py | 20 +++--- test/ref/1378FC64.mmhex | 1 + test/ref/98831F3A.mmhex | 1 + test/ref/FE3C6545.mmhex | 1 + test/test.py | 38 ++++++++++-- 8 files changed, 122 insertions(+), 33 deletions(-) create mode 100644 test/ref/1378FC64.mmhex create mode 100644 test/ref/98831F3A.mmhex create mode 100644 test/ref/FE3C6545.mmhex diff --git a/doc/wiki/install-mswin/Install-MMGen-and-Its-Dependencies-on-Microsoft-Windows.md b/doc/wiki/install-mswin/Install-MMGen-and-Its-Dependencies-on-Microsoft-Windows.md index 6177224f..7f5280de 100644 --- a/doc/wiki/install-mswin/Install-MMGen-and-Its-Dependencies-on-Microsoft-Windows.md +++ b/doc/wiki/install-mswin/Install-MMGen-and-Its-Dependencies-on-Microsoft-Windows.md @@ -165,8 +165,8 @@ Get the [zip archive][10] of the latest stable version from GitHub, unpack and i $ python setup.py build --compiler=mingw32 $ sudo ./setup.py install -If you wish, you may run the MMGen test suite to make sure your installation's -working: +After first installing and starting the [Bitcoin daemon][77], you may then run +the MMGen test suite to make sure your installation's working: $ test/test.py -s @@ -184,3 +184,4 @@ working: [31]: https://sourceforge.net/projects/mingw/files/MinGW/Extension/autoconf/autoconf2.5/autoconf2.5-2.68-1/autoconf2.5-2.68-1-mingw32-bin.tar.lzma [32]: https://sourceforge.net/projects/mingw/files/MinGW/Extension/automake/automake1.11/automake1.11-1.11.1-1/automake1.11-1.11.1-1-mingw32-bin.tar.lzma [33]: https://sourceforge.net/projects/mingw/files/MinGW/Extension/libtool/libtool-2.4-1/libtool-2.4-1-mingw32-bin.tar.lzma +[77]: Install-Bitcoind diff --git a/doc/wiki/using-mmgen/Getting-Started-with-MMGen.md b/doc/wiki/using-mmgen/Getting-Started-with-MMGen.md index 8af8b7f9..d29ab080 100644 --- a/doc/wiki/using-mmgen/Getting-Started-with-MMGen.md +++ b/doc/wiki/using-mmgen/Getting-Started-with-MMGen.md @@ -9,8 +9,8 @@ * Send a transaction #### Additional Features -* Using the mnemonic and seed features -* Mnemonics and seeds: additional information +* Using the mnemonic, seed and hexseed formats +* Mnemonics, seeds and hexseeds: additional information * Incognito wallets * Hidden incognito wallets @@ -308,7 +308,7 @@ by invoking the desired command with the `-h` or `--help` switch. ### Additional Features -#### Using the mnemonic and seed features: +#### Using the mnemonic, seed and hexseed formats: Continuing our example above, generate a mnemonic from the default wallet: @@ -387,10 +387,25 @@ Or you can do the same thing with 'mmgen-tool': $ mmgen-tool str2id6 'XnyC NfPH piuW dQ2d nM47 VU' 0fe02f -#### Mnemonics and seeds: additional information +Beginning with version 0.9.0, export to and generation from hexadecimal +(hexseed) format is also supported. Hexseed files are identical to seed files +but encoded in hexadecimal rather than base 58. They bear the extension +'.mmhex': -MMGen commands that take mnemonic and seed data may receive the data from a -prompt instead of a file. Just omit the file name and specify the input format: + $ cat FE3C6545.mmhex + afc3fe 456d 7f5f 1c4b fe3b c916 b875 60ae 6a3e + +You can easily check that a hexseed is correct by generating its Seed ID with +standard command-line tools: + + $ echo 456d 7f5f 1c4b fe3b c916 b875 60ae 6a3e | tr -d ' ' | xxd -r -p | sha256sum -b | xxd -r -p | sha256sum -b | cut -c 1-8 + fe3c6545 + +#### Mnemonics, seeds and hexseeds: additional information + +MMGen commands that take mnemonic, seed or hexseed data may receive the data +from a prompt instead of a file. Just omit the file name and specify the input +format: $ mmgen-walletconv -i words ... diff --git a/mmgen/seed.py b/mmgen/seed.py index c722f2ff..f6aebd5b 100755 --- a/mmgen/seed.py +++ b/mmgen/seed.py @@ -223,6 +223,9 @@ class SeedSourceUnenc(SeedSource): def _decrypt_retry(self): pass def _encrypt(self): pass + def _filename(self): + return '%s[%s].%s' % (self.seed.sid,self.seed.length,self.ext) + class SeedSourceEnc(SeedSource): _msg = { @@ -470,9 +473,6 @@ class Mnemonic (SeedSourceUnenc): return True - def _filename(self): - return '%s[%s].%s' % (self.seed.sid,self.seed.length,self.ext) - class SeedFile (SeedSourceUnenc): stdin_ok = True @@ -509,8 +509,7 @@ class SeedFile (SeedSourceUnenc): vmsg_r('Validating %s checksum...' % desc) - if not compare_chksums( - a,'checksum',make_chksum_6(b),'base 58 data'): + if not compare_chksums(a,'file',make_chksum_6(b),'computed',verbose=True): return False ret = b58decode_pad(b) @@ -527,8 +526,53 @@ class SeedFile (SeedSourceUnenc): return True - def _filename(self): - return '%s[%s].%s' % (self.seed.sid,self.seed.length,self.ext) +class HexSeedFile (SeedSourceUnenc): + + stdin_ok = True + fmt_codes = 'seedhex','hexseed','hex','mmhex' + desc = 'hexadecimal seed data' + ext = 'mmhex' + + def _format(self): + h = self.seed.hexdata + self.ssdata.chksum = make_chksum_6(h) + self.ssdata.hexseed = h + self.fmt_data = '%s %s\n' % (self.ssdata.chksum, split_into_cols(4,h)) + + def _deformat(self): + desc = self.desc + d = self.fmt_data.split() + try: + d[1] + chk,hstr = d[0],''.join(d[1:]) + except: + msg("'%s': invalid %s" % (self.fmt_data.strip(),desc)) + return False + + if not len(hstr)*4 in g.seed_lens: + msg('Invalid data length (%s) in %s' % (len(hstr),desc)) + return False + + if not is_chksum_6(chk): + msg("'%s': invalid checksum format in %s" % (chk, desc)) + return False + + if not is_hexstring(hstr): + msg("'%s': not a hexadecimal string, in %s" % (hstr, desc)) + return False + + vmsg_r('Validating %s checksum...' % desc) + + if not compare_chksums(chk,'file',make_chksum_6(hstr),'computed',verbose=True): + return False + + self.seed = Seed(unhexlify(hstr)) + self.ssdata.chksum = chk + self.ssdata.hexseed = hstr + + check_usr_seed_len(self.seed.length) + + return True class Wallet (SeedSourceEnc): @@ -614,7 +658,7 @@ class Wallet (SeedSourceEnc): chk = make_chksum_6(' '.join(lines[1:])) if not compare_chksums(lines[0],'master',chk,'computed', - hdr='For wallet master checksum'): + hdr='For wallet master checksum',verbose=True): return False return True @@ -658,7 +702,7 @@ class Wallet (SeedSourceEnc): return False if not compare_chksums(chk,key, - make_chksum_6(b58_val),'computed checksum'): + make_chksum_6(b58_val),'computed checksum',verbose=True): return False val = b58decode_pad(b58_val) diff --git a/mmgen/util.py b/mmgen/util.py index 0f3af1c5..88c1af3d 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -117,14 +117,14 @@ def qmsg_r(s,alt=False): if opt.quiet: if alt != False: sys.stderr.write(alt) else: sys.stderr.write(s) -def vmsg(s): - if opt.verbose: sys.stderr.write(s + '\n') -def vmsg_r(s): - if opt.verbose: sys.stderr.write(s) -def Vmsg(s): - if opt.verbose: sys.stdout.write(s + '\n') -def Vmsg_r(s): - if opt.verbose: sys.stdout.write(s) +def vmsg(s,force=False): + if opt.verbose or force: sys.stderr.write(s + '\n') +def vmsg_r(s,force=False): + if opt.verbose or force: sys.stderr.write(s) +def Vmsg(s,force=False): + if opt.verbose or force: sys.stdout.write(s + '\n') +def Vmsg_r(s,force=False): + if opt.verbose or force: sys.stdout.write(s) def dmsg(s): if opt.debug: sys.stdout.write(s + '\n') @@ -295,7 +295,7 @@ def get_hash_params(hash_preset): else: # Shouldn't be here die(3,"%s: invalid 'hash_preset' value" % hash_preset) -def compare_chksums(chk1, desc1, chk2, desc2, hdr='', die_on_fail=False): +def compare_chksums(chk1,desc1,chk2,desc2,hdr='',die_on_fail=False,verbose=False): if not chk1 == chk2: m = "%s ERROR: %s checksum (%s) doesn't match %s checksum (%s)"\ @@ -303,7 +303,7 @@ def compare_chksums(chk1, desc1, chk2, desc2, hdr='', die_on_fail=False): if die_on_fail: die(3,m) else: - vmsg(m) + vmsg(m,force=verbose) return False vmsg('%s checksum OK (%s)' % (capfirst(desc1),chk1)) diff --git a/test/ref/1378FC64.mmhex b/test/ref/1378FC64.mmhex new file mode 100644 index 00000000..a88443aa --- /dev/null +++ b/test/ref/1378FC64.mmhex @@ -0,0 +1 @@ +8a0088 456d 7f5f 1c4b fe3b c916 b875 60ae 6a3e 20da 3969 1cf5 3ded diff --git a/test/ref/98831F3A.mmhex b/test/ref/98831F3A.mmhex new file mode 100644 index 00000000..4d3f833a --- /dev/null +++ b/test/ref/98831F3A.mmhex @@ -0,0 +1 @@ +091c8f 456d 7f5f 1c4b fe3b c916 b875 60ae 6a3e 20da 3969 1cf5 3ded 010e 90a5 6e04 8e62 diff --git a/test/ref/FE3C6545.mmhex b/test/ref/FE3C6545.mmhex new file mode 100644 index 00000000..634a38fd --- /dev/null +++ b/test/ref/FE3C6545.mmhex @@ -0,0 +1 @@ +afc3fe 456d 7f5f 1c4b fe3b c916 b875 60ae 6a3e diff --git a/test/test.py b/test/test.py index 63cbf716..17bebd5b 100755 --- a/test/test.py +++ b/test/test.py @@ -203,6 +203,7 @@ cfgs = { 'sigtx': 'txsign', 'mmwords': 'export_mnemonic', 'mmseed': 'export_seed', + 'mmhex': 'export_hex', 'mmincog': 'export_incog', 'mmincox': 'export_incog_hex', hincog_fn: 'export_incog_hidden', @@ -407,12 +408,14 @@ cmd_group['main'] = OrderedDict([ # txdo must go after txsign ['txdo', (1,'online transaction', [[['sigtx','mmdat'],1]])], + ['export_hex', (1,'seed export to hexadecimal format', [[['mmdat'],1]])], ['export_seed', (1,'seed export to mmseed format', [[['mmdat'],1]])], ['export_mnemonic', (1,'seed export to mmwords format', [[['mmdat'],1]])], ['export_incog', (1,'seed export to mmincog format', [[['mmdat'],1]])], ['export_incog_hex',(1,'seed export to mmincog hex format', [[['mmdat'],1]])], ['export_incog_hidden',(1,'seed export to hidden mmincog format', [[['mmdat'],1]])], + ['addrgen_hex', (1,'address generation from mmhex file', [[['mmhex','addrs'],1]])], ['addrgen_seed', (1,'address generation from mmseed file', [[['mmseed','addrs'],1]])], ['addrgen_mnemonic',(1,'address generation from mmwords file',[[['mmwords','addrs'],1]])], ['addrgen_incog', (1,'address generation from mmincog file',[[['mmincog','addrs'],1]])], @@ -456,6 +459,7 @@ cmd_group['ref'] = ( # reading ('ref_wallet_chk', ([],'saved reference wallet')), ('ref_seed_chk', ([],'saved seed file')), + ('ref_hex_chk', ([],'saved mmhex file')), ('ref_mn_chk', ([],'saved mnemonic file')), ('ref_hincog_chk', ([],'saved hidden incog reference wallet')), ('ref_brain_chk', ([],'saved brainwallet')), @@ -481,6 +485,7 @@ cmd_group['conv_in'] = ( # reading ('ref_wallet_conv', 'conversion of saved reference wallet'), ('ref_mn_conv', 'conversion of saved mnemonic'), ('ref_seed_conv', 'conversion of saved seed file'), + ('ref_hex_conv', 'conversion of saved hexadecimal seed file'), ('ref_brain_conv', 'conversion of ref brainwallet'), ('ref_incog_conv', 'conversion of saved incog wallet'), ('ref_incox_conv', 'conversion of saved hex incog wallet'), @@ -491,6 +496,7 @@ cmd_group['conv_in'] = ( # reading cmd_group['conv_out'] = ( # writing ('ref_wallet_conv_out', 'ref seed conversion to wallet'), ('ref_mn_conv_out', 'ref seed conversion to mnemonic'), + ('ref_hex_conv_out', 'ref seed conversion to hex seed'), ('ref_seed_conv_out', 'ref seed conversion to seed'), ('ref_incog_conv_out', 'ref seed conversion to incog data'), ('ref_incox_conv_out', 'ref seed conversion to hex incog data'), @@ -1547,6 +1553,9 @@ class MMGenTestSuite(object): end_silence() ok() + def export_hex(self,name,wf,desc='hexadecimal seed data',out_fmt='hex',pf=None): + self.export_seed(name,wf,desc=desc,out_fmt=out_fmt,pf=pf) + def export_seed_dfl_wallet(self,name,pf,desc='seed data',out_fmt='seed'): self.export_seed(name,wf=None,desc=desc,out_fmt=out_fmt,pf=pf) @@ -1582,6 +1591,9 @@ class MMGenTestSuite(object): # t.no_overwrite() ok() + def addrgen_hex(self,name,wf,foo,desc='hexadecimal seed data',in_fmt='hex'): + self.addrgen_seed(name,wf,foo,desc=desc,in_fmt=in_fmt) + def addrgen_mnemonic(self,name,wf,foo): self.addrgen_seed(name,wf,foo,desc='mnemonic data',in_fmt='words') @@ -1795,6 +1807,9 @@ class MMGenTestSuite(object): def ref_seed_conv(self,name): self.ref_mn_conv(name,ext='mmseed',desc='Seed data') + def ref_hex_conv(self,name): + self.ref_mn_conv(name,ext='mmhex',desc='Hexadecimal seed data') + def ref_brain_conv(self,name): uopts = ['-i','b','-p','1','-l',str(cfg['seed_len'])] self.walletconv_in(name,None,'brainwallet',uopts,oo=True) @@ -1825,6 +1840,9 @@ class MMGenTestSuite(object): def ref_seed_conv_out(self,name): self.walletconv_out(name,'seed data','seed') + def ref_hex_conv_out(self,name): + self.walletconv_out(name,'hexadecimal seed data','hexseed') + def ref_incog_conv_out(self,name): self.walletconv_out(name,'incognito data',out_fmt='i',pw=True) @@ -1851,16 +1869,21 @@ class MMGenTestSuite(object): pf = None self.walletchk(name,wf,pf=pf,pw=True,sid=cfg['seed_id']) - from mmgen.seed import SeedFile - def ref_seed_chk(self,name,ext=SeedFile.ext): - wf = os.path.join(ref_dir,'%s.%s' % (cfg['seed_id'],ext)) + def ref_ss_chk(self,name,ss=None): + wf = os.path.join(ref_dir,'%s.%s' % (cfg['seed_id'],ss.ext)) + self.walletchk(name,wf,pf=None,desc=ss.desc,sid=cfg['seed_id']) + + def ref_seed_chk(self,name): from mmgen.seed import SeedFile - desc = ('mnemonic data','seed data')[ext==SeedFile.ext] - self.walletchk(name,wf,pf=None,desc=desc,sid=cfg['seed_id']) + self.ref_ss_chk(name,ss=SeedFile) + + def ref_hex_chk(self,name): + from mmgen.seed import HexSeedFile + self.ref_ss_chk(name,ss=HexSeedFile) def ref_mn_chk(self,name): from mmgen.seed import Mnemonic - self.ref_seed_chk(name,ext=Mnemonic.ext) + self.ref_ss_chk(name,ss=Mnemonic) def ref_brain_chk(self,name,bw_file=ref_bw_file): wf = os.path.join(ref_dir,bw_file) @@ -2060,6 +2083,7 @@ class MMGenTestSuite(object): 'ref_wallet_conv', 'ref_mn_conv', 'ref_seed_conv', + 'ref_hex_conv', 'ref_brain_conv', 'ref_incog_conv', 'ref_incox_conv', @@ -2068,6 +2092,7 @@ class MMGenTestSuite(object): 'ref_wallet_conv_out', 'ref_mn_conv_out', 'ref_seed_conv_out', + 'ref_hex_conv_out', 'ref_incog_conv_out', 'ref_incox_conv_out', 'ref_hincog_conv_out', @@ -2075,6 +2100,7 @@ class MMGenTestSuite(object): 'refwalletgen', 'refaddrgen', 'ref_seed_chk', + 'ref_hex_chk', 'ref_mn_chk', 'ref_brain_chk', 'ref_hincog_chk',