Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions nostr/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from hashlib import sha256
from typing import Optional

from secp256k1 import PublicKey
import coincurve

from .message_type import ClientMessageType

Expand Down Expand Up @@ -75,12 +75,8 @@ def add_event_ref(self, event_id: str):
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(
Expand Down
50 changes: 17 additions & 33 deletions nostr/key.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -23,36 +21,34 @@ def hex(self) -> str:
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]
return cls(bytes(raw_public_key))


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]
Expand All @@ -66,12 +62,13 @@ def hex(self) -> str:
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()
Expand Down Expand Up @@ -116,8 +113,8 @@ def decrypt_message(self, encoded_message: str, public_key_hex: str) -> str:
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:
Expand All @@ -131,9 +128,7 @@ def __eq__(self, other):
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")

Expand All @@ -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
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ plugins = ["pydantic.mypy"]
[[tool.mypy.overrides]]
module = [
"nostr.*",
"secp256k1.*",
]
follow_imports = "skip"
ignore_missing_imports = "True"
Expand Down