Package: pynostr
pynostrCI/CDPackagePython library for forNostr.This library is using coincurve instead of secp256k1, so pynostr can be used on windows. pynostr started as a fork frompython-nostrand is now developed on its own.This library works with python >= 3.7FeaturesNIPswith a relay-specific implementation are listed here.NIP-01: Basic protocol flow descriptionNIP-02: Contact List and PetnamesNIP-03: OpenTimestamps Attestations for EventsNIP-04: Encrypted Direct MessageNIP-05: Mapping Nostr keys to DNS-based internet identifiersNIP-06: Basic key derivation from mnemonic seed phraseNIP-08: Handling MentionsNIP-09: Event DeletionNIP-10: Conventions for clients' use of e and p tags in text eventsNIP-11: Relay Information DocumentNIP-12: Generic Tag QueriesNIP-13: Proof of WorkNIP-14: Subject tag in text events.NIP-15: End of Stored Events NoticeNIP-16: Event TreatmentNIP-19: bech32-encoded entitiesNIP-20: Command ResultsNIP-21: nostr: URL schemeNIP-22: Event created_at LimitsNIP-23: Long-form ContentNIP-25: ReactionsNIP-26: Delegated Event SigningNIP-28: Public ChatNIP-33: Parameterized Replaceable EventsNIP-36: Sensitive ContentNIP-40: Expiration TimestampNIP-42: Authentication of clients to relaysNIP-46: Nostr ConnectNIP-50: Keywords filterNIP-56: ReportingNIP-57: Lightning ZapsNIP-58: BadgesNIP-65: Relay List MetadataDifferences from python-nostrtornado websocketscoincurve secp256k1 implementationno proxiesno ssl_optionsrelay can be used to open a connection to a relaypython-nostrpynostrFilterFiltersFiltersFiltersListrelay_manager.open_connectionsrelay_manager.run_sync()relay_manager.close_connections()-private_key.sign_event(event)event.sign(private_key.hex())Installationpipinstallpynostrwith websocket-client supportpipinstallpynostr[websocket-client]The necessary coincurve can be installed on android inside termux:pkgupdate
pkginstallbuild-essential
pkginstallbinutils
pkginstallpython-cryptography
pipinstallcoincurve--no-binaryallUsageGenerate a keyfrompynostr.keyimportPrivateKeyprivate_key=PrivateKey()public_key=private_key.public_keyprint(f"Private key:{private_key.bech32()}")print(f"Public key:{public_key.bech32()}")Connect to relaysfrompynostr.relay_managerimportRelayManagerfrompynostr.filtersimportFiltersList,Filtersfrompynostr.eventimportEventKindimporttimeimportuuidrelay_manager=RelayManager(timeout=2)relay_manager.add_relay("wss://nostr-pub.wellorder.net")relay_manager.add_relay("wss://relay.damus.io")filters=FiltersList([Filters(kinds=[EventKind.TEXT_NOTE],limit=100)])subscription_id=uuid.uuid1().hexrelay_manager.add_subscription_on_all_relays(subscription_id,filters)relay_manager.run_sync()whilerelay_manager.message_pool.has_notices():notice_msg=relay_manager.message_pool.get_notice()print(notice_msg.content)whilerelay_manager.message_pool.has_events():event_msg=relay_manager.message_pool.get_event()print(event_msg.event.content)relay_manager.close_all_relay_connections()Connect to single relayfrompynostr.relayimportRelayfrompynostr.filtersimportFiltersList,Filtersfrompynostr.eventimportEventKindfrompynostr.base_relayimportRelayPolicyfrompynostr.message_poolimportMessagePoolimporttornado.ioloopfromtornadoimportgenimporttimeimportuuidmessage_pool=MessagePool(first_response_only=False)policy=RelayPolicy()io_loop=tornado.ioloop.IOLoop.current()r=Relay("wss://relay.damus.io",message_pool,io_loop,policy,timeout=2)filters=FiltersList([Filters(kinds=[EventKind.TEXT_NOTE],limit=100)])subscription_id=uuid.uuid1().hexr.add_subscription(subscription_id,filters)try:io_loop.run_sync(r.connect)exceptgen.Return:passio_loop.stop()whilemessage_pool.has_notices():notice_msg=message_pool.get_notice()print(notice_msg.content)whilemessage_pool.has_events():event_msg=message_pool.get_event()print(event_msg.event.content)Publish to relaysimportjsonimportsslimporttimeimportuuidfrompynostr.eventimportEventfrompynostr.relay_managerimportRelayManagerfrompynostr.filtersimportFiltersList,Filtersfrompynostr.message_typeimportClientMessageTypefrompynostr.keyimportPrivateKeyrelay_manager=RelayManager(timeout=6)relay_manager.add_relay("wss://nostr-pub.wellorder.net")relay_manager.add_relay("wss://relay.damus.io")private_key=PrivateKey()filters=FiltersList([Filters(authors=[private_key.public_key.hex()],limit=100)])subscription_id=uuid.uuid1().hexrelay_manager.add_subscription_on_all_relays(subscription_id,filters)event=Event("Hello Nostr")event.sign(private_key.hex())relay_manager.publish_event(event)relay_manager.run_sync()time.sleep(5)# allow the messages to sendwhilerelay_manager.message_pool.has_ok_notices():ok_msg=relay_manager.message_pool.get_ok_notice()print(ok_msg)whilerelay_manager.message_pool.has_events():event_msg=relay_manager.message_pool.get_event()print(event_msg.event.to_dict())Reply to a notefrompynostr.eventimportEventreply=Event(content="Sounds good!",)# create 'e' tag reference to the note you're replying toreply.add_event_ref(original_note_id)# create 'p' tag reference to the pubkey you're replying toreply.add_pubkey_ref(original_note_author_pubkey)reply.sign(private_key.hex())Send a DMfrompynostr.encrypted_dmimportEncryptedDirectMessagefrompynostr.keyimportPrivateKeyprivate_key=PrivateKey()recipient_pubkey=PrivateKey().public_key.hex()dm=EncryptedDirectMessage()dm.encrypt(private_key.hex(),recipient_pubkey=recipient_pubkey,cleartext_content="Secret message!")dm_event=dm.to_event()dm_event.sign(private_key.hex())NIP-26 delegationfrompynostr.delegationimportDelegationfrompynostr.eventimportEventKind,Eventfrompynostr.keyimportPrivateKey# Load your "identity" PK that you'd like to keep safely offlineidentity_pk=PrivateKey.from_nsec("nsec1...")# Create a new, disposable PK as the "delegatee" that can be "hot" in a Nostr clientdelegatee_pk=PrivateKey()# the "identity" PK will authorize "delegatee" to sign TEXT_NOTEs on its behalf for the next monthdelegation=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()],)event.sign(self.delegatee_pk.hex())# ...normal broadcast steps...NIP-13: Proof of Workfrompynostr.eventimportEventfrompynostr.powimportPowEventpe=PowEvent(difficulty=25)e=Event()e=pe.mine(e)assertpe.check_difficulty(e)Test SuiteSet up the test environmentInstall the test-runner dependencies:pip3 install -r test-requirements.txtThen make thepynostrpython module visible/importable to the tests by installing the local dev dir as an editable module:# from the repo root
pip3 install -e .Running the test suiteRun the whole test suite:# from the repo root
pytestRun a specific test file:pytest test/test_this_file.pyRun a specific test:pytest test/test_this_file.py::test_this_specific_testRunning tests with toxInstall toxpip install toxRun teststoxPre-commit-configInstallation$ pip install pre-commitUsing homebrew:$ brew install pre-commit$ pre-commit --version
pre-commit 2.10.0Install the git hook scripts$ pre-commit installRun against all the filespre-commit run --all-files
pre-commit run --show-diff-on-failure --color=always --all-filesUpdate package rev in pre-commit yamlpre-commitautoupdate
pre-commitrun--show-diff-on-failure--color=always--all-files