refactor: get rid of libsecp (#49)

* refactor: get rid of libsecp

* fixup!
This commit is contained in:
dni ⚡ 2025-11-04 10:30:00 +01:00 committed by GitHub
parent e66f997853
commit c871a42a85
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 20 additions and 41 deletions

View file

@ -5,7 +5,7 @@ from enum import IntEnum
from hashlib import sha256 from hashlib import sha256
from typing import Optional from typing import Optional
from secp256k1 import PublicKey import coincurve
from .message_type import ClientMessageType from .message_type import ClientMessageType
@ -75,12 +75,8 @@ class Event:
def verify(self) -> bool: def verify(self) -> bool:
assert self.public_key assert self.public_key
assert self.signature assert self.signature
pub_key = PublicKey( pub_key = coincurve.PublicKeyXOnly(bytes.fromhex(self.public_key))
bytes.fromhex("02" + self.public_key), True return pub_key.verify(bytes.fromhex(self.signature), bytes.fromhex(self.id))
) # add 02 for schnorr (bip340)
return pub_key.schnorr_verify(
bytes.fromhex(self.id), bytes.fromhex(self.signature), None, raw=True
)
def to_message(self) -> str: def to_message(self) -> str:
return json.dumps( return json.dumps(

View file

@ -1,9 +1,7 @@
import base64 import base64
import secrets import secrets
from typing import Optional
import secp256k1 import coincurve
from cffi import FFI
from cryptography.hazmat.primitives import padding from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
@ -23,15 +21,13 @@ class PublicKey:
return self.raw_bytes.hex() return self.raw_bytes.hex()
def verify_signed_message_hash(self, message_hash: str, sig: str) -> bool: def verify_signed_message_hash(self, message_hash: str, sig: str) -> bool:
pk = secp256k1.PublicKey(b"\x02" + self.raw_bytes, True) pk = coincurve.PublicKeyXOnly(self.raw_bytes)
return pk.schnorr_verify( return pk.verify(bytes.fromhex(sig), bytes.fromhex(message_hash))
bytes.fromhex(message_hash), bytes.fromhex(sig), None, True
)
@classmethod @classmethod
def from_npub(cls, npub: str): def from_npub(cls, npub: str):
"""Load a PublicKey from its bech32/npub form""" """Load a PublicKey from its bech32/npub form"""
hrp, data, spec = bech32_decode(npub) _, data, _ = bech32_decode(npub)
raw_data = convertbits(data, 5, 8) raw_data = convertbits(data, 5, 8)
assert raw_data assert raw_data
raw_public_key = raw_data[:-1] raw_public_key = raw_data[:-1]
@ -39,20 +35,20 @@ class PublicKey:
class PrivateKey: 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: if raw_secret is not None:
self.raw_secret = raw_secret self.raw_secret = raw_secret
else: else:
self.raw_secret = secrets.token_bytes(32) self.raw_secret = secrets.token_bytes(32)
sk = secp256k1.PrivateKey(self.raw_secret) sk = coincurve.PrivateKey(self.raw_secret)
assert sk.pubkey assert sk.public_key
self.public_key = PublicKey(sk.pubkey.serialize()[1:]) self.public_key = PublicKey(sk.public_key.format()[1:])
@classmethod @classmethod
def from_nsec(cls, nsec: str): def from_nsec(cls, nsec: str):
"""Load a PrivateKey from its bech32/nsec form""" """Load a PrivateKey from its bech32/nsec form"""
hrp, data, spec = bech32_decode(nsec) _, data, _ = bech32_decode(nsec)
raw_data = convertbits(data, 5, 8) raw_data = convertbits(data, 5, 8)
assert raw_data assert raw_data
raw_secret = raw_data[:-1] raw_secret = raw_data[:-1]
@ -66,12 +62,13 @@ class PrivateKey:
return self.raw_secret.hex() return self.raw_secret.hex()
def tweak_add(self, scalar: bytes) -> bytes: def tweak_add(self, scalar: bytes) -> bytes:
sk = secp256k1.PrivateKey(self.raw_secret) sk = coincurve.PrivateKey(self.raw_secret)
return sk.tweak_add(scalar) return sk.add(scalar).to_der()
def compute_shared_secret(self, public_key_hex: str) -> bytes: def compute_shared_secret(self, public_key_hex: str) -> bytes:
pk = secp256k1.PublicKey(bytes.fromhex("02" + public_key_hex), True) pk = coincurve.PublicKey(bytes.fromhex("02" + public_key_hex))
return pk.ecdh(self.raw_secret, hashfn=copy_x) sk = coincurve.PrivateKey(self.raw_secret)
return sk.ecdh(pk.format())
def encrypt_message(self, message: str, public_key_hex: str) -> str: def encrypt_message(self, message: str, public_key_hex: str) -> str:
padder = padding.PKCS7(128).padder() padder = padding.PKCS7(128).padder()
@ -116,8 +113,8 @@ class PrivateKey:
return unpadded_data.decode() return unpadded_data.decode()
def sign_message_hash(self, message_hash: bytes) -> str: def sign_message_hash(self, message_hash: bytes) -> str:
sk = secp256k1.PrivateKey(self.raw_secret) sk = coincurve.PrivateKey(self.raw_secret)
sig = sk.schnorr_sign(message_hash, None, raw=True) sig = sk.sign_schnorr(message_hash)
return sig.hex() return sig.hex()
def sign_event(self, event: Event) -> None: def sign_event(self, event: Event) -> None:
@ -131,9 +128,7 @@ class PrivateKey:
return self.raw_secret == other.raw_secret return self.raw_secret == other.raw_secret
def mine_vanity_key( def mine_vanity_key(prefix: str | None = None, suffix: str | None = None) -> PrivateKey:
prefix: Optional[str] = None, suffix: Optional[str] = None
) -> PrivateKey:
if prefix is None and suffix is None: if prefix is None and suffix is None:
raise ValueError("Expected at least one of 'prefix' or 'suffix' arguments") raise ValueError("Expected at least one of 'prefix' or 'suffix' arguments")
@ -149,14 +144,3 @@ def mine_vanity_key(
break break
return sk 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

View file

@ -29,7 +29,6 @@ plugins = ["pydantic.mypy"]
[[tool.mypy.overrides]] [[tool.mypy.overrides]]
module = [ module = [
"nostr.*", "nostr.*",
"secp256k1.*",
] ]
follow_imports = "skip" follow_imports = "skip"
ignore_missing_imports = "True" ignore_missing_imports = "True"