keygen.py 6.6 KB

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