bip39.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2022 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. bip39.py - Data and routines for BIP39 mnemonic seed phrases
  20. """
  21. from hashlib import sha256
  22. from .exception import *
  23. from .baseconv import baseconv
  24. from .util import is_hex_str
  25. def is_bip39_str(s):
  26. return bool( bip39.tohex(s.split(), wl_id='bip39') )
  27. # implements a subset of the baseconv API
  28. class bip39(baseconv):
  29. desc = { 'bip39': ('BIP39 mnemonic', 'BIP39 mnemonic seed phrase') }
  30. wl_chksums = { 'bip39': 'f18b9a84' }
  31. seedlen_map = { 'bip39': { 16:12, 24:18, 32:24 } }
  32. seedlen_map_rev = { 'bip39': { 12:16, 18:24, 24:32 } }
  33. from collections import namedtuple
  34. bc = namedtuple('bip39_constants',['chk_len','mn_len'])
  35. # ENT CS MS
  36. constants = {
  37. 128: bc(4, 12),
  38. 160: bc(5, 15),
  39. 192: bc(6, 18),
  40. 224: bc(7, 21),
  41. 256: bc(8, 24),
  42. }
  43. from .mn_bip39 import words
  44. digits = { 'bip39': words }
  45. @classmethod
  46. def nwords2seedlen(cls,nwords,in_bytes=False,in_hex=False):
  47. for k,v in cls.constants.items():
  48. if v.mn_len == nwords:
  49. return k//8 if in_bytes else k//4 if in_hex else k
  50. raise MnemonicError(f'{nwords!r}: invalid word length for BIP39 mnemonic')
  51. @classmethod
  52. def seedlen2nwords(cls,seed_len,in_bytes=False,in_hex=False):
  53. seed_bits = seed_len * 8 if in_bytes else seed_len * 4 if in_hex else seed_len
  54. try:
  55. return cls.constants[seed_bits].mn_len
  56. except:
  57. raise ValueError(f'{seed_bits!r}: invalid seed length for BIP39 mnemonic')
  58. @classmethod
  59. def tobytes(cls,*args,**kwargs):
  60. raise NotImplementedError('Method not supported')
  61. @classmethod
  62. def frombytes(cls,*args,**kwargs):
  63. raise NotImplementedError('Method not supported')
  64. @classmethod
  65. def tohex(cls,words,wl_id,pad=None):
  66. assert isinstance(words,(list,tuple)),'words must be list or tuple'
  67. assert wl_id == 'bip39',"'wl_id' must be 'bip39'"
  68. assert pad == None, f"{pad}: invalid 'pad' argument (must be None)"
  69. wl = cls.digits[wl_id]
  70. for n,w in enumerate(words):
  71. if w not in wl:
  72. raise MnemonicError(f'word #{n+1} is not in the BIP39 word list')
  73. res = ''.join(['{:011b}'.format(wl.index(w)) for w in words])
  74. for k,v in cls.constants.items():
  75. if len(words) == v.mn_len:
  76. bitlen = k
  77. break
  78. else:
  79. raise MnemonicError(f'{len(words)}: invalid BIP39 seed phrase length')
  80. seed_bin = res[:bitlen]
  81. chk_bin = res[bitlen:]
  82. seed_hex = '{:0{w}x}'.format(int(seed_bin,2),w=bitlen//4)
  83. seed_bytes = bytes.fromhex(seed_hex)
  84. chk_len = cls.constants[bitlen].chk_len
  85. chk_hex_chk = sha256(seed_bytes).hexdigest()
  86. chk_bin_chk = '{:0{w}b}'.format(int(chk_hex_chk,16),w=256)[:chk_len]
  87. if chk_bin != chk_bin_chk:
  88. raise MnemonicError('invalid BIP39 seed phrase checksum')
  89. return seed_hex
  90. @classmethod
  91. def fromhex(cls,seed_hex,wl_id,pad=None,tostr=False):
  92. assert is_hex_str(seed_hex),'seed data not a hexadecimal string'
  93. assert wl_id == 'bip39',"'wl_id' must be 'bip39'"
  94. assert tostr == False,"'tostr' must be False for 'bip39'"
  95. assert pad == None, f"{pad}: invalid 'pad' argument (must be None)"
  96. wl = cls.digits[wl_id]
  97. seed_bytes = bytes.fromhex(seed_hex)
  98. bitlen = len(seed_bytes) * 8
  99. assert bitlen in cls.constants, f'{bitlen}: invalid seed bit length'
  100. c = cls.constants[bitlen]
  101. chk_hex = sha256(seed_bytes).hexdigest()
  102. seed_bin = '{:0{w}b}'.format(int(seed_hex,16),w=bitlen)
  103. chk_bin = '{:0{w}b}'.format(int(chk_hex,16),w=256)[:c.chk_len]
  104. res = seed_bin + chk_bin
  105. return tuple(wl[int(res[i*11:(i+1)*11],2)] for i in range(c.mn_len))
  106. @classmethod
  107. def init_mn(cls,mn_id):
  108. assert mn_id == 'bip39', "'mn_id' must be 'bip39'"