manbytesgnu_site

Source files for manbytesgnu.org
git clone git://holbrook.no/manbytesgnu_site.git
Info | Log | Files | Refs

20250201_nostr_python.rst (11397B)


      1 Hello nostr
      2 ###########
      3 
      4 :date: 2025-03-20 19:29:35
      5 :category: Coding
      6 :author: Louis Holbrook
      7 :tags: python,nostr,bitcoin,websockets,rust
      8 :slug: hello-nostr-with-python
      9 :summary: Assembling and publishing a simple text message to a nostr relay.
     10 :lang: en
     11 :status: published
     12 
     13 
     14 Why nostr?!
     15 ===========
     16 
     17 Because it is simple.
     18 
     19 And by that I mean the base layer is simple. There is no black magic that needs to happen to get admitted, synced and discovered. 
     20 
     21 At its core, it goes something like this:
     22 
     23 1. Set up a server.
     24 2. Start putting content there.
     25 3. Point people to your server to find your content.
     26 4. They know it's you because you signed your content with a key.
     27 
     28 Of course, apart from 4, that's not really that different of a description from any communications infrastructure anyone would use.
     29 
     30 I understand that many think 4 is a big deal. But let's face it; most people couldn't care less about their digital sovereignity. The vast majority of crypto holders defer to others to keep their key safe, one way or another. It is a big deal because you *can*, but not because people *do*.
     31 
     32 However, for those who *do* care, who *want* to take responsiblity, nostr offers some things I haven't seen in any other project of its kind so far:
     33 
     34 1. The `core protocol <nostrcore_>`_ is a couple of pages, elegant and simple, and expressed in clear language.
     35 2. I can easily execute that same protocol with the most basic of tools.
     36 3. There exists server software that is easy to build, easy to set up and that does not require much resources to run.
     37 4. `"It does not rely on P2P techniques, and therefore it works." <https://github.com/nostr-protocol/nostr?tab=readme-ov-file#nostr---notes-and-other-stuff-transmitted-by-relays>`_
     38 
     39 In other words. To do something with nostr does not involve connecting to some infernal machine that abstracts all its complexity. It is *in itself not complex*.
     40 
     41 It is simple.
     42 
     43 I hope this post works as a demonstration of that.
     44 
     45 At the time of writing I am using:
     46 
     47 * ``linux 6.12.10``
     48 * ``python 3.13.1``
     49 * ``rust 1.81 (2dbb1af80 2024-08-20)``
     50 
     51 All rust applications below are built simply with ``cargo build --release``.
     52 
     53 
     54 The message
     55 ===========
     56 
     57 Please meet the script that will generate our scratch nostr message. All it took to write it was to read through the first `"Nostr Improvement Proposal" (NIP-01) <nostrcore_>`_. [1]_
     58 
     59 .. include:: code/hello-nostr-with-python/msg.py
     60         :code: python
     61         :name: msg.py
     62 
     63 To run the script, you will need the coincurve_ python module. As always, a virtual environment is recommended as usual, while you are playing around:
     64 
     65 .. code-block:: bash
     66 
     67         $ python -m venv .venv
     68         $ source .venv/bin/activate
     69         # let's install bech32, too, we will need it later
     70         $ pip install coincurve bech32
     71         $ python msg.py
     72 
     73 Once run, the script will produce the signed message ready to be sent to the relay.
     74 
     75 It will resemble the data below, but with a different value for the ``created_at``, which in turn results in different values for ``id`` and ``sig``.
     76 
     77 
     78 .. code-block:: json
     79 
     80         ["EVENT", {"id": "63b43ae8d74b5df17659a4663f256c6829994970ca6b08a5d068e0c01a460461", "pubkey": "cc9519ba6fb1cb0cca53743dc90c2418440cf637f8b891ce2f0e2dc5c5b3cf01", "created_at": 1738407317, "kind": 1, "tags": [], "content": "hello nostr ¶", "sig": "c2ccf3524908c54a53e5fc1405b56b514dcf1d80baa7c135514fce3dcafb6646920c560f612e3769d2682b318f44f880c7147cb810ca88ffea0496cda087c583"}]
     81 
     82 
     83 The runnning
     84 ============
     85 
     86 To share this with the world we need some service to read and write from.
     87 
     88 Starting out I am using the nostr-rs-relay_ (``v0.9.0``, ``git:ff65ec2acd781150a585a78e1c60b0cdb104698e``). It gives you enough debug output to follow what's going on, and it's easy to build [2]_ and configure:
     89 
     90 .. code-block:: ini
     91 
     92         # nostr-rs-relay configuration toml file
     93         # limited to the fields that should be changed
     94 
     95         [info] 
     96         relay_url = "ws://localhost"
     97         name = "my_first_nostr_node"
     98         description = "act local, think local"
     99         # same pubkey as the script above
    100         pubkey = cc9519ba6fb1cb0cca53743dc90c2418440cf637f8b891ce2f0e2dc5c5b3cf01
    101 
    102         [network]
    103         bind = 127.0.0.1
    104         port = 8081
    105 
    106 In the repository, make a new directory ``db`` and run it. The process starts a websocket server which is used to send and receive events. One of the log lines in the terminal should show you where it is listening.
    107 
    108 ..      code-block:: bash
    109 
    110         $ RUST_LOG=debug ./target/release/nostr-rs-relay -c config.toml -d db
    111         [...]
    112         2025-02-01T11:05:55.223024Z  INFO nostr_rs_relay::server: listening on: 127.0.0.1:8081
    113 
    114 
    115 The sending
    116 ===========
    117 
    118 We use websocat_ (``v1.14.0``, ``git:83c4375ac9a0475b7a8f3b75e49290f4486a4914``) to provide the terminal connection to the node.
    119 
    120 Add a bit of fanciness by piping the output of our message generator python script to the command:
    121 
    122 .. code-block:: bash
    123         
    124         $  python msg.py  | tee /dev/stderr | websocat -v ws://localhost:8080
    125         [INFO  websocat::lints] Auto-inserting the line mode
    126         [INFO  websocat::stdio_threaded_peer] get_stdio_peer (threaded)
    127         [INFO  websocat::ws_client_peer] get_ws_client_peer
    128         ["EVENT", {"id": "bac1d459b39ac0ba91951491e382b8b5648b149b509ea8585a369d0a84101447", "pubkey": "cc9519ba6fb1cb0cca53743dc90c2418440cf637f8b891ce2f0e2dc5c5b3cf01", "created_at": 1738408544, "kind": 1, "tags": [], "content": "hello nostr ¶", "sig": "322470a022f21fe4eae6b3652f4cca59a71d2e865791880117aba7bdb069bfed8780eea7874e0d475db71beef4c498b5a559bb4dd3ef87ecce68f157a7b64213"}]
    129         [INFO  websocat::net_peer] Failure during connecting TCP: Connection refused (os error 111)
    130         [INFO  websocat::net_peer] Connected to TCP 127.0.0.1:8081
    131         [INFO  websocat::ws_client_peer] Connected to ws
    132         ["OK","bac1d459b39ac0ba91951491e382b8b5648b149b509ea8585a369d0a84101447",true,""]
    133         [INFO  websocat::sessionserve] Forward finished
    134         [INFO  websocat::ws_peer] incoming None
    135         [INFO  websocat::sessionserve] Reverse finished
    136         [INFO  websocat::sessionserve] Both directions finished
    137 
    138 As we see, the response from the node (the line with ``"OK"`` as first value) indicates a success, and cites the ``id`` matching the ``id`` in the message json contents we emitted to ``stderr``.
    139 
    140 The message has now been published.
    141 
    142 
    143 The viewing
    144 ===========
    145 
    146 Of course we can use a similar trick to get the message back out. More on that a bit later.
    147 
    148 But for now, let's use a proper nostr client to view the published message.
    149 
    150 My introduction to nostr has been through gossip_ (``v0.13.0``, ``git:90712385f6f79b60c01ae588464be4c960e76836``), a rust-based graphical client with plenty of levers to pull, aswell as an easy way of running clients with different states and keys simultaneously.
    151 
    152 The only annoying thing about gossip I have found so far is that it will not accept localhost as a value when adding relays to use. So we will use our local hostname instead. Make sure you have an entry in your ``/etc/hosts`` and that it resolves:
    153        
    154 ..      code-block:: bash
    155 
    156         $ cat /etc/hosts
    157         [...]
    158         127.0.0.1       localhost       myhost
    159 
    160         $ getent ahostsv4 myhost
    161         127.0.0.1       STREAM localhost
    162         [...]
    163 
    164 Once built, create an empty data directory, and run gossip on it:
    165 
    166 ..      code-block:: bash
    167                
    168         $ mkdir foo
    169         $ RUST_LOG=debug GOSSIP_DIR=foo ./target/release/gossip
    170 
    171 You will go through a setup wizard, prompting you to create a new account, and then ask you for relays to use and people to follow.
    172 
    173 On the relay page, remove the one suggested and add instead ``ws://myhost:8081`` to the *Outbox* and *InBox* categories.
    174 
    175 .. image:: {static}/images/gossip_relay_init.png
    176 
    177 On the follow page, follow the public key of the message we already have published: ``cc9519ba6fb1cb0cca53743dc90c2418440cf637f8b891ce2f0e2dc5c5b3cf01``. [3]_ 
    178 
    179 Completing this step will take you to the main application page. Here, navigate to ``Relays -> My relays``, which should list the one relay added using the wizard. Expand the entry, activate the *Global Feed* switch.
    180 
    181 .. image:: {static}/images/gossip_relay_global.png
    182 
    183 Navigate back to the *Global* menu item. In a short while, the post made with the script should appear [4]_. 
    184 
    185 .. image:: {static}/images/gossip_feed.png
    186 
    187 With any luck, it should appear under the **Following** menu item, too.
    188 
    189 Getting hooked
    190 ==============
    191 
    192 Let us now read a response from the gossip client directly in the websocket connection.
    193 
    194 While in gossip, click the reply button on the post, write a message and send it.
    195 
    196 Then, choose the menu item *Settings* and copy the value in the *Public Key* entry (starts with ``npub...``)
    197 
    198 Back in the terminal, using a new script, we will now generate a subscription request for text message events signed with this public key.
    199 
    200 .. include:: code/hello-nostr-with-python/sub.py
    201    :code: python
    202 
    203 For the public key ``npub1p2vekljvajp4d3kqdwvha54d6ywh2j360xpl32ug70r8y009h86q7g3tr4`` we would do:
    204 
    205 .. code-block:: bash
    206 
    207         $ python sub.py npub1p2vekljvajp4d3kqdwvha54d6ywh2j360xpl32ug70r8y009h86q7g3tr4 | tee /dev/stderr | websocat ws://alto:8081
    208         ["REQ", "2c626a2ee46a1751b271447264b1eb524b4ed0f834d35a94d3d74d21ed003fde", {"authors": ["0a999b7e4cec8356c6c06b997ed2add11d754a3a7983f8ab88f3c6723de5b9f4"], "kinds": [1]}]
    209         ["EVENT","873a3ce558e0690c4d831bea2c74b24f40407ef2bc42e251792c0bf80e94eb28",{"id":"b1474751a1799c4785aa8272fba5f20034abe3312d38335ae5553c95045e03b3","pubkey":"0a999b7e4cec8356c6c06b997ed2add11d754a3a7983f8ab88f3c6723de5b9f4","created_at":1738415174,"kind":1,"tags":[["p","cc9519ba6fb1cb0cca53743dc90c2418440cf637f8b891ce2f0e2dc5c5b3cf01"],["e","55befa55c97b59fb4f206455246c6d14f0711c429502dc814036fd271a544ea6","ws://alto:8081/","root","cc9519ba6fb1cb0cca53743dc90c2418440cf637f8b891ce2f0e2dc5c5b3cf01"]],"content":"Hello back🙏🚀","sig":"1288e42970e746e6908350e434db27e408b40ff0290e7d54e32740a5dd6ceeca74cea410694eb5f1347099372fb2fdf8d58369c565fe2cc57a64be49c05dfa75"}]
    210         ["EOSE","873a3ce558e0690c4d831bea2c74b24f40407ef2bc42e251792c0bf80e94eb28"]
    211 
    212 
    213 And of course, while this connection is kept open, any other messages posted by the same key will appear in the terminal.
    214 
    215 .. _nostrcore: https://github.com/nostr-protocol/nips/blob/master/01.md
    216 
    217 .. _coincurve: https://github.com/ofek/coincurve
    218 
    219 .. _gossip: https://github.com/mikedilger/gossip
    220 
    221 .. _websocat: https://github.com/vi/websocat
    222 
    223 .. _nostr-rs-relay: https://github.com/scsibug/nostr-rs-relay
    224 
    225 ..
    226 
    227         .. [1] To be fair, in reality NIP-10 also, which is referenced by NIP-01, although the meat of it is already spelled out in the latter.
    228 
    229         .. [2] Simply ``cargo build --release``. If you are using the `rustup <https://rustup.rs/>`_ toolchain manager, you can set up provisions with ``rustup default 1.81`` first.
    230 
    231         .. [3] The public key will be encoded to the nostr `npub` encoding scheme, a `bech32` encoded address. It's really just semantics, and represents the same value. Have a look at  `python_nostr <https://github.com/jeffthibault/python-nostr/blob/main/nostr/key.py#L30>`_.
    232 
    233         .. [4] The synchronization logic in gossip still seems to be slightly lazy, so you may have to move back and forth between menu items to trigger redraws that will list the message.