Browse Source

use libsecp256k1 if available. abstract away ecc stuff. move symmetric crypto and hash functions to crypto.py

3.2.x
SomberNight 7 years ago
parent
commit
16e4827e8c
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 6
      .travis.yml
  2. 7
      gui/qt/main_window.py
  3. 442
      lib/bitcoin.py
  4. 14
      lib/commands.py
  5. 142
      lib/crypto.py
  6. 413
      lib/ecc.py
  7. 221
      lib/ecc_fast.py
  8. 33
      lib/keystore.py
  9. 10
      lib/paymentrequest.py
  10. 23
      lib/storage.py
  11. 17
      lib/tests/__init__.py
  12. 135
      lib/tests/test_bitcoin.py
  13. 7
      lib/tests/test_dnssec.py
  14. 4
      lib/tests/test_interface.py
  15. 8
      lib/tests/test_mnemonic.py
  16. 6
      lib/tests/test_simple_config.py
  17. 2
      lib/tests/test_storage_upgrade.py
  18. 10
      lib/tests/test_transaction.py
  19. 5
      lib/tests/test_util.py
  20. 4
      lib/tests/test_wallet.py
  21. 19
      lib/tests/test_wallet_vertical.py
  22. 34
      lib/transaction.py
  23. 2
      lib/wallet.py
  24. 9
      plugins/cosigner_pool/qt.py
  25. 34
      plugins/digitalbitbox/digitalbitbox.py
  26. 4
      plugins/trustedcoin/trustedcoin.py
  27. 11
      setup.py
  28. 2
      tox.ini

6
.travis.yml

@ -3,6 +3,12 @@ language: python
python: python:
- 3.5 - 3.5
- 3.6 - 3.6
addons:
apt:
sources:
- sourceline: 'ppa:tah83/secp256k1'
packages:
- libsecp256k1-0
install: install:
- pip install -r contrib/requirements/requirements-travis.txt - pip install -r contrib/requirements/requirements-travis.txt
cache: cache:

7
gui/qt/main_window.py

@ -39,7 +39,7 @@ import PyQt5.QtCore as QtCore
from .exception_window import Exception_Hook from .exception_window import Exception_Hook
from PyQt5.QtWidgets import * from PyQt5.QtWidgets import *
from electrum import keystore, simple_config from electrum import keystore, simple_config, ecc
from electrum.bitcoin import COIN, is_address, TYPE_ADDRESS from electrum.bitcoin import COIN, is_address, TYPE_ADDRESS
from electrum import constants from electrum import constants
from electrum.plugins import run_hook from electrum.plugins import run_hook
@ -2177,7 +2177,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
try: try:
# This can throw on invalid base64 # This can throw on invalid base64
sig = base64.b64decode(str(signature.toPlainText())) sig = base64.b64decode(str(signature.toPlainText()))
verified = bitcoin.verify_message(address, sig, message) verified = ecc.verify_message_with_address(address, sig, message)
except Exception as e: except Exception as e:
verified = False verified = False
if verified: if verified:
@ -2243,7 +2243,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
message = message_e.toPlainText() message = message_e.toPlainText()
message = message.encode('utf-8') message = message.encode('utf-8')
try: try:
encrypted = bitcoin.encrypt_message(message, pubkey_e.text()) public_key = ecc.ECPubkey(bfh(pubkey_e.text()))
encrypted = public_key.encrypt_message(message)
encrypted_e.setText(encrypted.decode('ascii')) encrypted_e.setText(encrypted.decode('ascii'))
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stdout) traceback.print_exc(file=sys.stdout)

442
lib/bitcoin.py

@ -24,19 +24,14 @@
# SOFTWARE. # SOFTWARE.
import hashlib import hashlib
import base64
import hmac import hmac
import os
import json
import ecdsa from .util import bfh, bh2u, BitcoinException, print_error, assert_bytes, to_bytes, inv_dict
import pyaes
from .util import bfh, bh2u, to_string, BitcoinException
from . import version from . import version
from .util import print_error, InvalidPassword, assert_bytes, to_bytes, inv_dict
from . import segwit_addr from . import segwit_addr
from . import constants from . import constants
from . import ecc
from .crypto import Hash, sha256, hash_160
################################## transactions ################################## transactions
@ -49,94 +44,6 @@ TYPE_ADDRESS = 0
TYPE_PUBKEY = 1 TYPE_PUBKEY = 1
TYPE_SCRIPT = 2 TYPE_SCRIPT = 2
# AES encryption
try:
from Cryptodome.Cipher import AES
except:
AES = None
class InvalidPadding(Exception):
pass
def append_PKCS7_padding(data):
assert_bytes(data)
padlen = 16 - (len(data) % 16)
return data + bytes([padlen]) * padlen
def strip_PKCS7_padding(data):
assert_bytes(data)
if len(data) % 16 != 0 or len(data) == 0:
raise InvalidPadding("invalid length")
padlen = data[-1]
if padlen > 16:
raise InvalidPadding("invalid padding byte (large)")
for i in data[-padlen:]:
if i != padlen:
raise InvalidPadding("invalid padding byte (inconsistent)")
return data[0:-padlen]
def aes_encrypt_with_iv(key, iv, data):
assert_bytes(key, iv, data)
data = append_PKCS7_padding(data)
if AES:
e = AES.new(key, AES.MODE_CBC, iv).encrypt(data)
else:
aes_cbc = pyaes.AESModeOfOperationCBC(key, iv=iv)
aes = pyaes.Encrypter(aes_cbc, padding=pyaes.PADDING_NONE)
e = aes.feed(data) + aes.feed() # empty aes.feed() flushes buffer
return e
def aes_decrypt_with_iv(key, iv, data):
assert_bytes(key, iv, data)
if AES:
cipher = AES.new(key, AES.MODE_CBC, iv)
data = cipher.decrypt(data)
else:
aes_cbc = pyaes.AESModeOfOperationCBC(key, iv=iv)
aes = pyaes.Decrypter(aes_cbc, padding=pyaes.PADDING_NONE)
data = aes.feed(data) + aes.feed() # empty aes.feed() flushes buffer
try:
return strip_PKCS7_padding(data)
except InvalidPadding:
raise InvalidPassword()
def EncodeAES(secret, s):
assert_bytes(s)
iv = bytes(os.urandom(16))
ct = aes_encrypt_with_iv(secret, iv, s)
e = iv + ct
return base64.b64encode(e)
def DecodeAES(secret, e):
e = bytes(base64.b64decode(e))
iv, e = e[:16], e[16:]
s = aes_decrypt_with_iv(secret, iv, e)
return s
def pw_encode(s, password):
if password:
secret = Hash(password)
return EncodeAES(secret, to_bytes(s, "utf8")).decode('utf8')
else:
return s
def pw_decode(s, password):
if password is not None:
secret = Hash(password)
try:
d = to_string(DecodeAES(secret, s), "utf8")
except Exception:
raise InvalidPassword()
return d
else:
return s
def rev_hex(s): def rev_hex(s):
return bh2u(bfh(s)[::-1]) return bh2u(bfh(s)[::-1])
@ -233,17 +140,6 @@ def add_number_to_script(i: int) -> bytes:
return bfh(push_script(script_num_to_hex(i))) return bfh(push_script(script_num_to_hex(i)))
def sha256(x):
x = to_bytes(x, 'utf8')
return bytes(hashlib.sha256(x).digest())
def Hash(x):
x = to_bytes(x, 'utf8')
out = bytes(sha256(sha256(x)))
return out
hash_encode = lambda x: bh2u(x[::-1]) hash_encode = lambda x: bh2u(x[::-1])
hash_decode = lambda x: bfh(x)[::-1] hash_decode = lambda x: bfh(x)[::-1]
hmac_sha_512 = lambda x, y: hmac.new(x, y, hashlib.sha512).digest() hmac_sha_512 = lambda x, y: hmac.new(x, y, hashlib.sha512).digest()
@ -287,40 +183,10 @@ def seed_type(x):
is_seed = lambda x: bool(seed_type(x)) is_seed = lambda x: bool(seed_type(x))
# pywallet openssl private key implementation
def i2o_ECPublicKey(pubkey, compressed=False):
# public keys are 65 bytes long (520 bits)
# 0x04 + 32-byte X-coordinate + 32-byte Y-coordinate
# 0x00 = point at infinity, 0x02 and 0x03 = compressed, 0x04 = uncompressed
# compressed keys: <sign> <x> where <sign> is 0x02 if y is even and 0x03 if y is odd
if compressed:
if pubkey.point.y() & 1:
key = '03' + '%064x' % pubkey.point.x()
else:
key = '02' + '%064x' % pubkey.point.x()
else:
key = '04' + \
'%064x' % pubkey.point.x() + \
'%064x' % pubkey.point.y()
return bfh(key)
# end pywallet openssl private key implementation
############ functions from pywallet ##################### ############ functions from pywallet #####################
def hash_160(public_key):
try:
md = hashlib.new('ripemd160')
md.update(sha256(public_key))
return md.digest()
except BaseException:
from . import ripemd
md = ripemd.new(sha256(public_key))
return md.digest()
def hash160_to_b58_address(h160: bytes, addrtype):
def hash160_to_b58_address(h160, addrtype):
s = bytes([addrtype]) s = bytes([addrtype])
s += h160 s += h160
return base_encode(s+Hash(s)[0:4], base=58) return base_encode(s+Hash(s)[0:4], base=58)
@ -342,7 +208,7 @@ def hash160_to_p2sh(h160, *, net=None):
net = constants.net net = constants.net
return hash160_to_b58_address(h160, net.ADDRTYPE_P2SH) return hash160_to_b58_address(h160, net.ADDRTYPE_P2SH)
def public_key_to_p2pkh(public_key): def public_key_to_p2pkh(public_key: bytes) -> str:
return hash160_to_p2pkh(hash_160(public_key)) return hash160_to_p2pkh(hash_160(public_key))
def hash_to_segwit_addr(h, witver, *, net=None): def hash_to_segwit_addr(h, witver, *, net=None):
@ -437,7 +303,7 @@ __b43chars = b'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$*+-./:'
assert len(__b43chars) == 43 assert len(__b43chars) == 43
def base_encode(v, base): def base_encode(v: bytes, base: int) -> str:
""" encode v, which is a string of bytes, to base58.""" """ encode v, which is a string of bytes, to base58."""
assert_bytes(v) assert_bytes(v)
if base not in (58, 43): if base not in (58, 43):
@ -535,7 +401,10 @@ SCRIPT_TYPES = {
} }
def serialize_privkey(secret, compressed, txin_type, internal_use=False): def serialize_privkey(secret: bytes, compressed: bool, txin_type: str,
internal_use: bool=False) -> str:
# we only export secrets inside curve range
secret = ecc.ECPrivkey.normalize_secret_bytes(secret)
if internal_use: if internal_use:
prefix = bytes([(SCRIPT_TYPES[txin_type] + constants.net.WIF_PREFIX) & 255]) prefix = bytes([(SCRIPT_TYPES[txin_type] + constants.net.WIF_PREFIX) & 255])
else: else:
@ -549,7 +418,7 @@ def serialize_privkey(secret, compressed, txin_type, internal_use=False):
return '{}:{}'.format(txin_type, base58_wif) return '{}:{}'.format(txin_type, base58_wif)
def deserialize_privkey(key): def deserialize_privkey(key: str) -> (str, bytes, bool):
if is_minikey(key): if is_minikey(key):
return 'p2pkh', minikey_to_private_key(key), True return 'p2pkh', minikey_to_private_key(key), True
@ -581,34 +450,19 @@ def deserialize_privkey(key):
if len(vch) not in [33, 34]: if len(vch) not in [33, 34]:
raise BitcoinException('invalid vch len for WIF key: {}'.format(len(vch))) raise BitcoinException('invalid vch len for WIF key: {}'.format(len(vch)))
compressed = len(vch) == 34 compressed = len(vch) == 34
return txin_type, vch[1:33], compressed secret_bytes = vch[1:33]
# we accept secrets outside curve range; cast into range here:
secret_bytes = ecc.ECPrivkey.normalize_secret_bytes(secret_bytes)
def regenerate_key(pk): return txin_type, secret_bytes, compressed
assert len(pk) == 32
return EC_KEY(pk)
def GetPubKey(pubkey, compressed=False):
return i2o_ECPublicKey(pubkey, compressed)
def GetSecret(pkey):
return bfh('%064x' % pkey.secret)
def is_compressed(sec): def is_compressed(sec):
return deserialize_privkey(sec)[2] return deserialize_privkey(sec)[2]
def public_key_from_private_key(pk, compressed):
pkey = regenerate_key(pk)
public_key = GetPubKey(pkey.pubkey, compressed)
return bh2u(public_key)
def address_from_private_key(sec): def address_from_private_key(sec):
txin_type, privkey, compressed = deserialize_privkey(sec) txin_type, privkey, compressed = deserialize_privkey(sec)
public_key = public_key_from_private_key(privkey, compressed) public_key = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
return pubkey_to_address(txin_type, public_key) return pubkey_to_address(txin_type, public_key)
def is_segwit_address(addr): def is_segwit_address(addr):
@ -654,242 +508,12 @@ def is_minikey(text):
def minikey_to_private_key(text): def minikey_to_private_key(text):
return sha256(text) return sha256(text)
from ecdsa.ecdsa import curve_secp256k1, generator_secp256k1
from ecdsa.curves import SECP256k1
from ecdsa.ellipticcurve import Point
from ecdsa.util import string_to_number, number_to_string
def msg_magic(message):
length = bfh(var_int(len(message)))
return b"\x18Bitcoin Signed Message:\n" + length + message
def verify_message(address, sig, message):
assert_bytes(sig, message)
try:
h = Hash(msg_magic(message))
public_key, compressed = pubkey_from_signature(sig, h)
# check public key using the address
pubkey = point_to_ser(public_key.pubkey.point, compressed)
for txin_type in ['p2pkh','p2wpkh','p2wpkh-p2sh']:
addr = pubkey_to_address(txin_type, bh2u(pubkey))
if address == addr:
break
else:
raise Exception("Bad signature")
# check message
public_key.verify_digest(sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)
return True
except Exception as e:
print_error("Verification error: {0}".format(e))
return False
def encrypt_message(message, pubkey, magic=b'BIE1'):
return EC_KEY.encrypt_message(message, bfh(pubkey), magic)
def chunks(l, n):
return [l[i:i+n] for i in range(0, len(l), n)]
def ECC_YfromX(x,curved=curve_secp256k1, odd=True):
_p = curved.p()
_a = curved.a()
_b = curved.b()
for offset in range(128):
Mx = x + offset
My2 = pow(Mx, 3, _p) + _a * pow(Mx, 2, _p) + _b % _p
My = pow(My2, (_p+1)//4, _p )
if curved.contains_point(Mx,My):
if odd == bool(My&1):
return [My,offset]
return [_p-My,offset]
raise Exception('ECC_YfromX: No Y found')
def negative_point(P):
return Point( P.curve(), P.x(), -P.y(), P.order() )
def point_to_ser(P, comp=True ):
if comp:
return bfh( ('%02x'%(2+(P.y()&1)))+('%064x'%P.x()) )
return bfh( '04'+('%064x'%P.x())+('%064x'%P.y()) )
def ser_to_point(Aser):
curve = curve_secp256k1
generator = generator_secp256k1
_r = generator.order()
assert Aser[0] in [0x02, 0x03, 0x04]
if Aser[0] == 0x04:
return Point( curve, string_to_number(Aser[1:33]), string_to_number(Aser[33:]), _r )
Mx = string_to_number(Aser[1:])
return Point( curve, Mx, ECC_YfromX(Mx, curve, Aser[0] == 0x03)[0], _r )
class MyVerifyingKey(ecdsa.VerifyingKey):
@classmethod
def from_signature(klass, sig, recid, h, curve):
""" See http://www.secg.org/download/aid-780/sec1-v2.pdf, chapter 4.1.6 """
from ecdsa import util, numbertheory
from . import msqr
curveFp = curve.curve
G = curve.generator
order = G.order()
# extract r,s from signature
r, s = util.sigdecode_string(sig, order)
# 1.1
x = r + (recid//2) * order
# 1.3
alpha = ( x * x * x + curveFp.a() * x + curveFp.b() ) % curveFp.p()
beta = msqr.modular_sqrt(alpha, curveFp.p())
y = beta if (beta - recid) % 2 == 0 else curveFp.p() - beta
# 1.4 the constructor checks that nR is at infinity
R = Point(curveFp, x, y, order)
# 1.5 compute e from message:
e = string_to_number(h)
minus_e = -e % order
# 1.6 compute Q = r^-1 (sR - eG)
inv_r = numbertheory.inverse_mod(r,order)
Q = inv_r * ( s * R + minus_e * G )
return klass.from_public_point( Q, curve )
def pubkey_from_signature(sig, h):
if len(sig) != 65:
raise Exception("Wrong encoding")
nV = sig[0]
if nV < 27 or nV >= 35:
raise Exception("Bad encoding")
if nV >= 31:
compressed = True
nV -= 4
else:
compressed = False
recid = nV - 27
return MyVerifyingKey.from_signature(sig[1:], recid, h, curve = SECP256k1), compressed
class MySigningKey(ecdsa.SigningKey):
"""Enforce low S values in signatures"""
def sign_number(self, number, entropy=None, k=None):
curve = SECP256k1
G = curve.generator
order = G.order()
r, s = ecdsa.SigningKey.sign_number(self, number, entropy, k)
if s > order//2:
s = order - s
return r, s
class EC_KEY(object):
def __init__( self, k ):
secret = string_to_number(k)
self.pubkey = ecdsa.ecdsa.Public_key( generator_secp256k1, generator_secp256k1 * secret )
self.privkey = ecdsa.ecdsa.Private_key( self.pubkey, secret )
self.secret = secret
def get_public_key(self, compressed=True):
return bh2u(point_to_ser(self.pubkey.point, compressed))
def sign(self, msg_hash):
private_key = MySigningKey.from_secret_exponent(self.secret, curve = SECP256k1)
public_key = private_key.get_verifying_key()
signature = private_key.sign_digest_deterministic(msg_hash, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_string)
assert public_key.verify_digest(signature, msg_hash, sigdecode = ecdsa.util.sigdecode_string)
return signature
def sign_message(self, message, is_compressed):
message = to_bytes(message, 'utf8')
signature = self.sign(Hash(msg_magic(message)))
for i in range(4):
sig = bytes([27 + i + (4 if is_compressed else 0)]) + signature
try:
self.verify_message(sig, message)
return sig
except Exception as e:
continue
else:
raise Exception("error: cannot sign message")
def verify_message(self, sig, message):
assert_bytes(message)
h = Hash(msg_magic(message))
public_key, compressed = pubkey_from_signature(sig, h)
# check public key
if point_to_ser(public_key.pubkey.point, compressed) != point_to_ser(self.pubkey.point, compressed):
raise Exception("Bad signature")
# check message
public_key.verify_digest(sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)
# ECIES encryption/decryption methods; AES-128-CBC with PKCS7 is used as the cipher; hmac-sha256 is used as the mac
@classmethod
def encrypt_message(self, message, pubkey, magic=b'BIE1'):
assert_bytes(message)
pk = ser_to_point(pubkey)
if not ecdsa.ecdsa.point_is_valid(generator_secp256k1, pk.x(), pk.y()):
raise Exception('invalid pubkey')
ephemeral_exponent = number_to_string(ecdsa.util.randrange(pow(2,256)), generator_secp256k1.order())
ephemeral = EC_KEY(ephemeral_exponent)
ecdh_key = point_to_ser(pk * ephemeral.privkey.secret_multiplier)
key = hashlib.sha512(ecdh_key).digest()
iv, key_e, key_m = key[0:16], key[16:32], key[32:]
ciphertext = aes_encrypt_with_iv(key_e, iv, message)
ephemeral_pubkey = bfh(ephemeral.get_public_key(compressed=True))
encrypted = magic + ephemeral_pubkey + ciphertext
mac = hmac.new(key_m, encrypted, hashlib.sha256).digest()
return base64.b64encode(encrypted + mac)
def decrypt_message(self, encrypted, magic=b'BIE1'):
encrypted = base64.b64decode(encrypted)
if len(encrypted) < 85:
raise Exception('invalid ciphertext: length')
magic_found = encrypted[:4]
ephemeral_pubkey = encrypted[4:37]
ciphertext = encrypted[37:-32]
mac = encrypted[-32:]
if magic_found != magic:
raise Exception('invalid ciphertext: invalid magic bytes')
try:
ephemeral_pubkey = ser_to_point(ephemeral_pubkey)
except AssertionError as e:
raise Exception('invalid ciphertext: invalid ephemeral pubkey')
if not ecdsa.ecdsa.point_is_valid(generator_secp256k1, ephemeral_pubkey.x(), ephemeral_pubkey.y()):
raise Exception('invalid ciphertext: invalid ephemeral pubkey')
ecdh_key = point_to_ser(ephemeral_pubkey * self.privkey.secret_multiplier)
key = hashlib.sha512(ecdh_key).digest()
iv, key_e, key_m = key[0:16], key[16:32], key[32:]
if mac != hmac.new(key_m, encrypted[:-32], hashlib.sha256).digest():
raise InvalidPassword()
return aes_decrypt_with_iv(key_e, iv, ciphertext)
###################################### BIP32 ############################## ###################################### BIP32 ##############################
random_seed = lambda n: "%032x"%ecdsa.util.randrange( pow(2,n) )
BIP32_PRIME = 0x80000000 BIP32_PRIME = 0x80000000
def get_pubkeys_from_secret(secret):
# public key
private_key = ecdsa.SigningKey.from_string( secret, curve = SECP256k1 )
public_key = private_key.get_verifying_key()
K = public_key.to_string()
K_compressed = GetPubKey(public_key.pubkey,True)
return K, K_compressed
# Child private key derivation function (from master private key) # Child private key derivation function (from master private key)
# k = master private key (32 bytes) # k = master private key (32 bytes)
# c = master chain code (extra entropy for key derivation) (32 bytes) # c = master chain code (extra entropy for key derivation) (32 bytes)
@ -904,12 +528,13 @@ def CKD_priv(k, c, n):
def _CKD_priv(k, c, s, is_prime): def _CKD_priv(k, c, s, is_prime):
order = generator_secp256k1.order() keypair = ecc.ECPrivkey(k)
keypair = EC_KEY(k) cK = keypair.get_public_key_bytes(compressed=True)
cK = GetPubKey(keypair.pubkey,True)
data = bytes([0]) + k + s if is_prime else cK + s data = bytes([0]) + k + s if is_prime else cK + s
I = hmac.new(c, data, hashlib.sha512).digest() I = hmac.new(c, data, hashlib.sha512).digest()
k_n = number_to_string( (string_to_number(I[0:32]) + string_to_number(k)) % order , order ) k_n = ecc.number_to_string(
(ecc.string_to_number(I[0:32]) + ecc.string_to_number(k)) % ecc.CURVE_ORDER,
ecc.CURVE_ORDER)
c_n = I[32:] c_n = I[32:]
return k_n, c_n return k_n, c_n
@ -920,18 +545,15 @@ def _CKD_priv(k, c, s, is_prime):
# This function allows us to find the nth public key, as long as n is # This function allows us to find the nth public key, as long as n is
# non-negative. If n is negative, we need the master private key to find it. # non-negative. If n is negative, we need the master private key to find it.
def CKD_pub(cK, c, n): def CKD_pub(cK, c, n):
if n & BIP32_PRIME: raise if n & BIP32_PRIME: raise Exception()
return _CKD_pub(cK, c, bfh(rev_hex(int_to_hex(n,4)))) return _CKD_pub(cK, c, bfh(rev_hex(int_to_hex(n,4))))
# helper function, callable with arbitrary string # helper function, callable with arbitrary string
def _CKD_pub(cK, c, s): def _CKD_pub(cK, c, s):
order = generator_secp256k1.order()
I = hmac.new(c, cK + s, hashlib.sha512).digest() I = hmac.new(c, cK + s, hashlib.sha512).digest()
curve = SECP256k1 pubkey = ecc.ECPrivkey(I[0:32]) + ecc.ECPubkey(cK)
pubkey_point = string_to_number(I[0:32])*curve.generator + ser_to_point(cK) cK_n = pubkey.get_public_key_bytes(compressed=True)
public_key = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 )
c_n = I[32:] c_n = I[32:]
cK_n = GetPubKey(public_key.pubkey,True)
return cK_n, c_n return cK_n, c_n
@ -949,7 +571,7 @@ def xpub_header(xtype, *, net=None):
def serialize_xprv(xtype, c, k, depth=0, fingerprint=b'\x00'*4, def serialize_xprv(xtype, c, k, depth=0, fingerprint=b'\x00'*4,
child_number=b'\x00'*4, *, net=None): child_number=b'\x00'*4, *, net=None):
if not (0 < string_to_number(k) < SECP256k1.order): if not ecc.is_secret_within_curve_range(k):
raise BitcoinException('Impossible xprv (not within curve order)') raise BitcoinException('Impossible xprv (not within curve order)')
xprv = xprv_header(xtype, net=net) \ xprv = xprv_header(xtype, net=net) \
+ bytes([depth]) + fingerprint + child_number + c + bytes([0]) + k + bytes([depth]) + fingerprint + child_number + c + bytes([0]) + k
@ -982,7 +604,7 @@ def deserialize_xkey(xkey, prv, *, net=None):
xtype = list(headers.keys())[list(headers.values()).index(header)] xtype = list(headers.keys())[list(headers.values()).index(header)]
n = 33 if prv else 32 n = 33 if prv else 32
K_or_k = xkey[13+n:] K_or_k = xkey[13+n:]
if prv and not (0 < string_to_number(K_or_k) < SECP256k1.order): if prv and not ecc.is_secret_within_curve_range(K_or_k):
raise BitcoinException('Impossible xprv (not within curve order)') raise BitcoinException('Impossible xprv (not within curve order)')
return xtype, depth, fingerprint, child_number, c, K_or_k return xtype, depth, fingerprint, child_number, c, K_or_k
@ -1015,7 +637,7 @@ def is_xprv(text):
def xpub_from_xprv(xprv): def xpub_from_xprv(xprv):
xtype, depth, fingerprint, child_number, c, k = deserialize_xprv(xprv) xtype, depth, fingerprint, child_number, c, k = deserialize_xprv(xprv)
K, cK = get_pubkeys_from_secret(k) cK = ecc.ECPrivkey(k).get_public_key_bytes(compressed=True)
return serialize_xpub(xtype, c, cK, depth, fingerprint, child_number) return serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
@ -1023,14 +645,16 @@ def bip32_root(seed, xtype):
I = hmac.new(b"Bitcoin seed", seed, hashlib.sha512).digest() I = hmac.new(b"Bitcoin seed", seed, hashlib.sha512).digest()
master_k = I[0:32] master_k = I[0:32]
master_c = I[32:] master_c = I[32:]
K, cK = get_pubkeys_from_secret(master_k) # create xprv first, as that will check if master_k is within curve order
xprv = serialize_xprv(xtype, master_c, master_k) xprv = serialize_xprv(xtype, master_c, master_k)
cK = ecc.ECPrivkey(master_k).get_public_key_bytes(compressed=True)
xpub = serialize_xpub(xtype, master_c, cK) xpub = serialize_xpub(xtype, master_c, cK)
return xprv, xpub return xprv, xpub
def xpub_from_pubkey(xtype, cK): def xpub_from_pubkey(xtype, cK):
assert cK[0] in [0x02, 0x03] if cK[0] not in (0x02, 0x03):
raise ValueError('Unexpected first byte: {}'.format(cK[0]))
return serialize_xpub(xtype, b'\x00'*32, cK) return serialize_xpub(xtype, b'\x00'*32, cK)
@ -1064,10 +688,10 @@ def bip32_private_derivation(xprv, branch, sequence):
parent_k = k parent_k = k
k, c = CKD_priv(k, c, i) k, c = CKD_priv(k, c, i)
depth += 1 depth += 1
_, parent_cK = get_pubkeys_from_secret(parent_k) parent_cK = ecc.ECPrivkey(parent_k).get_public_key_bytes(compressed=True)
fingerprint = hash_160(parent_cK)[0:4] fingerprint = hash_160(parent_cK)[0:4]
child_number = bfh("%08X"%i) child_number = bfh("%08X"%i)
K, cK = get_pubkeys_from_secret(k) cK = ecc.ECPrivkey(k).get_public_key_bytes(compressed=True)
xpub = serialize_xpub(xtype, c, cK, depth, fingerprint, child_number) xpub = serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
xprv = serialize_xprv(xtype, c, k, depth, fingerprint, child_number) xprv = serialize_xprv(xtype, c, k, depth, fingerprint, child_number)
return xprv, xpub return xprv, xpub

14
lib/commands.py

@ -33,7 +33,7 @@ import base64
from functools import wraps from functools import wraps
from decimal import Decimal from decimal import Decimal
from .import util from .import util, ecc
from .util import bfh, bh2u, format_satoshis, json_decode, print_error, json_encode from .util import bfh, bh2u, format_satoshis, json_decode, print_error, json_encode
from .import bitcoin from .import bitcoin
from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS
@ -219,7 +219,7 @@ class Commands:
sec = txin.get('privkey') sec = txin.get('privkey')
if sec: if sec:
txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec) txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec)
pubkey = bitcoin.public_key_from_private_key(privkey, compressed) pubkey = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
keypairs[pubkey] = privkey, compressed keypairs[pubkey] = privkey, compressed
txin['type'] = txin_type txin['type'] = txin_type
txin['x_pubkeys'] = [pubkey] txin['x_pubkeys'] = [pubkey]
@ -237,8 +237,8 @@ class Commands:
tx = Transaction(tx) tx = Transaction(tx)
if privkey: if privkey:
txin_type, privkey2, compressed = bitcoin.deserialize_privkey(privkey) txin_type, privkey2, compressed = bitcoin.deserialize_privkey(privkey)
pubkey = bitcoin.public_key_from_private_key(privkey2, compressed) pubkey_bytes = ecc.ECPrivkey(privkey2).get_public_key_bytes(compressed=compressed)
h160 = bitcoin.hash_160(bfh(pubkey)) h160 = bitcoin.hash_160(pubkey_bytes)
x_pubkey = 'fd' + bh2u(b'\x00' + h160) x_pubkey = 'fd' + bh2u(b'\x00' + h160)
tx.sign({x_pubkey:(privkey2, compressed)}) tx.sign({x_pubkey:(privkey2, compressed)})
else: else:
@ -405,7 +405,7 @@ class Commands:
"""Verify a signature.""" """Verify a signature."""
sig = base64.b64decode(signature) sig = base64.b64decode(signature)
message = util.to_bytes(message) message = util.to_bytes(message)
return bitcoin.verify_message(address, sig, message) return ecc.verify_message_with_address(address, sig, message)
def _mktx(self, outputs, fee, change_addr, domain, nocheck, unsigned, rbf, password, locktime=None): def _mktx(self, outputs, fee, change_addr, domain, nocheck, unsigned, rbf, password, locktime=None):
self.nocheck = nocheck self.nocheck = nocheck
@ -527,7 +527,9 @@ class Commands:
@command('') @command('')
def encrypt(self, pubkey, message): def encrypt(self, pubkey, message):
"""Encrypt a message with a public key. Use quotes if the message contains whitespaces.""" """Encrypt a message with a public key. Use quotes if the message contains whitespaces."""
return bitcoin.encrypt_message(message, pubkey) public_key = ecc.ECPubkey(bfh(pubkey))
encrypted = public_key.encrypt_message(message)
return encrypted
@command('wp') @command('wp')
def decrypt(self, pubkey, encrypted, password=None): def decrypt(self, pubkey, encrypted, password=None):

142
lib/crypto.py

@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2018 The Electrum developers
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import base64
import os
import hashlib
import pyaes
from .util import assert_bytes, InvalidPassword, to_bytes, to_string
try:
from Cryptodome.Cipher import AES
except:
AES = None
class InvalidPadding(Exception):
pass
def append_PKCS7_padding(data):
assert_bytes(data)
padlen = 16 - (len(data) % 16)
return data + bytes([padlen]) * padlen
def strip_PKCS7_padding(data):
assert_bytes(data)
if len(data) % 16 != 0 or len(data) == 0:
raise InvalidPadding("invalid length")
padlen = data[-1]
if padlen > 16:
raise InvalidPadding("invalid padding byte (large)")
for i in data[-padlen:]:
if i != padlen:
raise InvalidPadding("invalid padding byte (inconsistent)")
return data[0:-padlen]
def aes_encrypt_with_iv(key, iv, data):
assert_bytes(key, iv, data)
data = append_PKCS7_padding(data)
if AES:
e = AES.new(key, AES.MODE_CBC, iv).encrypt(data)
else:
aes_cbc = pyaes.AESModeOfOperationCBC(key, iv=iv)
aes = pyaes.Encrypter(aes_cbc, padding=pyaes.PADDING_NONE)
e = aes.feed(data) + aes.feed() # empty aes.feed() flushes buffer
return e
def aes_decrypt_with_iv(key, iv, data):
assert_bytes(key, iv, data)
if AES:
cipher = AES.new(key, AES.MODE_CBC, iv)
data = cipher.decrypt(data)
else:
aes_cbc = pyaes.AESModeOfOperationCBC(key, iv=iv)
aes = pyaes.Decrypter(aes_cbc, padding=pyaes.PADDING_NONE)
data = aes.feed(data) + aes.feed() # empty aes.feed() flushes buffer
try:
return strip_PKCS7_padding(data)
except InvalidPadding:
raise InvalidPassword()
def EncodeAES(secret, s):
assert_bytes(s)
iv = bytes(os.urandom(16))
ct = aes_encrypt_with_iv(secret, iv, s)
e = iv + ct
return base64.b64encode(e)
def DecodeAES(secret, e):
e = bytes(base64.b64decode(e))
iv, e = e[:16], e[16:]
s = aes_decrypt_with_iv(secret, iv, e)
return s
def pw_encode(s, password):
if password:
secret = Hash(password)
return EncodeAES(secret, to_bytes(s, "utf8")).decode('utf8')
else:
return s
def pw_decode(s, password):
if password is not None:
secret = Hash(password)
try:
d = to_string(DecodeAES(secret, s), "utf8")
except Exception:
raise InvalidPassword()
return d
else:
return s
def sha256(x: bytes) -> bytes:
x = to_bytes(x, 'utf8')
return bytes(hashlib.sha256(x).digest())
def Hash(x: bytes) -> bytes:
x = to_bytes(x, 'utf8')
out = bytes(sha256(sha256(x)))
return out
def hash_160(x: bytes) -> bytes:
try:
md = hashlib.new('ripemd160')
md.update(sha256(x))
return md.digest()
except BaseException:
from . import ripemd
md = ripemd.new(sha256(x))
return md.digest()

413
lib/ecc.py

@ -0,0 +1,413 @@
# -*- coding: utf-8 -*-
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2018 The Electrum developers
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import base64
import hmac
import hashlib
from typing import Union
import ecdsa
from ecdsa.ecdsa import curve_secp256k1, generator_secp256k1
from ecdsa.curves import SECP256k1
from ecdsa.ellipticcurve import Point
from ecdsa.util import string_to_number, number_to_string
from .util import bfh, bh2u, assert_bytes, print_error, to_bytes, InvalidPassword, profiler
from .crypto import (Hash, aes_encrypt_with_iv, aes_decrypt_with_iv)
from .ecc_fast import do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1
do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1()
CURVE_ORDER = SECP256k1.order
def generator():
return ECPubkey(bfh('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'))
def sig_string_from_der_sig(der_sig):
r, s = ecdsa.util.sigdecode_der(der_sig, CURVE_ORDER)
return ecdsa.util.sigencode_string(r, s, CURVE_ORDER)
def der_sig_from_sig_string(sig_string):
r, s = ecdsa.util.sigdecode_string(sig_string, CURVE_ORDER)
return ecdsa.util.sigencode_der_canonize(r, s, CURVE_ORDER)
def der_sig_from_r_and_s(r, s):
return ecdsa.util.sigencode_der_canonize(r, s, CURVE_ORDER)
def get_r_and_s_from_sig_string(sig_string):
r, s = ecdsa.util.sigdecode_string(sig_string, CURVE_ORDER)
return r, s
def sig_string_from_r_and_s(r, s):
return ecdsa.util.sigencode_string_canonize(r, s, CURVE_ORDER)
def point_to_ser(P, compressed=True) -> bytes:
if isinstance(P, tuple):
assert len(P) == 2, 'unexpected point: %s' % P
x, y = P
else:
x, y = P.x(), P.y()
if compressed:
return bfh(('%02x' % (2+(y&1))) + ('%064x' % x))
return bfh('04'+('%064x' % x)+('%064x' % y))
def get_y_coord_from_x(x, odd=True):
curve = curve_secp256k1
_p = curve.p()
_a = curve.a()
_b = curve.b()
for offset in range(128):
Mx = x + offset
My2 = pow(Mx, 3, _p) + _a * pow(Mx, 2, _p) + _b % _p
My = pow(My2, (_p + 1) // 4, _p)
if curve.contains_point(Mx, My):
if odd == bool(My & 1):
return My
return _p - My
raise Exception('ECC_YfromX: No Y found')
def ser_to_point(ser: bytes) -> (int, int):
if ser[0] not in (0x02, 0x03, 0x04):
raise ValueError('Unexpected first byte: {}'.format(ser[0]))
if ser[0] == 0x04:
return string_to_number(ser[1:33]), string_to_number(ser[33:])
x = string_to_number(ser[1:])
return x, get_y_coord_from_x(x, ser[0] == 0x03)
def _ser_to_python_ecdsa_point(ser: bytes) -> ecdsa.ellipticcurve.Point:
if ser[0] not in (0x02, 0x03, 0x04):
raise ValueError('Unexpected first byte: {}'.format(ser[0]))
x = string_to_number(ser[1:33])
if ser[0] == 0x04:
y = string_to_number(ser[33:])
else:
y = get_y_coord_from_x(x, ser[0] == 0x03)
return Point(curve_secp256k1, x, y, CURVE_ORDER)
class InvalidECPointException(Exception):
"""e.g. not on curve, or infinity"""
class _MyVerifyingKey(ecdsa.VerifyingKey):
@classmethod
def from_signature(klass, sig, recid, h, curve): # TODO use libsecp??
""" See http://www.secg.org/download/aid-780/sec1-v2.pdf, chapter 4.1.6 """
from ecdsa import util, numbertheory
from . import msqr
curveFp = curve.curve
G = curve.generator
order = G.order()
# extract r,s from signature
r, s = util.sigdecode_string(sig, order)
# 1.1
x = r + (recid//2) * order
# 1.3
alpha = ( x * x * x + curveFp.a() * x + curveFp.b() ) % curveFp.p()
beta = msqr.modular_sqrt(alpha, curveFp.p())
y = beta if (beta - recid) % 2 == 0 else curveFp.p() - beta
# 1.4 the constructor checks that nR is at infinity
try:
R = Point(curveFp, x, y, order)
except:
raise InvalidECPointException()
# 1.5 compute e from message:
e = string_to_number(h)
minus_e = -e % order
# 1.6 compute Q = r^-1 (sR - eG)
inv_r = numbertheory.inverse_mod(r,order)
Q = inv_r * ( s * R + minus_e * G )
return klass.from_public_point( Q, curve )
class _MySigningKey(ecdsa.SigningKey):
"""Enforce low S values in signatures"""
def sign_number(self, number, entropy=None, k=None):
r, s = ecdsa.SigningKey.sign_number(self, number, entropy, k)
if s > CURVE_ORDER//2:
s = CURVE_ORDER - s
return r, s
class ECPubkey(object):
def __init__(self, b: bytes):
assert_bytes(b)
point = _ser_to_python_ecdsa_point(b)
self._pubkey = ecdsa.ecdsa.Public_key(generator_secp256k1, point)
@classmethod
def from_sig_string(cls, sig_string: bytes, recid: int, msg_hash: bytes):
assert_bytes(sig_string)
if len(sig_string) != 64:
raise Exception('Wrong encoding')
if recid < 0 or recid > 3:
raise ValueError('recid is {}, but should be 0 <= recid <= 3'.format(recid))
ecdsa_verifying_key = _MyVerifyingKey.from_signature(sig_string, recid, msg_hash, curve=SECP256k1)
ecdsa_point = ecdsa_verifying_key.pubkey.point
return ECPubkey(point_to_ser(ecdsa_point))
@classmethod
def from_signature65(cls, sig: bytes, msg_hash: bytes):
if len(sig) != 65:
raise Exception("Wrong encoding")
nV = sig[0]
if nV < 27 or nV >= 35:
raise Exception("Bad encoding")
if nV >= 31:
compressed = True
nV -= 4
else:
compressed = False
recid = nV - 27
return cls.from_sig_string(sig[1:], recid, msg_hash), compressed
@classmethod
def from_point(cls, point):
_bytes = point_to_ser(point, compressed=False) # faster than compressed
return ECPubkey(_bytes)
def get_public_key_bytes(self, compressed=True):
return point_to_ser(self.point(), compressed)
def get_public_key_hex(self, compressed=True):
return bh2u(self.get_public_key_bytes(compressed))
def point(self) -> (int, int):
return self._pubkey.point.x(), self._pubkey.point.y()
def __mul__(self, other: int):
if not isinstance(other, int):
raise TypeError('multiplication not defined for ECPubkey and {}'.format(type(other)))
ecdsa_point = self._pubkey.point * other
return self.from_point(ecdsa_point)
def __rmul__(self, other: int):
return self * other
def __add__(self, other):
if not isinstance(other, ECPubkey):
raise TypeError('addition not defined for ECPubkey and {}'.format(type(other)))
ecdsa_point = self._pubkey.point + other._pubkey.point
return self.from_point(ecdsa_point)
def __eq__(self, other):
return self.get_public_key_bytes() == other.get_public_key_bytes()
def __ne__(self, other):
return not (self == other)
def verify_message_for_address(self, sig65: bytes, message: bytes) -> None:
assert_bytes(message)
h = Hash(msg_magic(message))
public_key, compressed = self.from_signature65(sig65, h)
# check public key
if public_key != self:
raise Exception("Bad signature")
# check message
self.verify_message_hash(sig65[1:], h)
def verify_message_hash(self, sig_string: bytes, msg_hash: bytes) -> None:
assert_bytes(sig_string)
if len(sig_string) != 64:
raise Exception('Wrong encoding')
ecdsa_point = self._pubkey.point
verifying_key = _MyVerifyingKey.from_public_point(ecdsa_point, curve=SECP256k1)
verifying_key.verify_digest(sig_string, msg_hash, sigdecode=ecdsa.util.sigdecode_string)
def encrypt_message(self, message: bytes, magic: bytes = b'BIE1'):
"""
ECIES encryption/decryption methods; AES-128-CBC with PKCS7 is used as the cipher; hmac-sha256 is used as the mac
"""
assert_bytes(message)
randint = ecdsa.util.randrange(CURVE_ORDER)
ephemeral_exponent = number_to_string(randint, CURVE_ORDER)
ephemeral = ECPrivkey(ephemeral_exponent)
ecdh_key = (self * ephemeral.secret_scalar).get_public_key_bytes(compressed=True)
key = hashlib.sha512(ecdh_key).digest()
iv, key_e, key_m = key[0:16], key[16:32], key[32:]
ciphertext = aes_encrypt_with_iv(key_e, iv, message)
ephemeral_pubkey = ephemeral.get_public_key_bytes(compressed=True)
encrypted = magic + ephemeral_pubkey + ciphertext
mac = hmac.new(key_m, encrypted, hashlib.sha256).digest()
return base64.b64encode(encrypted + mac)
@classmethod
def order(cls):
return CURVE_ORDER
def msg_magic(message: bytes) -> bytes:
from .bitcoin import var_int
length = bfh(var_int(len(message)))
return b"\x18Bitcoin Signed Message:\n" + length + message
def verify_message_with_address(address: str, sig65: bytes, message: bytes):
from .bitcoin import pubkey_to_address
assert_bytes(sig65, message)
try:
h = Hash(msg_magic(message))
public_key, compressed = ECPubkey.from_signature65(sig65, h)
# check public key using the address
pubkey_hex = public_key.get_public_key_hex(compressed)
for txin_type in ['p2pkh','p2wpkh','p2wpkh-p2sh']:
addr = pubkey_to_address(txin_type, pubkey_hex)
if address == addr:
break
else:
raise Exception("Bad signature")
# check message
public_key.verify_message_hash(sig65[1:], h)
return True
except Exception as e:
print_error("Verification error: {0}".format(e))
return False
def is_secret_within_curve_range(secret: Union[int, bytes]) -> bool:
if isinstance(secret, bytes):
secret = string_to_number(secret)
return 0 < secret < CURVE_ORDER
class ECPrivkey(ECPubkey):
def __init__(self, privkey_bytes: bytes):
assert_bytes(privkey_bytes)
if len(privkey_bytes) != 32:
raise Exception('unexpected size for secret. should be 32 bytes, not {}'.format(len(privkey_bytes)))
secret = string_to_number(privkey_bytes)
if not is_secret_within_curve_range(secret):
raise Exception('Invalid secret scalar (not within curve order)')
self.secret_scalar = secret
point = generator_secp256k1 * secret
super().__init__(point_to_ser(point))
self._privkey = ecdsa.ecdsa.Private_key(self._pubkey, secret)
@classmethod
def from_secret_scalar(cls, secret_scalar: int):
secret_bytes = number_to_string(secret_scalar, CURVE_ORDER)
return ECPrivkey(secret_bytes)
@classmethod
def from_arbitrary_size_secret(cls, privkey_bytes: bytes):
"""This method is only for legacy reasons. Do not introduce new code that uses it.
Unlike the default constructor, this method does not require len(privkey_bytes) == 32,
and the secret does not need to be within the curve order either.
"""
return ECPrivkey(cls.normalize_secret_bytes(privkey_bytes))
@classmethod
def normalize_secret_bytes(cls, privkey_bytes: bytes) -> bytes:
scalar = string_to_number(privkey_bytes) % CURVE_ORDER
if scalar == 0:
raise Exception('invalid EC private key scalar: zero')
privkey_32bytes = number_to_string(scalar, CURVE_ORDER)
return privkey_32bytes
def sign_transaction(self, hashed_preimage):
private_key = _MySigningKey.from_secret_exponent(self.secret_scalar, curve=SECP256k1)
sig = private_key.sign_digest_deterministic(hashed_preimage, hashfunc=hashlib.sha256,
sigencode=ecdsa.util.sigencode_der)
public_key = private_key.get_verifying_key()
if not public_key.verify_digest(sig, hashed_preimage, sigdecode=ecdsa.util.sigdecode_der):
raise Exception('Sanity check verifying our own signature failed.')
return sig
def sign_message(self, message, is_compressed):
def sign_with_python_ecdsa(msg_hash):
private_key = _MySigningKey.from_secret_exponent(self.secret_scalar, curve=SECP256k1)
public_key = private_key.get_verifying_key()
signature = private_key.sign_digest_deterministic(msg_hash, hashfunc=hashlib.sha256, sigencode=ecdsa.util.sigencode_string)
if not public_key.verify_digest(signature, msg_hash, sigdecode=ecdsa.util.sigdecode_string):
raise Exception('Sanity check verifying our own signature failed.')
return signature
def bruteforce_recid(sig_string):
for recid in range(4):
sig65 = construct_sig65(sig_string, recid, is_compressed)
try:
self.verify_message_for_address(sig65, message)
return sig65, recid
except Exception as e:
continue
else:
raise Exception("error: cannot sign message. no recid fits..")
message = to_bytes(message, 'utf8')
msg_hash = Hash(msg_magic(message))
sig_string = sign_with_python_ecdsa(msg_hash)
sig65, recid = bruteforce_recid(sig_string)
try:
self.verify_message_for_address(sig65, message)
return sig65
except Exception as e:
raise Exception("error: cannot sign message. self-verify sanity check failed")
def decrypt_message(self, encrypted, magic=b'BIE1'):
encrypted = base64.b64decode(encrypted)
if len(encrypted) < 85:
raise Exception('invalid ciphertext: length')
magic_found = encrypted[:4]
ephemeral_pubkey_bytes = encrypted[4:37]
ciphertext = encrypted[37:-32]
mac = encrypted[-32:]
if magic_found != magic:
raise Exception('invalid ciphertext: invalid magic bytes')
try:
ecdsa_point = _ser_to_python_ecdsa_point(ephemeral_pubkey_bytes)
except AssertionError as e:
raise Exception('invalid ciphertext: invalid ephemeral pubkey') from e
if not ecdsa.ecdsa.point_is_valid(generator_secp256k1, ecdsa_point.x(), ecdsa_point.y()):
raise Exception('invalid ciphertext: invalid ephemeral pubkey')
ephemeral_pubkey = ECPubkey(point_to_ser(ecdsa_point))
ecdh_key = (ephemeral_pubkey * self.secret_scalar).get_public_key_bytes(compressed=True)
key = hashlib.sha512(ecdh_key).digest()
iv, key_e, key_m = key[0:16], key[16:32], key[32:]
if mac != hmac.new(key_m, encrypted[:-32], hashlib.sha256).digest():
raise InvalidPassword()
return aes_decrypt_with_iv(key_e, iv, ciphertext)
def construct_sig65(sig_string, recid, is_compressed):
comp = 4 if is_compressed else 0
return bytes([27 + recid + comp]) + sig_string

221
lib/ecc_fast.py

@ -0,0 +1,221 @@
# taken (with minor modifications) from pycoin
# https://github.com/richardkiss/pycoin/blob/01b1787ed902df23f99a55deb00d8cd076a906fe/pycoin/ecdsa/native/secp256k1.py
import os
import sys
import traceback
import ctypes
from ctypes.util import find_library
from ctypes import (
byref, c_byte, c_int, c_uint, c_char_p, c_size_t, c_void_p, create_string_buffer, CFUNCTYPE, POINTER
)
import ecdsa
from .util import print_stderr, print_error
SECP256K1_FLAGS_TYPE_MASK = ((1 << 8) - 1)
SECP256K1_FLAGS_TYPE_CONTEXT = (1 << 0)
SECP256K1_FLAGS_TYPE_COMPRESSION = (1 << 1)
# /** The higher bits contain the actual data. Do not use directly. */
SECP256K1_FLAGS_BIT_CONTEXT_VERIFY = (1 << 8)
SECP256K1_FLAGS_BIT_CONTEXT_SIGN = (1 << 9)
SECP256K1_FLAGS_BIT_COMPRESSION = (1 << 8)
# /** Flags to pass to secp256k1_context_create. */
SECP256K1_CONTEXT_VERIFY = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY)
SECP256K1_CONTEXT_SIGN = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN)
SECP256K1_CONTEXT_NONE = (SECP256K1_FLAGS_TYPE_CONTEXT)
SECP256K1_EC_COMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION)
SECP256K1_EC_UNCOMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION)
# TODO double check ctypes arg/return value types against https://github.com/bitcoin-core/secp256k1/blob/master/src/secp256k1.c
def load_library():
library_path = ctypes.util.find_library('libsecp256k1') or \
ctypes.util.find_library('secp256k1')
if not library_path:
print_error('[ecc] info: libsecp256k1 library was not found, trying fallback name')
if sys.platform == 'darwin':
library_path = 'libsecp256k1.dylib'
elif sys.platform in ('windows', 'win32'):
library_path = 'libsecp256k1.dll'
else:
library_path = 'libsecp256k1.so.0'
secp256k1 = ctypes.cdll.LoadLibrary(library_path)
if not secp256k1:
print_stderr('[ecc] warning: libsecp256k1 library failed to load')
return None
try:
secp256k1.secp256k1_context_create.argtypes = [c_uint]
secp256k1.secp256k1_context_create.restype = c_void_p
secp256k1.secp256k1_context_randomize.argtypes = [c_void_p, c_char_p]
secp256k1.secp256k1_context_randomize.restype = c_int
secp256k1.secp256k1_ec_pubkey_create.argtypes = [c_void_p, c_void_p, c_char_p]
secp256k1.secp256k1_ec_pubkey_create.restype = c_int
secp256k1.secp256k1_ecdsa_sign.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p, c_void_p, c_void_p]
secp256k1.secp256k1_ecdsa_sign.restype = c_int
secp256k1.secp256k1_ecdsa_verify.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p]
secp256k1.secp256k1_ecdsa_verify.restype = c_int
secp256k1.secp256k1_ec_pubkey_parse.argtypes = [c_void_p, c_char_p, c_char_p, c_size_t]
secp256k1.secp256k1_ec_pubkey_parse.restype = c_int
secp256k1.secp256k1_ec_pubkey_serialize.argtypes = [c_void_p, c_char_p, c_void_p, c_char_p, c_uint]
secp256k1.secp256k1_ec_pubkey_serialize.restype = c_int
secp256k1.secp256k1_ecdsa_signature_parse_compact.argtypes = [c_void_p, c_char_p, c_char_p]
secp256k1.secp256k1_ecdsa_signature_parse_compact.restype = c_int
secp256k1.secp256k1_ecdsa_signature_serialize_compact.argtypes = [c_void_p, c_char_p, c_char_p]
secp256k1.secp256k1_ecdsa_signature_serialize_compact.restype = c_int
secp256k1.secp256k1_ec_pubkey_tweak_mul.argtypes = [c_void_p, c_char_p, c_char_p]
secp256k1.secp256k1_ec_pubkey_tweak_mul.restype = c_int
secp256k1.ctx = secp256k1.secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY)
r = secp256k1.secp256k1_context_randomize(secp256k1.ctx, os.urandom(32))
if r:
return secp256k1
else:
print_stderr('[ecc] warning: secp256k1_context_randomize failed')
return None
except (OSError, AttributeError):
#traceback.print_exc(file=sys.stderr)
print_stderr('[ecc] warning: libsecp256k1 library was found and loaded but there was an error when using it')
return None
class _patched_functions:
prepared_to_patch = False
monkey_patching_active = False
def _prepare_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1():
if not _libsecp256k1:
return
# save original functions so that we can undo patching (needed for tests)
_patched_functions.orig_sign = staticmethod(ecdsa.ecdsa.Private_key.sign)
_patched_functions.orig_verify = staticmethod(ecdsa.ecdsa.Public_key.verifies)
_patched_functions.orig_mul = staticmethod(ecdsa.ellipticcurve.Point.__mul__)
curve_secp256k1 = ecdsa.ecdsa.curve_secp256k1
curve_order = ecdsa.curves.SECP256k1.order
point_at_infinity = ecdsa.ellipticcurve.INFINITY
def mul(self: ecdsa.ellipticcurve.Point, other: int):
if self.curve() != curve_secp256k1:
# this operation is not on the secp256k1 curve; use original implementation
return _patched_functions.orig_mul(self, other)
other %= curve_order
if self == point_at_infinity or other == 0:
return point_at_infinity
pubkey = create_string_buffer(64)
public_pair_bytes = b'\4' + self.x().to_bytes(32, byteorder="big") + self.y().to_bytes(32, byteorder="big")
r = _libsecp256k1.secp256k1_ec_pubkey_parse(
_libsecp256k1.ctx, pubkey, public_pair_bytes, len(public_pair_bytes))
if not r:
return False
r = _libsecp256k1.secp256k1_ec_pubkey_tweak_mul(_libsecp256k1.ctx, pubkey, other.to_bytes(32, byteorder="big"))
if not r:
return point_at_infinity
pubkey_serialized = create_string_buffer(65)
pubkey_size = c_size_t(65)
_libsecp256k1.secp256k1_ec_pubkey_serialize(
_libsecp256k1.ctx, pubkey_serialized, byref(pubkey_size), pubkey, SECP256K1_EC_UNCOMPRESSED)
x = int.from_bytes(pubkey_serialized[1:33], byteorder="big")
y = int.from_bytes(pubkey_serialized[33:], byteorder="big")
return ecdsa.ellipticcurve.Point(curve_secp256k1, x, y, curve_order)
def sign(self: ecdsa.ecdsa.Private_key, hash: int, random_k: int):
# note: random_k is ignored
if self.public_key.curve != curve_secp256k1:
# this operation is not on the secp256k1 curve; use original implementation
return _patched_functions.orig_sign(self, hash, random_k)
secret_exponent = self.secret_multiplier
nonce_function = None
sig = create_string_buffer(64)
sig_hash_bytes = hash.to_bytes(32, byteorder="big")
_libsecp256k1.secp256k1_ecdsa_sign(
_libsecp256k1.ctx, sig, sig_hash_bytes, secret_exponent.to_bytes(32, byteorder="big"), nonce_function, None)
compact_signature = create_string_buffer(64)
_libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig)
r = int.from_bytes(compact_signature[:32], byteorder="big")
s = int.from_bytes(compact_signature[32:], byteorder="big")
return ecdsa.ecdsa.Signature(r, s)
def verify(self: ecdsa.ecdsa.Public_key, hash: int, signature: ecdsa.ecdsa.Signature):
if self.curve != curve_secp256k1:
# this operation is not on the secp256k1 curve; use original implementation
return _patched_functions.orig_verify(self, hash, signature)
sig = create_string_buffer(64)
input64 = signature.r.to_bytes(32, byteorder="big") + signature.s.to_bytes(32, byteorder="big")
r = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, input64)
if not r:
return False
r = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
public_pair_bytes = b'\4' + self.point.x().to_bytes(32, byteorder="big") + self.point.y().to_bytes(32, byteorder="big")
pubkey = create_string_buffer(64)
r = _libsecp256k1.secp256k1_ec_pubkey_parse(
_libsecp256k1.ctx, pubkey, public_pair_bytes, len(public_pair_bytes))
if not r:
return False
return 1 == _libsecp256k1.secp256k1_ecdsa_verify(_libsecp256k1.ctx, sig, hash.to_bytes(32, byteorder="big"), pubkey)
# save new functions so that we can (re-)do patching
_patched_functions.fast_sign = sign
_patched_functions.fast_verify = verify
_patched_functions.fast_mul = mul
_patched_functions.prepared_to_patch = True
def do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1():
if not _libsecp256k1:
print_stderr('[ecc] warning: libsecp256k1 library not available, falling back to python-ecdsa')
return
if not _patched_functions.prepared_to_patch:
raise Exception("can't patch python-ecdsa without preparations")
ecdsa.ecdsa.Private_key.sign = _patched_functions.fast_sign
ecdsa.ecdsa.Public_key.verifies = _patched_functions.fast_verify
ecdsa.ellipticcurve.Point.__mul__ = _patched_functions.fast_mul
# ecdsa.ellipticcurve.Point.__add__ = ... # TODO??
_patched_functions.monkey_patching_active = True
def undo_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1():
if not _libsecp256k1:
return
if not _patched_functions.prepared_to_patch:
raise Exception("can't patch python-ecdsa without preparations")
ecdsa.ecdsa.Private_key.sign = _patched_functions.orig_sign
ecdsa.ecdsa.Public_key.verifies = _patched_functions.orig_verify
ecdsa.ellipticcurve.Point.__mul__ = _patched_functions.orig_mul
_patched_functions.monkey_patching_active = False
def is_using_fast_ecc():
return _patched_functions.monkey_patching_active
try:
_libsecp256k1 = load_library()
except:
_libsecp256k1 = None
traceback.print_exc(file=sys.stderr)
_prepare_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1()

33
lib/keystore.py

@ -26,8 +26,10 @@
from unicodedata import normalize from unicodedata import normalize
from . import bitcoin from . import bitcoin, ecc
from .bitcoin import * from .bitcoin import *
from .ecc import string_to_number, number_to_string
from .crypto import pw_decode, pw_encode
from . import constants from . import constants
from .util import (PrintError, InvalidPassword, hfu, WalletFileException, from .util import (PrintError, InvalidPassword, hfu, WalletFileException,
BitcoinException) BitcoinException)
@ -90,12 +92,12 @@ class Software_KeyStore(KeyStore):
def sign_message(self, sequence, message, password): def sign_message(self, sequence, message, password):
privkey, compressed = self.get_private_key(sequence, password) privkey, compressed = self.get_private_key(sequence, password)
key = regenerate_key(privkey) key = ecc.ECPrivkey(privkey)
return key.sign_message(message, compressed) return key.sign_message(message, compressed)
def decrypt_message(self, sequence, message, password): def decrypt_message(self, sequence, message, password):
privkey, compressed = self.get_private_key(sequence, password) privkey, compressed = self.get_private_key(sequence, password)
ec = regenerate_key(privkey) ec = ecc.ECPrivkey(privkey)
decrypted = ec.decrypt_message(message) decrypted = ec.decrypt_message(message)
return decrypted return decrypted
@ -141,7 +143,7 @@ class Imported_KeyStore(Software_KeyStore):
def import_privkey(self, sec, password): def import_privkey(self, sec, password):
txin_type, privkey, compressed = deserialize_privkey(sec) txin_type, privkey, compressed = deserialize_privkey(sec)
pubkey = public_key_from_private_key(privkey, compressed) pubkey = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
# re-serialize the key so the internal storage format is consistent # re-serialize the key so the internal storage format is consistent
serialized_privkey = serialize_privkey( serialized_privkey = serialize_privkey(
privkey, compressed, txin_type, internal_use=True) privkey, compressed, txin_type, internal_use=True)
@ -159,7 +161,7 @@ class Imported_KeyStore(Software_KeyStore):
sec = pw_decode(self.keypairs[pubkey], password) sec = pw_decode(self.keypairs[pubkey], password)
txin_type, privkey, compressed = deserialize_privkey(sec) txin_type, privkey, compressed = deserialize_privkey(sec)
# this checks the password # this checks the password
if pubkey != public_key_from_private_key(privkey, compressed): if pubkey != ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed):
raise InvalidPassword() raise InvalidPassword()
return privkey, compressed return privkey, compressed
@ -381,9 +383,8 @@ class Old_KeyStore(Deterministic_KeyStore):
@classmethod @classmethod
def mpk_from_seed(klass, seed): def mpk_from_seed(klass, seed):
secexp = klass.stretch_key(seed) secexp = klass.stretch_key(seed)
master_private_key = ecdsa.SigningKey.from_secret_exponent(secexp, curve = SECP256k1) privkey = ecc.ECPrivkey.from_secret_scalar(secexp)
master_public_key = master_private_key.get_verifying_key().to_string() return privkey.get_public_key_hex(compressed=False)[2:]
return bh2u(master_public_key)
@classmethod @classmethod
def stretch_key(self, seed): def stretch_key(self, seed):
@ -399,18 +400,16 @@ class Old_KeyStore(Deterministic_KeyStore):
@classmethod @classmethod
def get_pubkey_from_mpk(self, mpk, for_change, n): def get_pubkey_from_mpk(self, mpk, for_change, n):
z = self.get_sequence(mpk, for_change, n) z = self.get_sequence(mpk, for_change, n)
master_public_key = ecdsa.VerifyingKey.from_string(bfh(mpk), curve = SECP256k1) master_public_key = ecc.ECPubkey(bfh('04'+mpk))
pubkey_point = master_public_key.pubkey.point + z*SECP256k1.generator public_key = master_public_key + z*ecc.generator()
public_key2 = ecdsa.VerifyingKey.from_public_point(pubkey_point, curve = SECP256k1) return public_key.get_public_key_hex(compressed=False)
return '04' + bh2u(public_key2.to_string())
def derive_pubkey(self, for_change, n): def derive_pubkey(self, for_change, n):
return self.get_pubkey_from_mpk(self.mpk, for_change, n) return self.get_pubkey_from_mpk(self.mpk, for_change, n)
def get_private_key_from_stretched_exponent(self, for_change, n, secexp): def get_private_key_from_stretched_exponent(self, for_change, n, secexp):
order = generator_secp256k1.order() secexp = (secexp + self.get_sequence(self.mpk, for_change, n)) % ecc.CURVE_ORDER
secexp = (secexp + self.get_sequence(self.mpk, for_change, n)) % order pk = number_to_string(secexp, ecc.CURVE_ORDER)
pk = number_to_string(secexp, generator_secp256k1.order())
return pk return pk
def get_private_key(self, sequence, password): def get_private_key(self, sequence, password):
@ -423,8 +422,8 @@ class Old_KeyStore(Deterministic_KeyStore):
def check_seed(self, seed): def check_seed(self, seed):
secexp = self.stretch_key(seed) secexp = self.stretch_key(seed)
master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) master_private_key = ecc.ECPrivkey.from_secret_scalar(secexp)
master_public_key = master_private_key.get_verifying_key().to_string() master_public_key = master_private_key.get_public_key_bytes(compressed=False)[1:]
if master_public_key != bfh(self.mpk): if master_public_key != bfh(self.mpk):
print_error('invalid password (mpk)', self.mpk, bh2u(master_public_key)) print_error('invalid password (mpk)', self.mpk, bh2u(master_public_key))
raise InvalidPassword() raise InvalidPassword()

10
lib/paymentrequest.py

@ -38,6 +38,7 @@ except ImportError:
sys.exit("Error: could not find paymentrequest_pb2.py. Create it with 'protoc --proto_path=lib/ --python_out=lib/ lib/paymentrequest.proto'") sys.exit("Error: could not find paymentrequest_pb2.py. Create it with 'protoc --proto_path=lib/ --python_out=lib/ lib/paymentrequest.proto'")
from . import bitcoin from . import bitcoin
from . import ecc
from . import util from . import util
from .util import print_error, bh2u, bfh from .util import print_error, bh2u, bfh
from .util import export_meta, import_meta from .util import export_meta, import_meta
@ -206,9 +207,9 @@ class PaymentRequest:
if pr.pki_type == "dnssec+btc": if pr.pki_type == "dnssec+btc":
self.requestor = alias self.requestor = alias
address = info.get('address') address = info.get('address')
pr.signature = '' pr.signature = b''
message = pr.SerializeToString() message = pr.SerializeToString()
if bitcoin.verify_message(address, sig, message): if ecc.verify_message_with_address(address, sig, message):
self.error = 'Verified with DNSSEC' self.error = 'Verified with DNSSEC'
return True return True
else: else:
@ -321,10 +322,9 @@ def sign_request_with_alias(pr, alias, alias_privkey):
pr.pki_type = 'dnssec+btc' pr.pki_type = 'dnssec+btc'
pr.pki_data = str(alias) pr.pki_data = str(alias)
message = pr.SerializeToString() message = pr.SerializeToString()
ec_key = bitcoin.regenerate_key(alias_privkey) ec_key = ecc.ECPrivkey(alias_privkey)
address = bitcoin.address_from_private_key(alias_privkey)
compressed = bitcoin.is_compressed(alias_privkey) compressed = bitcoin.is_compressed(alias_privkey)
pr.signature = ec_key.sign_message(message, compressed, address) pr.signature = ec_key.sign_message(message, compressed)
def verify_cert_chain(chain): def verify_cert_chain(chain):

23
lib/storage.py

@ -33,10 +33,11 @@ import pbkdf2, hmac, hashlib
import base64 import base64
import zlib import zlib
from .util import PrintError, profiler, InvalidPassword, WalletFileException from .util import PrintError, profiler, InvalidPassword, WalletFileException, bfh
from .plugins import run_hook, plugin_loaders from .plugins import run_hook, plugin_loaders
from .keystore import bip44_derivation from .keystore import bip44_derivation
from . import bitcoin from . import bitcoin
from . import ecc
# seed_version is now used for the version of the wallet file # seed_version is now used for the version of the wallet file
@ -162,9 +163,10 @@ class WalletStorage(PrintError):
def file_exists(self): def file_exists(self):
return self.path and os.path.exists(self.path) return self.path and os.path.exists(self.path)
def get_key(self, password): @staticmethod
secret = pbkdf2.PBKDF2(password, '', iterations = 1024, macmodule = hmac, digestmodule = hashlib.sha512).read(64) def get_eckey_from_password(password):
ec_key = bitcoin.EC_KEY(secret) secret = pbkdf2.PBKDF2(password, '', iterations=1024, macmodule=hmac, digestmodule=hashlib.sha512).read(64)
ec_key = ecc.ECPrivkey.from_arbitrary_size_secret(secret)
return ec_key return ec_key
def _get_encryption_magic(self): def _get_encryption_magic(self):
@ -177,13 +179,13 @@ class WalletStorage(PrintError):
raise WalletFileException('no encryption magic for version: %s' % v) raise WalletFileException('no encryption magic for version: %s' % v)
def decrypt(self, password): def decrypt(self, password):
ec_key = self.get_key(password) ec_key = self.get_eckey_from_password(password)
if self.raw: if self.raw:
enc_magic = self._get_encryption_magic() enc_magic = self._get_encryption_magic()
s = zlib.decompress(ec_key.decrypt_message(self.raw, enc_magic)) s = zlib.decompress(ec_key.decrypt_message(self.raw, enc_magic))
else: else:
s = None s = None
self.pubkey = ec_key.get_public_key() self.pubkey = ec_key.get_public_key_hex()
s = s.decode('utf8') s = s.decode('utf8')
self.load_data(s) self.load_data(s)
@ -191,7 +193,7 @@ class WalletStorage(PrintError):
"""Raises an InvalidPassword exception on invalid password""" """Raises an InvalidPassword exception on invalid password"""
if not self.is_encrypted(): if not self.is_encrypted():
return return
if self.pubkey and self.pubkey != self.get_key(password).get_public_key(): if self.pubkey and self.pubkey != self.get_eckey_from_password(password).get_public_key_hex():
raise InvalidPassword() raise InvalidPassword()
def set_keystore_encryption(self, enable): def set_keystore_encryption(self, enable):
@ -202,8 +204,8 @@ class WalletStorage(PrintError):
if enc_version is None: if enc_version is None:
enc_version = self._encryption_version enc_version = self._encryption_version
if password and enc_version != STO_EV_PLAINTEXT: if password and enc_version != STO_EV_PLAINTEXT:
ec_key = self.get_key(password) ec_key = self.get_eckey_from_password(password)
self.pubkey = ec_key.get_public_key() self.pubkey = ec_key.get_public_key_hex()
self._encryption_version = enc_version self._encryption_version = enc_version
else: else:
self.pubkey = None self.pubkey = None
@ -253,7 +255,8 @@ class WalletStorage(PrintError):
s = bytes(s, 'utf8') s = bytes(s, 'utf8')
c = zlib.compress(s) c = zlib.compress(s)
enc_magic = self._get_encryption_magic() enc_magic = self._get_encryption_magic()
s = bitcoin.encrypt_message(c, self.pubkey, enc_magic) public_key = ecc.ECPubkey(bfh(self.pubkey))
s = public_key.encrypt_message(c, enc_magic)
s = s.decode('utf8') s = s.decode('utf8')
temp_path = "%s.tmp.%s" % (self.path, os.getpid()) temp_path = "%s.tmp.%s" % (self.path, os.getpid())

17
lib/tests/__init__.py

@ -1,9 +1,24 @@
import unittest import unittest
import threading
from lib import constants from lib import constants
class TestCaseForTestnet(unittest.TestCase): # some unit tests are modifying globals; sorry.
class SequentialTestCase(unittest.TestCase):
test_lock = threading.Lock()
def setUp(self):
super().setUp()
self.test_lock.acquire()
def tearDown(self):
super().tearDown()
self.test_lock.release()
class TestCaseForTestnet(SequentialTestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):

135
lib/tests/test_bitcoin.py

@ -1,21 +1,26 @@
import base64 import base64
import unittest import unittest
import sys import sys
from ecdsa.util import number_to_string
from lib import bitcoin
from lib.bitcoin import ( from lib.bitcoin import (
generator_secp256k1, point_to_ser, public_key_to_p2pkh, EC_KEY, public_key_to_p2pkh,
bip32_root, bip32_public_derivation, bip32_private_derivation, pw_encode, bip32_root, bip32_public_derivation, bip32_private_derivation,
pw_decode, Hash, public_key_from_private_key, address_from_private_key, Hash, address_from_private_key,
is_address, is_private_key, xpub_from_xprv, is_new_seed, is_old_seed, is_address, is_private_key, xpub_from_xprv, is_new_seed, is_old_seed,
var_int, op_push, address_to_script, regenerate_key, var_int, op_push, address_to_script,
verify_message, deserialize_privkey, serialize_privkey, is_segwit_address, deserialize_privkey, serialize_privkey, is_segwit_address,
is_b58_address, address_to_scripthash, is_minikey, is_compressed, is_xpub, is_b58_address, address_to_scripthash, is_minikey, is_compressed, is_xpub,
xpub_type, is_xprv, is_bip32_derivation, seed_type, EncodeBase58Check, xpub_type, is_xprv, is_bip32_derivation, seed_type, EncodeBase58Check,
script_num_to_hex, push_script, add_number_to_script) script_num_to_hex, push_script, add_number_to_script)
from lib import ecc, crypto, ecc_fast
from lib.ecc import number_to_string, string_to_number
from lib.transaction import opcodes from lib.transaction import opcodes
from lib.util import bfh, bh2u from lib.util import bfh, bh2u
from lib import constants from lib import constants
from lib.storage import WalletStorage
from . import SequentialTestCase
from . import TestCaseForTestnet from . import TestCaseForTestnet
@ -26,27 +31,54 @@ except ImportError:
sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'") sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
class Test_bitcoin(unittest.TestCase): def needs_test_with_all_ecc_implementations(func):
"""Function decorator to run a unit test twice:
once when libsecp256k1 is not available, once when it is.
NOTE: this is inherently sequential;
tests running in parallel would break things
"""
def run_test(*args, **kwargs):
ecc_fast.undo_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1()
try:
# first test without libsecp
func(*args, **kwargs)
finally:
# if libsecp is not available, we are done
if not ecc_fast._libsecp256k1:
return
ecc_fast.do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1()
# if libsecp is available, test again now
func(*args, **kwargs)
return run_test
class Test_bitcoin(SequentialTestCase):
def test_libsecp256k1_is_available(self):
# we want the unit testing framework to test with libsecp256k1 available.
self.assertTrue(bool(ecc_fast._libsecp256k1))
@needs_test_with_all_ecc_implementations
def test_crypto(self): def test_crypto(self):
for message in [b"Chancellor on brink of second bailout for banks", b'\xff'*512]: for message in [b"Chancellor on brink of second bailout for banks", b'\xff'*512]:
self._do_test_crypto(message) self._do_test_crypto(message)
def _do_test_crypto(self, message): def _do_test_crypto(self, message):
G = generator_secp256k1 G = ecc.generator()
_r = G.order() _r = G.order()
pvk = ecdsa.util.randrange( pow(2,256) ) %_r pvk = ecdsa.util.randrange(_r)
Pub = pvk*G Pub = pvk*G
pubkey_c = point_to_ser(Pub,True) pubkey_c = Pub.get_public_key_bytes(True)
#pubkey_u = point_to_ser(Pub,False) #pubkey_u = point_to_ser(Pub,False)
addr_c = public_key_to_p2pkh(pubkey_c) addr_c = public_key_to_p2pkh(pubkey_c)
#print "Private key ", '%064x'%pvk #print "Private key ", '%064x'%pvk
eck = EC_KEY(number_to_string(pvk,_r)) eck = ecc.ECPrivkey(number_to_string(pvk,_r))
#print "Compressed public key ", pubkey_c.encode('hex') #print "Compressed public key ", pubkey_c.encode('hex')
enc = EC_KEY.encrypt_message(message, pubkey_c) enc = ecc.ECPubkey(pubkey_c).encrypt_message(message)
dec = eck.decrypt_message(enc) dec = eck.decrypt_message(enc)
self.assertEqual(message, dec) self.assertEqual(message, dec)
@ -57,15 +89,16 @@ class Test_bitcoin(unittest.TestCase):
signature = eck.sign_message(message, True) signature = eck.sign_message(message, True)
#print signature #print signature
EC_KEY.verify_message(eck, signature, message) eck.verify_message_for_address(signature, message)
@needs_test_with_all_ecc_implementations
def test_msg_signing(self): def test_msg_signing(self):
msg1 = b'Chancellor on brink of second bailout for banks' msg1 = b'Chancellor on brink of second bailout for banks'
msg2 = b'Electrum' msg2 = b'Electrum'
def sign_message_with_wif_privkey(wif_privkey, msg): def sign_message_with_wif_privkey(wif_privkey, msg):
txin_type, privkey, compressed = deserialize_privkey(wif_privkey) txin_type, privkey, compressed = deserialize_privkey(wif_privkey)
key = regenerate_key(privkey) key = ecc.ECPrivkey(privkey)
return key.sign_message(msg, compressed) return key.sign_message(msg, compressed)
sig1 = sign_message_with_wif_privkey( sig1 = sign_message_with_wif_privkey(
@ -81,30 +114,61 @@ class Test_bitcoin(unittest.TestCase):
self.assertEqual(sig1_b64, b'H/9jMOnj4MFbH3d7t4yCQ9i7DgZU/VZ278w3+ySv2F4yIsdqjsc5ng3kmN8OZAThgyfCZOQxZCWza9V5XzlVY0Y=') self.assertEqual(sig1_b64, b'H/9jMOnj4MFbH3d7t4yCQ9i7DgZU/VZ278w3+ySv2F4yIsdqjsc5ng3kmN8OZAThgyfCZOQxZCWza9V5XzlVY0Y=')
self.assertEqual(sig2_b64, b'G84dmJ8TKIDKMT9qBRhpX2sNmR0y5t+POcYnFFJCs66lJmAs3T8A6Sbpx7KA6yTQ9djQMabwQXRrDomOkIKGn18=') self.assertEqual(sig2_b64, b'G84dmJ8TKIDKMT9qBRhpX2sNmR0y5t+POcYnFFJCs66lJmAs3T8A6Sbpx7KA6yTQ9djQMabwQXRrDomOkIKGn18=')
self.assertTrue(verify_message(addr1, sig1, msg1)) self.assertTrue(ecc.verify_message_with_address(addr1, sig1, msg1))
self.assertTrue(verify_message(addr2, sig2, msg2)) self.assertTrue(ecc.verify_message_with_address(addr2, sig2, msg2))
self.assertFalse(verify_message(addr1, b'wrong', msg1)) self.assertFalse(ecc.verify_message_with_address(addr1, b'wrong', msg1))
self.assertFalse(verify_message(addr1, sig2, msg1)) self.assertFalse(ecc.verify_message_with_address(addr1, sig2, msg1))
@needs_test_with_all_ecc_implementations
def test_decrypt_message(self):
key = WalletStorage.get_eckey_from_password('pw123')
self.assertEqual(b'me<(s_s)>age', key.decrypt_message(b'QklFMQMDFtgT3zWSQsa+Uie8H/WvfUjlu9UN9OJtTt3KlgKeSTi6SQfuhcg1uIz9hp3WIUOFGTLr4RNQBdjPNqzXwhkcPi2Xsbiw6UCNJncVPJ6QBg=='))
self.assertEqual(b'me<(s_s)>age', key.decrypt_message(b'QklFMQKXOXbylOQTSMGfo4MFRwivAxeEEkewWQrpdYTzjPhqjHcGBJwdIhB7DyRfRQihuXx1y0ZLLv7XxLzrILzkl/H4YUtZB4uWjuOAcmxQH4i/Og=='))
self.assertEqual(b'hey_there' * 100, key.decrypt_message(b'QklFMQLOOsabsXtGQH8edAa6VOUa5wX8/DXmxX9NyHoAx1a5bWgllayGRVPeI2bf0ZdWK0tfal0ap0ZIVKbd2eOJybqQkILqT6E1/Syzq0Zicyb/AA1eZNkcX5y4gzloxinw00ubCA8M7gcUjJpOqbnksATcJ5y2YYXcHMGGfGurWu6uJ/UyrNobRidWppRMW5yR9/6utyNvT6OHIolCMEf7qLcmtneoXEiz51hkRdZS7weNf9mGqSbz9a2NL3sdh1A0feHIjAZgcCKcAvksNUSauf0/FnIjzTyPRpjRDMeDC8Ci3sGiuO3cvpWJwhZfbjcS26KmBv2CHWXfRRNFYOInHZNIXWNAoBB47Il5bGSMd+uXiGr+SQ9tNvcu+BiJNmFbxYqg+oQ8dGAl1DtvY2wJVY8k7vO9BIWSpyIxfGw7EDifhc5vnOmGe016p6a01C3eVGxgl23UYMrP7+fpjOcPmTSF4rk5U5ljEN3MSYqlf1QEv0OqlI9q1TwTK02VBCjMTYxDHsnt04OjNBkNO8v5uJ4NR+UUDBEp433z53I59uawZ+dbk4v4ZExcl8EGmKm3Gzbal/iJ/F7KQuX2b/ySEhLOFVYFWxK73X1nBvCSK2mC2/8fCw8oI5pmvzJwQhcCKTdEIrz3MMvAHqtPScDUOjzhXxInQOCb3+UBj1PPIdqkYLvZss1TEaBwYZjLkVnK2MBj7BaqT6Rp6+5A/fippUKHsnB6eYMEPR2YgDmCHL+4twxHJG6UWdP3ybaKiiAPy2OHNP6PTZ0HrqHOSJzBSDD+Z8YpaRg29QX3UEWlqnSKaan0VYAsV1VeaN0XFX46/TWO0L5tjhYVXJJYGqo6tIQJymxATLFRF6AZaD1Mwd27IAL04WkmoQoXfO6OFfwdp/shudY/1gBkDBvGPICBPtnqkvhGF+ZF3IRkuPwiFWeXmwBxKHsRx/3+aJu32Ml9+za41zVk2viaxcGqwTc5KMexQFLAUwqhv+aIik7U+5qk/gEVSuRoVkihoweFzKolNF+BknH2oB4rZdPixag5Zje3DvgjsSFlOl69W/67t/Gs8htfSAaHlsB8vWRQr9+v/lxTbrAw+O0E+sYGoObQ4qQMyQshNZEHbpPg63eWiHtJJnrVBvOeIbIHzoLDnMDsWVWZSMzAQ1vhX1H5QLgSEbRlKSliVY03kDkh/Nk/KOn+B2q37Ialq4JcRoIYFGJ8AoYEAD0tRuTqFddIclE75HzwaNG7NyKW1plsa72ciOPwsPJsdd5F0qdSQ3OSKtooTn7uf6dXOc4lDkfrVYRlZ0PX'))
@needs_test_with_all_ecc_implementations
def test_encrypt_message(self):
key = WalletStorage.get_eckey_from_password('secret_password77')
msgs = [
bytes([0] * 555),
b'cannot think of anything funny'
]
for plaintext in msgs:
ciphertext1 = key.encrypt_message(plaintext)
ciphertext2 = key.encrypt_message(plaintext)
self.assertEqual(plaintext, key.decrypt_message(ciphertext1))
self.assertEqual(plaintext, key.decrypt_message(ciphertext2))
self.assertNotEqual(ciphertext1, ciphertext2)
@needs_test_with_all_ecc_implementations
def test_sign_transaction(self):
eckey1 = ecc.ECPrivkey(bfh('7e1255fddb52db1729fc3ceb21a46f95b8d9fe94cc83425e936a6c5223bb679d'))
sig1 = eckey1.sign_transaction(bfh('5a548b12369a53faaa7e51b5081829474ebdd9c924b3a8230b69aa0be254cd94'))
self.assertEqual(bfh('3045022100902a288b98392254cd23c0e9a49ac6d7920f171b8249a48e484b998f1874a2010220723d844826828f092cf400cb210c4fa0b8cd1b9d1a7f21590e78e022ff6476b9'), sig1)
eckey2 = ecc.ECPrivkey(bfh('c7ce8c1462c311eec24dff9e2532ac6241e50ae57e7d1833af21942136972f23'))
sig2 = eckey2.sign_transaction(bfh('642a2e66332f507c92bda910158dfe46fc10afbf72218764899d3af99a043fac'))
self.assertEqual(bfh('30440220618513f4cfc87dde798ce5febae7634c23e7b9254a1eabf486be820f6a7c2c4702204fef459393a2b931f949e63ced06888f35e286e446dc46feb24b5b5f81c6ed52'), sig2)
def test_aes_homomorphic(self): def test_aes_homomorphic(self):
"""Make sure AES is homomorphic.""" """Make sure AES is homomorphic."""
payload = u'\u66f4\u7a33\u5b9a\u7684\u4ea4\u6613\u5e73\u53f0' payload = u'\u66f4\u7a33\u5b9a\u7684\u4ea4\u6613\u5e73\u53f0'
password = u'secret' password = u'secret'
enc = pw_encode(payload, password) enc = crypto.pw_encode(payload, password)
dec = pw_decode(enc, password) dec = crypto.pw_decode(enc, password)
self.assertEqual(dec, payload) self.assertEqual(dec, payload)
def test_aes_encode_without_password(self): def test_aes_encode_without_password(self):
"""When not passed a password, pw_encode is noop on the payload.""" """When not passed a password, pw_encode is noop on the payload."""
payload = u'\u66f4\u7a33\u5b9a\u7684\u4ea4\u6613\u5e73\u53f0' payload = u'\u66f4\u7a33\u5b9a\u7684\u4ea4\u6613\u5e73\u53f0'
enc = pw_encode(payload, None) enc = crypto.pw_encode(payload, None)
self.assertEqual(payload, enc) self.assertEqual(payload, enc)
def test_aes_deencode_without_password(self): def test_aes_deencode_without_password(self):
"""When not passed a password, pw_decode is noop on the payload.""" """When not passed a password, pw_decode is noop on the payload."""
payload = u'\u66f4\u7a33\u5b9a\u7684\u4ea4\u6613\u5e73\u53f0' payload = u'\u66f4\u7a33\u5b9a\u7684\u4ea4\u6613\u5e73\u53f0'
enc = pw_decode(payload, None) enc = crypto.pw_decode(payload, None)
self.assertEqual(payload, enc) self.assertEqual(payload, enc)
def test_aes_decode_with_invalid_password(self): def test_aes_decode_with_invalid_password(self):
@ -112,8 +176,8 @@ class Test_bitcoin(unittest.TestCase):
payload = u"blah" payload = u"blah"
password = u"uber secret" password = u"uber secret"
wrong_password = u"not the password" wrong_password = u"not the password"
enc = pw_encode(payload, password) enc = crypto.pw_encode(payload, password)
self.assertRaises(Exception, pw_decode, enc, wrong_password) self.assertRaises(Exception, crypto.pw_decode, enc, wrong_password)
def test_hash(self): def test_hash(self):
"""Make sure the Hash function does sha256 twice""" """Make sure the Hash function does sha256 twice"""
@ -238,7 +302,7 @@ class Test_bitcoin_testnet(TestCaseForTestnet):
self.assertEqual(address_to_script('2NE4ZdmxFmUgwu5wtfoN2gVniyMgRDYq1kk'), 'a914e4567743d378957cd2ee7072da74b1203c1a7a0b87') self.assertEqual(address_to_script('2NE4ZdmxFmUgwu5wtfoN2gVniyMgRDYq1kk'), 'a914e4567743d378957cd2ee7072da74b1203c1a7a0b87')
class Test_xprv_xpub(unittest.TestCase): class Test_xprv_xpub(SequentialTestCase):
xprv_xpub = ( xprv_xpub = (
# Taken from test vectors in https://en.bitcoin.it/wiki/BIP_0032_TestVectors # Taken from test vectors in https://en.bitcoin.it/wiki/BIP_0032_TestVectors
@ -269,6 +333,7 @@ class Test_xprv_xpub(unittest.TestCase):
return xpub, xprv return xpub, xprv
@needs_test_with_all_ecc_implementations
def test_bip32(self): def test_bip32(self):
# see https://en.bitcoin.it/wiki/BIP_0032_TestVectors # see https://en.bitcoin.it/wiki/BIP_0032_TestVectors
xpub, xprv = self._do_test_bip32("000102030405060708090a0b0c0d0e0f", "m/0'/1/2'/2/1000000000") xpub, xprv = self._do_test_bip32("000102030405060708090a0b0c0d0e0f", "m/0'/1/2'/2/1000000000")
@ -279,12 +344,14 @@ class Test_xprv_xpub(unittest.TestCase):
self.assertEqual("xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", xpub) self.assertEqual("xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", xpub)
self.assertEqual("xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j", xprv) self.assertEqual("xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j", xprv)
@needs_test_with_all_ecc_implementations
def test_xpub_from_xprv(self): def test_xpub_from_xprv(self):
"""We can derive the xpub key from a xprv.""" """We can derive the xpub key from a xprv."""
for xprv_details in self.xprv_xpub: for xprv_details in self.xprv_xpub:
result = xpub_from_xprv(xprv_details['xprv']) result = xpub_from_xprv(xprv_details['xprv'])
self.assertEqual(result, xprv_details['xpub']) self.assertEqual(result, xprv_details['xpub'])
@needs_test_with_all_ecc_implementations
def test_is_xpub(self): def test_is_xpub(self):
for xprv_details in self.xprv_xpub: for xprv_details in self.xprv_xpub:
xpub = xprv_details['xpub'] xpub = xprv_details['xpub']
@ -292,11 +359,13 @@ class Test_xprv_xpub(unittest.TestCase):
self.assertFalse(is_xpub('xpub1nval1d')) self.assertFalse(is_xpub('xpub1nval1d'))
self.assertFalse(is_xpub('xpub661MyMwAqRbcFWohJWt7PHsFEJfZAvw9ZxwQoDa4SoMgsDDM1T7WK3u9E4edkC4ugRnZ8E4xDZRpk8Rnts3Nbt97dPwT52WRONGBADWRONG')) self.assertFalse(is_xpub('xpub661MyMwAqRbcFWohJWt7PHsFEJfZAvw9ZxwQoDa4SoMgsDDM1T7WK3u9E4edkC4ugRnZ8E4xDZRpk8Rnts3Nbt97dPwT52WRONGBADWRONG'))
@needs_test_with_all_ecc_implementations
def test_xpub_type(self): def test_xpub_type(self):
for xprv_details in self.xprv_xpub: for xprv_details in self.xprv_xpub:
xpub = xprv_details['xpub'] xpub = xprv_details['xpub']
self.assertEqual(xprv_details['xtype'], xpub_type(xpub)) self.assertEqual(xprv_details['xtype'], xpub_type(xpub))
@needs_test_with_all_ecc_implementations
def test_is_xprv(self): def test_is_xprv(self):
for xprv_details in self.xprv_xpub: for xprv_details in self.xprv_xpub:
xprv = xprv_details['xprv'] xprv = xprv_details['xprv']
@ -388,7 +457,7 @@ class Test_xprv_xpub_testnet(TestCaseForTestnet):
self.assertTrue(xkey_b58.startswith(xpub_headers_b58[xtype])) self.assertTrue(xkey_b58.startswith(xpub_headers_b58[xtype]))
class Test_keyImport(unittest.TestCase): class Test_keyImport(SequentialTestCase):
priv_pub_addr = ( priv_pub_addr = (
{'priv': 'KzMFjMC2MPadjvX5Cd7b8AKKjjpBSoRKUTpoAtN6B3J9ezWYyXS6', {'priv': 'KzMFjMC2MPadjvX5Cd7b8AKKjjpBSoRKUTpoAtN6B3J9ezWYyXS6',
@ -475,19 +544,22 @@ class Test_keyImport(unittest.TestCase):
'scripthash': '60ad5a8b922f758cd7884403e90ee7e6f093f8d21a0ff24c9a865e695ccefdf1'}, 'scripthash': '60ad5a8b922f758cd7884403e90ee7e6f093f8d21a0ff24c9a865e695ccefdf1'},
) )
@needs_test_with_all_ecc_implementations
def test_public_key_from_private_key(self): def test_public_key_from_private_key(self):
for priv_details in self.priv_pub_addr: for priv_details in self.priv_pub_addr:
txin_type, privkey, compressed = deserialize_privkey(priv_details['priv']) txin_type, privkey, compressed = deserialize_privkey(priv_details['priv'])
result = public_key_from_private_key(privkey, compressed) result = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
self.assertEqual(priv_details['pub'], result) self.assertEqual(priv_details['pub'], result)
self.assertEqual(priv_details['txin_type'], txin_type) self.assertEqual(priv_details['txin_type'], txin_type)
self.assertEqual(priv_details['compressed'], compressed) self.assertEqual(priv_details['compressed'], compressed)
@needs_test_with_all_ecc_implementations
def test_address_from_private_key(self): def test_address_from_private_key(self):
for priv_details in self.priv_pub_addr: for priv_details in self.priv_pub_addr:
addr2 = address_from_private_key(priv_details['priv']) addr2 = address_from_private_key(priv_details['priv'])
self.assertEqual(priv_details['address'], addr2) self.assertEqual(priv_details['address'], addr2)
@needs_test_with_all_ecc_implementations
def test_is_valid_address(self): def test_is_valid_address(self):
for priv_details in self.priv_pub_addr: for priv_details in self.priv_pub_addr:
addr = priv_details['address'] addr = priv_details['address']
@ -503,6 +575,7 @@ class Test_keyImport(unittest.TestCase):
self.assertFalse(is_address("not an address")) self.assertFalse(is_address("not an address"))
@needs_test_with_all_ecc_implementations
def test_is_private_key(self): def test_is_private_key(self):
for priv_details in self.priv_pub_addr: for priv_details in self.priv_pub_addr:
self.assertTrue(is_private_key(priv_details['priv'])) self.assertTrue(is_private_key(priv_details['priv']))
@ -511,30 +584,34 @@ class Test_keyImport(unittest.TestCase):
self.assertFalse(is_private_key(priv_details['address'])) self.assertFalse(is_private_key(priv_details['address']))
self.assertFalse(is_private_key("not a privkey")) self.assertFalse(is_private_key("not a privkey"))
@needs_test_with_all_ecc_implementations
def test_serialize_privkey(self): def test_serialize_privkey(self):
for priv_details in self.priv_pub_addr: for priv_details in self.priv_pub_addr:
txin_type, privkey, compressed = deserialize_privkey(priv_details['priv']) txin_type, privkey, compressed = deserialize_privkey(priv_details['priv'])
priv2 = serialize_privkey(privkey, compressed, txin_type) priv2 = serialize_privkey(privkey, compressed, txin_type)
self.assertEqual(priv_details['exported_privkey'], priv2) self.assertEqual(priv_details['exported_privkey'], priv2)
@needs_test_with_all_ecc_implementations
def test_address_to_scripthash(self): def test_address_to_scripthash(self):
for priv_details in self.priv_pub_addr: for priv_details in self.priv_pub_addr:
sh = address_to_scripthash(priv_details['address']) sh = address_to_scripthash(priv_details['address'])
self.assertEqual(priv_details['scripthash'], sh) self.assertEqual(priv_details['scripthash'], sh)
@needs_test_with_all_ecc_implementations
def test_is_minikey(self): def test_is_minikey(self):
for priv_details in self.priv_pub_addr: for priv_details in self.priv_pub_addr:
minikey = priv_details['minikey'] minikey = priv_details['minikey']
priv = priv_details['priv'] priv = priv_details['priv']
self.assertEqual(minikey, is_minikey(priv)) self.assertEqual(minikey, is_minikey(priv))
@needs_test_with_all_ecc_implementations
def test_is_compressed(self): def test_is_compressed(self):
for priv_details in self.priv_pub_addr: for priv_details in self.priv_pub_addr:
self.assertEqual(priv_details['compressed'], self.assertEqual(priv_details['compressed'],
is_compressed(priv_details['priv'])) is_compressed(priv_details['priv']))
class Test_seeds(unittest.TestCase): class Test_seeds(SequentialTestCase):
""" Test old and new seeds. """ """ Test old and new seeds. """
mnemonics = { mnemonics = {

7
lib/tests/test_dnssec.py

@ -1,11 +1,14 @@
import unittest
import dns import dns
from lib import dnssec from lib import dnssec
from . import SequentialTestCase
from .test_bitcoin import needs_test_with_all_ecc_implementations
class TestDnsSec(unittest.TestCase):
class TestDnsSec(SequentialTestCase):
@needs_test_with_all_ecc_implementations
def test_python_validate_rrsig_ecdsa(self): def test_python_validate_rrsig_ecdsa(self):
rrset = dns.rrset.from_text("getmonero.org.", 3599, 1, 48, rrset = dns.rrset.from_text("getmonero.org.", 3599, 1, 48,
"257 3 13 mdsswUyr3DPW132mOi8V9xESWE8jTo0d xCjjnopKl+GqJxpVXckHAeF+KkxLbxIL fDLUT0rAK9iUzy1L53eKGQ==", "257 3 13 mdsswUyr3DPW132mOi8V9xESWE8jTo0d xCjjnopKl+GqJxpVXckHAeF+KkxLbxIL fDLUT0rAK9iUzy1L53eKGQ==",

4
lib/tests/test_interface.py

@ -2,8 +2,10 @@ import unittest
from lib import interface from lib import interface
from . import SequentialTestCase
class TestInterface(unittest.TestCase):
class TestInterface(SequentialTestCase):
def test_match_host_name(self): def test_match_host_name(self):
self.assertTrue(interface._match_hostname('asd.fgh.com', 'asd.fgh.com')) self.assertTrue(interface._match_hostname('asd.fgh.com', 'asd.fgh.com'))

8
lib/tests/test_mnemonic.py

@ -4,8 +4,10 @@ from lib import mnemonic
from lib import old_mnemonic from lib import old_mnemonic
from lib.util import bh2u from lib.util import bh2u
from . import SequentialTestCase
class Test_NewMnemonic(unittest.TestCase):
class Test_NewMnemonic(SequentialTestCase):
def test_to_seed(self): def test_to_seed(self):
seed = mnemonic.Mnemonic.mnemonic_to_seed(mnemonic='foobar', passphrase='none') seed = mnemonic.Mnemonic.mnemonic_to_seed(mnemonic='foobar', passphrase='none')
@ -22,7 +24,7 @@ class Test_NewMnemonic(unittest.TestCase):
self.assertEqual(m.mnemonic_encode(i), seed) self.assertEqual(m.mnemonic_encode(i), seed)
class Test_OldMnemonic(unittest.TestCase): class Test_OldMnemonic(SequentialTestCase):
def test(self): def test(self):
seed = '8edad31a95e7d59f8837667510d75a4d' seed = '8edad31a95e7d59f8837667510d75a4d'
@ -31,7 +33,7 @@ class Test_OldMnemonic(unittest.TestCase):
self.assertEqual(result, words.split()) self.assertEqual(result, words.split())
self.assertEqual(old_mnemonic.mn_decode(result), seed) self.assertEqual(old_mnemonic.mn_decode(result), seed)
class Test_BIP39Checksum(unittest.TestCase): class Test_BIP39Checksum(SequentialTestCase):
def test(self): def test(self):
mnemonic = u'gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog' mnemonic = u'gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog'

6
lib/tests/test_simple_config.py

@ -8,8 +8,10 @@ import shutil
from io import StringIO from io import StringIO
from lib.simple_config import (SimpleConfig, read_user_config) from lib.simple_config import (SimpleConfig, read_user_config)
from . import SequentialTestCase
class Test_SimpleConfig(unittest.TestCase):
class Test_SimpleConfig(SequentialTestCase):
def setUp(self): def setUp(self):
super(Test_SimpleConfig, self).setUp() super(Test_SimpleConfig, self).setUp()
@ -109,7 +111,7 @@ class Test_SimpleConfig(unittest.TestCase):
self.assertEqual({"something": "a"}, result) self.assertEqual({"something": "a"}, result)
class TestUserConfig(unittest.TestCase): class TestUserConfig(SequentialTestCase):
def setUp(self): def setUp(self):
super(TestUserConfig, self).setUp() super(TestUserConfig, self).setUp()

2
lib/tests/test_storage_upgrade.py

@ -6,6 +6,8 @@ from lib.wallet import Wallet
from lib.tests.test_wallet import WalletTestCase from lib.tests.test_wallet import WalletTestCase
from . import SequentialTestCase
# TODO add other wallet types: 2fa, xpub-only # TODO add other wallet types: 2fa, xpub-only
# TODO hw wallet with client version 2.6.x (single-, and multiacc) # TODO hw wallet with client version 2.6.x (single-, and multiacc)

10
lib/tests/test_transaction.py

@ -5,12 +5,16 @@ from lib.bitcoin import TYPE_ADDRESS
from lib.keystore import xpubkey_to_address from lib.keystore import xpubkey_to_address
from lib.util import bh2u, bfh from lib.util import bh2u, bfh
from . import SequentialTestCase
from .test_bitcoin import needs_test_with_all_ecc_implementations
unsigned_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000005701ff4c53ff0488b21e03ef2afea18000000089689bff23e1e7fb2f161daa37270a97a3d8c2e537584b2d304ecb47b86d21fc021b010d3bd425f8cf2e04824bfdf1f1f5ff1d51fadd9a41f9e3fb8dd3403b1bfe00000000ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000' unsigned_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000005701ff4c53ff0488b21e03ef2afea18000000089689bff23e1e7fb2f161daa37270a97a3d8c2e537584b2d304ecb47b86d21fc021b010d3bd425f8cf2e04824bfdf1f1f5ff1d51fadd9a41f9e3fb8dd3403b1bfe00000000ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000'
signed_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000006c493046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d985012102e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae35cdb84d2f6ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000' signed_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000006c493046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d985012102e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae35cdb84d2f6ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000'
v2_blob = "0200000001191601a44a81e061502b7bfbc6eaa1cef6d1e6af5308ef96c9342f71dbf4b9b5000000006b483045022100a6d44d0a651790a477e75334adfb8aae94d6612d01187b2c02526e340a7fd6c8022028bdf7a64a54906b13b145cd5dab21a26bd4b85d6044e9b97bceab5be44c2a9201210253e8e0254b0c95776786e40984c1aa32a7d03efa6bdacdea5f421b774917d346feffffff026b20fa04000000001976a914024db2e87dd7cfd0e5f266c5f212e21a31d805a588aca0860100000000001976a91421919b94ae5cefcdf0271191459157cdb41c4cbf88aca6240700" v2_blob = "0200000001191601a44a81e061502b7bfbc6eaa1cef6d1e6af5308ef96c9342f71dbf4b9b5000000006b483045022100a6d44d0a651790a477e75334adfb8aae94d6612d01187b2c02526e340a7fd6c8022028bdf7a64a54906b13b145cd5dab21a26bd4b85d6044e9b97bceab5be44c2a9201210253e8e0254b0c95776786e40984c1aa32a7d03efa6bdacdea5f421b774917d346feffffff026b20fa04000000001976a914024db2e87dd7cfd0e5f266c5f212e21a31d805a588aca0860100000000001976a91421919b94ae5cefcdf0271191459157cdb41c4cbf88aca6240700"
signed_segwit_blob = "01000000000101b66d722484f2db63e827ebf41d02684fed0c6550e85015a6c9d41ef216a8a6f00000000000fdffffff0280c3c90100000000160014b65ce60857f7e7892b983851c2a8e3526d09e4ab64bac30400000000160014c478ebbc0ab2097706a98e10db7cf101839931c4024730440220789c7d47f876638c58d98733c30ae9821c8fa82b470285dcdf6db5994210bf9f02204163418bbc44af701212ad42d884cc613f3d3d831d2d0cc886f767cca6e0235e012103083a6dc250816d771faa60737bfe78b23ad619f6b458e0a1f1688e3a0605e79c00000000" signed_segwit_blob = "01000000000101b66d722484f2db63e827ebf41d02684fed0c6550e85015a6c9d41ef216a8a6f00000000000fdffffff0280c3c90100000000160014b65ce60857f7e7892b983851c2a8e3526d09e4ab64bac30400000000160014c478ebbc0ab2097706a98e10db7cf101839931c4024730440220789c7d47f876638c58d98733c30ae9821c8fa82b470285dcdf6db5994210bf9f02204163418bbc44af701212ad42d884cc613f3d3d831d2d0cc886f767cca6e0235e012103083a6dc250816d771faa60737bfe78b23ad619f6b458e0a1f1688e3a0605e79c00000000"
class TestBCDataStream(unittest.TestCase):
class TestBCDataStream(SequentialTestCase):
def test_compact_size(self): def test_compact_size(self):
s = transaction.BCDataStream() s = transaction.BCDataStream()
@ -51,8 +55,9 @@ class TestBCDataStream(unittest.TestCase):
self.assertEqual(s.read_bytes(4), b'r') self.assertEqual(s.read_bytes(4), b'r')
self.assertEqual(s.read_bytes(1), b'') self.assertEqual(s.read_bytes(1), b'')
class TestTransaction(unittest.TestCase): class TestTransaction(SequentialTestCase):
@needs_test_with_all_ecc_implementations
def test_tx_unsigned(self): def test_tx_unsigned(self):
expected = { expected = {
'inputs': [{ 'inputs': [{
@ -97,6 +102,7 @@ class TestTransaction(unittest.TestCase):
blob = str(tx) blob = str(tx)
self.assertEqual(transaction.deserialize(blob), expected) self.assertEqual(transaction.deserialize(blob), expected)
@needs_test_with_all_ecc_implementations
def test_tx_signed(self): def test_tx_signed(self):
expected = { expected = {
'inputs': [{ 'inputs': [{

5
lib/tests/test_util.py

@ -1,7 +1,10 @@
import unittest import unittest
from lib.util import format_satoshis, parse_URI from lib.util import format_satoshis, parse_URI
class TestUtil(unittest.TestCase): from . import SequentialTestCase
class TestUtil(SequentialTestCase):
def test_format_satoshis(self): def test_format_satoshis(self):
result = format_satoshis(1234) result = format_satoshis(1234)

4
lib/tests/test_wallet.py

@ -8,6 +8,8 @@ import json
from io import StringIO from io import StringIO
from lib.storage import WalletStorage, FINAL_SEED_VERSION from lib.storage import WalletStorage, FINAL_SEED_VERSION
from . import SequentialTestCase
class FakeSynchronizer(object): class FakeSynchronizer(object):
@ -18,7 +20,7 @@ class FakeSynchronizer(object):
self.store.append(address) self.store.append(address)
class WalletTestCase(unittest.TestCase): class WalletTestCase(SequentialTestCase):
def setUp(self): def setUp(self):
super(WalletTestCase, self).setUp() super(WalletTestCase, self).setUp()

19
lib/tests/test_wallet_vertical.py

@ -13,6 +13,8 @@ from lib.wallet import TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT
from plugins.trustedcoin import trustedcoin from plugins.trustedcoin import trustedcoin
from . import TestCaseForTestnet from . import TestCaseForTestnet
from . import SequentialTestCase
from .test_bitcoin import needs_test_with_all_ecc_implementations
class WalletIntegrityHelper: class WalletIntegrityHelper:
@ -57,8 +59,9 @@ class WalletIntegrityHelper:
# TODO passphrase/seed_extension # TODO passphrase/seed_extension
class TestWalletKeystoreAddressIntegrityForMainnet(unittest.TestCase): class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase):
@needs_test_with_all_ecc_implementations
@mock.patch.object(storage.WalletStorage, '_write') @mock.patch.object(storage.WalletStorage, '_write')
def test_electrum_seed_standard(self, mock_write): def test_electrum_seed_standard(self, mock_write):
seed_words = 'cycle rocket west magnet parrot shuffle foot correct salt library feed song' seed_words = 'cycle rocket west magnet parrot shuffle foot correct salt library feed song'
@ -78,6 +81,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(unittest.TestCase):
self.assertEqual(w.get_receiving_addresses()[0], '1NNkttn1YvVGdqBW4PR6zvc3Zx3H5owKRf') self.assertEqual(w.get_receiving_addresses()[0], '1NNkttn1YvVGdqBW4PR6zvc3Zx3H5owKRf')
self.assertEqual(w.get_change_addresses()[0], '1KSezYMhAJMWqFbVFB2JshYg69UpmEXR4D') self.assertEqual(w.get_change_addresses()[0], '1KSezYMhAJMWqFbVFB2JshYg69UpmEXR4D')
@needs_test_with_all_ecc_implementations
@mock.patch.object(storage.WalletStorage, '_write') @mock.patch.object(storage.WalletStorage, '_write')
def test_electrum_seed_segwit(self, mock_write): def test_electrum_seed_segwit(self, mock_write):
seed_words = 'bitter grass shiver impose acquire brush forget axis eager alone wine silver' seed_words = 'bitter grass shiver impose acquire brush forget axis eager alone wine silver'
@ -97,6 +101,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(unittest.TestCase):
self.assertEqual(w.get_receiving_addresses()[0], 'bc1q3g5tmkmlvxryhh843v4dz026avatc0zzr6h3af') self.assertEqual(w.get_receiving_addresses()[0], 'bc1q3g5tmkmlvxryhh843v4dz026avatc0zzr6h3af')
self.assertEqual(w.get_change_addresses()[0], 'bc1qdy94n2q5qcp0kg7v9yzwe6wvfkhnvyzje7nx2p') self.assertEqual(w.get_change_addresses()[0], 'bc1qdy94n2q5qcp0kg7v9yzwe6wvfkhnvyzje7nx2p')
@needs_test_with_all_ecc_implementations
@mock.patch.object(storage.WalletStorage, '_write') @mock.patch.object(storage.WalletStorage, '_write')
def test_electrum_seed_old(self, mock_write): def test_electrum_seed_old(self, mock_write):
seed_words = 'powerful random nobody notice nothing important anyway look away hidden message over' seed_words = 'powerful random nobody notice nothing important anyway look away hidden message over'
@ -115,6 +120,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(unittest.TestCase):
self.assertEqual(w.get_receiving_addresses()[0], '1FJEEB8ihPMbzs2SkLmr37dHyRFzakqUmo') self.assertEqual(w.get_receiving_addresses()[0], '1FJEEB8ihPMbzs2SkLmr37dHyRFzakqUmo')
self.assertEqual(w.get_change_addresses()[0], '1KRW8pH6HFHZh889VDq6fEKvmrsmApwNfe') self.assertEqual(w.get_change_addresses()[0], '1KRW8pH6HFHZh889VDq6fEKvmrsmApwNfe')
@needs_test_with_all_ecc_implementations
@mock.patch.object(storage.WalletStorage, '_write') @mock.patch.object(storage.WalletStorage, '_write')
def test_electrum_seed_2fa(self, mock_write): def test_electrum_seed_2fa(self, mock_write):
seed_words = 'kiss live scene rude gate step hip quarter bunker oxygen motor glove' seed_words = 'kiss live scene rude gate step hip quarter bunker oxygen motor glove'
@ -148,6 +154,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(unittest.TestCase):
self.assertEqual(w.get_receiving_addresses()[0], '35L8XmCDoEBKeaWRjvmZvoZvhp8BXMMMPV') self.assertEqual(w.get_receiving_addresses()[0], '35L8XmCDoEBKeaWRjvmZvoZvhp8BXMMMPV')
self.assertEqual(w.get_change_addresses()[0], '3PeZEcumRqHSPNN43hd4yskGEBdzXgY8Cy') self.assertEqual(w.get_change_addresses()[0], '3PeZEcumRqHSPNN43hd4yskGEBdzXgY8Cy')
@needs_test_with_all_ecc_implementations
@mock.patch.object(storage.WalletStorage, '_write') @mock.patch.object(storage.WalletStorage, '_write')
def test_bip39_seed_bip44_standard(self, mock_write): def test_bip39_seed_bip44_standard(self, mock_write):
seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial' seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial'
@ -166,6 +173,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(unittest.TestCase):
self.assertEqual(w.get_receiving_addresses()[0], '16j7Dqk3Z9DdTdBtHcCVLaNQy9MTgywUUo') self.assertEqual(w.get_receiving_addresses()[0], '16j7Dqk3Z9DdTdBtHcCVLaNQy9MTgywUUo')
self.assertEqual(w.get_change_addresses()[0], '1GG5bVeWgAp5XW7JLCphse14QaC4qiHyWn') self.assertEqual(w.get_change_addresses()[0], '1GG5bVeWgAp5XW7JLCphse14QaC4qiHyWn')
@needs_test_with_all_ecc_implementations
@mock.patch.object(storage.WalletStorage, '_write') @mock.patch.object(storage.WalletStorage, '_write')
def test_bip39_seed_bip49_p2sh_segwit(self, mock_write): def test_bip39_seed_bip49_p2sh_segwit(self, mock_write):
seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial' seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial'
@ -184,6 +192,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(unittest.TestCase):
self.assertEqual(w.get_receiving_addresses()[0], '35ohQTdNykjkF1Mn9nAVEFjupyAtsPAK1W') self.assertEqual(w.get_receiving_addresses()[0], '35ohQTdNykjkF1Mn9nAVEFjupyAtsPAK1W')
self.assertEqual(w.get_change_addresses()[0], '3KaBTcviBLEJajTEMstsA2GWjYoPzPK7Y7') self.assertEqual(w.get_change_addresses()[0], '3KaBTcviBLEJajTEMstsA2GWjYoPzPK7Y7')
@needs_test_with_all_ecc_implementations
@mock.patch.object(storage.WalletStorage, '_write') @mock.patch.object(storage.WalletStorage, '_write')
def test_bip39_seed_bip84_native_segwit(self, mock_write): def test_bip39_seed_bip84_native_segwit(self, mock_write):
# test case from bip84 # test case from bip84
@ -203,6 +212,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(unittest.TestCase):
self.assertEqual(w.get_receiving_addresses()[0], 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu') self.assertEqual(w.get_receiving_addresses()[0], 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu')
self.assertEqual(w.get_change_addresses()[0], 'bc1q8c6fshw2dlwun7ekn9qwf37cu2rn755upcp6el') self.assertEqual(w.get_change_addresses()[0], 'bc1q8c6fshw2dlwun7ekn9qwf37cu2rn755upcp6el')
@needs_test_with_all_ecc_implementations
@mock.patch.object(storage.WalletStorage, '_write') @mock.patch.object(storage.WalletStorage, '_write')
def test_electrum_multisig_seed_standard(self, mock_write): def test_electrum_multisig_seed_standard(self, mock_write):
seed_words = 'blast uniform dragon fiscal ensure vast young utility dinosaur abandon rookie sure' seed_words = 'blast uniform dragon fiscal ensure vast young utility dinosaur abandon rookie sure'
@ -225,6 +235,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(unittest.TestCase):
self.assertEqual(w.get_receiving_addresses()[0], '32ji3QkAgXNz6oFoRfakyD3ys1XXiERQYN') self.assertEqual(w.get_receiving_addresses()[0], '32ji3QkAgXNz6oFoRfakyD3ys1XXiERQYN')
self.assertEqual(w.get_change_addresses()[0], '36XWwEHrrVCLnhjK5MrVVGmUHghr9oWTN1') self.assertEqual(w.get_change_addresses()[0], '36XWwEHrrVCLnhjK5MrVVGmUHghr9oWTN1')
@needs_test_with_all_ecc_implementations
@mock.patch.object(storage.WalletStorage, '_write') @mock.patch.object(storage.WalletStorage, '_write')
def test_electrum_multisig_seed_segwit(self, mock_write): def test_electrum_multisig_seed_segwit(self, mock_write):
seed_words = 'snow nest raise royal more walk demise rotate smooth spirit canyon gun' seed_words = 'snow nest raise royal more walk demise rotate smooth spirit canyon gun'
@ -247,6 +258,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(unittest.TestCase):
self.assertEqual(w.get_receiving_addresses()[0], 'bc1qvzezdcv6vs5h45ugkavp896e0nde5c5lg5h0fwe2xyfhnpkxq6gq7pnwlc') self.assertEqual(w.get_receiving_addresses()[0], 'bc1qvzezdcv6vs5h45ugkavp896e0nde5c5lg5h0fwe2xyfhnpkxq6gq7pnwlc')
self.assertEqual(w.get_change_addresses()[0], 'bc1qxqf840dqswcmu7a8v82fj6ej0msx08flvuy6kngr7axstjcaq6us9hrehd') self.assertEqual(w.get_change_addresses()[0], 'bc1qxqf840dqswcmu7a8v82fj6ej0msx08flvuy6kngr7axstjcaq6us9hrehd')
@needs_test_with_all_ecc_implementations
@mock.patch.object(storage.WalletStorage, '_write') @mock.patch.object(storage.WalletStorage, '_write')
def test_bip39_multisig_seed_bip45_standard(self, mock_write): def test_bip39_multisig_seed_bip45_standard(self, mock_write):
seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial' seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial'
@ -269,6 +281,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(unittest.TestCase):
self.assertEqual(w.get_receiving_addresses()[0], '3JPTQ2nitVxXBJ1yhMeDwH6q417UifE3bN') self.assertEqual(w.get_receiving_addresses()[0], '3JPTQ2nitVxXBJ1yhMeDwH6q417UifE3bN')
self.assertEqual(w.get_change_addresses()[0], '3FGyDuxgUDn2pSZe5xAJH1yUwSdhzDMyEE') self.assertEqual(w.get_change_addresses()[0], '3FGyDuxgUDn2pSZe5xAJH1yUwSdhzDMyEE')
@needs_test_with_all_ecc_implementations
@mock.patch.object(storage.WalletStorage, '_write') @mock.patch.object(storage.WalletStorage, '_write')
def test_bip39_multisig_seed_p2sh_segwit(self, mock_write): def test_bip39_multisig_seed_p2sh_segwit(self, mock_write):
# bip39 seed: pulse mixture jazz invite dune enrich minor weapon mosquito flight fly vapor # bip39 seed: pulse mixture jazz invite dune enrich minor weapon mosquito flight fly vapor
@ -332,6 +345,7 @@ class TestWalletSending(TestCaseForTestnet):
ks = keystore.from_seed(seed_words, '', False) ks = keystore.from_seed(seed_words, '', False)
return WalletIntegrityHelper.create_standard_wallet(ks, gap_limit=2) return WalletIntegrityHelper.create_standard_wallet(ks, gap_limit=2)
@needs_test_with_all_ecc_implementations
@mock.patch.object(storage.WalletStorage, '_write') @mock.patch.object(storage.WalletStorage, '_write')
def test_sending_between_p2wpkh_and_compressed_p2pkh(self, mock_write): def test_sending_between_p2wpkh_and_compressed_p2pkh(self, mock_write):
wallet1 = self.create_standard_wallet_from_seed('bitter grass shiver impose acquire brush forget axis eager alone wine silver') wallet1 = self.create_standard_wallet_from_seed('bitter grass shiver impose acquire brush forget axis eager alone wine silver')
@ -382,6 +396,7 @@ class TestWalletSending(TestCaseForTestnet):
self.assertEqual((0, funding_output_value - 250000 - 5000 + 100000, 0), wallet1.get_balance()) self.assertEqual((0, funding_output_value - 250000 - 5000 + 100000, 0), wallet1.get_balance())
self.assertEqual((0, 250000 - 5000 - 100000, 0), wallet2.get_balance()) self.assertEqual((0, 250000 - 5000 - 100000, 0), wallet2.get_balance())
@needs_test_with_all_ecc_implementations
@mock.patch.object(storage.WalletStorage, '_write') @mock.patch.object(storage.WalletStorage, '_write')
def test_sending_between_p2sh_2of3_and_uncompressed_p2pkh(self, mock_write): def test_sending_between_p2sh_2of3_and_uncompressed_p2pkh(self, mock_write):
wallet1a = WalletIntegrityHelper.create_multisig_wallet( wallet1a = WalletIntegrityHelper.create_multisig_wallet(
@ -451,6 +466,7 @@ class TestWalletSending(TestCaseForTestnet):
self.assertEqual((0, funding_output_value - 370000 - 5000 + 100000, 0), wallet1a.get_balance()) self.assertEqual((0, funding_output_value - 370000 - 5000 + 100000, 0), wallet1a.get_balance())
self.assertEqual((0, 370000 - 5000 - 100000, 0), wallet2.get_balance()) self.assertEqual((0, 370000 - 5000 - 100000, 0), wallet2.get_balance())
@needs_test_with_all_ecc_implementations
@mock.patch.object(storage.WalletStorage, '_write') @mock.patch.object(storage.WalletStorage, '_write')
def test_sending_between_p2wsh_2of3_and_p2wsh_p2sh_2of2(self, mock_write): def test_sending_between_p2wsh_2of3_and_p2wsh_p2sh_2of2(self, mock_write):
wallet1a = WalletIntegrityHelper.create_multisig_wallet( wallet1a = WalletIntegrityHelper.create_multisig_wallet(
@ -538,6 +554,7 @@ class TestWalletSending(TestCaseForTestnet):
self.assertEqual((0, funding_output_value - 165000 - 5000 + 100000, 0), wallet1a.get_balance()) self.assertEqual((0, funding_output_value - 165000 - 5000 + 100000, 0), wallet1a.get_balance())
self.assertEqual((0, 165000 - 5000 - 100000, 0), wallet2a.get_balance()) self.assertEqual((0, 165000 - 5000 - 100000, 0), wallet2a.get_balance())
@needs_test_with_all_ecc_implementations
@mock.patch.object(storage.WalletStorage, '_write') @mock.patch.object(storage.WalletStorage, '_write')
def test_sending_between_p2sh_1of2_and_p2wpkh_p2sh(self, mock_write): def test_sending_between_p2sh_1of2_and_p2wpkh_p2sh(self, mock_write):
wallet1a = WalletIntegrityHelper.create_multisig_wallet( wallet1a = WalletIntegrityHelper.create_multisig_wallet(

34
lib/transaction.py

@ -31,6 +31,7 @@ from typing import Sequence, Union
from .util import print_error, profiler from .util import print_error, profiler
from . import ecc
from . import bitcoin from . import bitcoin
from .bitcoin import * from .bitcoin import *
import struct import struct
@ -653,18 +654,18 @@ class Transaction:
if sig in sigs1: if sig in sigs1:
continue continue
pre_hash = Hash(bfh(self.serialize_preimage(i))) pre_hash = Hash(bfh(self.serialize_preimage(i)))
# der to string sig_string = ecc.sig_string_from_der_sig(bfh(sig[:-2]))
order = ecdsa.ecdsa.generator_secp256k1.order()
r, s = ecdsa.util.sigdecode_der(bfh(sig[:-2]), order)
sig_string = ecdsa.util.sigencode_string(r, s, order)
compressed = True
for recid in range(4): for recid in range(4):
public_key = MyVerifyingKey.from_signature(sig_string, recid, pre_hash, curve = SECP256k1) try:
pubkey = bh2u(point_to_ser(public_key.pubkey.point, compressed)) public_key = ecc.ECPubkey.from_sig_string(sig_string, recid, pre_hash)
if pubkey in pubkeys: except ecc.InvalidECPointException:
public_key.verify_digest(sig_string, pre_hash, sigdecode = ecdsa.util.sigdecode_string) # the point might not be on the curve for some recid values
j = pubkeys.index(pubkey) continue
print_error("adding sig", i, j, pubkey, sig) pubkey_hex = public_key.get_public_key_hex(compressed=True)
if pubkey_hex in pubkeys:
public_key.verify_message_hash(sig_string, pre_hash)
j = pubkeys.index(pubkey_hex)
print_error("adding sig", i, j, pubkey_hex, sig)
self.add_signature_to_txin(self._inputs[i], j, sig) self.add_signature_to_txin(self._inputs[i], j, sig)
#self._inputs[i]['x_pubkeys'][j] = pubkey #self._inputs[i]['x_pubkeys'][j] = pubkey
break break
@ -1067,7 +1068,7 @@ class Transaction:
if x_pubkey in keypairs.keys(): if x_pubkey in keypairs.keys():
print_error("adding signature for", x_pubkey) print_error("adding signature for", x_pubkey)
sec, compressed = keypairs.get(x_pubkey) sec, compressed = keypairs.get(x_pubkey)
pubkey = public_key_from_private_key(sec, compressed) pubkey = ecc.ECPrivkey(sec).get_public_key_hex(compressed=compressed)
# add signature # add signature
sig = self.sign_txin(i, sec) sig = self.sign_txin(i, sec)
self.add_signature_to_txin(txin, j, sig) self.add_signature_to_txin(txin, j, sig)
@ -1079,13 +1080,8 @@ class Transaction:
def sign_txin(self, txin_index, privkey_bytes): def sign_txin(self, txin_index, privkey_bytes):
pre_hash = Hash(bfh(self.serialize_preimage(txin_index))) pre_hash = Hash(bfh(self.serialize_preimage(txin_index)))
pkey = regenerate_key(privkey_bytes) privkey = ecc.ECPrivkey(privkey_bytes)
secexp = pkey.secret sig = privkey.sign_transaction(pre_hash)
private_key = bitcoin.MySigningKey.from_secret_exponent(secexp, curve=SECP256k1)
public_key = private_key.get_verifying_key()
sig = private_key.sign_digest_deterministic(pre_hash, hashfunc=hashlib.sha256, sigencode=ecdsa.util.sigencode_der)
if not public_key.verify_digest(sig, pre_hash, sigdecode=ecdsa.util.sigdecode_der):
raise Exception('Sanity check verifying our own signature failed.')
sig = bh2u(sig) + '01' sig = bh2u(sig) + '01'
return sig return sig

2
lib/wallet.py

@ -114,7 +114,7 @@ def append_utxos_to_inputs(inputs, network, pubkey, txin_type, imax):
def sweep_preparations(privkeys, network, imax=100): def sweep_preparations(privkeys, network, imax=100):
def find_utxos_for_privkey(txin_type, privkey, compressed): def find_utxos_for_privkey(txin_type, privkey, compressed):
pubkey = bitcoin.public_key_from_private_key(privkey, compressed) pubkey = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
append_utxos_to_inputs(inputs, network, pubkey, txin_type, imax) append_utxos_to_inputs(inputs, network, pubkey, txin_type, imax)
keypairs[pubkey] = privkey, compressed keypairs[pubkey] = privkey, compressed
inputs = [] inputs = []

9
plugins/cosigner_pool/qt.py

@ -30,7 +30,7 @@ from PyQt5.QtGui import *
from PyQt5.QtCore import * from PyQt5.QtCore import *
from PyQt5.QtWidgets import QPushButton from PyQt5.QtWidgets import QPushButton
from electrum import bitcoin, util, keystore from electrum import bitcoin, util, keystore, ecc
from electrum import transaction from electrum import transaction
from electrum.plugins import BasePlugin, hook from electrum.plugins import BasePlugin, hook
from electrum.i18n import _ from electrum.i18n import _
@ -174,7 +174,8 @@ class Plugin(BasePlugin):
if not self.cosigner_can_sign(tx, xpub): if not self.cosigner_can_sign(tx, xpub):
continue continue
raw_tx_bytes = bfh(str(tx)) raw_tx_bytes = bfh(str(tx))
message = bitcoin.encrypt_message(raw_tx_bytes, bh2u(K)).decode('ascii') public_key = ecc.ECPubkey(K)
message = public_key.encrypt_message(raw_tx_bytes).decode('ascii')
try: try:
server.put(_hash, message) server.put(_hash, message)
except Exception as e: except Exception as e:
@ -214,8 +215,8 @@ class Plugin(BasePlugin):
if not xprv: if not xprv:
return return
try: try:
k = bh2u(bitcoin.deserialize_xprv(xprv)[-1]) k = bitcoin.deserialize_xprv(xprv)[-1]
EC = bitcoin.EC_KEY(bfh(k)) EC = ecc.ECPrivkey(k)
message = bh2u(EC.decrypt_message(message)) message = bh2u(EC.decrypt_message(message))
except Exception as e: except Exception as e:
traceback.print_exc(file=sys.stdout) traceback.print_exc(file=sys.stdout)

34
plugins/digitalbitbox/digitalbitbox.py

@ -5,8 +5,11 @@
try: try:
import electrum import electrum
from electrum.bitcoin import TYPE_ADDRESS, push_script, var_int, msg_magic, Hash, verify_message, pubkey_from_signature, point_to_ser, public_key_to_p2pkh, EncodeAES, DecodeAES, MyVerifyingKey, is_address from electrum.crypto import Hash, EncodeAES, DecodeAES
from electrum.bitcoin import serialize_xpub, deserialize_xpub from electrum.bitcoin import (TYPE_ADDRESS, push_script, var_int, public_key_to_p2pkh, is_address,
serialize_xpub, deserialize_xpub)
from electrum import ecc
from electrum.ecc import msg_magic
from electrum.wallet import Standard_Wallet from electrum.wallet import Standard_Wallet
from electrum import constants from electrum import constants
from electrum.transaction import Transaction from electrum.transaction import Transaction
@ -27,9 +30,6 @@ try:
import base64 import base64
import os import os
import sys import sys
from ecdsa.ecdsa import generator_secp256k1
from ecdsa.util import sigencode_der
from ecdsa.curves import SECP256k1
DIGIBOX = True DIGIBOX = True
except ImportError as e: except ImportError as e:
DIGIBOX = False DIGIBOX = False
@ -476,19 +476,21 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
if 'recid' in reply['sign'][0]: if 'recid' in reply['sign'][0]:
# firmware > v2.1.1 # firmware > v2.1.1
sig = bytes([27 + int(reply['sign'][0]['recid'], 16) + 4]) + binascii.unhexlify(reply['sign'][0]['sig']) sig_string = binascii.unhexlify(reply['sign'][0]['sig'])
pk, compressed = pubkey_from_signature(sig, msg_hash) recid = int(reply['sign'][0]['recid'], 16)
pk = point_to_ser(pk.pubkey.point, compressed) sig = ecc.construct_sig65(sig_string, recid, True)
addr = public_key_to_p2pkh(pk) pubkey, compressed = ecc.ECPubkey.from_signature65(sig, msg_hash)
if verify_message(addr, sig, message) is False: addr = public_key_to_p2pkh(pubkey.get_public_key_bytes(compressed=compressed))
if ecc.verify_message_with_address(addr, sig, message) is False:
raise Exception(_("Could not sign message")) raise Exception(_("Could not sign message"))
elif 'pubkey' in reply['sign'][0]: elif 'pubkey' in reply['sign'][0]:
# firmware <= v2.1.1 # firmware <= v2.1.1
for i in range(4): for recid in range(4):
sig = bytes([27 + i + 4]) + binascii.unhexlify(reply['sign'][0]['sig']) sig_string = binascii.unhexlify(reply['sign'][0]['sig'])
sig = ecc.construct_sig65(sig_string, recid, True)
try: try:
addr = public_key_to_p2pkh(binascii.unhexlify(reply['sign'][0]['pubkey'])) addr = public_key_to_p2pkh(binascii.unhexlify(reply['sign'][0]['pubkey']))
if verify_message(addr, sig, message): if ecc.verify_message_with_address(addr, sig, message):
break break
except Exception: except Exception:
continue continue
@ -634,8 +636,8 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
recid = int(signed['recid'], 16) recid = int(signed['recid'], 16)
s = binascii.unhexlify(signed['sig']) s = binascii.unhexlify(signed['sig'])
h = inputhasharray[i] h = inputhasharray[i]
pk = MyVerifyingKey.from_signature(s, recid, h, curve = SECP256k1) pk = ecc.ECPubkey.from_sig_string(s, recid, h)
pk = to_hexstr(point_to_ser(pk.pubkey.point, True)) pk = pk.get_public_key_hex(compressed=True)
elif 'pubkey' in signed: elif 'pubkey' in signed:
# firmware <= v2.1.1 # firmware <= v2.1.1
pk = signed['pubkey'] pk = signed['pubkey']
@ -643,7 +645,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
continue continue
sig_r = int(signed['sig'][:64], 16) sig_r = int(signed['sig'][:64], 16)
sig_s = int(signed['sig'][64:], 16) sig_s = int(signed['sig'][64:], 16)
sig = sigencode_der(sig_r, sig_s, generator_secp256k1.order()) sig = ecc.der_sig_from_r_and_s(sig_r, sig_s)
sig = to_hexstr(sig) + '01' sig = to_hexstr(sig) + '01'
Transaction.add_signature_to_txin(txin, ii, sig) Transaction.add_signature_to_txin(txin, ii, sig)
tx._inputs[i] = txin tx._inputs[i] = txin

4
plugins/trustedcoin/trustedcoin.py

@ -31,7 +31,7 @@ from urllib.parse import urljoin
from urllib.parse import quote from urllib.parse import quote
import electrum import electrum
from electrum import bitcoin from electrum import bitcoin, ecc
from electrum import constants from electrum import constants
from electrum import keystore from electrum import keystore
from electrum.bitcoin import * from electrum.bitcoin import *
@ -593,7 +593,7 @@ class TrustedCoinPlugin(BasePlugin):
def f(xprv): def f(xprv):
_, _, _, _, c, k = deserialize_xprv(xprv) _, _, _, _, c, k = deserialize_xprv(xprv)
pk = bip32_private_key([0, 0], k, c) pk = bip32_private_key([0, 0], k, c)
key = regenerate_key(pk) key = ecc.ECPrivkey(pk)
sig = key.sign_message(message, True) sig = key.sign_message(message, True)
return base64.b64encode(sig).decode() return base64.b64encode(sig).decode()

11
setup.py

@ -40,13 +40,18 @@ if platform.system() in ['Linux', 'FreeBSD', 'DragonFly']:
(os.path.join(usr_share, icons_dirname), ['icons/electrum.png']) (os.path.join(usr_share, icons_dirname), ['icons/electrum.png'])
] ]
extras_require = {
'hardware': requirements_hw,
'fast': ['pycryptodomex'],
}
extras_require['full'] = extras_require['hardware'] + extras_require['fast']
setup( setup(
name="Electrum", name="Electrum",
version=version.ELECTRUM_VERSION, version=version.ELECTRUM_VERSION,
install_requires=requirements, install_requires=requirements,
extras_require={ extras_require=extras_require,
'full': requirements_hw + ['pycryptodomex'],
},
packages=[ packages=[
'electrum', 'electrum',
'electrum_gui', 'electrum_gui',

2
tox.ini

@ -8,3 +8,5 @@ deps=
commands= commands=
coverage run --source=lib -m py.test -v coverage run --source=lib -m py.test -v
coverage report coverage report
extras=
fast

Loading…
Cancel
Save