mnemonic.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2023 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. tool.mnemonic: Mnemonic routines for the 'mmgen-tool' utility
  20. """
  21. from collections import namedtuple
  22. from .common import tool_cmd_base,options_annot_str
  23. from ..baseconv import baseconv
  24. from ..xmrseed import xmrseed
  25. from ..bip39 import bip39
  26. dfl_mnemonic_fmt = 'mmgen'
  27. mft = namedtuple('mnemonic_format',['fmt','pad','conv_cls'])
  28. mnemonic_fmts = {
  29. 'mmgen': mft( 'words', 'seed', baseconv ),
  30. 'bip39': mft( 'bip39', None, bip39 ),
  31. 'xmrseed': mft( 'xmrseed', None, xmrseed ),
  32. }
  33. mn_opts_disp = 'seed phrase format ' + options_annot_str(mnemonic_fmts)
  34. class tool_cmd(tool_cmd_base):
  35. """
  36. seed phrase utilities
  37. Supported seed phrase formats: 'mmgen' (default), 'bip39', 'xmrseed'
  38. IMPORTANT NOTE: MMGen’s default seed phrase format uses the Electrum
  39. wordlist, however seed phrases are computed using a different algorithm
  40. and are NOT Electrum-compatible!
  41. BIP39 support is fully compatible with the standard, allowing users to
  42. import and export seed entropy from BIP39-compatible wallets. However,
  43. users should be aware that BIP39 support does not imply BIP32 support!
  44. MMGen uses its own key derivation scheme differing from the one described
  45. by the BIP32 protocol.
  46. For Monero (‘xmrseed’) seed phrases, input data is reduced to a spendkey
  47. before conversion so that a canonical seed phrase is produced. This is
  48. required because Monero seeds, unlike ordinary wallet seeds, are tied
  49. to a concrete key/address pair. To manually generate a Monero spendkey,
  50. use the ‘hex2wif’ command.
  51. """
  52. def _xmr_reduce(self,bytestr):
  53. from ..protocol import init_proto
  54. proto = init_proto('xmr')
  55. if len(bytestr) != proto.privkey_len:
  56. die(1,'{!r}: invalid bit length for Monero private key (must be {})'.format(
  57. len(bytestr*8),
  58. proto.privkey_len*8 ))
  59. return proto.preprocess_key(bytestr,None)
  60. def _do_random_mn(self,nbytes:int,fmt:str):
  61. assert nbytes in (16,24,32), 'nbytes must be 16, 24 or 32'
  62. from ..crypto import Crypto
  63. randbytes = Crypto().get_random(nbytes)
  64. if fmt == 'xmrseed':
  65. randbytes = self._xmr_reduce(randbytes)
  66. from ..opts import opt
  67. if opt.verbose:
  68. from ..util import msg
  69. msg(f'Seed: {randbytes.hex()}')
  70. return self.hex2mn(randbytes.hex(),fmt=fmt)
  71. def mn_rand128(self, fmt:mn_opts_disp = dfl_mnemonic_fmt ):
  72. "generate a random 128-bit mnemonic seed phrase"
  73. return self._do_random_mn(16,fmt)
  74. def mn_rand192(self, fmt:mn_opts_disp = dfl_mnemonic_fmt ):
  75. "generate a random 192-bit mnemonic seed phrase"
  76. return self._do_random_mn(24,fmt)
  77. def mn_rand256(self, fmt:mn_opts_disp = dfl_mnemonic_fmt ):
  78. "generate a random 256-bit mnemonic seed phrase"
  79. return self._do_random_mn(32,fmt)
  80. def hex2mn( self, hexstr:'sstr', fmt:mn_opts_disp = dfl_mnemonic_fmt ):
  81. "convert a 16, 24 or 32-byte hexadecimal string to a mnemonic seed phrase"
  82. if fmt == 'xmrseed':
  83. hexstr = self._xmr_reduce(bytes.fromhex(hexstr)).hex()
  84. f = mnemonic_fmts[fmt]
  85. return ' '.join( f.conv_cls(fmt).fromhex(hexstr,f.pad) )
  86. def mn2hex( self, seed_mnemonic:'sstr', fmt:mn_opts_disp = dfl_mnemonic_fmt ):
  87. "convert a mnemonic seed phrase to a hexadecimal string"
  88. f = mnemonic_fmts[fmt]
  89. return f.conv_cls(fmt).tohex( seed_mnemonic.split(), f.pad )
  90. def mn2hex_interactive( self,
  91. fmt: mn_opts_disp = dfl_mnemonic_fmt,
  92. mn_len: 'length of seed phrase in words' = 24,
  93. print_mn: 'print the seed phrase after entry' = False ):
  94. "convert an interactively supplied mnemonic seed phrase to a hexadecimal string"
  95. from ..mn_entry import mn_entry
  96. mn = mn_entry(fmt).get_mnemonic_from_user(25 if fmt == 'xmrseed' else mn_len,validate=False)
  97. if print_mn:
  98. from ..util import msg
  99. msg(mn)
  100. return self.mn2hex(seed_mnemonic=mn,fmt=fmt)
  101. def mn_stats(self, fmt:mn_opts_disp = dfl_mnemonic_fmt ):
  102. "show stats for a mnemonic wordlist"
  103. return mnemonic_fmts[fmt].conv_cls(fmt).check_wordlist()
  104. def mn_printlist(self,
  105. fmt: mn_opts_disp = dfl_mnemonic_fmt,
  106. enum: 'enumerate the list' = False,
  107. pager: 'send output to pager' = False ):
  108. "print a mnemonic wordlist"
  109. ret = mnemonic_fmts[fmt].conv_cls(fmt).get_wordlist()
  110. if enum:
  111. ret = [f'{n:>4} {e}' for n,e in enumerate(ret)]
  112. return '\n'.join(ret)