$ -weight: 500;">pip -weight: 500;">install coincurve==21.0.0 websocket-client==1.8.0
-weight: 500;">pip -weight: 500;">install coincurve==21.0.0 websocket-client==1.8.0
-weight: 500;">pip -weight: 500;">install coincurve==21.0.0 websocket-client==1.8.0
# nostr_keys.py
import os
from coincurve import PrivateKey def new_keypair() -> tuple[str, str]: """Return (privkey_hex, pubkey_hex_xonly) — both 64-char lowercase hex.""" sk = PrivateKey(os.urandom(32)) sk_hex = sk.secret.hex() # x-only pubkey: strip the 0x02/0x03 byte from the compressed form pk_xonly = sk.public_key.format(compressed=True)[1:] return sk_hex, pk_xonly.hex() def load_keypair(privkey_hex: str) -> tuple[PrivateKey, str]: sk = PrivateKey(bytes.fromhex(privkey_hex.strip())) pk_xonly = sk.public_key.format(compressed=True)[1:] return sk, pk_xonly.hex()
# nostr_keys.py
import os
from coincurve import PrivateKey def new_keypair() -> tuple[str, str]: """Return (privkey_hex, pubkey_hex_xonly) — both 64-char lowercase hex.""" sk = PrivateKey(os.urandom(32)) sk_hex = sk.secret.hex() # x-only pubkey: strip the 0x02/0x03 byte from the compressed form pk_xonly = sk.public_key.format(compressed=True)[1:] return sk_hex, pk_xonly.hex() def load_keypair(privkey_hex: str) -> tuple[PrivateKey, str]: sk = PrivateKey(bytes.fromhex(privkey_hex.strip())) pk_xonly = sk.public_key.format(compressed=True)[1:] return sk, pk_xonly.hex()
# nostr_keys.py
import os
from coincurve import PrivateKey def new_keypair() -> tuple[str, str]: """Return (privkey_hex, pubkey_hex_xonly) — both 64-char lowercase hex.""" sk = PrivateKey(os.urandom(32)) sk_hex = sk.secret.hex() # x-only pubkey: strip the 0x02/0x03 byte from the compressed form pk_xonly = sk.public_key.format(compressed=True)[1:] return sk_hex, pk_xonly.hex() def load_keypair(privkey_hex: str) -> tuple[PrivateKey, str]: sk = PrivateKey(bytes.fromhex(privkey_hex.strip())) pk_xonly = sk.public_key.format(compressed=True)[1:] return sk, pk_xonly.hex()
[0, <pubkey_hex>, <created_at>, <kind>, <tags>, <content>]
[0, <pubkey_hex>, <created_at>, <kind>, <tags>, <content>]
[0, <pubkey_hex>, <created_at>, <kind>, <tags>, <content>]
import json def serialize(pubkey_hex: str, created_at: int, kind: int, tags: list, content: str) -> bytes: payload = [0, pubkey_hex, created_at, kind, tags, content] return json.dumps( payload, ensure_ascii=False, separators=(",", ":"), sort_keys=False, ).encode("utf-8")
import json def serialize(pubkey_hex: str, created_at: int, kind: int, tags: list, content: str) -> bytes: payload = [0, pubkey_hex, created_at, kind, tags, content] return json.dumps( payload, ensure_ascii=False, separators=(",", ":"), sort_keys=False, ).encode("utf-8")
import json def serialize(pubkey_hex: str, created_at: int, kind: int, tags: list, content: str) -> bytes: payload = [0, pubkey_hex, created_at, kind, tags, content] return json.dumps( payload, ensure_ascii=False, separators=(",", ":"), sort_keys=False, ).encode("utf-8")
import hashlib
import time
import uuid def build_event(sk, pubkey_hex: str, kind: int, content: str, tags=None) -> dict: tags = tags or [] created_at = int(time.time()) serialized = serialize(pubkey_hex, created_at, kind, tags, content) event_id = hashlib.sha256(serialized).digest() sig = sk.sign_schnorr(event_id) # 64 bytes, deterministic + aux-rand internally return { "id": event_id.hex(), "pubkey": pubkey_hex, "created_at": created_at, "kind": kind, "tags": tags, "content": content, "sig": sig.hex(), }
import hashlib
import time
import uuid def build_event(sk, pubkey_hex: str, kind: int, content: str, tags=None) -> dict: tags = tags or [] created_at = int(time.time()) serialized = serialize(pubkey_hex, created_at, kind, tags, content) event_id = hashlib.sha256(serialized).digest() sig = sk.sign_schnorr(event_id) # 64 bytes, deterministic + aux-rand internally return { "id": event_id.hex(), "pubkey": pubkey_hex, "created_at": created_at, "kind": kind, "tags": tags, "content": content, "sig": sig.hex(), }
import hashlib
import time
import uuid def build_event(sk, pubkey_hex: str, kind: int, content: str, tags=None) -> dict: tags = tags or [] created_at = int(time.time()) serialized = serialize(pubkey_hex, created_at, kind, tags, content) event_id = hashlib.sha256(serialized).digest() sig = sk.sign_schnorr(event_id) # 64 bytes, deterministic + aux-rand internally return { "id": event_id.hex(), "pubkey": pubkey_hex, "created_at": created_at, "kind": kind, "tags": tags, "content": content, "sig": sig.hex(), }
import json
import websocket RELAYS = [ "wss://relay.damus.io", "wss://nos.lol", "wss://nostr.mom", "wss://nostr-pub.wellorder.net", "wss://relay.primal.net", "wss://offchain.pub", "wss://relay.snort.social",
] def publish(event: dict, timeout: float = 4.0) -> list[tuple[str, bool]]: results = [] payload = json.dumps(["EVENT", event]) for url in RELAYS: try: ws = websocket.create_connection(url, timeout=timeout) ws.send(payload) # Optional: read one frame to confirm OK, but do not block the loop on it try: resp = ws.recv() ok = '"OK"' in resp and event["id"] in resp and "true" in resp.lower() except Exception: ok = True # relay silently accepted ws.close() results.append((url, ok)) except Exception as e: results.append((url, False)) return results
import json
import websocket RELAYS = [ "wss://relay.damus.io", "wss://nos.lol", "wss://nostr.mom", "wss://nostr-pub.wellorder.net", "wss://relay.primal.net", "wss://offchain.pub", "wss://relay.snort.social",
] def publish(event: dict, timeout: float = 4.0) -> list[tuple[str, bool]]: results = [] payload = json.dumps(["EVENT", event]) for url in RELAYS: try: ws = websocket.create_connection(url, timeout=timeout) ws.send(payload) # Optional: read one frame to confirm OK, but do not block the loop on it try: resp = ws.recv() ok = '"OK"' in resp and event["id"] in resp and "true" in resp.lower() except Exception: ok = True # relay silently accepted ws.close() results.append((url, ok)) except Exception as e: results.append((url, False)) return results
import json
import websocket RELAYS = [ "wss://relay.damus.io", "wss://nos.lol", "wss://nostr.mom", "wss://nostr-pub.wellorder.net", "wss://relay.primal.net", "wss://offchain.pub", "wss://relay.snort.social",
] def publish(event: dict, timeout: float = 4.0) -> list[tuple[str, bool]]: results = [] payload = json.dumps(["EVENT", event]) for url in RELAYS: try: ws = websocket.create_connection(url, timeout=timeout) ws.send(payload) # Optional: read one frame to confirm OK, but do not block the loop on it try: resp = ws.recv() ok = '"OK"' in resp and event["id"] in resp and "true" in resp.lower() except Exception: ok = True # relay silently accepted ws.close() results.append((url, ok)) except Exception as e: results.append((url, False)) return results
# nostr_publish.py — full working publisher in ~60 lines
import hashlib
import json
import os
import time
import websocket
from coincurve import PrivateKey RELAYS = [ "wss://relay.damus.io", "wss://nos.lol", "wss://nostr.mom", "wss://nostr-pub.wellorder.net", "wss://relay.primal.net", "wss://offchain.pub", "wss://relay.snort.social",
] def load(privkey_hex): sk = PrivateKey(bytes.fromhex(privkey_hex.strip())) pk = sk.public_key.format(compressed=True)[1:].hex() return sk, pk def serialize(pk, ts, kind, tags, content): return json.dumps( [0, pk, ts, kind, tags, content], ensure_ascii=False, separators=(",", ":"), ).encode("utf-8") def build(sk, pk, content, kind=1, tags=None): tags = tags or [] ts = int(time.time()) msg = serialize(pk, ts, kind, tags, content) eid = hashlib.sha256(msg).digest() sig = sk.sign_schnorr(eid) return { "id": eid.hex(), "pubkey": pk, "created_at": ts, "kind": kind, "tags": tags, "content": content, "sig": sig.hex(), } def publish(event, relays=RELAYS, timeout=4.0): payload = json.dumps(["EVENT", event]) ok_count = 0 for url in relays: try: ws = websocket.create_connection(url, timeout=timeout) ws.send(payload) try: resp = ws.recv() if '"OK"' in resp and event["id"] in resp: ok_count += 1 except Exception: ok_count += 1 ws.close() except Exception: pass return ok_count if __name__ == "__main__": sk_hex = open(os.environ["NOSTR_KEY_PATH"]).read().strip() sk, pk = load(sk_hex) evt = build(sk, pk, "hello from 60 lines of python") n = publish(evt) print(f"event_id={evt['id']} relays_ok={n}")
# nostr_publish.py — full working publisher in ~60 lines
import hashlib
import json
import os
import time
import websocket
from coincurve import PrivateKey RELAYS = [ "wss://relay.damus.io", "wss://nos.lol", "wss://nostr.mom", "wss://nostr-pub.wellorder.net", "wss://relay.primal.net", "wss://offchain.pub", "wss://relay.snort.social",
] def load(privkey_hex): sk = PrivateKey(bytes.fromhex(privkey_hex.strip())) pk = sk.public_key.format(compressed=True)[1:].hex() return sk, pk def serialize(pk, ts, kind, tags, content): return json.dumps( [0, pk, ts, kind, tags, content], ensure_ascii=False, separators=(",", ":"), ).encode("utf-8") def build(sk, pk, content, kind=1, tags=None): tags = tags or [] ts = int(time.time()) msg = serialize(pk, ts, kind, tags, content) eid = hashlib.sha256(msg).digest() sig = sk.sign_schnorr(eid) return { "id": eid.hex(), "pubkey": pk, "created_at": ts, "kind": kind, "tags": tags, "content": content, "sig": sig.hex(), } def publish(event, relays=RELAYS, timeout=4.0): payload = json.dumps(["EVENT", event]) ok_count = 0 for url in relays: try: ws = websocket.create_connection(url, timeout=timeout) ws.send(payload) try: resp = ws.recv() if '"OK"' in resp and event["id"] in resp: ok_count += 1 except Exception: ok_count += 1 ws.close() except Exception: pass return ok_count if __name__ == "__main__": sk_hex = open(os.environ["NOSTR_KEY_PATH"]).read().strip() sk, pk = load(sk_hex) evt = build(sk, pk, "hello from 60 lines of python") n = publish(evt) print(f"event_id={evt['id']} relays_ok={n}")
# nostr_publish.py — full working publisher in ~60 lines
import hashlib
import json
import os
import time
import websocket
from coincurve import PrivateKey RELAYS = [ "wss://relay.damus.io", "wss://nos.lol", "wss://nostr.mom", "wss://nostr-pub.wellorder.net", "wss://relay.primal.net", "wss://offchain.pub", "wss://relay.snort.social",
] def load(privkey_hex): sk = PrivateKey(bytes.fromhex(privkey_hex.strip())) pk = sk.public_key.format(compressed=True)[1:].hex() return sk, pk def serialize(pk, ts, kind, tags, content): return json.dumps( [0, pk, ts, kind, tags, content], ensure_ascii=False, separators=(",", ":"), ).encode("utf-8") def build(sk, pk, content, kind=1, tags=None): tags = tags or [] ts = int(time.time()) msg = serialize(pk, ts, kind, tags, content) eid = hashlib.sha256(msg).digest() sig = sk.sign_schnorr(eid) return { "id": eid.hex(), "pubkey": pk, "created_at": ts, "kind": kind, "tags": tags, "content": content, "sig": sig.hex(), } def publish(event, relays=RELAYS, timeout=4.0): payload = json.dumps(["EVENT", event]) ok_count = 0 for url in relays: try: ws = websocket.create_connection(url, timeout=timeout) ws.send(payload) try: resp = ws.recv() if '"OK"' in resp and event["id"] in resp: ok_count += 1 except Exception: ok_count += 1 ws.close() except Exception: pass return ok_count if __name__ == "__main__": sk_hex = open(os.environ["NOSTR_KEY_PATH"]).read().strip() sk, pk = load(sk_hex) evt = build(sk, pk, "hello from 60 lines of python") n = publish(evt) print(f"event_id={evt['id']} relays_ok={n}")
from coincurve import PublicKey
PublicKey.from_xonly(bytes.fromhex(event["pubkey"])).verify_schnorr( bytes.fromhex(event["sig"]), bytes.fromhex(event["id"]),
)
from coincurve import PublicKey
PublicKey.from_xonly(bytes.fromhex(event["pubkey"])).verify_schnorr( bytes.fromhex(event["sig"]), bytes.fromhex(event["id"]),
)
from coincurve import PublicKey
PublicKey.from_xonly(bytes.fromhex(event["pubkey"])).verify_schnorr( bytes.fromhex(event["sig"]), bytes.fromhex(event["id"]),
) - No whitespace between elements.
- Unicode escape sequences only for characters below 0x20 and the five mandatory escapes (\n, \", \\, \r, \t, \b, \f).
- Everything above 0x20 — including non-ASCII — goes through as UTF-8 bytes, not \uXXXX.