key.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. #!/usr/bin/env python3
  2. #
  3. # MMGen Wallet, a terminal-based cryptocurrency wallet
  4. # Copyright (C)2013-2025 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. key: MMGen public and private key objects
  20. """
  21. from .objmethods import HiliteStr, InitErrors, MMGenObject
  22. from .obj import ImmutableAttr, get_obj
  23. class WifKey(HiliteStr, InitErrors):
  24. """
  25. Initialize a WIF key, checking its well-formedness.
  26. The numeric validity of the private key it encodes is not checked.
  27. """
  28. width = 53
  29. color = 'blue'
  30. def __new__(cls, proto, wif):
  31. if isinstance(wif, cls):
  32. return wif
  33. try:
  34. assert wif.isascii() and wif.isalnum(), 'not an ASCII alphanumeric string'
  35. proto.decode_wif(wif) # raises exception on error
  36. return str.__new__(cls, wif)
  37. except Exception as e:
  38. return cls.init_fail(e, wif)
  39. def is_wif(proto, s):
  40. return get_obj(WifKey, proto=proto, wif=s, silent=True, return_bool=True)
  41. class PubKey(bytes, InitErrors, MMGenObject): # TODO: add some real checks
  42. def __new__(cls, s, compressed):
  43. try:
  44. assert isinstance(s, bytes)
  45. me = bytes.__new__(cls, s)
  46. me.compressed = compressed
  47. return me
  48. except Exception as e:
  49. return cls.init_fail(e, s)
  50. class PrivKey(bytes, InitErrors, MMGenObject):
  51. """
  52. Input: a) raw, non-preprocessed bytes; or b) WIF key.
  53. Output: preprocessed key bytes, plus WIF key in 'wif' attribute
  54. For coins without a WIF format, 'wif' contains the preprocessed hex.
  55. The numeric validity of the resulting key is always checked.
  56. """
  57. color = 'red'
  58. width = 32
  59. trunc_ok = False
  60. compressed = ImmutableAttr(bool, typeconv=False)
  61. wif = ImmutableAttr(WifKey, typeconv=False)
  62. # initialize with (priv_bin, compressed), WIF or self
  63. def __new__(cls, proto, s=None, *, compressed=None, wif=None, pubkey_type=None):
  64. if isinstance(s, cls):
  65. return s
  66. if wif:
  67. try:
  68. assert s is None, "'wif' and key hex args are mutually exclusive"
  69. assert wif.isascii() and wif.isalnum(), 'not an ASCII alphanumeric string'
  70. k = proto.decode_wif(wif) # raises exception on error
  71. me = bytes.__new__(cls, k.sec)
  72. me.compressed = k.compressed
  73. me.pubkey_type = k.pubkey_type
  74. me.wif = str.__new__(WifKey, wif) # check has been done
  75. me.orig_bytes = None
  76. if k.sec != proto.preprocess_key(k.sec, k.pubkey_type):
  77. from .util import die
  78. die('PrivateKeyError',
  79. f'{proto.cls_name} WIF key {me.wif!r} encodes private key with invalid value {me}')
  80. me.proto = proto
  81. return me
  82. except Exception as e:
  83. return cls.init_fail(e, s, objname=f'{proto.coin} WIF key')
  84. else:
  85. try:
  86. assert s, 'private key bytes data missing'
  87. assert isinstance(s, bytes), 'input is not bytes'
  88. assert pubkey_type is not None, "'pubkey_type' arg missing"
  89. assert len(s) == cls.width, f'key length must be {cls.width} bytes'
  90. if pubkey_type == 'password': # skip WIF creation and pre-processing for passwds
  91. me = bytes.__new__(cls, s)
  92. else:
  93. assert compressed is not None, "'compressed' arg missing"
  94. assert type(compressed) is bool, (
  95. f"'compressed' must be of type bool, not {type(compressed).__name__}")
  96. me = bytes.__new__(cls, proto.preprocess_key(s, pubkey_type))
  97. me.wif = WifKey(proto, proto.encode_wif(me, pubkey_type, compressed=compressed))
  98. me.compressed = compressed
  99. me.pubkey_type = pubkey_type
  100. me.orig_bytes = s # save the non-preprocessed key
  101. me.proto = proto
  102. return me
  103. except Exception as e:
  104. return cls.init_fail(e, s)