Install the nostr library
mainInstall the library using pip. Note that the library was developed with Python 3.9.5 in mind.
pip install nostrrepository·main·Indexed 43 days ago
https://github.com/jeffthibault/python-nostrA Python library for building Nostr clients, providing tools for key management, relay communication via RelayManager, event creation, and NIP-26 delegation. It supports Bech32/Bech32m encoding, encrypted direct messages, and event filtering for subscribing to relay data.
Install the library using pip. Note that the library was developed with Python 3.9.5 in mind.
pip install nostrUse the EncryptedDirectMessage class to create a private message for a specific recipient. The message must be signed by your PrivateKey before being published via RelayManager.
from nostr.event import EncryptedDirectMessage
dm = EncryptedDirectMessage(
recipient_pubkey=recipient_pubkey,
cleartext_content="Secret message!"
)
private_key.sign_event(dm)
relay_manager.publish_event(dm)To publish a message, create an Event, sign it with a PrivateKey, and then use RelayManager.publish_event(event).
import ssl
import time
from nostr.event import Event
from nostr.relay_manager import RelayManager
from nostr.key import PrivateKey
relay_manager = RelayManager()
relay_manager.add_relay("wss://nostr-pub.wellorder.net")
relay_manager.add_relay("wss://relay.damus.io")
relay_manager.open_connections({"cert_reqs": ssl.CERT_NONE})
time.sleep(1.25)
private_key = PrivateKey()
event = Event("Hello Nostr")
private_key.sign_event(event)
relay_manager.publish_event(event)
time.sleep(1)
relay_manager.close_connections()When replying to a note, use add_event_ref to include the ID of the original note (an 'e' tag) and add_pubkey_ref to include the author's public key (a 'p' tag).
from nostr.event import Event
reply = Event(
content="Hey, that's a great point!",
)
# create 'e' tag reference to the note you're replying to
reply.add_event_ref(original_note_id)
# create 'p' tag reference to the pubkey you're replying to
reply.add_pubkey_ref(original_note_author_pubkey)
private_key.sign_event(reply)
relay_manager.publish_event(reply)NIP-26 allows an 'identity' key (kept offline) to authorize a 'delegatee' key (used in hot clients) to sign specific event kinds for a set duration.
Delegation object specifying the delegator_pubkey, delegatee_pubkey, event_kind, and duration_secs.identity_pk.sign_delegation(delegation).tags=[delegation.get_tag()].Note: Delegations cannot be revoked. It is best practice to use a relatively short duration_secs.
from nostr.delegation import Delegation
from nostr.event import EventKind, Event
from nostr.key import PrivateKey
# Load your "identity" PK that you'd like to keep safely offline
identity_pk = PrivateKey.from_nsec("nsec1...")
# Create a new, disposable PK as the "delegatee" that can be "hot" in a Nostr client
delegatee_pk = PrivateKey()
# the "identity" PK will authorize "delegatee" to sign TEXT_NOTEs on its behalf for the next month
delegation = Delegation(
delegator_pubkey=identity_pk.public_key.hex(),
delegatee_pubkey=delegatee_pk.public_key.hex(),
event_kind=EventKind.TEXT_NOTE,
duration_secs=30*24*60*60
)
identity_pk.sign_delegation(delegation)
event = Event(
"Hello, NIP-26!",
tags=[delegation.get_tag()],
)
delegatee_pk.sign_event(event)
# ...normal broadcast steps...To listen for specific events (like TEXT_NOTE from specific authors), use Filters and Filter objects.
Filter with criteria (e.g., authors, kinds).Filters object.relay_manager.add_subscription(subscription_id, filters) to register the interest.relay_manager.publish_message(json_request) to send the subscription request to the relays.relay_manager.message_pool.has_events() and retrieve events using get_event().import json
import ssl
import time
from nostr.filter import Filter, Filters
from nostr.event import Event, EventKind
from nostr.relay_manager import RelayManager
from nostr.message_type import ClientMessageType
filters = Filters([Filter(authors=[<a nostr pubkey in hex>], kinds=[EventKind.TEXT_NOTE])])
subscription_id = <a string to identify a subscription>
request = [ClientMessageType.REQUEST, subscription_id]
request.extend(filters.to_json_array())
relay_manager = RelayManager()
relay_manager.add_subscription(subscription_id, filters)
relay_manager.open_connections({"cert_reqs": ssl.CERT_NONE})
time.sleep(1.25)
message = json.dumps(request)
relay_manager.publish_message(message)
time.sleep(1)
while relay_manager.message_pool.has_events():
event_msg = relay_manager.message_pool.get_event()
print(event_msg.event.content)
relay_manager.close_connections()Use RelayManager to manage connections to multiple WebSocket relays.
RelayManager..add_relay(url)..open_connections(options). Note that passing {"cert_reqs": ssl.CERT_NONE} disables SSL certificate verification.time.sleep() to allow connections to stabilize before interacting with the message_pool.relay_manager.message_pool.get_notice().import ssl
import time
from nostr.relay_manager import RelayManager
relay_manager = RelayManager()
relay_manager.add_relay("wss://nostr-pub.wellorder.net")
relay_manager.add_relay("wss://relay.damus.io")
relay_manager.open_connections({"cert_reqs": ssl.CERT_NONE}) # NOTE: This disables ssl certificate verification
time.sleep(1.25) # allow the connections to open
while relay_manager.message_pool.has_notices():
notice_msg = relay_manager.message_pool.get_notice()
print(notice_msg.content)
relay_manager.close_connections()Use the PrivateKey class from nostr.key to generate a new key pair. You can retrieve the public key and export the private key in Bech32 format using .bech32().
from nostr.key import PrivateKey
private_key = PrivateKey()
public_key = private_key.public_key
print(f"Private key: {private_key.bech32()}")
print(f"Public key: {public_key.bech32()}")The PrivateKey class provides methods for handling encrypted communication:
encrypt_message(message, public_key_hex): Encrypts a string using AES-CBC. The output is a string containing the base64-encoded ciphertext and the IV, separated by ?iv=.decrypt_message(encoded_message, public_key_hex): Decrypts the string produced by encrypt_message.encrypt_dm(dm): A convenience method for EncryptedDirectMessage objects. It uses the cleartext_content field of the DM and the recipient_pubkey to populate the content field with the encrypted string.# Using EncryptedDirectMessage object
# dm.cleartext_content must be set
# dm.recipient_pubkey must be set
private_key.encrypt_dm(dm)Use publish_event to broadcast a signed Nostr Event to all relays in the manager that are configured for writing (relay.policy.should_write).
Validation Logic: Before publishing, the manager performs two checks:
event.signature is not None.event.verify() to ensure the signature is valid.If either check fails, a RelayException is raised.
from nostr.event import Event
# Assuming 'event' is a valid, signed Event object
try:
manager.publish_event(event)
except RelayException as e:
print(f"Failed to publish: {e}")You can subscribe to specific event filters on a single relay or across all managed relays.
Use add_subscription_on_relay to target one URL. This will raise a RelayException if the relay is not configured to allow reading (relay.policy.should_read) or if the URL is unknown.
Use add_subscription_on_all_relays to broadcast a subscription to every relay in the manager that is configured for reading.
Parameters:
id (str): A unique identifier for the subscription.filters (Filters): The Nostr filter object defining what events to listen for.url (str): (For single relay) The target relay URL.from nostr.filter import Filters
filters = Filters(kinds=[1])
# Subscribe on all relays
manager.add_subscription_on_all_relays(id="sub_1", filters=filters)
# Subscribe on a specific relay
manager.add_subscription_on_relay(url="wss://relay.example.com", id="sub_2", filters=filters)The Relay class is the primary interface for connecting to a Nostr relay, managing subscriptions, and publishing messages. It handles the underlying WebSocket connection, automatic reconnection logic, and message routing to a MessagePool.
connect(): Establishes the WebSocket connection to the relay URL. It supports SSL options and proxy configurations.close(): Gracefully closes the connection.publish(message: str): Adds a message to an internal queue to be sent to the relay via a background worker.add_subscription(id, filters: Filters): Registers a new subscription with a unique ID and specific event filters.close_subscription(id: str): Removes an existing subscription by its ID.update_subscription(id: str, filters: Filters): Updates the filters for an existing subscription ID.When initializing a Relay, you can provide:
url: The WebSocket URL of the relay.message_pool: An instance of MessagePool where incoming messages are stored.policy: A RelayPolicy object to control reading/writing permissions.ssl_options: A dictionary for SSL configuration.proxy_config: A RelayProxyConnectionConfig object for proxying connections.from nostr.relay import Relay
from nostr.message_pool import MessagePool
from nostr.filter import Filters
# Initialize components
pool = MessagePool()
filters = Filters(...) # Define your filters
# Create and connect the relay
relay = Relay(url="wss://relay.example.com", message_pool=pool)
relay.connect()
# Manage subscriptions
relay.add_subscription("sub_1", filters)
# Publish a message
relay.publish('["EVENT", "sub_1", {...}]')