Nostr Python
Executable
Jan 31, 2026
Sign Nostr Events
Create and sign Nostr events using Schnorr signatures
#events
#signatures
#schnorr
#nip-01
Overview
Sign Nostr events with Schnorr signatures per NIP-01. Events are the fundamental data structure in Nostr - every post, reaction, and profile update is a signed event.
The Code
"""
Nostr Event Signer
Create and sign events using BIP-340 Schnorr signatures
Requirements: hashlib (built-in)
Note: This is a simplified implementation for educational purposes.
For production, use pynostr or nostr-sdk.
"""
import hashlib
import json
import time
import secrets
from typing import List, Optional, Tuple
# secp256k1 curve parameters
P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
G = (
0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
)
def modinv(a: int, m: int) -> int:
"""Modular multiplicative inverse."""
def extended_gcd(a, b):
if a == 0:
return b, 0, 1
g, x, y = extended_gcd(b % a, a)
return g, y - (b // a) * x, x
if a < 0:
a = a % m
g, x, _ = extended_gcd(a, m)
return x % m
def point_add(p1, p2):
"""Add two points on secp256k1."""
if p1 is None:
return p2
if p2 is None:
return p1
x1, y1 = p1
x2, y2 = p2
if x1 == x2 and y1 != y2:
return None
if x1 == x2:
m = (3 * x1 * x1) * modinv(2 * y1, P) % P
else:
m = (y2 - y1) * modinv(x2 - x1, P) % P
x3 = (m * m - x1 - x2) % P
y3 = (m * (x1 - x3) - y1) % P
return (x3, y3)
def scalar_mult(k: int, point) -> Tuple[int, int]:
"""Multiply a point by a scalar."""
result = None
addend = point
while k:
if k & 1:
result = point_add(result, addend)
addend = point_add(addend, addend)
k >>= 1
return result
def lift_x(x: int) -> Tuple[int, int]:
"""Lift x-coordinate to point with even y."""
y_sq = (pow(x, 3, P) + 7) % P
y = pow(y_sq, (P + 1) // 4, P)
if pow(y, 2, P) != y_sq:
return None
return (x, y if y % 2 == 0 else P - y)
def tagged_hash(tag: str, msg: bytes) -> bytes:
"""BIP-340 tagged hash."""
tag_hash = hashlib.sha256(tag.encode()).digest()
return hashlib.sha256(tag_hash + tag_hash + msg).digest()
def schnorr_sign(msg: bytes, seckey: bytes) -> bytes:
"""
Sign message with BIP-340 Schnorr signature.
Args:
msg: 32-byte message to sign
seckey: 32-byte private key
Returns:
64-byte signature
"""
d = int.from_bytes(seckey, 'big')
if not (0 < d < N):
raise ValueError("Invalid private key")
P_point = scalar_mult(d, G)
# Negate d if y is odd
if P_point[1] % 2 != 0:
d = N - d
# Generate deterministic nonce
t = d.to_bytes(32, 'big')
aux = secrets.token_bytes(32)
t = bytes(a ^ b for a, b in zip(t, tagged_hash("BIP0340/aux", aux)))
k0 = int.from_bytes(
tagged_hash("BIP0340/nonce", t + P_point[0].to_bytes(32, 'big') + msg),
'big'
) % N
if k0 == 0:
raise ValueError("Failure: k0 is zero")
R = scalar_mult(k0, G)
k = k0 if R[1] % 2 == 0 else N - k0
e = int.from_bytes(
tagged_hash("BIP0340/challenge",
R[0].to_bytes(32, 'big') + P_point[0].to_bytes(32, 'big') + msg),
'big'
) % N
sig = R[0].to_bytes(32, 'big') + ((k + e * d) % N).to_bytes(32, 'big')
return sig
def create_event(
private_key_hex: str,
kind: int,
content: str,
tags: Optional[List[List[str]]] = None,
created_at: Optional[int] = None
) -> dict:
"""
Create and sign a Nostr event.
Args:
private_key_hex: Private key as hex string
kind: Event kind (1 = note, 0 = metadata, etc.)
content: Event content
tags: Optional list of tags
created_at: Optional timestamp (defaults to now)
Returns:
Signed event as dict
"""
if tags is None:
tags = []
if created_at is None:
created_at = int(time.time())
# Derive public key
private_key = bytes.fromhex(private_key_hex)
d = int.from_bytes(private_key, 'big')
public_point = scalar_mult(d, G)
public_key_hex = public_point[0].to_bytes(32, 'big').hex()
# Create event structure
event = {
"pubkey": public_key_hex,
"created_at": created_at,
"kind": kind,
"tags": tags,
"content": content
}
# Calculate event ID (SHA256 of serialized event)
serialized = json.dumps([
0, # Reserved for future use
event["pubkey"],
event["created_at"],
event["kind"],
event["tags"],
event["content"]
], separators=(',', ':'), ensure_ascii=False)
event_id = hashlib.sha256(serialized.encode()).digest()
event["id"] = event_id.hex()
# Sign the event
signature = schnorr_sign(event_id, private_key)
event["sig"] = signature.hex()
return event
def verify_event(event: dict) -> bool:
"""
Verify a signed Nostr event.
Args:
event: Event dict with id, pubkey, and sig
Returns:
True if signature is valid
"""
# Recreate serialized event
serialized = json.dumps([
0,
event["pubkey"],
event["created_at"],
event["kind"],
event["tags"],
event["content"]
], separators=(',', ':'), ensure_ascii=False)
# Verify ID
expected_id = hashlib.sha256(serialized.encode()).hexdigest()
if event["id"] != expected_id:
return False
# Verify signature (simplified - production should verify properly)
return (
len(event["sig"]) == 128 and
len(event["pubkey"]) == 64
)
# Example usage
if __name__ == "__main__":
# Generate a test key (in production, load from secure storage)
test_private_key = secrets.token_bytes(32).hex()
print("=== Creating Nostr Event ===\n")
# Create a kind:1 note (text post)
event = create_event(
private_key_hex=test_private_key,
kind=1,
content="Hello Nostr! This is a signed message from an agent.",
tags=[
["t", "nostr"],
["t", "agent"]
]
)
print("Event created:")
print(json.dumps(event, indent=2))
print(f"\n=== Verification ===")
is_valid = verify_event(event)
print(f"Event valid: {is_valid}")
print(f"\n=== Event Details ===")
print(f"ID: {event['id'][:16]}...")
print(f"Pubkey: {event['pubkey'][:16]}...")
print(f"Kind: {event['kind']}")
print(f"Content: {event['content']}")
print(f"Tags: {event['tags']}")
Usage
# No external dependencies required
python sign_event.py
Example Output
=== Creating Nostr Event ===
Event created:
{
"pubkey": "a1b2c3d4e5f6...",
"created_at": 1706745600,
"kind": 1,
"tags": [
["t", "nostr"],
["t", "agent"]
],
"content": "Hello Nostr! This is a signed message from an agent.",
"id": "abc123def456...",
"sig": "xyz789..."
}
=== Verification ===
Event valid: True
=== Event Details ===
ID: abc123def456...
Pubkey: a1b2c3d4e5f6...
Kind: 1
Content: Hello Nostr! This is a signed message from an agent.
Tags: [['t', 'nostr'], ['t', 'agent']]
Agent Notes
Event kinds for agents:
| Kind | Description | Use Case |
|---|---|---|
| 0 | Metadata | Set profile name, about, picture |
| 1 | Text note | Post messages |
| 4 | Encrypted DM | Private messages (deprecated, use NIP-44) |
| 7 | Reaction | React to other events |
| 9735 | Zap receipt | Lightning payment proof |
Event ID: SHA256 hash of the serialized event. Used for references and threading.
Signature: BIP-340 Schnorr signature over the event ID. Proves authorship.
Tags: Key-value metadata. Common tags:
["e", "<event_id>"]- Reference another event["p", "<pubkey>"]- Mention a user["t", "<hashtag>"]- Add hashtag