diff --git a/nostr/event.py b/nostr/event.py index 3abb018..994c0f4 100644 --- a/nostr/event.py +++ b/nostr/event.py @@ -5,7 +5,7 @@ from enum import IntEnum from hashlib import sha256 from typing import Optional -from secp256k1 import PublicKey +import coincurve from .message_type import ClientMessageType @@ -75,12 +75,8 @@ class Event: def verify(self) -> bool: assert self.public_key assert self.signature - pub_key = PublicKey( - bytes.fromhex("02" + self.public_key), True - ) # add 02 for schnorr (bip340) - return pub_key.schnorr_verify( - bytes.fromhex(self.id), bytes.fromhex(self.signature), None, raw=True - ) + pub_key = coincurve.PublicKeyXOnly(bytes.fromhex(self.public_key)) + return pub_key.verify(bytes.fromhex(self.signature), bytes.fromhex(self.id)) def to_message(self) -> str: return json.dumps( diff --git a/nostr/key.py b/nostr/key.py index 471f646..f7b4e81 100644 --- a/nostr/key.py +++ b/nostr/key.py @@ -1,9 +1,7 @@ import base64 import secrets -from typing import Optional -import secp256k1 -from cffi import FFI +import coincurve from cryptography.hazmat.primitives import padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes @@ -23,15 +21,13 @@ class PublicKey: return self.raw_bytes.hex() def verify_signed_message_hash(self, message_hash: str, sig: str) -> bool: - pk = secp256k1.PublicKey(b"\x02" + self.raw_bytes, True) - return pk.schnorr_verify( - bytes.fromhex(message_hash), bytes.fromhex(sig), None, True - ) + pk = coincurve.PublicKeyXOnly(self.raw_bytes) + return pk.verify(bytes.fromhex(sig), bytes.fromhex(message_hash)) @classmethod def from_npub(cls, npub: str): """Load a PublicKey from its bech32/npub form""" - hrp, data, spec = bech32_decode(npub) + _, data, _ = bech32_decode(npub) raw_data = convertbits(data, 5, 8) assert raw_data raw_public_key = raw_data[:-1] @@ -39,20 +35,20 @@ class PublicKey: class PrivateKey: - def __init__(self, raw_secret: Optional[bytes] = None) -> None: + def __init__(self, raw_secret: bytes | None = None) -> None: if raw_secret is not None: self.raw_secret = raw_secret else: self.raw_secret = secrets.token_bytes(32) - sk = secp256k1.PrivateKey(self.raw_secret) - assert sk.pubkey - self.public_key = PublicKey(sk.pubkey.serialize()[1:]) + sk = coincurve.PrivateKey(self.raw_secret) + assert sk.public_key + self.public_key = PublicKey(sk.public_key.format()[1:]) @classmethod def from_nsec(cls, nsec: str): """Load a PrivateKey from its bech32/nsec form""" - hrp, data, spec = bech32_decode(nsec) + _, data, _ = bech32_decode(nsec) raw_data = convertbits(data, 5, 8) assert raw_data raw_secret = raw_data[:-1] @@ -66,12 +62,13 @@ class PrivateKey: return self.raw_secret.hex() def tweak_add(self, scalar: bytes) -> bytes: - sk = secp256k1.PrivateKey(self.raw_secret) - return sk.tweak_add(scalar) + sk = coincurve.PrivateKey(self.raw_secret) + return sk.add(scalar).to_der() def compute_shared_secret(self, public_key_hex: str) -> bytes: - pk = secp256k1.PublicKey(bytes.fromhex("02" + public_key_hex), True) - return pk.ecdh(self.raw_secret, hashfn=copy_x) + pk = coincurve.PublicKey(bytes.fromhex("02" + public_key_hex)) + sk = coincurve.PrivateKey(self.raw_secret) + return sk.ecdh(pk.format()) def encrypt_message(self, message: str, public_key_hex: str) -> str: padder = padding.PKCS7(128).padder() @@ -116,8 +113,8 @@ class PrivateKey: return unpadded_data.decode() def sign_message_hash(self, message_hash: bytes) -> str: - sk = secp256k1.PrivateKey(self.raw_secret) - sig = sk.schnorr_sign(message_hash, None, raw=True) + sk = coincurve.PrivateKey(self.raw_secret) + sig = sk.sign_schnorr(message_hash) return sig.hex() def sign_event(self, event: Event) -> None: @@ -131,9 +128,7 @@ class PrivateKey: return self.raw_secret == other.raw_secret -def mine_vanity_key( - prefix: Optional[str] = None, suffix: Optional[str] = None -) -> PrivateKey: +def mine_vanity_key(prefix: str | None = None, suffix: str | None = None) -> PrivateKey: if prefix is None and suffix is None: raise ValueError("Expected at least one of 'prefix' or 'suffix' arguments") @@ -149,14 +144,3 @@ def mine_vanity_key( break return sk - - -ffi = FFI() - - -@ffi.callback( - "int (unsigned char *, const unsigned char *, const unsigned char *, void *)" -) -def copy_x(output, x32, y32, data): - ffi.memmove(output, x32, 32) - return 1 diff --git a/pyproject.toml b/pyproject.toml index 8299c91..bb9cbc7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,6 @@ plugins = ["pydantic.mypy"] [[tool.mypy.overrides]] module = [ "nostr.*", - "secp256k1.*", ] follow_imports = "skip" ignore_missing_imports = "True"