From 8705e57b8f98d7042dff4461177baae4efc0312a Mon Sep 17 00:00:00 2001 From: MMGen Date: Wed, 16 Oct 2019 17:12:57 +0000 Subject: [PATCH] mmgen-passgen: support BIP39 passwords Examples: Generate three 24-word BIP39 mnemonic seed phrases from your default wallet for your Trezor device: $ mmgen-passgen --passwd-fmt=bip39 mytrezor 1-3 Same, but generate 12-word seed phrases: $ mmgen-passgen --passwd-fmt=bip39 --passwd-len=12 mytrezor 1-3 Relevant tests: $ test/test.py ref ref3 --- mmgen/addr.py | 72 +++++++++++++++++-- mmgen/bip39.py | 15 ++++ mmgen/main_passgen.py | 6 +- ...3A-фубар@crypto.org-bip39-12[1,4,1100].pws | 6 ++ ...3A-фубар@crypto.org-bip39-18[1,4,1100].pws | 6 ++ ...3A-фубар@crypto.org-bip39-24[1,4,1100].pws | 6 ++ ...„убар@crypto.org-hex2bip39-12[1,4,1100].pws | 6 ++ test/test_py_d/ts_ref.py | 16 +++++ test/test_py_d/ts_ref_3seed.py | 28 ++++++++ 9 files changed, 153 insertions(+), 8 deletions(-) create mode 100644 test/ref/98831F3A-фубар@crypto.org-bip39-12[1,4,1100].pws create mode 100644 test/ref/98831F3A-фубар@crypto.org-bip39-18[1,4,1100].pws create mode 100644 test/ref/98831F3A-фубар@crypto.org-bip39-24[1,4,1100].pws create mode 100644 test/ref/98831F3A-фубар@crypto.org-hex2bip39-12[1,4,1100].pws diff --git a/mmgen/addr.py b/mmgen/addr.py index af9a1efe..a51f08ef 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -594,7 +594,7 @@ Removed {{}} duplicate WIF key{{}} from keylist (also in {pnm} key-address file out.append('# Record this value to a secure location.\n') if type(self) == PasswordList: - lbl = '{} {} {}:{}'.format(self.al_id.sid,self.pw_id_str,self.pw_fmt,self.pw_len) + lbl = '{} {} {}:{}'.format(self.al_id.sid,self.pw_id_str,self.pw_fmt_disp,self.pw_len) else: bc,mt = g.proto.base_coin,self.al_id.mmtype l_coin = [] if bc == 'BTC' else [g.coin] if bc == 'ETH' else [bc] @@ -780,6 +780,10 @@ class KeyList(AddrList): ext = 'keys' chksum_rec_f = lambda foo,e: (str(e.idx), e.addr, e.sec.wif) +def is_bip39_str(s): + from mmgen.bip39 import bip39 + return bool(bip39.tohex(s.split(),wl_id='bip39')) + from collections import namedtuple class PasswordList(AddrList): msgs = { @@ -792,6 +796,13 @@ class PasswordList(AddrList): # password. The label may contain any printable ASCII symbol. # """.strip().format(n=TwComment.max_screen_width,pnm=pnm), + 'file_header_bip39': """ +# {pnm} BIP39 password file +# +# This file is editable. +# Everything following a hash symbol '#' is a comment and ignored by {pnm}. +# +""".strip().format(pnm=pnm), 'record_chksum': """ Record this checksum: it will be used to verify the password file in the future """.strip() @@ -813,10 +824,14 @@ Record this checksum: it will be used to verify the password file in the future pw_info = { 'b32': pwinfo(10, 42 ,24, None, 'base32 password', is_b32_str), # 32**24 < 2**128 'b58': pwinfo(8, 36 ,20, None, 'base58 password', is_b58_str), # 58**20 < 2**128 + 'bip39': pwinfo(12, 24 ,24, [12,18,24], 'BIP39 mnemonic', is_bip39_str), 'hex': pwinfo(32, 64 ,64, [32,48,64], 'hexadecimal password', is_hex_str), } chksum_rec_f = lambda foo,e: (str(e.idx), e.passwd) + feature_warn_fs = 'WARNING: {!r} is a potentially dangerous feature. Use at your own risk!' + hex2bip39 = False + def __init__( self,infile=None,seed=None, pw_idxs=None,pw_id_str=None,pw_len=None,pw_fmt=None, chk_params_only=False): @@ -835,24 +850,35 @@ Record this checksum: it will be used to verify the password file in the future self.set_pw_len(pw_len) if chk_params_only: return + if self.hex2bip39: + ymsg(self.feature_warn_fs.format(pw_fmt)) self.set_pw_len_vs_seed_len(pw_len,seed) self.al_id = AddrListID(seed.sid,MMGenPasswordType('P')) self.data = self.generate(seed,pw_idxs) + if self.pw_fmt == 'bip39': + self.msgs['file_header'] = self.msgs['file_header_bip39'] + self.num_addrs = len(self.data) self.fmt_data = '' self.chksum = AddrListChksum(self) - fs = '{}-{}-{}-{}[{{}}]'.format(self.al_id.sid,self.pw_id_str,self.pw_fmt,self.pw_len) + fs = '{}-{}-{}-{}[{{}}]'.format(self.al_id.sid,self.pw_id_str,self.pw_fmt_disp,self.pw_len) self.id_str = AddrListIDStr(self,fs) qmsg('Checksum for {} data {}: {}'.format(self.data_desc,self.id_str.hl(),self.chksum.hl())) qmsg(self.msgs[('record_chksum','check_chksum')[bool(infile)]]) def set_pw_fmt(self,pw_fmt): - if pw_fmt not in self.pw_info: + if pw_fmt == 'hex2bip39': + self.hex2bip39 = True + self.pw_fmt = 'bip39' + self.pw_fmt_disp = 'hex2bip39' + else: + self.pw_fmt = pw_fmt + self.pw_fmt_disp = pw_fmt + if self.pw_fmt not in self.pw_info: m = '{!r}: invalid password format. Valid formats: {}' - raise InvalidPasswdFormat(m.format(pw_fmt,', '.join(sorted(self.pw_info)))) - self.pw_fmt = pw_fmt + raise InvalidPasswdFormat(m.format(self.pw_fmt,', '.join(sorted(self.pw_info)))) def chk_pw_len(self,passwd=None): if passwd is None: @@ -888,6 +914,10 @@ Record this checksum: it will be used to verify the password file in the future if pf == 'hex': pw_bytes = self.pw_len // 2 good_pw_len = seed.byte_len * 2 + elif pf == 'bip39': + from mmgen.bip39 import bip39 + pw_bytes = bip39.nwords2seedlen(self.pw_len,in_bytes=True) + good_pw_len = bip39.seedlen2nwords(len(seed.data),in_bytes=True) elif pf in ('b32','b58'): pw_int = (32 if pf == 'b32' else 58) ** self.pw_len pw_bytes = pw_int.bit_length() // 8 @@ -901,7 +931,7 @@ Record this checksum: it will be used to verify the password file in the future 'Re-run the command, specifying a password length of {} or less' ) die(1,(m1+'\n'+m2).format(len(seed.data) * 8,good_pw_len)) - if pf == 'hex' and pw_bytes < seed.byte_len: + if pf in ('bip39','hex') and pw_bytes < seed.byte_len: m1 = 'WARNING: requested {} length has less entropy than underlying seed!' m2 = 'Is this what you want?' if not keypress_confirm((m1+'\n'+m2).format(self.pw_info[pf].desc),default_yes=True): @@ -912,6 +942,11 @@ Record this checksum: it will be used to verify the password file in the future if self.pw_fmt == 'hex': # take most significant part return hex_sec[:self.pw_len] + elif self.pw_fmt == 'bip39': + from mmgen.bip39 import bip39 + pw_len_hex = bip39.nwords2seedlen(self.pw_len,in_hex=True) + # take most significant part + return ' '.join(bip39.fromhex(hex_sec[:pw_len_hex],wl_id='bip39')) else: # take least significant part return baseconv.fromhex(hex_sec,self.pw_fmt,pad=self.pw_len,tostr=True)[-self.pw_len:] @@ -919,7 +954,7 @@ Record this checksum: it will be used to verify the password file in the future def check_format(self,pw): if not self.pw_info[self.pw_fmt].chk_func(pw): raise ValueError('Password is not valid {} data'.format(self.pw_info[self.pw_fmt].desc)) - pwlen = len(pw) + pwlen = len(pw.split()) if self.pw_fmt == 'bip39' else len(pw) if pwlen != self.pw_len: raise ValueError('Password has incorrect length ({} != {})'.format(pwlen,self.pw_len)) return True @@ -929,9 +964,32 @@ Record this checksum: it will be used to verify the password file in the future # set of passwords to be generated: this is what we want. # NB: In original implementation, pw_id_str was 'baseN', not 'bN' scramble_key = '{}:{}:{}'.format(self.pw_fmt,self.pw_len,self.pw_id_str) + + if self.hex2bip39: + from mmgen.bip39 import bip39 + pwlen = bip39.nwords2seedlen(self.pw_len,in_hex=True) + scramble_key = '{}:{}:{}'.format('hex',pwlen,self.pw_id_str) + from mmgen.crypto import scramble_seed return scramble_seed(seed,scramble_key.encode()) + def get_line(self,lines): + self.line_ctr += 1 + if self.pw_fmt == 'bip39': + ret = lines.pop(0).split(None,self.pw_len+1) + if len(ret) > self.pw_len+1: + m1 = 'extraneous text {!r} found after password'.format(ret[self.pw_len+1]) + m2 = '[bare comments not allowed in BIP39 password files]' + m = m1+' '+m2 + elif len(ret) < self.pw_len+1: + m = 'invalid password length {}'.format(len(ret)-1) + else: + return (ret[0],' '.join(ret[1:self.pw_len+1]),'') + raise ValueError(m) + else: + ret = lines.pop(0).split(None,2) + return ret if len(ret) == 3 else ret + [''] + class AddrData(MMGenObject): msgs = { 'too_many_acct_addresses': """ diff --git a/mmgen/bip39.py b/mmgen/bip39.py index a91eebcf..b454cf5e 100755 --- a/mmgen/bip39.py +++ b/mmgen/bip39.py @@ -2092,6 +2092,21 @@ zoo } digits = { 'bip39': words } + @classmethod + def nwords2seedlen(cls,nwords,in_bytes=False,in_hex=False): + for k,v in cls.constants.items(): + if v[1] == nwords: + return int(k)//8 if in_bytes else int(k)//4 if in_hex else int(k) + raise MnemonicError('{!r}: invalid word length for BIP39 mnemonic'.format(nwords)) + + @classmethod + def seedlen2nwords(cls,seed_len,in_bytes=False,in_hex=False): + seed_bits = seed_len * 8 if in_bytes else seed_len * 4 if in_hex else seed_len + try: + return cls.constants[str(seed_bits)][1] + except: + raise ValueError('{!r}: invalid seed length for BIP39 mnemonic'.format(seed_bits)) + @classmethod def tohex(cls,words,wl_id,pad=None): assert isinstance(words,(list,tuple)),'words must be list or tuple' diff --git a/mmgen/main_passgen.py b/mmgen/main_passgen.py index 713182d0..397b8477 100755 --- a/mmgen/main_passgen.py +++ b/mmgen/main_passgen.py @@ -93,6 +93,10 @@ EXAMPLES: Generate ten base32 passwords of length {i32.dfl_len} for Alice's email account: {g.prog_name} --passwd-fmt=b32 alice@nowhere.com 1-10 + Generate three BIP39 mnemonic seed phrases of length {i39.dfl_len} for Alice's + Trezor device: + {g.prog_name} --passwd-fmt=bip39 mytrezor 1-3 + All passwords are cryptographically unlinkable with each other, including passwords with the same format but different length, so Alice needn't worry about inadvertent reuse of private data. @@ -117,7 +121,7 @@ FMT CODES: kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]) ), 'notes': lambda s: s.format( - o=opts,g=g,i58=pwi['b58'],i32=pwi['b32'], + o=opts,g=g,i58=pwi['b58'],i32=pwi['b32'],i39=pwi['bip39'], ml=MMGenPWIDString.max_len, fs="', '".join(MMGenPWIDString.forbidden), n_pw=help_notes('passwd'), diff --git a/test/ref/98831F3A-фубар@crypto.org-bip39-12[1,4,1100].pws b/test/ref/98831F3A-фубар@crypto.org-bip39-12[1,4,1100].pws new file mode 100644 index 00000000..44196d8d --- /dev/null +++ b/test/ref/98831F3A-фубар@crypto.org-bip39-12[1,4,1100].pws @@ -0,0 +1,6 @@ +# checksum: BF57 02A3 5229 CF18 +98831F3A фубар@crypto.org bip39:12 { + 1 end later squirrel find token lens spring bubble solution museum uncover cat + 4 auto nice blood insane buffalo client slender warrior thumb glass sing sunset + 1100 swing rude gas illness silly cherry differ payment retreat whip sausage quiz +} diff --git a/test/ref/98831F3A-фубар@crypto.org-bip39-18[1,4,1100].pws b/test/ref/98831F3A-фубар@crypto.org-bip39-18[1,4,1100].pws new file mode 100644 index 00000000..3ed43c75 --- /dev/null +++ b/test/ref/98831F3A-фубар@crypto.org-bip39-18[1,4,1100].pws @@ -0,0 +1,6 @@ +# checksum: 31D3 1656 B7DC 27CF +98831F3A фубар@crypto.org bip39:18 { + 1 dirt broom flag tragic wool grape image window paddle wrap enough negative jewel tiger theory have relief nephew + 4 shrug catch neutral artwork zebra circle defy cream wild resist east custom recycle ability enact cable office inhale + 1100 kitchen visa way various scrap odor syrup armed crater expire cushion table work better design afford umbrella market +} diff --git a/test/ref/98831F3A-фубар@crypto.org-bip39-24[1,4,1100].pws b/test/ref/98831F3A-фубар@crypto.org-bip39-24[1,4,1100].pws new file mode 100644 index 00000000..a33b0779 --- /dev/null +++ b/test/ref/98831F3A-фубар@crypto.org-bip39-24[1,4,1100].pws @@ -0,0 +1,6 @@ +# checksum: E565 3A59 7D91 4671 +98831F3A фубар@crypto.org bip39:24 { + 1 letter sphere indoor maze surprise orange much state clinic sibling demise suggest tobacco economy resist lawsuit flock napkin report air rule catch cheap toilet + 4 perfect squeeze jump term critic elegant satisfy apology chair soap they visual chimney keep absurd goddess time move party blade crawl enhance token uncover + 1100 accident dad dilemma fault uphold dutch pony scout luxury puppy wave response gossip chronic denial spoon energy ugly uncover enlist regular ceiling found gesture +} diff --git a/test/ref/98831F3A-фубар@crypto.org-hex2bip39-12[1,4,1100].pws b/test/ref/98831F3A-фубар@crypto.org-hex2bip39-12[1,4,1100].pws new file mode 100644 index 00000000..0ed787df --- /dev/null +++ b/test/ref/98831F3A-фубар@crypto.org-hex2bip39-12[1,4,1100].pws @@ -0,0 +1,6 @@ +# checksum: 93AD 4AE2 03D1 8A0A +98831F3A фубар@crypto.org hex2bip39:12 { + 1 session mobile spirit butter hand hip people rebuild flight there tower armed + 4 cycle humor able term august budget lend cover usual grocery mountain journey + 1100 reward tongue emotion kid humor sight lunar bread remove clinic fun valve +} diff --git a/test/test_py_d/ts_ref.py b/test/test_py_d/ts_ref.py index 2f62a568..4ea5401b 100755 --- a/test/test_py_d/ts_ref.py +++ b/test/test_py_d/ts_ref.py @@ -48,6 +48,10 @@ class TestSuiteRef(TestSuiteBase,TestSuiteShared): 'ref_passwdfile_hex_32': '98831F3A-фубар@crypto.org-hex-32[1,4,1100].pws', 'ref_passwdfile_hex_48': '98831F3A-фубар@crypto.org-hex-48[1,4,1100].pws', 'ref_passwdfile_hex_64': '98831F3A-фубар@crypto.org-hex-64[1,4,1100].pws', + 'ref_passwdfile_bip39_12': '98831F3A-фубар@crypto.org-bip39-12[1,4,1100].pws', + 'ref_passwdfile_bip39_18': '98831F3A-фубар@crypto.org-bip39-18[1,4,1100].pws', + 'ref_passwdfile_bip39_24': '98831F3A-фубар@crypto.org-bip39-24[1,4,1100].pws', + 'ref_passwdfile_hex2bip39_12': '98831F3A-фубар@crypto.org-hex2bip39-12[1,4,1100].pws', 'ref_tx_file': { # data shared with ref_altcoin, autosign 'btc': ('0B8D5A[15.31789,14,tl=1320969600].rawtx', '0C7115[15.86255,14,tl=1320969600].testnet.rawtx'), @@ -90,6 +94,10 @@ class TestSuiteRef(TestSuiteBase,TestSuiteShared): 'ref_passwdfile_hex_32_chksum': '05C7 3678 E25E BC32', 'ref_passwdfile_hex_48_chksum': '7DBB FFD0 633E DE6F', 'ref_passwdfile_hex_64_chksum': 'F11D CB0A 8AE3 4D21', + 'ref_passwdfile_bip39_12_chksum': 'BF57 02A3 5229 CF18', + 'ref_passwdfile_bip39_18_chksum': '31D3 1656 B7DC 27CF', + 'ref_passwdfile_bip39_24_chksum': 'E565 3A59 7D91 4671', + 'ref_passwdfile_hex2bip39_12_chksum': '93AD 4AE2 03D1 8A0A', } cmd_group = ( # TODO: move to tooltest2 ('ref_words_to_subwallet_chk1','subwallet generation from reference words file (long subseed)'), @@ -110,6 +118,10 @@ class TestSuiteRef(TestSuiteBase,TestSuiteShared): ('ref_passwdfile_chk_hex_32','saved reference password file (hexadecimal, 32 chars)'), ('ref_passwdfile_chk_hex_48','saved reference password file (hexadecimal, 48 chars)'), ('ref_passwdfile_chk_hex_64','saved reference password file (hexadecimal, 64 chars)'), + ('ref_passwdfile_chk_bip39_12','saved reference password file (BIP39, 12 words)'), + ('ref_passwdfile_chk_bip39_18','saved reference password file (BIP39, 18 words)'), + ('ref_passwdfile_chk_bip39_24','saved reference password file (BIP39, 24 words)'), + ('ref_passwdfile_chk_hex2bip39_12','saved reference password file (hex-to-BIP39, 12 words)'), # Create the fake inputs: # ('txcreate8', 'transaction creation (8)'), @@ -233,6 +245,10 @@ class TestSuiteRef(TestSuiteBase,TestSuiteShared): def ref_passwdfile_chk_hex_32(self): return self.ref_passwdfile_chk(key='hex_32',pat='Hexadec.*len.* 32\n') def ref_passwdfile_chk_hex_48(self): return self.ref_passwdfile_chk(key='hex_48',pat='Hexadec.*len.* 48\n') def ref_passwdfile_chk_hex_64(self): return self.ref_passwdfile_chk(key='hex_64',pat='Hexadec.*len.* 64\n') + def ref_passwdfile_chk_bip39_12(self): return self.ref_passwdfile_chk(key='bip39_12',pat='BIP39.*len.* 12\n') + def ref_passwdfile_chk_bip39_18(self): return self.ref_passwdfile_chk(key='bip39_18',pat='BIP39.*len.* 18\n') + def ref_passwdfile_chk_bip39_24(self): return self.ref_passwdfile_chk(key='bip39_24',pat='BIP39.*len.* 24\n') + def ref_passwdfile_chk_hex2bip39_12(self): return self.ref_passwdfile_chk(key='hex2bip39_12',pat='BIP39.*len.* 12\n') def ref_tx_chk(self): fn = self.sources['ref_tx_file'][g.coin.lower()][bool(self.tn_ext)] diff --git a/test/test_py_d/ts_ref_3seed.py b/test/test_py_d/ts_ref_3seed.py index e455fdf7..19c181d2 100755 --- a/test/test_py_d/ts_ref_3seed.py +++ b/test/test_py_d/ts_ref_3seed.py @@ -77,6 +77,10 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared): 'ref_b32passwdgen_1': '37B6 C218 2ABC 7508', 'ref_hexpasswdgen_1': '8E99 E696 84CE E7D5', 'ref_hexpasswdgen_half_1': '8E99 E696 84CE E7D5', + 'ref_bip39_12_passwdgen_1': '834F CF45 0B33 8AF0', + 'ref_bip39_18_passwdgen_1': '834F CF45 0B33 8AF0', + 'ref_bip39_24_passwdgen_1': '834F CF45 0B33 8AF0', + 'ref_hex2bip39_24_passwdgen_1': '91AF E735 A31D 72A0', 'refaddrgen_legacy_2': { 'btc': ('8C17 A5FA 0470 6E89','764C 66F9 7502 AAEA'), 'ltc': ('2B77 A009 D5D0 22AD','51D1 979D 0A35 F24B'), @@ -114,6 +118,10 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared): 'ref_b32passwdgen_2': '2A28 C5C7 36EC 217A', 'ref_hexpasswdgen_2': '88F9 0D48 3A7E 7CC2', 'ref_hexpasswdgen_half_2': '59F3 8F48 861E 1186', + 'ref_bip39_12_passwdgen_2': 'D32D B8D7 A840 250B', + 'ref_bip39_18_passwdgen_2': '0FAA 78DD A6BA 31AD', + 'ref_bip39_24_passwdgen_2': '0FAA 78DD A6BA 31AD', + 'ref_hex2bip39_24_passwdgen_2': '0E8E 23C9 923F 7C2D', 'refaddrgen_legacy_3': { 'btc': ('6FEF 6FB9 7B13 5D91','424E 4326 CFFE 5F51'), 'ltc': ('AD52 C3FE 8924 AAF0','4EBE 2E85 E969 1B30'), @@ -151,6 +159,10 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared): 'ref_b32passwdgen_3': 'F6C1 CDFB 97D9 FCAE', 'ref_hexpasswdgen_3': 'BD4F A0AC 8628 4BE4', 'ref_hexpasswdgen_half_3': 'FBDD F733 FFB9 21C1', + 'ref_bip39_12_passwdgen_3': 'A86E EA14 974A 1B0E', + 'ref_bip39_18_passwdgen_3': 'EF87 9904 88E2 5884', + 'ref_bip39_24_passwdgen_3': 'EBE8 2A8F 8F8C 7DBD', + 'ref_hex2bip39_24_passwdgen_3': '93FA 5EFD 33F3 760E', } cmd_group = ( # reading @@ -182,6 +194,10 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared): ('ref_b32passwdgen', (['mmdat',pwfile],'new refwallet passwd file chksum (base32)')), ('ref_hexpasswdgen', (['mmdat',pwfile],'new refwallet passwd file chksum (hex)')), ('ref_hexpasswdgen_half',(['mmdat',pwfile],'new refwallet passwd file chksum (hex, half-length)')), + ('ref_bip39_12_passwdgen',(['mmdat',pwfile],'new refwallet passwd file chksum (BIP39, 12 words)')), + ('ref_bip39_18_passwdgen',(['mmdat',pwfile],'new refwallet passwd file chksum (BIP39, up to 18 words)')), + ('ref_bip39_24_passwdgen',(['mmdat',pwfile],'new refwallet passwd file chksum (BIP39, up to 24 words)')), + ('ref_hex2bip39_24_passwdgen',(['mmdat',pwfile],'new refwallet passwd file chksum (hex-to-BIP39, up to 24 words)')), ) def __init__(self,trunner,cfgs,spawn): @@ -339,3 +355,15 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared): def ref_hexpasswdgen_half(self,wf,pf): ea = ['--passwd-fmt=hex','--passwd-len=h','--accept-defaults'] return self.addrgen(wf,pf,check_ref=True,ftype='passhex',id_str='фубар@crypto.org',extra_args=ea,stdout=1) + + def ref_bip39_passwdgen(self,wf,pf,req_pw_len,pw_fmt='bip39',stdout=False): + pw_len = min(req_pw_len,{'1':12,'2':18,'3':24}[self.test_name[-1]]) + ea = ['--passwd-fmt='+pw_fmt,'--passwd-len={}'.format(pw_len),'--accept-defaults'] + return self.addrgen( + wf,pf,check_ref=True,ftype='passbip39',id_str='фубар@crypto.org',extra_args=ea,stdout=stdout) + + def ref_bip39_12_passwdgen(self,wf,pf): return self.ref_bip39_passwdgen(wf,pf,12,stdout=True) + def ref_bip39_18_passwdgen(self,wf,pf): return self.ref_bip39_passwdgen(wf,pf,18,stdout=True) + def ref_bip39_24_passwdgen(self,wf,pf): return self.ref_bip39_passwdgen(wf,pf,24) + + def ref_hex2bip39_24_passwdgen(self,wf,pf): return self.ref_bip39_passwdgen(wf,pf,24,'hex2bip39')