{"id":13832741,"url":"https://github.com/holgern/pynostr","last_synced_at":"2025-09-08T18:32:55.808Z","repository":{"id":65564191,"uuid":"594836220","full_name":"holgern/pynostr","owner":"holgern","description":"Python library for nostr","archived":false,"fork":false,"pushed_at":"2025-08-07T05:56:41.000Z","size":215,"stargazers_count":76,"open_issues_count":10,"forks_count":19,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-09-08T18:32:08.484Z","etag":null,"topics":["nostr","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/holgern.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-01-29T19:33:05.000Z","updated_at":"2025-09-08T03:30:14.000Z","dependencies_parsed_at":"2024-01-13T16:29:01.686Z","dependency_job_id":"427ef08e-cbd8-4930-91e5-eba42958c474","html_url":"https://github.com/holgern/pynostr","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/holgern/pynostr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/holgern%2Fpynostr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/holgern%2Fpynostr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/holgern%2Fpynostr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/holgern%2Fpynostr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/holgern","download_url":"https://codeload.github.com/holgern/pynostr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/holgern%2Fpynostr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274229373,"owners_count":25245188,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-08T02:00:09.813Z","response_time":121,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["nostr","python"],"created_at":"2024-08-04T11:00:29.335Z","updated_at":"2025-09-08T18:32:55.783Z","avatar_url":"https://github.com/holgern.png","language":"Python","funding_links":[],"categories":["Install from Source","Libraries"],"sub_categories":["Nostr","Client reviews and/or comparisons"],"readme":"# pynostr\n\n## Installation\n\n```bash\npip install pynostr\n```\n\nwith websocket-client support\n\n```bash\npip install pynostr[websocket-client]\n```\n\nThe necessary coincurve can be installed on android inside termux:\n\n```bash\npkg update\npkg install build-essential\npkg install binutils\npkg install python-cryptography\npip install coincurve --no-binary all\n```\n\n|         |                                                                                                                                                                                                                                                                                                                                                                                                                                                          |\n| ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| CI/CD   | [![codecov](https://codecov.io/gh/holgern/pynostr/branch/main/graph/badge.svg?token=jIyk1cnhIx)](https://codecov.io/gh/holgern/pynostr) [![CircleCI](https://dl.circleci.com/status-badge/img/gh/holgern/pynostr/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/holgern/pynostr/tree/main)                                                                                                                                   |\n| Package | [![PyPI - Version](https://img.shields.io/pypi/v/pynostr.svg?logo=pypi\u0026label=PyPI\u0026logoColor=gold)](https://pypi.org/project/pynostr/) [![PyPI - Downloads](https://img.shields.io/pypi/dm/pynostr.svg?color=blue\u0026label=Downloads\u0026logo=pypi\u0026logoColor=gold)](https://pypi.org/project/pynostr/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pynostr.svg?logo=python\u0026label=Python\u0026logoColor=gold)](https://pypi.org/project/pynostr/) |\n\n---\n\nPython library for for [Nostr](https://github.com/nostr-protocol/nostr).\n\nThis library is using coincurve instead of secp256k1, so pynostr can be used on windows. pynostr started as a fork from [python-nostr](https://github.com/jeffthibault/python-nostr)\nand is now developed on its own.\n\nThis library works with python \u003e= 3.7\n\n## Features\n\n[NIPs](https://github.com/nostr-protocol/nips) with a relay-specific implementation are listed here.\n\n- [x] NIP-01: Basic protocol flow description\n- [x] NIP-02: Contact List and Petnames\n- [x] NIP-03: OpenTimestamps Attestations for Events\n- [x] NIP-04: Encrypted Direct Message\n- [x] NIP-05: Mapping Nostr keys to DNS-based internet identifiers\n- [ ] NIP-06: Basic key derivation from mnemonic seed phrase\n- [ ] NIP-08: Handling Mentions\n- [ ] NIP-09: Event Deletion\n- [x] NIP-10: Conventions for clients' use of e and p tags in text events\n- [x] NIP-11: Relay Information Document\n- [ ] NIP-12: Generic Tag Queries\n- [x] NIP-13: Proof of Work\n- [ ] NIP-14: Subject tag in text events.\n- [x] NIP-15: End of Stored Events Notice\n- [ ] NIP-16: Event Treatment\n- [x] NIP-19: bech32-encoded entities\n- [ ] NIP-20: Command Results\n- [ ] NIP-21: nostr: URL scheme\n- [ ] NIP-22: Event created_at Limits\n- [ ] NIP-23: Long-form Content\n- [ ] NIP-25: Reactions\n- [x] NIP-26: Delegated Event Signing\n- [ ] NIP-28: Public Chat\n- [ ] NIP-33: Parameterized Replaceable Events\n- [ ] NIP-36: Sensitive Content\n- [ ] NIP-40: Expiration Timestamp\n- [ ] NIP-42: Authentication of clients to relays\n- [ ] NIP-46: Nostr Connect\n- [ ] NIP-50: Keywords filter\n- [x] NIP-56: Reporting\n- [ ] NIP-57: Lightning Zaps\n- [ ] NIP-58: Badges\n- [x] NIP-65: Relay List Metadata\n\n## Differences from python-nostr\n\n- tornado websockets\n- coincurve secp256k1 implementation\n- no proxies\n- no ssl_options\n- relay can be used to open a connection to a relay\n\n| python-nostr                      | pynostr                       |\n| --------------------------------- | ----------------------------- |\n| Filter                            | Filters                       |\n| Filters                           | FiltersList                   |\n| relay_manager.open_connections    | relay_manager.run_sync()      |\n| relay_manager.close_connections() | -                             |\n| private_key.sign_event(event)     | event.sign(private_key.hex()) |\n\n## Usage\n\n**Generate a key**\n\n```python\nfrom pynostr.key import PrivateKey\n\nprivate_key = PrivateKey()\npublic_key = private_key.public_key\nprint(f\"Private key: {private_key.bech32()}\")\nprint(f\"Public key: {public_key.bech32()}\")\n```\n\n**Connect to relays**\n\n```python\nfrom pynostr.relay_manager import RelayManager\nfrom pynostr.filters import FiltersList, Filters\nfrom pynostr.event import EventKind\nimport time\nimport uuid\n\nrelay_manager = RelayManager(timeout=2)\nrelay_manager.add_relay(\"wss://nostr-pub.wellorder.net\")\nrelay_manager.add_relay(\"wss://relay.damus.io\")\nfilters = FiltersList([Filters(kinds=[EventKind.TEXT_NOTE], limit=100)])\nsubscription_id = uuid.uuid1().hex\nrelay_manager.add_subscription_on_all_relays(subscription_id, filters)\nrelay_manager.run_sync()\nwhile relay_manager.message_pool.has_notices():\n    notice_msg = relay_manager.message_pool.get_notice()\n    print(notice_msg.content)\nwhile relay_manager.message_pool.has_events():\n    event_msg = relay_manager.message_pool.get_event()\n    print(event_msg.event.content)\nrelay_manager.close_all_relay_connections()\n```\n\n**Connect to single relay**\n\n```python\nfrom pynostr.relay import Relay\nfrom pynostr.filters import FiltersList, Filters\nfrom pynostr.event import EventKind\nfrom pynostr.base_relay import RelayPolicy\nfrom pynostr.message_pool import MessagePool\nimport tornado.ioloop\nfrom tornado import gen\nimport time\nimport uuid\n\nmessage_pool = MessagePool(first_response_only=False)\npolicy = RelayPolicy()\nio_loop = tornado.ioloop.IOLoop.current()\nr = Relay(\n    \"wss://relay.damus.io\",\n    message_pool,\n    io_loop,\n    policy,\n    timeout=2\n)\nfilters = FiltersList([Filters(kinds=[EventKind.TEXT_NOTE], limit=100)])\nsubscription_id = uuid.uuid1().hex\n\nr.add_subscription(subscription_id, filters)\n\ntry:\n    io_loop.run_sync(r.connect)\nexcept gen.Return:\n    pass\nio_loop.stop()\n\nwhile message_pool.has_notices():\n    notice_msg = message_pool.get_notice()\n    print(notice_msg.content)\nwhile message_pool.has_events():\n    event_msg = message_pool.get_event()\n    print(event_msg.event.content)\n```\n\n**Publish to relays**\n\n```python\nimport json\nimport ssl\nimport time\nimport uuid\nfrom pynostr.event import Event\nfrom pynostr.relay_manager import RelayManager\nfrom pynostr.filters import FiltersList, Filters\nfrom pynostr.message_type import ClientMessageType\nfrom pynostr.key import PrivateKey\n\nrelay_manager = RelayManager(timeout=6)\nrelay_manager.add_relay(\"wss://nostr-pub.wellorder.net\")\nrelay_manager.add_relay(\"wss://relay.damus.io\")\nprivate_key = PrivateKey()\n\nfilters = FiltersList([Filters(authors=[private_key.public_key.hex()], limit=100)])\nsubscription_id = uuid.uuid1().hex\nrelay_manager.add_subscription_on_all_relays(subscription_id, filters)\n\nevent = Event(\"Hello Nostr\")\nevent.sign(private_key.hex())\n\nrelay_manager.publish_event(event)\nrelay_manager.run_sync()\ntime.sleep(5) # allow the messages to send\nwhile relay_manager.message_pool.has_ok_notices():\n    ok_msg = relay_manager.message_pool.get_ok_notice()\n    print(ok_msg)\nwhile relay_manager.message_pool.has_events():\n    event_msg = relay_manager.message_pool.get_event()\n    print(event_msg.event.to_dict())\n\n```\n\n**Reply to a note**\n\n```python\nfrom pynostr.event import Event\nreply = Event(\n  content=\"Sounds good!\",\n)\n# create 'e' tag reference to the note you're replying to\nreply.add_event_ref(original_note_id)\n# create 'p' tag reference to the pubkey you're replying to\nreply.add_pubkey_ref(original_note_author_pubkey)\nreply.sign(private_key.hex())\n```\n\n**Send a DM**\n\n```python\nfrom pynostr.encrypted_dm import EncryptedDirectMessage\nfrom pynostr.key import PrivateKey\nprivate_key = PrivateKey()\nrecipient_pubkey = PrivateKey().public_key.hex()\ndm = EncryptedDirectMessage()\ndm.encrypt(private_key.hex(),\n  recipient_pubkey=recipient_pubkey,\n  cleartext_content=\"Secret message!\"\n)\ndm_event = dm.to_event()\ndm_event.sign(private_key.hex())\n```\n\n**Decrypt DMs**\nNote: This code assumes you first subscribed to relays and filtered for events of kind 4 where the author is your pubkey.\n\n```python\n# Initial DM\nfrom pynostr.encrypted_dm import EncryptedDirectMessage\nfrom pynostr.key import PrivateKey\ndm_event0 = {\n    \"id\": \"abc123...\",\n    \"pubkey\": \"xyz123...\",\n    \"created_at\": 0000000000,\n    \"kind\": 4,\n    \"tags\": [\n        [\n            \"p\",\n            \"ijk098...\"\n        ]\n    ],\n    \"content\": \"encoded_content?iv=encoded_iv\",\n    \"sig\": \"efghijk098123...\"\n}\nreceiver_sk = PrivateKey(\"sk_hex\")\nreceiver_pk = receiver_sk.public_key or dm_event0[\"tags\"][0][1]\nsender_pk = dm_event0[\"pubkey\"]\nencrypted_msg_from_sender_to_receiver = dm_event0[\"content\"]\nenc_dm = EncryptedDirectMessage(\n    receiver_pk.hex(),\n    sender_pk,\n    encrypted_message=encrypted_msg_from_sender_to_receiver,\n)\nenc_dm.decrypt(receiver_sk.hex())\nmessage = enc_dm.cleartext_content\nprint(message)\n\n# Response DM\ndm_event1 = {\n    \"id\": \"123abc...\",\n    \"pubkey\": \"ijk098...\",\n    \"created_at\": 0000000000,\n    \"kind\": 4,\n    \"tags\": [\n        [\n            \"p\",\n            \"xyz123...\"\n        ]\n    ],\n    \"content\": \"encoded_content?iv=encoded_iv\",\n    \"sig\": \"abcdef123456...\"\n}\n\nreceiver_sk = PrivateKey(\"sk_hex\")\nreceiver_pk = receiver_sk.public_key or dm_event1[\"tags\"][0][1]\nsender_pk = dm_event1[\"pubkey\"]\nencrypted_msg_from_sender_to_receiver = dm_event1[\"content\"]\nenc_dm = EncryptedDirectMessage(\n    receiver_pk.hex(),\n    sender_pk,\n    encrypted_message=encrypted_msg_from_sender_to_receiver,\n)\nenc_dm.decrypt(receiver_sk.hex())\nmessage = enc_dm.cleartext_content\nprint(message)\n```\n\n**NIP-26 delegation**\n\n```python\nfrom pynostr.delegation import Delegation\nfrom pynostr.event import EventKind, Event\nfrom pynostr.key import PrivateKey\n\n# Load your \"identity\" PK that you'd like to keep safely offline\nidentity_pk = PrivateKey.from_nsec(\"nsec1...\")\n\n# Create a new, disposable PK as the \"delegatee\" that can be \"hot\" in a Nostr client\ndelegatee_pk = PrivateKey()\n\n# the \"identity\" PK will authorize \"delegatee\" to sign TEXT_NOTEs on its behalf for the next month\ndelegation = Delegation(\n    delegator_pubkey=identity_pk.public_key.hex(),\n    delegatee_pubkey=delegatee_pk.public_key.hex(),\n    event_kind=EventKind.TEXT_NOTE,\n    duration_secs=30*24*60*60\n)\n\nidentity_pk.sign_delegation(delegation)\n\nevent = Event(\n    \"Hello, NIP-26!\",\n    tags=[delegation.get_tag()],\n)\nevent.sign(self.delegatee_pk.hex())\n\n# ...normal broadcast steps...\n```\n\n**NIP-13: Proof of Work**\n\n```python\nfrom pynostr.event import Event\nfrom pynostr.pow import PowEvent\npe = PowEvent(difficulty=25)\ne=Event()\ne=pe.mine(e)\nassert pe.check_difficulty(e)\n```\n\n## Test Suite\n\n### Set up the test environment\n\nInstall the test-runner dependencies:\n\n```\npip3 install -r test-requirements.txt\n```\n\nThen make the `pynostr` python module visible/importable to the tests by installing the local dev dir as an editable module:\n\n```\n# from the repo root\npip3 install -e .\n```\n\n### Running the test suite\n\nRun the whole test suite:\n\n```\n# from the repo root\npytest\n```\n\nRun a specific test file:\n\n```\npytest test/test_this_file.py\n```\n\nRun a specific test:\n\n```\npytest test/test_this_file.py::test_this_specific_test\n```\n\n### Running tests with tox\n\nInstall tox\n\n```\npip install tox\n```\n\nRun tests\n\n```\ntox\n```\n\n## Pre-commit-config\n\n### Installation\n\n```\n$ pip install pre-commit\n```\n\n### Using homebrew:\n\n```\n$ brew install pre-commit\n```\n\n```\n$ pre-commit --version\npre-commit 2.10.0\n```\n\n### Install the git hook scripts\n\n```\n$ pre-commit install\n```\n\n### Run against all the files\n\n```\npre-commit run --all-files\npre-commit run --show-diff-on-failure --color=always --all-files\n```\n\n### Update package rev in pre-commit yaml\n\n```bash\npre-commit autoupdate\npre-commit run --show-diff-on-failure --color=always --all-files\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fholgern%2Fpynostr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fholgern%2Fpynostr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fholgern%2Fpynostr/lists"}