diff --git a/lbry/wallet/bip32.py b/lbry/wallet/bip32.py index acf0190e03..f9d96164e9 100644 --- a/lbry/wallet/bip32.py +++ b/lbry/wallet/bip32.py @@ -92,6 +92,10 @@ def __init__(self, ledger, pubkey, chain_code, n, depth, parent=None): else: self.verifying_key = self._verifying_key_from_pubkey(pubkey) + @classmethod + def from_compressed(cls, public_key_bytes, ledger=None) -> 'PublicKey': + return cls(ledger, public_key_bytes, bytes((0,)*32), 0, 0) + @classmethod def _verifying_key_from_pubkey(cls, pubkey): """ Converts a 33-byte compressed pubkey into an coincurve.PublicKey object. """ @@ -137,9 +141,35 @@ def extended_key(self): self.pubkey_bytes ) - def verify(self, signature, data): - """ Produce a signature for piece of data by double hashing it and signing the hash. """ - return self.verifying_key.verify(signature, data, hasher=double_sha256) + def verify(self, signature, digest) -> bool: + """ Verify that a signature is valid for a 32 byte digest. """ + + if len(signature) != 64: + raise ValueError('Signature must be 64 bytes long.') + + if len(digest) != 32: + raise ValueError('Digest must be 32 bytes long.') + + key = self.verifying_key + + raw_signature = libsecp256k1_ffi.new('secp256k1_ecdsa_signature *') + + parsed = libsecp256k1.secp256k1_ecdsa_signature_parse_compact( + key.context.ctx, raw_signature, signature + ) + assert parsed == 1 + + normalized_signature = libsecp256k1_ffi.new('secp256k1_ecdsa_signature *') + + libsecp256k1.secp256k1_ecdsa_signature_normalize( + key.context.ctx, normalized_signature, raw_signature + ) + + verified = libsecp256k1.secp256k1_ecdsa_verify( + key.context.ctx, normalized_signature, digest, key.public_key + ) + + return bool(verified) class PrivateKey(_KeyBase): diff --git a/lbry/wallet/transaction.py b/lbry/wallet/transaction.py index 67839aa583..a6f3bf376e 100644 --- a/lbry/wallet/transaction.py +++ b/lbry/wallet/transaction.py @@ -4,13 +4,6 @@ from binascii import hexlify, unhexlify from typing import List, Iterable, Optional, Tuple -from coincurve import PublicKey as cPublicKey -from coincurve.ecdsa import deserialize_compact, cdata_to_der -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.primitives.asymmetric.utils import Prehashed -from cryptography.exceptions import InvalidSignature - from lbry.error import InsufficientFundsError from lbry.crypto.hash import hash160, sha256 from lbry.crypto.base58 import Base58 @@ -25,7 +18,7 @@ from .bcd_data_stream import BCDataStream from .hash import TXRef, TXRefImmutable from .util import ReadOnlyList -from .bip32 import PrivateKey +from .bip32 import PrivateKey, PublicKey if typing.TYPE_CHECKING: from lbry.wallet.account import Account @@ -426,18 +419,9 @@ def get_signature_digest(self, ledger): @staticmethod def is_signature_valid(signature, digest, public_key_bytes): - signature = cdata_to_der(deserialize_compact(signature)) - public_key = cPublicKey(public_key_bytes) - is_valid = public_key.verify(signature, digest, None) - if not is_valid: # try old way - # ytsync signed claims don't seem to validate with coincurve - try: - pk = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256K1(), public_key_bytes) - pk.verify(signature, digest, ec.ECDSA(Prehashed(hashes.SHA256()))) - return True - except (ValueError, InvalidSignature): - pass - return is_valid + return PublicKey\ + .from_compressed(public_key_bytes)\ + .verify(signature, digest) def is_signed_by(self, channel: 'Output', ledger=None): return self.is_signature_valid(