SomberNight
7 years ago
28 changed files with 1086 additions and 538 deletions
@ -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() |
@ -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 |
@ -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() |
Loading…
Reference in new issue