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