nostr-protocol / nips

Nostr Implementation Possibilities
2.35k stars 566 forks source link

(Tutorial) Nostr for Dummies: How to implement NOSTR Protocol from scratch with Python (NIP-01) #971

Open ionextdebug opened 9 months ago

ionextdebug commented 9 months ago

Baby steps to implement your backend for NOSTR using only general purpose packages (no NOSTR Toolkit or SDK). Original: https://fullstackengineer.hashnode.dev/how-to-implement-nostr-protocol-from-scratch-with-python-nip-01

This tutorial teach you the simple way to implement NOSTR Protocol without NOSTR toolkit. The focus is only to implement a simple event [publish step].

IMPORTANT: You can use any relay from nostr.watch, the relay applied in this tutorial is only for example usage - it's NOT a recommendation OR preference.

CLAUSE 1: To have a well-formed Relay or Client you MUST support NIP-01 features and syntax. Another NIPs are all optional, and you CAN create your own custom features.

Follow the bellow code reading the comments to understand what's happening.

MORE INFORMATION: This is a complementary tutorial, if you need more details about the meaning of each word please refer to NIP-01 declaration.

# Package installation for Python in a non-Jupyter environment
pip install websockets secp256k1 sha256

# Package installation on Jupyter Notebook environment (uncomment if you are using the Jupyter Notebook)
# ! pip install websockets # Connect you with the Relay
# ! pip install secp256k1 # Generate the private key and public key
# ! pip install sha256 # Generate needed hash

# package importation
import asyncio
import websockets
import secrets
import secp256k1
import time
import json
from hashlib import sha256
from time import time

# The custom content of note that will be publish in this tutorial
content = "Hello, world!" #input("What's your message to the world?")

# Token applied to generate public-private keypair
secrets_token_32bit = secrets.token_bytes(32)
# User's private key (use print(private_key.hex()) to print)
private_key = secp256k1.PrivateKey(secrets_token_32bit)
# User's public key
public_key = private_key.pubkey.serialize()[1:].hex()

# Definition of required elements according to NIP-01
tags = []
kind = 0
timestamp = int(time()) # timestamp in SECONDS

# Define the property "event_id"
event_id = sha256(
           json.dumps([
                      0, # ZERO is a CONSTANT, don't confuse with kind
                       public_key,
                       timestamp,
                       kind,
                       tags,
                       content], separators=(',', ':'))
                .encode('utf-8'))
                .hexdigest()

# Define the property "sig"
sig = private_key.schnorr_sign(bytes.fromhex(event_id), None, raw=True).hex()

# Define the note to send to Relays
note = json.dumps(["EVENT", {
  "id": event_id,
  "pubkey": public_key,
  "created_at": timestamp,
  "kind": kind,
  "tags": tags,
  "content": content,
  "sig": sig
}], separators=(',', ':'))

# Define the Relay for this tutorial (only for example)
relay : str = "wss://relay.geekiam.services"

# Connect to the Relay using Socket and send the note
async with websockets.connect(relay) as websocket:

    await websocket.send(note)
    print(f">>> {note}")

    greeting = await websocket.recv()
    print(f"<<< {greeting}")

You will receive the answer from the Relay. If the note was well-formed then the note will be published for the NOSTR network of relays and clients, if ill-formed you will receive an error message.

ghost commented 9 months ago

It might be better to use the coincurve, as it's more cross-platform and properly maintained, unlike secp256k1