From 67eaf782b65a615bf0062e4c7bac0de71702ffe9 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Tue, 4 Oct 2022 13:08:54 +0000 Subject: [PATCH] keygen.py: modularize protocol-specific code --- mmgen/addr.py | 26 +--- mmgen/keygen.py | 214 +++++++----------------------- mmgen/proto/secp256k1/__init__.py | 0 mmgen/proto/secp256k1/keygen.py | 72 ++++++++++ mmgen/proto/xmr/keygen.py | 91 +++++++++++++ mmgen/proto/zec/keygen.py | 46 +++++++ setup.cfg | 1 + test/overlay/__init__.py | 1 + 8 files changed, 258 insertions(+), 193 deletions(-) create mode 100755 mmgen/proto/secp256k1/__init__.py create mode 100755 mmgen/proto/secp256k1/keygen.py create mode 100755 mmgen/proto/xmr/keygen.py create mode 100755 mmgen/proto/zec/keygen.py diff --git a/mmgen/addr.py b/mmgen/addr.py index 15a2858f..807d8cee 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -25,6 +25,7 @@ from collections import namedtuple from .objmethods import Hilite,InitErrors,MMGenObject from .obj import ImmutableAttr,MMGenIdx,HexStr,get_obj from .seed import SeedID +from .keygen import KeyGenerator # stub ati = namedtuple('addrtype_info', ['name','pubkey_type','compressed','gen_method','addr_fmt','wif_label','extra_attrs','desc']) @@ -186,31 +187,6 @@ class MoneroViewKey(HexStr): class ZcashViewKey(CoinAddr): hex_width = 128 -def KeyGenerator(proto,pubkey_type,backend=None,silent=False): - """ - factory function returning a key generator backend for the specified pubkey type - """ - assert pubkey_type in proto.pubkey_types, f'{pubkey_type!r}: invalid pubkey type for coin {proto.coin}' - - from .keygen import keygen_backend,_check_backend - - pubkey_type_cls = getattr(keygen_backend,pubkey_type) - - from .opts import opt - backend = backend or opt.keygen_backend - - if backend: - _check_backend(backend,pubkey_type) - - backend_id = pubkey_type_cls.backends[int(backend) - 1 if backend else 0] - - backend_clsname = getattr( - pubkey_type_cls, - backend_id.replace('-','_') - ).test_avail(silent=silent) - - return getattr(pubkey_type_cls,backend_clsname)() - def AddrGenerator(proto,addr_type): """ factory function returning an address generator for the specified address type diff --git a/mmgen/keygen.py b/mmgen/keygen.py index f408ea68..03639a03 100755 --- a/mmgen/keygen.py +++ b/mmgen/keygen.py @@ -17,11 +17,11 @@ # along with this program. If not, see . """ -keygen.py: Public key generation classes for the MMGen suite +keygen.py: Public key generation initialization code for the MMGen suite """ from collections import namedtuple -from .key import PubKey,PrivKey +from .key import PrivKey keygen_public_data = namedtuple( 'keygen_public_data', [ @@ -47,174 +47,29 @@ class keygen_base: def test_avail(cls,silent=False): return cls.__name__ -class keygen_backend: - - class std: - backends = ('libsecp256k1','python-ecdsa') - - class libsecp256k1(keygen_base): - - def __init__(self): - from .secp256k1 import priv2pub - self.priv2pub = priv2pub - - def to_pubkey(self,privkey): - return PubKey( - s = self.priv2pub( privkey, int(privkey.compressed) ), - compressed = privkey.compressed ) - - @classmethod - def test_avail(cls,silent=False): - try: - from .secp256k1 import priv2pub - if not priv2pub(bytes.fromhex('deadbeef'*8),1): - from .util import die - die( 'ExtensionModuleError', - 'Unable to execute priv2pub() from secp256k1 extension module' ) - return cls.__name__ - except Exception as e: - if not silent: - from .util import ymsg - ymsg(str(e)) - from .util import qmsg - qmsg('Using (slow) native Python ECDSA library for public key generation') - return 'python_ecdsa' - - class python_ecdsa(keygen_base): - - def __init__(self): - import ecdsa - self.ecdsa = ecdsa - - def to_pubkey(self,privkey): - """ - devdoc/guide_wallets.md: - Uncompressed public keys start with 0x04; compressed public keys begin with 0x03 or - 0x02 depending on whether they're greater or less than the midpoint of the curve. - """ - def privnum2pubkey(numpriv,compressed=False): - pko = self.ecdsa.SigningKey.from_secret_exponent(numpriv,curve=self.ecdsa.SECP256k1) - # pubkey = x (32 bytes) + y (32 bytes) (unsigned big-endian) - pubkey = pko.get_verifying_key().to_string() - if compressed: # discard Y coord, replace with appropriate version byte - # even y: <0, odd y: >0 -- https://bitcointalk.org/index.php?topic=129652.0 - return (b'\x02',b'\x03')[pubkey[-1] & 1] + pubkey[:32] - else: - return b'\x04' + pubkey - - return PubKey( - s = privnum2pubkey( int.from_bytes(privkey,'big'), compressed=privkey.compressed ), - compressed = privkey.compressed ) - - class monero: - backends = ('nacl','ed25519ll-djbec','ed25519') - - class base(keygen_base): - - def __init__(self): - - from .proto.xmr.params import mainnet - self.proto_cls = mainnet - - from .util import get_keccak - self.keccak_256 = get_keccak() - - def to_viewkey(self,privkey): - return self.proto_cls.preprocess_key( - self.proto_cls, - self.keccak_256(privkey).digest(), - None ) - - class nacl(base): - - def __init__(self): - super().__init__() - from nacl.bindings import crypto_scalarmult_ed25519_base_noclamp - self.scalarmultbase = crypto_scalarmult_ed25519_base_noclamp - - def to_pubkey(self,privkey): - return PubKey( - self.scalarmultbase( privkey ) + - self.scalarmultbase( self.to_viewkey(privkey) ), - compressed = privkey.compressed - ) - - class ed25519(base): - - def __init__(self): - super().__init__() - from .contrib.ed25519 import edwards,encodepoint,B,scalarmult - self.edwards = edwards - self.encodepoint = encodepoint - self.B = B - self.scalarmult = scalarmult - - def scalarmultbase(self,privnum): - """ - Source and license for scalarmultbase function: - https://github.com/bigreddmachine/MoneroPy/blob/master/moneropy/crypto/ed25519.py - Copyright (c) 2014-2016, The Monero Project - All rights reserved. - """ - if privnum == 0: - return [0, 1] - Q = self.scalarmult(self.B, privnum//2) - Q = self.edwards(Q, Q) - if privnum & 1: - Q = self.edwards(Q, self.B) - return Q - - @staticmethod - def rev_bytes2int(in_bytes): - return int.from_bytes( in_bytes[::-1], 'big' ) - - def to_pubkey(self,privkey): - return PubKey( - self.encodepoint( self.scalarmultbase( self.rev_bytes2int(privkey) )) + - self.encodepoint( self.scalarmultbase( self.rev_bytes2int(self.to_viewkey(privkey)) )), - compressed = privkey.compressed - ) - - class ed25519ll_djbec(ed25519): - - def __init__(self): - super().__init__() - from .contrib.ed25519ll_djbec import scalarmult - self.scalarmult = scalarmult - - class zcash_z: - backends = ('nacl',) - - class nacl(keygen_base): - - def __init__(self): - from nacl.bindings import crypto_scalarmult_base - self.crypto_scalarmult_base = crypto_scalarmult_base - from .sha2 import Sha256 - self.Sha256 = Sha256 - - def zhash256(self,s,t): - s = bytearray(s + bytes(32)) - s[0] |= 0xc0 - s[32] = t - return self.Sha256(s,preprocess=False).digest() - - def to_pubkey(self,privkey): - return PubKey( - self.zhash256(privkey,0) - + self.crypto_scalarmult_base(self.zhash256(privkey,1)), - compressed = privkey.compressed - ) - - def to_viewkey(self,privkey): - vk = bytearray( self.zhash256(privkey,0) + self.zhash256(privkey,1) ) - vk[32] &= 0xf8 - vk[63] &= 0x7f - vk[63] |= 0x40 - return vk +backend_data = { + 'std': { + 'backends': ('libsecp256k1','python-ecdsa'), + 'package': 'secp256k1', + }, + 'monero': { + 'backends': ('nacl','ed25519ll-djbec','ed25519'), + 'package': 'xmr', + }, + 'zcash_z': { + 'backends': ('nacl',), + 'package': 'zec', + }, +} def get_backends(pubkey_type): - return getattr(keygen_backend,pubkey_type).backends + return backend_data[pubkey_type]['backends'] + +def get_pubkey_type_cls(pubkey_type): + import importlib + return getattr( + importlib.import_module(f'mmgen.proto.{backend_data[pubkey_type]["package"]}.keygen'), + 'backend' ) def _check_backend(backend,pubkey_type,desc='keygen backend'): @@ -244,3 +99,26 @@ def check_backend(proto,backend,addr_type): backend, pubkey_type, desc = '--keygen-backend parameter' ) + +def KeyGenerator(proto,pubkey_type,backend=None,silent=False): + """ + factory function returning a key generator backend for the specified pubkey type + """ + assert pubkey_type in proto.pubkey_types, f'{pubkey_type!r}: invalid pubkey type for coin {proto.coin}' + + pubkey_type_cls = get_pubkey_type_cls(pubkey_type) + + from .opts import opt + backend = backend or opt.keygen_backend + + if backend: + _check_backend(backend,pubkey_type) + + backend_id = backend_data[pubkey_type]['backends'][int(backend) - 1 if backend else 0] + + backend_clsname = getattr( + pubkey_type_cls, + backend_id.replace('-','_') + ).test_avail(silent=silent) + + return getattr(pubkey_type_cls,backend_clsname)() diff --git a/mmgen/proto/secp256k1/__init__.py b/mmgen/proto/secp256k1/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/mmgen/proto/secp256k1/keygen.py b/mmgen/proto/secp256k1/keygen.py new file mode 100755 index 00000000..51e392aa --- /dev/null +++ b/mmgen/proto/secp256k1/keygen.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# +# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet +# Copyright (C)2013-2022 The MMGen Project +# Licensed under the GNU General Public License, Version 3: +# https://www.gnu.org/licenses +# Public project repositories: +# https://github.com/mmgen/mmgen +# https://gitlab.com/mmgen/mmgen + +""" +proto.secp256k1.keygen: secp256k1 public key generation backends for the MMGen suite +""" + +from ...key import PubKey +from ...keygen import keygen_base + +class backend: + + class libsecp256k1(keygen_base): + + def __init__(self): + from ...secp256k1 import priv2pub + self.priv2pub = priv2pub + + def to_pubkey(self,privkey): + return PubKey( + s = self.priv2pub( privkey, int(privkey.compressed) ), + compressed = privkey.compressed ) + + @classmethod + def test_avail(cls,silent=False): + try: + from ...secp256k1 import priv2pub + if not priv2pub(bytes.fromhex('deadbeef'*8),1): + from ...util import die + die( 'ExtensionModuleError', + 'Unable to execute priv2pub() from secp256k1 extension module' ) + return cls.__name__ + except Exception as e: + if not silent: + from ...util import ymsg + ymsg(str(e)) + from ...util import qmsg + qmsg('Using (slow) native Python ECDSA library for public key generation') + return 'python_ecdsa' + + class python_ecdsa(keygen_base): + + def __init__(self): + import ecdsa + self.ecdsa = ecdsa + + def to_pubkey(self,privkey): + """ + devdoc/guide_wallets.md: + Uncompressed public keys start with 0x04; compressed public keys begin with 0x03 or + 0x02 depending on whether they're greater or less than the midpoint of the curve. + """ + def privnum2pubkey(numpriv,compressed=False): + pko = self.ecdsa.SigningKey.from_secret_exponent(numpriv,curve=self.ecdsa.SECP256k1) + # pubkey = x (32 bytes) + y (32 bytes) (unsigned big-endian) + pubkey = pko.get_verifying_key().to_string() + if compressed: # discard Y coord, replace with appropriate version byte + # even y: <0, odd y: >0 -- https://bitcointalk.org/index.php?topic=129652.0 + return (b'\x02',b'\x03')[pubkey[-1] & 1] + pubkey[:32] + else: + return b'\x04' + pubkey + + return PubKey( + s = privnum2pubkey( int.from_bytes(privkey,'big'), compressed=privkey.compressed ), + compressed = privkey.compressed ) diff --git a/mmgen/proto/xmr/keygen.py b/mmgen/proto/xmr/keygen.py new file mode 100755 index 00000000..82606c66 --- /dev/null +++ b/mmgen/proto/xmr/keygen.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# +# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet +# Copyright (C)2013-2022 The MMGen Project +# Licensed under the GNU General Public License, Version 3: +# https://www.gnu.org/licenses +# Public project repositories: +# https://github.com/mmgen/mmgen +# https://gitlab.com/mmgen/mmgen + +""" +proto.xmr.keygen: Monero public key generation backends for the MMGen suite +""" + +from ...key import PubKey +from ...keygen import keygen_base + +class backend: + + class base(keygen_base): + + def __init__(self): + + from ...proto.xmr.params import mainnet + self.proto_cls = mainnet + + from ...util import get_keccak + self.keccak_256 = get_keccak() + + def to_viewkey(self,privkey): + return self.proto_cls.preprocess_key( + self.proto_cls, + self.keccak_256(privkey).digest(), + None ) + + class nacl(base): + + def __init__(self): + super().__init__() + from nacl.bindings import crypto_scalarmult_ed25519_base_noclamp + self.scalarmultbase = crypto_scalarmult_ed25519_base_noclamp + + def to_pubkey(self,privkey): + return PubKey( + self.scalarmultbase( privkey ) + + self.scalarmultbase( self.to_viewkey(privkey) ), + compressed = privkey.compressed + ) + + class ed25519(base): + + def __init__(self): + super().__init__() + from ...contrib.ed25519 import edwards,encodepoint,B,scalarmult + self.edwards = edwards + self.encodepoint = encodepoint + self.B = B + self.scalarmult = scalarmult + + def scalarmultbase(self,privnum): + """ + Source and license for scalarmultbase function: + https://github.com/bigreddmachine/MoneroPy/blob/master/moneropy/crypto/ed25519.py + Copyright (c) 2014-2016, The Monero Project + All rights reserved. + """ + if privnum == 0: + return [0, 1] + Q = self.scalarmult(self.B, privnum//2) + Q = self.edwards(Q, Q) + if privnum & 1: + Q = self.edwards(Q, self.B) + return Q + + @staticmethod + def rev_bytes2int(in_bytes): + return int.from_bytes( in_bytes[::-1], 'big' ) + + def to_pubkey(self,privkey): + return PubKey( + self.encodepoint( self.scalarmultbase( self.rev_bytes2int(privkey) )) + + self.encodepoint( self.scalarmultbase( self.rev_bytes2int(self.to_viewkey(privkey)) )), + compressed = privkey.compressed + ) + + class ed25519ll_djbec(ed25519): + + def __init__(self): + super().__init__() + from ...contrib.ed25519ll_djbec import scalarmult + self.scalarmult = scalarmult diff --git a/mmgen/proto/zec/keygen.py b/mmgen/proto/zec/keygen.py new file mode 100755 index 00000000..6fb270cb --- /dev/null +++ b/mmgen/proto/zec/keygen.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# +# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet +# Copyright (C)2013-2022 The MMGen Project +# Licensed under the GNU General Public License, Version 3: +# https://www.gnu.org/licenses +# Public project repositories: +# https://github.com/mmgen/mmgen +# https://gitlab.com/mmgen/mmgen + +""" +proto.zec.keygen: Zcash-Z public key generation backend for the MMGen suite +""" + +from ...key import PubKey +from ...keygen import keygen_base + +class backend: + + class nacl(keygen_base): + + def __init__(self): + from nacl.bindings import crypto_scalarmult_base + self.crypto_scalarmult_base = crypto_scalarmult_base + from ...sha2 import Sha256 + self.Sha256 = Sha256 + + def zhash256(self,s,t): + s = bytearray(s + bytes(32)) + s[0] |= 0xc0 + s[32] = t + return self.Sha256(s,preprocess=False).digest() + + def to_pubkey(self,privkey): + return PubKey( + self.zhash256(privkey,0) + + self.crypto_scalarmult_base(self.zhash256(privkey,1)), + compressed = privkey.compressed + ) + + def to_viewkey(self,privkey): + vk = bytearray( self.zhash256(privkey,0) + self.zhash256(privkey,1) ) + vk[32] &= 0xf8 + vk[63] &= 0x7f + vk[63] |= 0x40 + return vk diff --git a/setup.cfg b/setup.cfg index 2710d169..38c97a1b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -56,6 +56,7 @@ packages = mmgen.proto.eth.tx mmgen.proto.eth.tw mmgen.proto.ltc + mmgen.proto.secp256k1 mmgen.proto.xmr mmgen.proto.zec mmgen.share diff --git a/test/overlay/__init__.py b/test/overlay/__init__.py index aea2274d..2b471b5c 100644 --- a/test/overlay/__init__.py +++ b/test/overlay/__init__.py @@ -53,6 +53,7 @@ def overlay_setup(repo_root): 'mmgen.proto.eth.tx', 'mmgen.proto.eth.tw', 'mmgen.proto.ltc', + 'mmgen.proto.secp256k1', 'mmgen.proto.xmr', 'mmgen.proto.zec', 'mmgen.share',