Oddbean new post about | logout

Notes by Yuki Kishimoto | export

 hey nostr:npub1drvpzev3syqt0kjrls50050uzf25gehpz9vgdw08hvex7e0vgfeq0eseet, rust-nostr.org and rus... 
 Thanks! Should be fixed now 
 From 151f0abb89e6ded8fa60557ca9d3c2469a68ce47 Mon Sep 17 00:00:00 2001
From: DanConwayDev     
 Thanks! I already done a similar change locally that I not pushed yet, so I'm going to close this. 
 I still need to manually approve every nip46 read pubkey request when it gets sent.
Even when i s... 
 The `NostrConnect` client, after the first `get_public_key` request, remembers the user public key and will no longer ask for it for that session. 
 Hey nostr:nprofile1qqsx3kq3vkgczq9hmfplc28h687py42yvms3zkyxh8nmkvn0vhkyyuspz4mhxue69uhkummnw3ezum... 
 Currently, when gossip is enabled, are used all relays in user lists (there isn't a relays score at the moment). This will be improved in the next versions where will be kept a relays and gossip state into the database.

But anyway, it's weird that it require hours to complete. Can you share a code example? 
 I openes a PR: https://github.com/Pleb5/muse/pull/1

In my tests, after those changes, the WoT calculation takes between 7 and 13 secs.

 
 The timeout in this case means "try to connect to the relays and wait for the connection for X seconds at most". 
 Ahh ok. I'll think about that. But it's pretty easy to write a blocking wrapper, including only the stuff you need. This could be a solution for your above request:

```toml
[dependencies]
nostr-sdk = "0.36"
tokio = { version = "1.41", features = ["full"] }
```

```rust
use std::sync::LazyLock;

use nostr_sdk::prelude::*;
use tokio::runtime::Runtime;

static RUNTIME: LazyLock<Runtime> = LazyLock::new(|| Runtime::new().unwrap());

pub struct ClientBlocking {
    inner: Client,
}

impl ClientBlocking {
    pub fn add_relay(&self, url: &str) -> Result<bool> {
        RUNTIME.block_on(async {
            Ok(self.inner.add_relay(url).await?)
        })
    }

    pub fn connect(&self) {
        RUNTIME.block_on(async {
            self.inner.connect().await
        })
    }

    pub fn send_event(&self, event: Event) -> Result<Output<EventId>> {
        RUNTIME.block_on(async {
            Ok(self.inner.send_event(event).await?)
        })
    }

    pub fn subscribe(&self, filters: Vec<Filter>) -> Result<Output<SubscriptionId>> {
        RUNTIME.block_on(async {
            Ok(self.inner.subscribe(filters, None).await?)
        })
    }

    pub fn handle_notifications<F>(&self, func: F) -> Result<()>
    where
        F: Fn(RelayPoolNotification) -> Result<bool>,
    {
        let mut notifications = self.inner.notifications();
        while let Ok(notification) = RUNTIME.block_on(notifications.recv()) {
            let shutdown: bool = RelayPoolNotification::Shutdown == notification;
            let exit: bool = func(notification)?;
            if exit || shutdown {
                break;
            }
        }
        Ok(())
    }
}
``` 
 To reduce speed compilation you can disable all default features (I'll probably disable all feature by default in future) and, to reduce the binary size, change some `profile.release` config.

Tests for the example I shared yesterday:

Default features and default release profile: 
* compilation time: 43 secs
* cdylib size: 479kb
* staticlib size: 58Mb

Without default features but with default release profile:
* compilation time: 31 secs
* cdylib size: 479kb
* staticlib size: 49Mb

Without default features and with LTO enabled, codegen-units set to 1 and abort on panic (but still optimized for performance instead of size):
* compilation time: 35 secs
* cdylib size: 437kb
* staticlib size: 12Mb

Same as above but optimized for size (not aggressive):
* compilation time: 30 secs
* cdylib size: 427kb
* staticlib size: 9.5Mb

I not plan to write a sync version of the SDK, also if very very basic. But I can try to reduce the above values as much as possible.
 
 Do you mean for Kotlin? I would say yes, but this will bring back the maintainability "issues" on my side.

If you need just a basic blocking client I could help to write and integrate it in your project, but you'll have to maintain it by yourself. It can be extended/adjusted depending on your project needs.

Just to know, are there problems with async version in kotlin? Do it increase code complexity? 
 Ahh ok. I'll think about that. But it's pretty easy to write a blocking wrapper, including only the stuff you need. This could be a solution for your above request:

```toml
[dependencies]
nostr-sdk = "0.36"
tokio = { version = "1.41", features = ["full"] }
```

```rust
use std::sync::LazyLock;

use nostr_sdk::prelude::*;
use tokio::runtime::Runtime;

static RUNTIME: LazyLock<Runtime> = LazyLock::new(|| Runtime::new().unwrap());

pub struct ClientBlocking {
    inner: Client,
}

impl ClientBlocking {
    pub fn add_relay(&self, url: &str) -> Result<bool> {
        RUNTIME.block_on(async {
            Ok(self.inner.add_relay(url).await?)
        })
    }

    pub fn connect(&self) {
        RUNTIME.block_on(async {
            self.inner.connect().await
        })
    }

    pub fn send_event(&self, event: Event) -> Result<Output<EventId>> {
        RUNTIME.block_on(async {
            Ok(self.inner.send_event(event).await?)
        })
    }

    pub fn subscribe(&self, filters: Vec<Filter>) -> Result<Output<SubscriptionId>> {
        RUNTIME.block_on(async {
            Ok(self.inner.subscribe(filters, None).await?)
        })
    }

    pub fn handle_notifications<F>(&self, func: F) -> Result<()>
    where
        F: Fn(RelayPoolNotification) -> Result<bool>,
    {
        let mut notifications = self.inner.notifications();
        while let Ok(notification) = RUNTIME.block_on(notifications.recv()) {
            let shutdown: bool = RelayPoolNotification::Shutdown == notification;
            let exit: bool = func(notification)?;
            if exit || shutdown {
                break;
            }
        }
        Ok(())
    }
}
``` 
 ## rust-nostr v0.36 is out! 🦀

### Summary

Many, many improvements to `Relay` and `RelayPool` performance (reduced atomic operations and switched to async concurrency), add `NostrSigner` trait, better methods and struct names (`fetch_events` instead of `get_events_of`, `sync` instead of `reconcile`, `NostrConnect` instead of `Nip46Signer` and so on), add `LocalRelay` and allow to easily serve it as hidden onion service with the embedded tor client, allow to keep track of negentropy sync progress, almost halved the weight of JavaScript SDK bindings (from ~6.3MB to ~3.6MB), new experimental Flutter package, some fixes and many more!

Note for Python, Kotlin, Swift and JavaScript devs: unfortunately I can't mark things as deprecated in bindings, so this release have many breaking changes, sorry :(

Note for devs who are using `nostr-protocol` (Python), `org.rust-nostr:nostr` (Kotlin), `nostr-swift` (Swift) or `@rust-nostr/nostr` (JavaScript) libraries: these packages are now deprecated. Only the `nostr-sdk` library will be released, which include everything that was in those libraries.

Full changelog: https://rust-nostr.org/changelog

### Contributors

Thanks to all contributors!

* @RydalWater
* @rodant
* @w3ird

### Links

https://rust-nostr.org
https://rust-nostr.org/donate

#rustnostr #nostr #rustlang #programming #rust #python #javascript #kotlin #swift #flutter 
 Do you mean for Kotlin? I would say yes, but this will bring back the maintainability "issues" on my side.

If you need just a basic blocking client I could help to write and integrate it in your project, but you'll have to maintain it by yourself. It can be extended/adjusted depending on your project needs.

Just to know, are there problems with async version in kotlin? Do it increase code complexity? 
 Testing rust-nostr NIP46 signer [connect] 
 Hahah, it was already available, I was just testing it again for some adjustment. I accidentally used my account for test 😂 
 In last version is called `Nip46Signer`. In the next one will be renamed to `NostrConnect`. 
 Testing rust-nostr NIP46 signer [connect] 
 🥲
2024-10-28T08:48:47.494004Z ERROR nostr_relay_pool::relay::inner: Impossible to connect to '... 
 Try with `wss://relay.snort.social` 
 No, according to NIP01 only single-letter tag are allowed in filter.

Is the "dinosaur" string included in `content` field? The `search` filter generally look only in `content` field. 
 I have a CLI (ngit) that is usually used to read but sometimes used to write events.
I'm trying a... 
 I reworked it, now it's always on demand (commit 4dbfa94876b136869e2feea75288f0c6a0beaa0e). 
 I removed the connection timeout (was set to 10 secs) so from 4dbfa94876b136869e2feea75288f0c6a0beaa0e commit the timeout is used only for requests. 
 The `NostrSigner::nip46` internally only construct the `NostrSigner` enumeration.
At a certain point the `Nip46Signer` have to wait for a response from the signer. Can you point me where you use it in the code? 
 Ok, maybe I found the issue. The connection with signer is already initialized on demand. But, the bootstrap is executed when  `signer_public_key` method is called... I saw that in your code you are calling the `NostrSigner::public_key` method, that internally call the `signer_public_key` method. For this reason it block.

I'll rework it 
 Try this commit: ede2a91ef7d3738b36a03243697fbd20efd335a1 
 It's sent a `sign_event` request to the signer. 
 In general the AUTH requests are handled in another thread, so will not block the main one. 
 According to new NIP-46 clarifications, the remote signer public key could be different from the user public key. So I have to adjust it again. This means that if you'll call `NostrSigner::public_key` method with a NIP46 signer, it will block until the signer reply or the timeout is reached. After the first call is executed correctly, the pk is cached. 
 In new version (for now in master branch) `NostrSigner::public_key` return the user public key.
In the last release depends by the signer, but during my tests I saw that the same key is used (so the signer key match the user one), at least in amber and nsec.app.  
 Thanks, I'll investigate. 
 The `not subscribed` error is returned when subscription fails with all relays. What relays are you using? Maybe is related to NIP42 auth 
 No, my bad. The subscription can fail only if relay not support read operations, if the filter is empty or for timeout. In the log what do you see when it fail and return `not subscribed`? 
 Found the issue, tomorrow I'll push the fix. 
 Thanks! 
 Fixed at 7587fba8ee72041689aa46c1436ecfa73d75d381 
 I've added `NostrConnect::set_user_public_key` at commit ce05916f4acef2351e6ab648e4e7296a6d10f8d9 
 Want to try a #DVM idea for freelancers.
Also, want it to have nothing to do with js.
Is rust-nos... 
 I still consider it in alpha state due to the API breaking changes and some thing under the hood that should be reworked, but it's pretty stable and probably mature (I work on it from 2 years).

The outbox model is available from `v0.35` but it's a first implementation and could have issues.

I'm aware of many project that use it, but not famous (mobile) clients.
I know that Lume, Coop, Mostro, ngit-cli, Mutiny, Voyage (only the nostr lib, not nostr-sdk), DVM python projects, joinstr and whitenoise use it.
You can find others here (only the ones published on github): https://github.com/rust-nostr/nostr/network/dependents 
 ## rust-nostr v0.35 is out! 🦀

### Summary

Add gossip model support, deprecate `SQLite` database in favor of `LMDB` (fork of @Mike Dilger's pocket database), add support to negentropy v1 (old version is still supported!), add `MockRelay` (a local disposable relay for tests), allow usage of embedded tor client on mobile devices, many improvements, bugs fix and more!

Full changelog: https://rust-nostr.org/changelog

### Contributors

Thanks to all contributors!

* @Melonion
* @w3ird 
* @RydalWater 
* nanikamado

### Links

https://rust-nostr.org
https://rust-nostr.org/donate

#rustnostr #nostr #rustlang #programming #rust #python #javascript #kotlin #swift 
 Unfortunately no. At least, for sure not for `wasm32-unknown-unknown` target. Maybe `wasm32-wasi`. 
 Yeah, I've done many changes but the query algorithm and indexing are almost the same.
I removed the event store to support windows targets too. 
 Not yet! 
 SQLite not work too on WASM (at least the rusqlite bindings). 
 New version of `rust-ntfy` is out!

This version include support for authentication with access token.

https://github.com/shadowylab/ntfy

#rustlang #programming #rust #ntfy 
 blossom feature

Have you considered creating an optional blossom feature so that blossom server ... 
 Yes, sure, thank you!

Currently I'm using `reqwest` for NIP05 and NIP11, so I would say to use it also for blossom. If there is a better one (that works on WASM too), it can be replaced. 
 Keys::generate は次のバージョンでもう少し速くなる。 
 Who is on your short list of fantastic #nostr and #Bitcoin developers?

Please tag your favorite(... 
 🧡 
 verify が重いので rust-nostr を使うと多少軽くなる
ただそれでも大量のイ... 
 I opened a PR: https://github.com/SnowCait/cloudflare-sandbox/pull/1

I reduced a little bit the size. I'll investigate to understand what cause this size 
 I found the cause: it's the signature verification method.

Removing it the size (unzipped) is 642kb.

The verify signature method internally call only `secp.verify_schnorr` method (`secp256k1` library). 
 nostr:npub1useke4f9maul5nf67dj0m9sq6jcsmnjzzk4ycvldwl4qss35fvgqjdk5ks can your app do that? 
 In rust-nostr, from `v0.34`, is available an embedded tor client.

Here is a python example (just call `embedded_tor` on client `Options`):

https://github.com/rust-nostr/nostr/blob/c4270c075a12ad0bc4a268ef5074089a4b421cee/bindings/nostr-sdk-ffi/bindings-python/examples/tor.py 
 How to pullout nostr:profile in python? Thanks
#asknostr 
 ```python
from nostr_protocol import Keys, Nip19Profile
# OR `from nostr_sdk import Keys, Nip19Profile`

keys = Keys.parse("<private-key>") # nsec or hex
public_key = keys.public_key()

profile = Nip19Profile(public_key, relays=["wss://relay.damus.io"])
print(f"NIP-19: {profile.to_bech32()}")
print(f"NIP-21: {profile.to_nostr_uri()}")
```

You can learn more here (NIP-19 and NIP-21 sections): https://rust-nostr.org/ 
 inefficiencies potentially introduced in v0.34.0

FYI i needed to increase the timeout during tes... 
 With which `EventSource`? 
 How many relays are you using? Only 1 or more? 
 Can you try this commit? 

5094b373643f1629e435a5d09709d395bb5825e6 
 Ok, I'm going to publish the patch 
 rust-nostr の verify は遅い
generate は pubkey の生成を含んでるのでその分
sign ... 
 bindings はRustを直接使うよりも遅い。JSでは verify メソッドに0.35msかかりましたが、Rustでは0.022msでした。 
 New version of `rust-ntfy` is out!

https://github.com/shadowylab/ntfy

#rustlang #programming #rust #ntfy 
 nostr:npub1drvpzev3syqt0kjrls50050uzf25gehpz9vgdw08hvex7e0vgfeq0eseet Hi, I was trying the nostr ... 
 You have to save the `bunker` URI in the localstorage for future connections. You can get it after first connection by calling `Nip46Signer::bunker_uri` method.

https://github.com/rust-nostr/nostr/issues/491 
 ## rust-nostr v0.34 is out! 🦀

### Summary

Add embedded tor client support, allow to open databases with a limited capacity (automatically discard old events when max capacity is reached), add `Client::stream_events_of` as alternative method to `Client::get_events_of` (stream events instead of waiting for `EOSE` and collect into a list), add search capability (NIP-50) support to `Filter::match_event` and databases, add NIP-31 and NIP-70 support, add option to autoconnect relay on `Client::add_relay` method call (currently disabled by default), rework the `get_events_of` methods behaviour for better consistency (`RelayPool::get_events_of` and `Relay::get_events_of` get events only from remote relay/s while `Client::get_events_of` allow to choose the source of events: `database`, `relays` or `both`), bugs fix and more!

Full changelog: https://rust-nostr.org/changelog

### Contributors

Thanks to all contributors!

* @reya
* @RydalWater

### Links

https://rust-nostr.org
https://rust-nostr.org/donate

#rustnostr #nostr #rustlang #programming #rust #python #javascript #kotlin #swift 
 Damus iOS technically censors you from creating an empty post. true freedom on nostr means the ab... 
 Ehi nostr:npub1drvpzev3syqt0kjrls50050uzf25gehpz9vgdw08hvex7e0vgfeq0eseet I see you are working o... 
 Hey, unfortunately is not available on rust-nostr yet. I was working on it but not finished yet. I hope to complete it soon! 
 No, it not support that. Only received events are persistent if you are using indexeddb.

NostrConnectURI not support de/serialization with serde but you can use the `.to_string()` method to serialize it as str and `NostrConnectURI::parse` to parse it. 
 Anyone know what’s the fastest embedded key/value store ? (Which is written in Rust or has a Ru... 
 `database.query` not returning specific coordinate

nostr:naddr1qqrkxcf3xyuxzcgzyzsq3hh327t0h2dq6... 
 Weird, I'm not able to reproduce the issue.

Can you provide a simple example to reproduce it?

I tried this but works:

```rust
use nostr_sdk::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    tracing_subscriber::fmt::init();

    let database = SQLiteDatabase::open("./db/sqlite.db").await?;
    let client: Client = ClientBuilder::default().database(database).build();

    client.add_relay("wss://relay.damus.io").await?;
    client.connect().await;

    // Negentropy reconcile
    let filter = Filter::new().kind(Kind::GitRepoAnnouncement);
    client
        .reconcile(filter, NegentropyOptions::default())
        .await?;

    // Query events from database
    let coordinate = [
        // rust-nostr repo
        Coordinate::parse("naddr1qq98yatnwskkummnw3eqz9thwden5te0wp6hyurvv4ex2mrp0yhxxmmdqgsx3kq3vkgczq9hmfplc28h687py42yvms3zkyxh8nmkvn0vhkyyusrqsqqqaue3uzvg2")?,
        // example repo
        Coordinate::parse("naddr1qqrkxcf3xyuxzcgpp4mhxue69uhkummn9ekx7mqzyzsq3hh327t0h2dq6matqn5064cgj2zanl2stkj6s0lg4t2h5dty6qcyqqq80xg2w8nml")?
    ];
    let filter = Filter::new()
        .kind(Kind::GitRepoAnnouncement)
        .identifiers(
            coordinate
                .iter()
                .map(|c| c.identifier.clone())
                .collect::<Vec<String>>(),
        )
        .authors(
            coordinate
                .iter()
                .map(|c| c.public_key)
                .collect::<Vec<PublicKey>>(),
        );
    let events = client.database().query(vec![filter], Order::Desc).await?;
    for event in events.into_iter() {
        println!("{}", event.as_json());
    }

    Ok(())
}
```

The output that I receive is:
```json
{"id":"7f7a9fac2584002b6beb97d62989affdbd4b13659b277fa9d90a034f44dbdee2","pubkey":"a008def15796fba9a0d6fab04e8fd57089285d9fd505da5a83fe8aad57a3564d","created_at":1722589461,"kind":30617,"tags":[["d","ca118aa"],["r","ca118aac0e3fa285da167a315f3e68b2654ff792","euc"],["name","Example Repo"],["description","demonstrate ngit and gitworkshop features"],["clone","https://github.com/DanConwayDev/ExampleRepo.git"],["web","https://gitworkshop.dev/repo/ca118aa"],["relays","wss://relay.nostr.band/","wss://nos.lol/","wss://relay.damus.io/"],["maintainers","a008def15796fba9a0d6fab04e8fd57089285d9fd505da5a83fe8aad57a3564d"],["alt","git repository: Example Repo"]],"content":"","sig":"d5e9ef370add7e06f9b5706a40674a47597c4dfa0961fc77db4aa76108b2423bd331ddcffdedd0ff1513c6b75263e85b8c9a6d85435a68845d08300b889ce915"}
{"id":"6988cf03ed1e6f4c684ef04997b087820fb36bc6650912bc241e45d35db8fb53","pubkey":"68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272","created_at":1719967094,"kind":30617,"tags":[["d","rust-nostr"],["r","73f7c34fd424cae57a0da1be1f5fd31d9b8075c0"],["name","rust-nostr"],["description","Nostr protocol implementation, SDK and FFI"],["clone","https://github.com/rust-nostr/nostr.git"],["web","https://rust-nostr.org"],["relays","wss://relay.damus.io","wss://nos.lol","wss://nostr.mom","wss://nostr.oxtr.dev","wss://relay.nostr.bg"],["maintainers","68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272"]],"content":"","sig":"84af432928a39f892b117e86c2bd205fa3dc2e840e3373eb1229a0719707f5f5621e7379a48a6ef309a33089b29cdb8b0f2bb0d8f432e443586f29117a968f11"}
``` 
 Are you on v0.33? On `master` branch seems to not happen. Weird.
 
 Should be fixed at 141c782b67c1d07479ad6dd902772d403a46c0f5

Let me know if you still have issues 
 `ToBech32 for Coordinate` return `String` rather than `Result`

this is obviously only a ... 
 Ah sorry, according to bech32 could happen if the passed data is too long. So, in the case of `Coordinate`, if the identifier is huge the code will return an error. It's unlikely that happen for `Coordinate` but possible with other structs like `Nip19Profile` (ex. adding many relays).

https://github.com/rust-nostr/nostr/pull/394 
 ## rust-nostr v0.33.0 is out! 🦀

### Summary

Better outputs for send/batch/reconcile methods (ex. you can now easily know where a message/event is successfully published and where/why failed), allow to change NIP-42 option after client initialization, increase max stack size for JS bindings to prevent "memory access out of bounds" error, expose more objects/methods for JS bindings, dry run option for negentropy reconciliation, get NIP-46 relay from NIP-05 profile, bug fixes (NIP-42 auth not works correctly, NIP-46 "ACK" message not handled, ...) and more!

Full changelog: https://rust-nostr.org/changelog

### Contributors

Thanks to all contributors!

* @DanConwayDev
* @xy
* @dcadenas
* @RydalWater
* @AB

### Links

https://rust-nostr.org
https://rust-nostr.org/donate

#rustnostr #nostr 
 feat: negentropy reconcile reporting

feature request: `relay.reconcile` return the ids that were... 
 I tried to add the send failures at commit `bdd250dab7f107b78aae63214c115336248e8367`.
What do you think of that solution? I'm not still very convinced but for now could be good.
I'll try to add receive errors in the v0.34 release, in next few days I'll release v0.33 
Event not found
 Thanks, it's a bug introduced during some changes to `get_events_of` method: by default the `MemoryDatabase` not store events so can't query. The zap method internally call `get_events_of` to get the pubkey metadata.

I'm going to fix it