keygen: move unsafe pubkey generation backends to test suite
Though the unsafe implementations in this commit and the preceding ones are/were only used for testing, their presence in the code base triggers certain code vulnerability checkers, so best to move them out of harm’s way.
This commit is contained in:
parent
d34f36e4c3
commit
75b881a6d3
11 changed files with 125 additions and 98 deletions
|
|
@ -22,6 +22,7 @@ include test/ref/*/*/*
|
|||
include test/ref/*/*/*/*
|
||||
include test/overlay/fakemods/mmgen/*.py
|
||||
include test/overlay/fakemods/mmgen/*/*.py
|
||||
include test/overlay/fakemods/mmgen/*/*/*.py
|
||||
include test/overlay/fakemods/mmgen/*/*/*/*.py
|
||||
|
||||
include test/test-release.sh
|
||||
|
|
|
|||
|
|
@ -260,7 +260,7 @@ class Config(Lockable):
|
|||
xmrwallet_compat = False
|
||||
priority = 0
|
||||
|
||||
test_suite = False # 25 references
|
||||
test_suite = False # 22 references
|
||||
|
||||
# external use:
|
||||
_opts = None
|
||||
|
|
|
|||
|
|
@ -33,11 +33,7 @@ keygen_public_data = namedtuple(
|
|||
class keygen_base:
|
||||
|
||||
def __init__(self, cfg):
|
||||
if not (self.production_safe or cfg.test_suite):
|
||||
from .util import die
|
||||
die(2,
|
||||
f'Public key generator {type(self).__name__!r} is not safe from timing attacks '
|
||||
'and may only be used in a testing environment')
|
||||
pass
|
||||
|
||||
def gen_data(self, privkey):
|
||||
assert isinstance(privkey, PrivKey)
|
||||
|
|
@ -56,10 +52,10 @@ class keygen_base:
|
|||
|
||||
backend_data = {
|
||||
'std': {
|
||||
'backends': ('libsecp256k1', 'python-ecdsa'),
|
||||
'backends': ('libsecp256k1',),
|
||||
'package': 'secp256k1'},
|
||||
'monero': {
|
||||
'backends': ('nacl', 'ed25519ll-djbec', 'ed25519'),
|
||||
'backends': ('nacl',),
|
||||
'package': 'xmr'},
|
||||
'zcash_z': {
|
||||
'backends': ('nacl',),
|
||||
|
|
|
|||
|
|
@ -38,40 +38,9 @@ class backend:
|
|||
|
||||
@classmethod
|
||||
def get_clsname(cls, cfg, *, silent=False):
|
||||
try:
|
||||
from .secp256k1 import pubkey_gen
|
||||
if not pubkey_gen(bytes.fromhex('deadbeef'*8), 1):
|
||||
from ...util import die
|
||||
die('ExtensionModuleError',
|
||||
'Unable to execute pubkey_gen() from secp256k1 extension module')
|
||||
return cls.__name__
|
||||
except ImportError as e:
|
||||
if not silent:
|
||||
from ...util import ymsg
|
||||
ymsg(str(e))
|
||||
cfg._util.qmsg('Using (slow) native Python ECDSA library for public key generation')
|
||||
return 'python_ecdsa'
|
||||
|
||||
class python_ecdsa(keygen_base):
|
||||
|
||||
production_safe = False
|
||||
|
||||
def __init__(self, cfg):
|
||||
super().__init__(cfg)
|
||||
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):
|
||||
pk = self.ecdsa.SigningKey.from_secret_exponent(numpriv, curve=self.ecdsa.SECP256k1)
|
||||
# vk_bytes = x (32 bytes) + y (32 bytes) (unsigned big-endian)
|
||||
return pubkey_format(pk.verifying_key.to_string(), compressed)
|
||||
|
||||
return PubKey(
|
||||
s = privnum2pubkey(int.from_bytes(privkey, 'big'), compressed=privkey.compressed),
|
||||
compressed = privkey.compressed)
|
||||
from .secp256k1 import pubkey_gen
|
||||
if not pubkey_gen(bytes.fromhex('deadbeef'*8), 1):
|
||||
from ...util import die
|
||||
die('ExtensionModuleError',
|
||||
'Unable to execute pubkey_gen() from secp256k1 extension module')
|
||||
return cls.__name__
|
||||
|
|
|
|||
|
|
@ -34,8 +34,6 @@ class backend:
|
|||
|
||||
class nacl(base):
|
||||
|
||||
production_safe = True
|
||||
|
||||
def __init__(self, cfg):
|
||||
super().__init__(cfg)
|
||||
from nacl.bindings import crypto_scalarmult_ed25519_base_noclamp
|
||||
|
|
@ -45,50 +43,4 @@ class backend:
|
|||
return PubKey(
|
||||
self.scalarmultbase(privkey) +
|
||||
self.scalarmultbase(self.to_viewkey(privkey)),
|
||||
compressed = privkey.compressed
|
||||
)
|
||||
|
||||
class ed25519(base):
|
||||
|
||||
production_safe = False
|
||||
|
||||
def __init__(self, cfg):
|
||||
super().__init__(cfg)
|
||||
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, cfg):
|
||||
super().__init__(cfg)
|
||||
from ...contrib.ed25519ll_djbec import scalarmult
|
||||
self.scalarmult = scalarmult
|
||||
compressed = privkey.compressed)
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ class backend:
|
|||
|
||||
class nacl(keygen_base):
|
||||
|
||||
production_safe = True
|
||||
|
||||
def __init__(self, cfg):
|
||||
super().__init__(cfg)
|
||||
from nacl.bindings import crypto_scalarmult_base
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ test.include.ecc: elliptic curve utilities for the MMGen test suite
|
|||
"""
|
||||
|
||||
import ecdsa, hashlib
|
||||
from mmgen.proto.secp256k1.keygen import pubkey_format
|
||||
from mmgen.proto.secp256k1.keygen import overlay_fake_pubkey_format
|
||||
|
||||
def _pubkey_to_pub_point(vk_bytes):
|
||||
try:
|
||||
|
|
@ -38,7 +38,7 @@ def pubkey_tweak_add_pyecdsa(vk_bytes, pk_addend_bytes):
|
|||
ecdsa.SigningKey.from_secret_exponent(pk_addend, curve=ecdsa.SECP256k1).verifying_key.pubkey.point
|
||||
)
|
||||
_check_pub_point(point_sum, vk_bytes, pk_addend_bytes)
|
||||
return pubkey_format(
|
||||
return overlay_fake_pubkey_format(
|
||||
ecdsa.VerifyingKey.from_public_point(point_sum, curve=ecdsa.curves.SECP256k1).to_string(),
|
||||
compressed = len(vk_bytes) == 33)
|
||||
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ def test_tx(src, cfg, vec):
|
|||
vmsg('\n TX info:\n ' + '\n '.join(tx_info(tx, proto)) + '\n')
|
||||
|
||||
tx.verify_sig(cfg, proto, parms.account_number)
|
||||
tx.verify_sig(cfg, proto, parms.account_number, backend='ecdsa')
|
||||
|
||||
pubkey = tx.authInfo.signerInfos[0].publicKey.key.data
|
||||
vec_txid2 = getattr(vec, 'txid2', None)
|
||||
|
|
|
|||
4
test/overlay/fakemods/mmgen/keygen.py
Normal file
4
test/overlay/fakemods/mmgen/keygen.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
from .keygen_orig import *
|
||||
|
||||
backend_data['std']['backends'] = ('libsecp256k1', 'python-ecdsa')
|
||||
backend_data['monero']['backends'] = ('nacl', 'ed25519ll-djbec', 'ed25519')
|
||||
45
test/overlay/fakemods/mmgen/proto/secp256k1/keygen.py
Normal file
45
test/overlay/fakemods/mmgen/proto/secp256k1/keygen.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# MMGen Wallet, a terminal-based cryptocurrency wallet
|
||||
# Copyright (C)2013-2026 The MMGen Project <mmgen@tuta.io>
|
||||
# Licensed under the GNU General Public License, Version 3:
|
||||
# https://www.gnu.org/licenses
|
||||
# Public project repositories:
|
||||
# https://github.com/mmgen/mmgen-wallet
|
||||
# https://gitlab.com/mmgen/mmgen-wallet
|
||||
|
||||
"""
|
||||
test.overlay.fakemods.mmgen.proto.secp256k1.keygen:
|
||||
testing secp256k1 public key generation backends for the MMGen suite
|
||||
"""
|
||||
|
||||
from .keygen_orig import *
|
||||
|
||||
def overlay_fake_pubkey_format(vk_bytes, compressed):
|
||||
# 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')[vk_bytes[-1] & 1] + vk_bytes[:32] if compressed else b'\x04' + vk_bytes
|
||||
|
||||
class overlay_fake_backend:
|
||||
|
||||
class python_ecdsa(keygen_base):
|
||||
|
||||
def __init__(self, cfg):
|
||||
super().__init__(cfg)
|
||||
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):
|
||||
pk = self.ecdsa.SigningKey.from_secret_exponent(numpriv, curve=self.ecdsa.SECP256k1)
|
||||
# vk_bytes = x (32 bytes) + y (32 bytes) (unsigned big-endian)
|
||||
return overlay_fake_pubkey_format(pk.verifying_key.to_string(), compressed)
|
||||
|
||||
return PubKey(
|
||||
s = privnum2pubkey(int.from_bytes(privkey, 'big'), compressed=privkey.compressed),
|
||||
compressed = privkey.compressed)
|
||||
|
||||
backend.python_ecdsa = overlay_fake_backend.python_ecdsa
|
||||
61
test/overlay/fakemods/mmgen/proto/xmr/keygen.py
Normal file
61
test/overlay/fakemods/mmgen/proto/xmr/keygen.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# MMGen Wallet, a terminal-based cryptocurrency wallet
|
||||
# Copyright (C)2013-2026 The MMGen Project <mmgen@tuta.io>
|
||||
# Licensed under the GNU General Public License, Version 3:
|
||||
# https://www.gnu.org/licenses
|
||||
# Public project repositories:
|
||||
# https://github.com/mmgen/mmgen-wallet
|
||||
# https://gitlab.com/mmgen/mmgen-wallet
|
||||
|
||||
"""
|
||||
test.overlay.fakemods.mmgen.proto.xmr.keygen:
|
||||
testing Monero public key generation backends for the MMGen suite
|
||||
"""
|
||||
|
||||
from .keygen_orig import *
|
||||
|
||||
class overlay_fake_backend:
|
||||
|
||||
class ed25519(backend.base):
|
||||
|
||||
def __init__(self, cfg):
|
||||
super().__init__(cfg)
|
||||
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, cfg):
|
||||
super().__init__(cfg)
|
||||
from ...contrib.ed25519ll_djbec import scalarmult
|
||||
self.scalarmult = scalarmult
|
||||
|
||||
backend.ed25519 = overlay_fake_backend.ed25519
|
||||
backend.ed25519ll_djbec = overlay_fake_backend.ed25519ll_djbec
|
||||
Loading…
Add table
Add a link
Reference in a new issue