keygen.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  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. keygen.py: Public key generation classes for the MMGen suite
  20. """
  21. from collections import namedtuple
  22. from .key import PubKey,PrivKey
  23. keygen_public_data = namedtuple(
  24. 'keygen_public_data', [
  25. 'pubkey',
  26. 'viewkey_bytes',
  27. 'pubkey_type',
  28. 'compressed' ])
  29. class keygen_base:
  30. def gen_data(self,privkey):
  31. assert isinstance(privkey,PrivKey)
  32. return keygen_public_data(
  33. self.to_pubkey(privkey),
  34. self.to_viewkey(privkey),
  35. privkey.pubkey_type,
  36. privkey.compressed )
  37. def to_viewkey(self,privkey):
  38. return None
  39. class keygen_backend:
  40. class std:
  41. backends = ('libsecp256k1','python-ecdsa')
  42. class libsecp256k1(keygen_base):
  43. def __init__(self):
  44. from .secp256k1 import priv2pub
  45. self.priv2pub = priv2pub
  46. def to_pubkey(self,privkey):
  47. return PubKey(
  48. s = self.priv2pub( privkey, int(privkey.compressed) ),
  49. compressed = privkey.compressed )
  50. @classmethod
  51. def test_avail(cls,silent=False):
  52. try:
  53. from .secp256k1 import priv2pub
  54. if not priv2pub(bytes.fromhex('deadbeef'*8),1):
  55. from .exception import ExtensionModuleError
  56. raise ExtensionModuleError('Unable to execute priv2pub() from secp256k1 extension module')
  57. return True
  58. except Exception as e:
  59. if not silent:
  60. from .util import ymsg
  61. ymsg(str(e))
  62. return False
  63. class python_ecdsa(keygen_base):
  64. def __init__(self):
  65. import ecdsa
  66. self.ecdsa = ecdsa
  67. def to_pubkey(self,privkey):
  68. """
  69. devdoc/guide_wallets.md:
  70. Uncompressed public keys start with 0x04; compressed public keys begin with 0x03 or
  71. 0x02 depending on whether they're greater or less than the midpoint of the curve.
  72. """
  73. def privnum2pubkey(numpriv,compressed=False):
  74. pko = self.ecdsa.SigningKey.from_secret_exponent(numpriv,curve=self.ecdsa.SECP256k1)
  75. # pubkey = x (32 bytes) + y (32 bytes) (unsigned big-endian)
  76. pubkey = pko.get_verifying_key().to_string()
  77. if compressed: # discard Y coord, replace with appropriate version byte
  78. # even y: <0, odd y: >0 -- https://bitcointalk.org/index.php?topic=129652.0
  79. return (b'\x02',b'\x03')[pubkey[-1] & 1] + pubkey[:32]
  80. else:
  81. return b'\x04' + pubkey
  82. return PubKey(
  83. s = privnum2pubkey( int.from_bytes(privkey,'big'), compressed=privkey.compressed ),
  84. compressed = privkey.compressed )
  85. class monero:
  86. backends = ('nacl','ed25519ll_djbec','ed25519')
  87. class base(keygen_base):
  88. def __init__(self):
  89. from .protocol import CoinProtocol
  90. self.proto_cls = CoinProtocol.Monero
  91. from .util import get_keccak
  92. self.keccak_256 = get_keccak()
  93. def to_viewkey(self,privkey):
  94. return self.proto_cls.preprocess_key(
  95. self.proto_cls,
  96. self.keccak_256(privkey).digest(),
  97. None )
  98. class nacl(base):
  99. def __init__(self):
  100. super().__init__()
  101. from nacl.bindings import crypto_scalarmult_ed25519_base_noclamp
  102. self.scalarmultbase = crypto_scalarmult_ed25519_base_noclamp
  103. def to_pubkey(self,privkey):
  104. return PubKey(
  105. self.scalarmultbase( privkey ) +
  106. self.scalarmultbase( self.to_viewkey(privkey) ),
  107. compressed = privkey.compressed
  108. )
  109. class ed25519(base):
  110. def __init__(self):
  111. super().__init__()
  112. from .ed25519 import edwards,encodepoint,B,scalarmult
  113. self.edwards = edwards
  114. self.encodepoint = encodepoint
  115. self.B = B
  116. self.scalarmult = scalarmult
  117. def scalarmultbase(self,privnum):
  118. """
  119. Source and license for scalarmultbase function:
  120. https://github.com/bigreddmachine/MoneroPy/blob/master/moneropy/crypto/ed25519.py
  121. Copyright (c) 2014-2016, The Monero Project
  122. All rights reserved.
  123. """
  124. if privnum == 0:
  125. return [0, 1]
  126. Q = self.scalarmult(self.B, privnum//2)
  127. Q = self.edwards(Q, Q)
  128. if privnum & 1:
  129. Q = self.edwards(Q, self.B)
  130. return Q
  131. @staticmethod
  132. def rev_bytes2int(in_bytes):
  133. return int.from_bytes( in_bytes[::-1], 'big' )
  134. def to_pubkey(self,privkey):
  135. return PubKey(
  136. self.encodepoint( self.scalarmultbase( self.rev_bytes2int(privkey) )) +
  137. self.encodepoint( self.scalarmultbase( self.rev_bytes2int(self.to_viewkey(privkey)) )),
  138. compressed = privkey.compressed
  139. )
  140. class ed25519ll_djbec(ed25519):
  141. def __init__(self):
  142. super().__init__()
  143. from .ed25519ll_djbec import scalarmult
  144. self.scalarmult = scalarmult
  145. class zcash_z:
  146. backends = ('nacl',)
  147. class nacl(keygen_base):
  148. def __init__(self):
  149. from nacl.bindings import crypto_scalarmult_base
  150. self.crypto_scalarmult_base = crypto_scalarmult_base
  151. from .sha2 import Sha256
  152. self.Sha256 = Sha256
  153. def zhash256(self,s,t):
  154. s = bytearray(s + bytes(32))
  155. s[0] |= 0xc0
  156. s[32] = t
  157. return self.Sha256(s,preprocess=False).digest()
  158. def to_pubkey(self,privkey):
  159. return PubKey(
  160. self.zhash256(privkey,0)
  161. + self.crypto_scalarmult_base(self.zhash256(privkey,1)),
  162. compressed = privkey.compressed
  163. )
  164. def to_viewkey(self,privkey):
  165. vk = bytearray( self.zhash256(privkey,0) + self.zhash256(privkey,1) )
  166. vk[32] &= 0xf8
  167. vk[63] &= 0x7f
  168. vk[63] |= 0x40
  169. return vk
  170. def get_backends(pubkey_type):
  171. return getattr(keygen_backend,pubkey_type).backends
  172. def _check_backend(backend,pubkey_type,desc='keygen backend'):
  173. from .util import is_int,qmsg,die
  174. assert is_int(backend), f'illegal value for {desc} (must be an integer)'
  175. backends = get_backends(pubkey_type)
  176. if not (1 <= int(backend) <= len(backends)):
  177. die(1,
  178. f'{backend}: {desc} out of range\n' +
  179. f'Configured backends: ' +
  180. ' '.join( f'{n}:{k}' for n,k in enumerate(backends,1) )
  181. )
  182. qmsg(f'Using backend {backends[int(backend)-1]!r} for public key generation')
  183. return True
  184. def check_backend(proto,backend,addr_type):
  185. from .addr import MMGenAddrType
  186. pubkey_type = MMGenAddrType(proto,addr_type or proto.dfl_mmtype).pubkey_type
  187. return _check_backend(
  188. backend,
  189. pubkey_type,
  190. desc = '--keygen-backend parameter' )