rust-nostr

Project Homepage | Repository | Become a supporter

The nostr development kit to build fast, stable and efficient apps for any environment!

Why rust-nostr?

  • Multi-Language Support: We support native API's including Rust, Python, JavaScript, Kotlin and Swift so you can build nostr apps in your preferred programming language.

  • Multi-Platform Support: Write nostr apps for desktop, server, mobile, web and/or embedded devices!

  • WASM Compatibility: Most of our libraries compile to WebAssembly so that they can be integrated into web applications.

  • High performance: Powered by Rust's unparalleled performance and memory safety, our libraries offers speed, stability and reliability. The same features are extended to its bindings in other languages, ensuring optimal stability across diverse development environments.

  • Broad NIP Support: Support to most relevant NIPs.

  • Customizable: The libraries are built in modular way, allowing to build customized nostr apps.

Libraries

  • Nostr: Implementation of the nostr protocol
  • Nostr Database: Databases abstraction, indexes and in-memory database implementation
  • Nostr SDK: High level nostr client library

Communication

It's available a Matrix space with some rooms at #rustnostr:matrix.org.

State

These libraries are in ALPHA state, things that are implemented generally work but the API will change in breaking ways.

Donations

rust-nostr is free and open-source. This means we do not earn any revenue by selling it. Instead, we rely on your financial support. If you actively use any of the rust-nostr libs/software/services, then please donate.

License

This project is distributed under the MIT software license.

Nostr

This section include documentation for the nostr library for all the supported languages (Rust and bindings).

If you're writing a typical Nostr client or bot, you may be interested in nostr-sdk.

Installing the library

Rust

Add the nostr dependency in your Cargo.toml file:

[dependencies]
nostr = "0.32"

Alternatively, you can add it directly from git source:

[dependencies]
nostr = { git = "https://github.com/rust-nostr/nostr", tag = "v0.32.0" }

Info

To use a specific commit, use rev instead of tag.

Import the library in your code:

use nostr::prelude::*;
Python

The nostr-protocol package is available on the public PyPI:

pip install nostr-protocol 

Alternatively, you can manually add the dependency in your requrements.txt, setup.py, etc.:

nostr-protocol==0.32.1

Import the library in your code:

from nostr_protocol import *

Support matrix

The wheels are distributed for the following python versions and platforms. If your version/platform is not currently supported, you can compile the wheel by your self following these instructions.

Python version

3.83.93.103.113.123.13

Platform support

OSx64aarch64armi686
Linux
macOS
Windows

Known issues

No running event loop

If you receive no running event loop error at runtime, add the following line to your code:

import asyncio
from nostr_sdk import uniffi_set_event_loop

uniffi_set_event_loop(asyncio.get_running_loop())
JavaScript

The nostr package is available on the public npmjs:

npm i @rust-nostr/nostr 

Alternatively, you can manually add the dependency in your package.json file:

{
    "dependencies": {
        "@rust-nostr/nostr": "0.32.0"
    }
}

WASM

This library to work require to load the WASM code.

Load in async context

const { loadWasmAsync } = require("@rust-nostr/nostr");

async function main() {
    // Load WASM
    await loadWasmAsync();

    // ...
}

main();

Load in sync context

const { loadWasmSync } = require("@rust-nostr/nostr");

function main() {
    // Load WASM
    loadWasmSync();

    // ...
}

main();
Kotlin

To use the Kotlin language bindings for nostr in your Android project add the following to your gradle dependencies:

repositories {
    mavenCentral()
}

dependencies { 
    implementation("org.rust-nostr:nostr:0.32.1")
}

Import the library in your code:

import rust.nostr.protocol.*

Known issues

JNA dependency

Depending on the JVM version you use, you might not have the JNA dependency on your classpath. The exception thrown will be

class file for com.sun.jna.Pointer not found

The solution is to add JNA as a dependency like so:

dependencies {
    // ...
    implementation("net.java.dev.jna:jna:5.12.0@aar")
}
Swift

Xcode

Via File > Add Packages..., add

https://github.com/rust-nostr/nostr-swift.git

as a package dependency in Xcode.

Swift Package

Add the following to the dependencies array in your Package.swift:

.package(url: "https://github.com/rust-nostr/nostr-swift.git", from: "0.32.1"),

Import the library in your code:

import Nostr

Keys

Generate new random keys

To generate a new key pair use the generate() method:

Rust
pub fn generate() -> Result<()> {
    let keys = Keys::generate();

    let public_key = keys.public_key();
    let secret_key = keys.secret_key()?;

    println!("Public key (hex): {}", public_key);
    println!("Public key (bech32): {}", public_key.to_bech32()?);
    println!("Secret key (hex): {}", keys.secret_key()?.to_secret_hex());
    println!("Secret key (bech32): {}", secret_key.to_bech32()?);
    
    Ok(())
}
Python
def generate():
    keys = Keys.generate()
    public_key = keys.public_key()
    secret_key = keys.secret_key()

    print("Keys:")
    print(" Public keys:")
    print(f"     hex:    {public_key.to_hex()}")
    print(f"     bech32: {public_key.to_bech32()}")
    print()
    print(" Secret keys:")
    print(f"     hex:    {secret_key.to_hex()}")
    print(f"     bech32: {secret_key.to_bech32()}")
JavaScript
function generate() {
    // Load WASM
    loadWasmSync();

    // Generate new random keys
    let keys = Keys.generate();
    console.log("Public key (hex): ", keys.publicKey.toHex());
    console.log("Secret key (hex): ", keys.secretKey.toHex());

    console.log("Public key (bech32): ", keys.publicKey.toBech32());
    console.log("Secret key (bech32): ", keys.secretKey.toBech32());
}
Kotlin
fun generate() {
    val keys = Keys.generate();

    val publicKey = keys.publicKey();
    val secretKey = keys.secretKey();

    println("Public key (hex): ${publicKey.toHex()}");
    println("Public key (bech32): ${publicKey.toBech32()}");

    println("Secret key (hex): ${secretKey.toHex()}");
    println("Secret key (bech32): ${secretKey.toHex()}");
}
Swift
import Nostr
import Foundation

func keys() {
    // TODO
}

Restore from hex and/or bech32 secret key

Rust
pub fn restore() -> Result<()> {
    // Parse keys directly from secret key
    let keys = Keys::parse("secret-key")?;
    
    // Parse secret key and construct keys
    let secret_key = SecretKey::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")?;
    let keys = Keys::new(secret_key);

    // Restore from bech32
    let secret_key = SecretKey::from_bech32("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?;
    let keys = Keys::new(secret_key);

    // Restore from hex
    let secret_key = SecretKey::from_hex("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")?;
    let keys = Keys::new(secret_key);

    Ok(())
}
Python
def restore():
    keys = Keys.parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")

    secret_key = SecretKey.from_hex("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
    keys = Keys(secret_key)

    secret_key = SecretKey.from_bech32("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")
    keys = Keys(secret_key)
JavaScript
function restore() {
    // Load WASM
    loadWasmSync();

    // Parse Keys directly from secret key
    let keys1 = Keys.parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85");

    // Parse secret key and construct keys
    let secretKey = SecretKey.fromBech32("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85");
    let keys2 = new Keys(secretKey);
    console.log("Secret key (hex): ", keys2.secretKey.toHex());

    // Construct Keys from public key
    let publicKey = PublicKey.fromHex("7b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e");
    let keys3 = Keys.fromPublicKey(publicKey);
}
Kotlin
fun restore() {
    var keys = Keys.parse("hex or bech32 secret key")

    // Parse from hex
    var secretKey = SecretKey.fromHex("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")
    keys = Keys(secretKey = secretKey)

    secretKey = SecretKey.fromBech32("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")
    keys = Keys(secretKey = secretKey)
}
Swift

TODO

Generate vanity keys

Rust
pub fn vanity() -> Result<()> {
    let keys = Keys::vanity(vec!["0000", "yuk", "yuk0"], true, 8)?;
    println!("Secret key: {}", keys.secret_key()?.to_bech32()?);
    println!("Public key: {}", keys.public_key().to_bech32()?);
    Ok(())
}
Python
def vanity():
    keys = Keys.vanity(["yuk0"], True, 8)
    print(" Vanity:")
    print(f"     Public keys: {keys.public_key().to_bech32()}")
    print(f"     Secret keys: {keys.secret_key().to_bech32()}")
JavaScript
function vanity() {
    // Load WASM
    loadWasmSync();

    // NOTE: NOT SUPPORTED YET!

    // Generate vanity keys
    // let keys = Keys.vanity(["yuk0"], true, 1);
    // console.log("Public key (bech32): ", keys.publicKey.toBech32());
    // console.log("Secret key (bech32): ", keys.secretKey.toBech32());
}
Kotlin
fun vanity() {
    val keys = Keys.vanity(listOf("yuk0"), true, 4u)

    println("Public key: ${keys.publicKey().toBech32()}");
    println("Secret key: ${keys.secretKey().toHex()}");
}
Swift
import Nostr
import Foundation

func vanity() {
    // TODO
}

Event

Serialize/deserialize to/from JSON

Rust
use nostr::prelude::*;

pub fn event() -> Result<()> {
    // Deserialize from json
    let json = r#"{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","sig":"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}"#;
    let event = Event::from_json(json)?;

    // Serialize as json
    let json = event.as_json();
    println!("{json}");

    Ok(())
}
Python
from nostr_protocol import *

def event_json():
    # Deserialize from json
    json = '{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","sig":"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}'
    event = Event.from_json(json)

    # Serialize as json
    json = event.as_json()
    print("\nEvent JSON:")
    print(f" {json}")
JavaScript
const { loadWasmSync, Event } = require("@rust-nostr/nostr");

function eventJson() {
    // Load WASM
    loadWasmSync();

    // Deserialize from json
    let json1 = '{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","sig":"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}'
    let event = Event.fromJson(json1)

    // Serialize as json
    let json2 = event.asJson()
    console.log(json2);
}

module.exports.eventJson = eventJson;
Kotlin
fun json() {
    // Deserialize from json
    val json =
        "{\"content\":\"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==\",\"created_at\":1640839235,\"id\":\"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45\",\"kind\":4,\"pubkey\":\"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785\",\"sig\":\"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd\",\"tags\":[[\"p\",\"13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d\"]]}"
    val event = Event.fromJson(json)

    // Serialize as json
    println(event.asJson())
}
Swift
import Nostr
import Foundation

func json() {
    // TODO
}

Compose with event builder

A convenient way to compose events is by using the EventBuilder. It allow to compose standard and/or custom events.

Rust
use nostr::prelude::*;

pub fn event() -> Result<()> {
    let keys = Keys::generate();

    // Compose custom event
    let custom_event = EventBuilder::new(Kind::Custom(1111), "", []).to_event(&keys)?;

    // Compose text note
    let textnote_event = EventBuilder::text_note("Hello", []).to_event(&keys)?;

    // Compose reply to above text note
    let reply_event = EventBuilder::text_note("Reply to hello", [Tag::event(textnote_event.id)])
        .to_event(&keys)?;

    // Compose POW event
    let pow_event =
        EventBuilder::text_note("Another reply with POW", [Tag::event(textnote_event.id)])
            .to_pow_event(&keys, 20)?;

    Ok(())
}
Python
from nostr_protocol import *

def event_builder():
    keys = Keys.generate()

    # Compose custom event
    custom_event = EventBuilder(Kind(1111), "", []).to_event(keys)

    # Compose text note
    textnote_event = EventBuilder.text_note("Hello", []).to_event(keys)

    # Compose reply to above text note
    reply_event = EventBuilder.text_note("Reply to hello", [Tag.event(textnote_event.id())]).to_event(keys)

    # Compose POW event
    pow_event = EventBuilder.text_note("Another reply with POW", [Tag.event(textnote_event.id())]).to_pow_event(keys, 20)
JavaScript
const { Keys, loadWasmSync, EventBuilder, Tag, Timestamp } = require("@rust-nostr/nostr");

function eventBuilder() {
    // Load WASM
    loadWasmSync();

    let keys = Keys.generate();

    // Compose custom event
    let customEvent = new EventBuilder(1111, "", []).toEvent(keys);

    // Compose text note
    let textnoteEvent = EventBuilder.textNote("Hello", []).toEvent(keys);

    // Compose reply to above text note
    let replyEvent =
        EventBuilder.textNote("Reply to hello", [Tag.event(textnoteEvent.id)])
            .toEvent(keys);

    // Compose POW event
    let powEvent =
        EventBuilder.textNote("Another reply with POW", [Tag.event(textnoteEvent.id)])
            .toPowEvent(keys, 20);

    // Compose note with custom timestamp
    let customTimestamp =
        EventBuilder.textNote("Note with custom timestamp", [])
            .customCreatedAt(Timestamp.fromSecs(12345678))
            .toEvent(keys);
}

module.exports.eventBuilder = eventBuilder;
Kotlin
fun builder() {
    val keys = Keys.generate();

    // Compose custom event
    val customEvent = EventBuilder(Kind(1111u), "", listOf()).toEvent(keys);

    // Compose text note
    val textNoteEvent = EventBuilder.textNote("Hello", listOf()).toEvent(keys);

    // Compose reply to above text note
    val replyEvent = EventBuilder.textNote("Reply to hello", listOf(Tag.event(textNoteEvent.id())))
        .toEvent(keys);

    // Compose POW event
    val powEvent =
    EventBuilder.textNote("Another reply with POW", listOf(Tag.event(textNoteEvent.id())))
        .toPowEvent(keys, 20u);
    println(powEvent.asJson())
}
Swift
import Nostr
import Foundation

func builder() {
    // TODO
}

Event ID

Kind

Timestamp

Tag

Messages

Client Message

Filter

Relay Message

Serialize/deserialize to/from JSON

Rust
use nostr::prelude::*;

pub fn relay_message() -> Result<()> {
    // Deserialize from json
    let json = r#"["EVENT", "random_string", {"id":"70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5","pubkey":"379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe","created_at":1612809991,"kind":1,"tags":[],"content":"test","sig":"273a9cd5d11455590f4359500bccb7a89428262b96b3ea87a756b770964472f8c3e87f5d5e64d8d2e859a71462a3f477b554565c4f2f326cb01dd7620db71502"}]"#;
    let msg = RelayMessage::from_json(json)?;

    // Serialize as json
    let json = msg.as_json();
    println!("{json}");

    Ok(())
}
Python

TODO

JavaScript
const { loadWasmSync, RelayMessage } = require("@rust-nostr/nostr");

function relayMessageJson() {
    // Load WASM
    loadWasmSync();

    // Deserialize from json
    let json1 = '["EVENT", "random_string", {"id":"70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5","pubkey":"379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe","created_at":1612809991,"kind":1,"tags":[],"content":"test","sig":"273a9cd5d11455590f4359500bccb7a89428262b96b3ea87a756b770964472f8c3e87f5d5e64d8d2e859a71462a3f477b554565c4f2f326cb01dd7620db71502"}]'
    let msg = RelayMessage.fromJson(json1)

    // Serialize as json
    let json2 = msg.asJson()
    console.log(json2);
}

module.exports.relayMessageJson = relayMessageJson;
Kotlin

TODO

Swift

TODO

NIPs

NIP-01

The Event struct represents the structure for an event in Nostr. Many of the NIPs define specific content and tags that are required to correctly represent a kind. The nostr crate ships with a set of NIP-specific utilities for working with certain event kinds.

Metadata (NIP-01)

Rust

Use the Metadata struct to deserialize the content of an event into a struct.

    let event = EventBuilder::new(Kind::Metadata, content, vec![]).to_event(&keys)?;
    let metadata = Metadata::from_json(&event.content)?;

If you have an existing metadata object, it can be used with the EventBuilder struct to create an EventBuilder with the metadata already attached.

    let metadata = Metadata::from_json(content)?;
    let event = EventBuilder::metadata(&metadata).to_event(&keys)?;

For documentation on the available struct attributes, check out the Metadata documentation.

Python

Using the Metadata class to build the metadata object and the EventBuilder class to create a Metadata event.

    # Create metadata object with desired content
    metadata_content = Metadata()\
        .set_name("TestName")\
        .set_display_name("PyTestur")\
        .set_about("This is a Test Account for Rust Nostr Python Bindings")\
        .set_website("https://rust-nostr.org/")\
        .set_picture("https://avatars.githubusercontent.com/u/123304603?s=200&v=4")\
        .set_banner("https://nostr-resources.com/assets/images/cover.png")\
        .set_nip05("TestName@rustNostr.com")

    # Build metadata event and assign content
    builder = EventBuilder.metadata(metadata_content)

    # Signed event and print details
    print("Creating Metadata Event:")
    event = builder.to_event(keys)

    print(" Event Details:")
    print(f"     Author    : {event.author().to_bech32()}")
    print(f"     Kind      : {event.kind().as_u16()}")
    print(f"     Content   : {event.content()}")
    print(f"     Datetime  : {event.created_at().to_human_datetime()}")
    print(f"     Signature : {event.signature()}")
    print(f"     Verify    : {event.verify()}")
    print(f"     JSON      : {event.as_json()}")

Use the Metadata class to deserialize the content of an exsiting metadata event.

    # Deserialize Metadata from event
    print("Deserializing Metadata Event:")
    metadata = Metadata().from_json(event.content())
    
    print(" Metadata Details:")
    print(f"     Name      : {metadata.get_name()}")
    print(f"     Display   : {metadata.get_display_name()}")
    print(f"     About     : {metadata.get_about()}")
    print(f"     Website   : {metadata.get_website()}")
    print(f"     Picture   : {metadata.get_picture()}")
    print(f"     Banner    : {metadata.get_banner()}")
    print(f"     NIP05     : {metadata.get_nip05()}")
JavaScript

TODO

Kotlin

TODO

Swift

TODO

NIP-05

As a part of the kind 0 metadata events the optional key nip05 is used to set and internet identifier value (e.g. TestName@rustNostr.com). Clients can then use this information to make GET requests with the form https://<domain>/.well-known/nostr.json?name=<local-part>.

Mapping Nostr keys to DNS-based internet identifiers (NIP-05)

Rust

TODO

Python

Using the Metadata class to build the metadata object and incorporate the NIP-05 identifier with the set_nip05() method.

For more details on metadata (or general) events please refer back to the examples provided for NIP-01.

    # Create metadata object with name and NIP05
    metadata = Metadata() \
        .set_name("TestName") \
        .set_nip05("TestName@rustNostr.com")

For verification of NIP-05 identifiers associated with a given PublicKey object we can the verify_nip05() function as follows:

    print("Verify NIP-05:")
    nip_05 = "yuki@yukikishimoto.com"
    public_key = PublicKey.parse("npub1drvpzev3syqt0kjrls50050uzf25gehpz9vgdw08hvex7e0vgfeq0eseet")
    proxy = None
    if await verify_nip05(public_key, nip_05, proxy):
        print(f"     '{nip_05}' verified, for {public_key.to_bech32()}")
    else:
        print(f"     Unable to verify NIP-05, for {public_key.to_bech32()}")

To retrieve a sharable profile identifier (as specified in NIP-19) the get_nip05_profile() function can be called with the NIP-05 value passed as an argument.

    print("Profile NIP-05:")
    nip_05 = "yuki@yukikishimoto.com"
    profile = await get_nip05_profile(nip_05)
    print(f"     {nip_05} Profile: {profile.to_bech32()}")
JavaScript

TODO

Kotlin

TODO

Swift

TODO

NIP-06

In accordance with the Bitcoin Improvement Proposal 0039 (BIP-39) we can derive Nostr keys using seed phrases as a source of entropy. This is handled by the FromMnemonic Trait and its associated methods.

The default functionality is to generate a single key-pair at the derivation path 0. However, it is also possible to perform more advanced derivations by incrementing the account, enabling generation of many sets of keys from a single seed.

For more examples of key generation please refer back to the Keys section of this book.

Key derivation from mnemonic seed phrase (NIP-06)

Rust

TODO

Python

Using the from_mnemonic() method in conjunction with the Keys class to derived a basic set of Nostr keys from a 24 word seed phrase.

Note that this example uses the Mnemonic class from the python-mnemonic package (the reference implementation of BIP-39) to randomly generate example seed phrases.

    # Generate random Seed Phrase (24 words e.g. 256 bits entropy)
    print("Keys from 24 word Seed Phrase:")
    words = Mnemonic("english").generate(strength=256)
    passphrase = ""

    # Use Seed Phrase to generate basic Nostr keys
    keys = Keys.from_mnemonic(words, passphrase)

    print(f" Seed Words (24)  : {words}")
    print(f" Public key bech32: {keys.public_key().to_bech32()}")
    print(f" Secret key bech32: {keys.secret_key().to_bech32()}")

As well as deriving basic keys from a 24 word seed we can also use seed phrases of other lengths such as 18 words or, as in this example, 12 words.

    # Generate random Seed Phrase (12 words e.g. 128 bits entropy)
    print("Keys from 12 word Seed Phrase:")
    words = Mnemonic("english").generate(strength=128)
    passphrase = ""

    # Use Seed Phrase to generate basic Nostr keys
    keys = Keys.from_mnemonic(words, passphrase)

    print(f" Seed Words (12)  : {words}")
    print(f" Public key bech32: {keys.public_key().to_bech32()}")
    print(f" Secret key bech32: {keys.secret_key().to_bech32()}")

Advanced key derivation functionality (for accounts) can be accessed by the from_mnemonic() method. To do this we use the account argument which accepts an integer to specify the derivation path.

    # Advanced (with accounts) from the example wordlist
    words = "leader monkey parrot ring guide accident before fence cannon height naive bean"
    passphrase = ""

    print("Accounts (0-5) from 12 word Seed Phrase (with passphrase):")
    print(f" Seed Words (12): {words}")
    print(" Accounts (0-5) :")

    # Use Seed Phrase and account to multiple Nostr keys
    for account in range(0,6):
        nsec = Keys.from_mnemonic(words, passphrase, account).secret_key().to_bech32()
        print(f"     Account #{account} bech32: {nsec}")

This final example utilizes the same seed as for the previous example, but also includes a passphrase. It illustrates the effect of inclusion of a passphrase on the key derivation.

    # Advanced (with accounts) from the same wordlist with in inclusion of passphrase
    words = "leader monkey parrot ring guide accident before fence cannon height naive bean"
    passphrase = "RustNostr"
    print("Accounts (0-5) from 12 word Seed Phrase (with passphrase):")
    print(f" Seed Words (12): {words}")
    print(f" Passphrase     : {passphrase}")
    print(" Accounts (0-5) :")

    # Use Seed Phrase and account to multiple Nostr keys
    for account in range(0,6):
        nsec = Keys.from_mnemonic(words, passphrase, account).secret_key().to_bech32()
        print(f"     Account #{account} bech32: {nsec}")
JavaScript

TODO

Kotlin

TODO

Swift

TODO

NIP-07

NIP-19

Bech-32 encoding is utilized for the primary purpose of transportability between users/client/applications. In addition to bech-32 encoding of the data a series of prefixes are also used to help easily differentiate between different data objects. npub/nsec for public and private keys, respectively and note for note ids.

Extra metadata may be included when communicating between applications to make cross compatibility more streamlined. These follow a type-length-value structure and have the following possible prefixes: nprofile, nevent, nrelay and naddr.

The nip19 module and associated Nip19Event and Nip19Profile structs can be used to handle construction and interpretation of these data.

bech32-encoded entities (NIP-19)

Rust

TODO

Python

For most of these examples you will see that the to_bech32() and from_bech32() methods generally facilitate encoding or decoding objects per the NIP-19 standard.

Public and Private (or secret) keys in npub and nsec formats.

    print(f" Public key: {keys.public_key().to_bech32()}")
    print(f" Secret key: {keys.secret_key().to_bech32()}")

Simple note presented in NIP-19 format.

    event = EventBuilder.text_note("Hello from Rust Nostr Python bindings!", []).to_event(keys)
    print(f" Event     : {event.id().to_bech32()}")

Using the Nip19Profile class to create a shareable nprofile that includes relay data to help other applications to locate the profile data.

    # Create NIP-19 profile including relays data
    relays = ["wss://relay.damus.io"]
    nprofile = Nip19Profile(keys.public_key(),relays)
    print(f" Profile (encoded): {nprofile.to_bech32()}")

Using the Nip19 class to decode the previously shared profile data. This class helps generalize the decoding process for all NIP-19 objects.

    # Decode NIP-19 profile
    decode_nprofile = Nip19.from_bech32(nprofile.to_bech32())
    print(f" Profile (decoded): {decode_nprofile}")

Using the Nip19Event class to create a shareable nevent that includes author and relay data. This is followed by decoding the event object.

    # Create NIP-19 event including author and relays data
    nevent = Nip19Event(event.id(), keys.public_key(), kind=None, relays=relays)
    print(f" Event (encoded): {nevent.to_bech32()}")
    # Decode NIP-19 event
    decode_nevent = Nip19.from_bech32(nevent.to_bech32())
    print(f" Event (decoded): {decode_nevent}")

Using the Coordinate class to generate the coordinates for a replaceable event (in this case Metadata). This is followed by decoding the object.

    # Create NIP-19 coordinate
    coord = Coordinate(Kind(0),keys.public_key())
    print(f" Coordinate (encoded): {coord.to_bech32()}")
    # Decode NIP-19 coordinate
    decode_coord = Nip19.from_bech32(coord.to_bech32())
    print(f" Coordinate (decoded): {decode_coord}")
JavaScript

TODO

Kotlin

TODO

Swift

TODO

NIP-21

This NIP is intended to extend the interoperability of the network be defining the URI scheme for Nostr as nostr:. This prefix is then followed by identifiers as specified in NIP-19 (with the exclusion of nsec). For more information on the bech32 encoding used for NIP-19 please refer to the earlier examples.

The nip21 module and associated NostrURI trait can be used to handle data encoded with this format.

URI Scheme (NIP-21)

Rust

TODO

Python

Generally speaking the simplest way for handling NIP-21 objects is by the to_nostr_uri() and from_nostr_uri() methods for encoding or decoding data, respectively.

Additionally, if it is unclear what type of Nip21 object we're handling then the Nip21 class, in conjunction with the parse() and as_enum() methods, can be used to parse these objects without knowing ahead of what they are.

Public key:

    keys = Keys.generate()

    # URI npub
    pk_uri = keys.public_key().to_nostr_uri()
    print(f" Public key (URI):    {pk_uri}")

    # bech32 npub
    pk_parse = Nip21.parse(pk_uri)
    if pk_parse.as_enum().is_pubkey():
        pk_bech32 = PublicKey.from_nostr_uri(pk_uri).to_bech32()
        print(f" Public key (bech32): {pk_bech32}")

Note:

    event = EventBuilder.text_note("Hello from Rust Nostr Python bindings!", []).to_event(keys)

    # URI note
    note_uri = event.id().to_nostr_uri()
    print(f" Event (URI):    {note_uri}")

    # bech32 note
    note_pasre = Nip21.parse(note_uri)
    if note_pasre.as_enum().is_note():
        event_bech32 = EventId.from_nostr_uri(note_uri).to_bech32()
        print(f" Event (bech32): {event_bech32}")

Profile identifier:

    relays = ["wss://relay.damus.io"]
    nprofile = Nip19Profile(keys.public_key(), relays)

    # URI nprofile
    nprofile_uri = nprofile.to_nostr_uri()
    print(f" Profile (URI):    {nprofile_uri}")

    # bech32 nprofile
    nprofile_parse = Nip21.parse(nprofile_uri)
    if nprofile_parse.as_enum().is_profile():
        nprofile_bech32 = Nip19Profile.from_nostr_uri(nprofile_uri).to_bech32()
        print(f" Profile (bech32): {nprofile_bech32}")

Event identifier:

    relays = ["wss://relay.damus.io"]
    nevent = Nip19Event(event.id(), keys.public_key(), kind=None, relays=relays)

    # URI nevent
    nevent_uri = nevent.to_nostr_uri()
    print(f" Event (URI):    {nevent_uri}")

    # bech32 nevent
    nevent_parse = Nip21.parse(nevent_uri)
    if nevent_parse.as_enum().is_event():
        nevent_bech32 = Nip19Event.from_nostr_uri(nevent_uri).to_bech32()
        print(f" Event (bech32): {nevent_bech32}")

Coordinate identifier:

    coord = Coordinate(Kind(0), keys.public_key())

    # URI naddr
    coord_uri = coord.to_nostr_uri()
    print(f" Coordinate (URI):    {coord_uri}")

    # bech32 naddr
    coord_parse = Nip21.parse(coord_uri)
    if coord_parse.as_enum().is_coord():
        coord_bech32 = Coordinate.from_nostr_uri(coord_uri).to_bech32()
        print(f" Coordinate (bech32): {coord_bech32}")
JavaScript

TODO

Kotlin

TODO

Swift

TODO

NIP-44

Rust
use nostr::prelude::*;

pub fn run() -> Result<()> {
    let keys = Keys::generate();
    
    let pk = PublicKey::from_hex("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")?;

    let ciphertext = nip44::encrypt(keys.secret_key()?, &pk, "my message", nip44::Version::V2)?;
    println!("Encrypted: {ciphertext}");

    let plaintext = nip44::decrypt(keys.secret_key()?, &pk, ciphertext)?;
    println!("Decrypted: {plaintext}");

    Ok(())
}
Python
from nostr_protocol import Keys, PublicKey, nip44_encrypt, nip44_decrypt, Nip44Version

def nip44():
    print("\nEncrypting and Decrypting Messages (NIP-44):")
    keys = Keys.generate()

    pk = PublicKey.from_hex("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")

    ciphertext = nip44_encrypt(keys.secret_key(), pk, "my message", Nip44Version.V2)
    print(f" Encrypted: {ciphertext}")

    plaintext = nip44_decrypt(keys.secret_key(), pk, ciphertext)
    print(f" Decrypted: {plaintext}")
JavaScript
const { Keys, PublicKey, nip44Encrypt, nip44Decrypt, NIP44Version, loadWasmSync } = require("@rust-nostr/nostr");

function run() {
    loadWasmSync();

    let keys = Keys.generate();
    
    let public_key = PublicKey.fromHex("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798");

    let ciphertext = nip44Encrypt(keys.secretKey, public_key, "my message", NIP44Version.V2)
    console.log("Encrypted: " + ciphertext)

    let plaintext = nip44Decrypt(keys.secretKey, public_key, ciphertext)
    console.log("Decrypted: " + plaintext)
}

module.exports.run = run;
Kotlin

TODO

Swift
import Nostr
import Foundation

func nip44() {
    // TODO
}

NIP-59 - Gift Wrap

https://github.com/nostr-protocol/nips/blob/master/59.md

Rust
use nostr::prelude::*;

pub fn run() -> Result<()> {
    // Sender keys
    let alice_keys = Keys::parse("5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a")?;

    // Receiver Keys
    let bob_keys = Keys::parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?;

    // Compose rumor
    let rumor: UnsignedEvent = EventBuilder::text_note("Test", []).to_unsigned_event(alice_keys.public_key());

    // Build gift wrap with sender keys
    let gw: Event = EventBuilder::gift_wrap(&alice_keys, &bob_keys.public_key(), rumor, None)?;
    println!("Gift Wrap: {}", gw.as_json());

    // Extract rumor from gift wrap with receiver keys
    let UnwrappedGift { sender, rumor } = nip59::extract_rumor(&bob_keys, &gw)?;
    println!("Sender: {sender}");
    println!("Rumor: {}", rumor.as_json());

    Ok(())
}
Python
from nostr_protocol import Keys, EventBuilder, Event, gift_wrap, UnwrappedGift, UnsignedEvent


def nip59():
    print("\nGift Wrapping (NIP-59):")
    # Sender Keys
    alice_keys = Keys.parse("5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a")

    # Receiver Keys
    bob_keys = Keys.parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")

    # Compose rumor
    rumor = EventBuilder.text_note("Test", []).to_unsigned_event(alice_keys.public_key())

    # Build gift wrap with sender keys
    gw: Event = gift_wrap(alice_keys, bob_keys.public_key(), rumor, None)
    print(f" Gift Wrap:\n{gw.as_json()}")

    # Extract rumor from gift wrap with receiver keys
    print("\n Unwrapped Gift:")
    unwrapped_gift = UnwrappedGift.from_gift_wrap(bob_keys, gw)
    sender = unwrapped_gift.sender()
    rumor: UnsignedEvent = unwrapped_gift.rumor()
    print(f"     Sender: {sender.to_bech32()}")
    print(f"     Rumor: {rumor.as_json()}")
JavaScript
const { Keys, EventBuilder, UnwrappedGift, loadWasmSync } = require("@rust-nostr/nostr");

function run() {
    loadWasmSync();

    // Sender Keys
    const alice_keys = Keys.parse("5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a")

    // Receiver Keys
    const bob_keys = Keys.parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")

    // Compose rumor
    const rumor = EventBuilder.textNote("Test", []).toUnsignedEvent(alice_keys.publicKey)

    // Build gift wrap with sender keys
    const gw = EventBuilder.giftWrap(alice_keys, bob_keys.publicKey, rumor)
    console.log("Gift Wrap: " + gw.asJson())

    // Extract rumor from gift wrap with receiver keys
    let unwrapped_gift = UnwrappedGift.fromGiftWrap(bob_keys, gw);
    console.log("Sender: ", unwrapped_gift.sender.toBech32())
    console.log("Rumor: ", unwrapped_gift.rumor.asJson())
}

module.exports.run = run;
Kotlin

TODO

Swift
import Nostr
import Foundation

func nip59() {
    // TODO
}

NIP-65

Either the Event Builder struct and associated relay_list() function, or, the Tag struct and associated relay_metadata() function, can be used to construct NIP-65 compliant events (kind:10002), which are designed to advertise user's preferred relays from which content can be retrieved and/or published.

Relay List Metadata (NIP-65)

Rust

TODO

Python

The simpleist way to create relay metadata events is via the relay_list() method and EventBuilder class. To do this we pass the method a dictionary containing the relay URL (key) and READ/WRITE (value), which is set using the RelayMetadata class.

Note that the where no read or write value is specified (e.g. None), these should be handled as both read and write by clients (as indicated in the NIP-65 specification).

    # Create relay dictionary
    relays_dict = {
        "wss://relay.damus.io": RelayMetadata.READ,
        "wss://relay.primal.net": RelayMetadata.WRITE,
        "wss://relay.nostr.band": None
    }
    
    # Build/sign event
    builder = EventBuilder.relay_list(relays_dict)
    event = builder.to_event(keys)

    # Print event as json
    print(f" Event: {event.as_json()}")

As an alternative approach, the Tag class and relay_metadata() method can be used to create individual tag objects for inclusion in a purpose built kind:10002 event.

    # Create relay metadata tags
    tag1 = Tag.relay_metadata("wss://relay.damus.io", RelayMetadata.READ)
    tag2 = Tag.relay_metadata("wss://relay.primal.net", RelayMetadata.WRITE)
    tag3 = Tag.relay_metadata("wss://relay.nostr.band", None)

    # Build/sign event
    kind = Kind(10002)
    content = ""
    tags = [tag1,tag2,tag3]
    builder = EventBuilder(kind,content,tags)
    event = builder.to_event(keys)

    # Print event as json
    print(f" Event: {event.as_json()}")
JavaScript

TODO

Kotlin

TODO

Swift

TODO

Nostr SDK

This section include documentation for the nostr-sdk library for all the supported languages (Rust and bindings).

This library depends on nostr library so, before continue, take a look to the nostr docs.

If you're writing a typical Nostr client or bot, this is likely the library you need.

However, the library is designed in a modular way and depends on several other lower-level crates. If you're attempting something more custom, you might be interested in nostr library.

Installing the library

Rust

Add the nostr-sdk dependency in your Cargo.toml file:

[dependencies]
nostr-sdk = "0.32"

Alternatively, you can add it directly from git source:

[dependencies]
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", tag = "v0.32.0" }

Info

To use a specific commit, use rev instead of tag.

Import the library in your code:

#![allow(unused)]
fn main() {
use nostr_sdk::prelude::*;
}
Python

The nostr-sdk package is available on the public PyPI:

pip install nostr-sdk 

Alternatively, you can manually add the dependency in your requrements.txt, setup.py, etc.:

nostr-sdk==0.32.2

Import the library in your code:

from nostr_sdk import *

Support matrix

The wheels are distributed for the following python versions and platforms. If your version/platform is not currently supported, you can compile the wheel by your self following these instructions.

Python version

3.83.93.103.113.123.13

Platform support

OSx64aarch64armi686
Linux
macOS
Windows

Known issues

No running event loop

If you receive no running event loop error at runtime, add the following line to your code:

import asyncio
from nostr_sdk import uniffi_set_event_loop

uniffi_set_event_loop(asyncio.get_running_loop())
JavaScript

The nostr-sdk package is available on the public npmjs:

npm i @rust-nostr/nostr-sdk

Alternatively, you can manually add the dependency in your package.json file:

{
    "dependencies": {
        "@rust-nostr/nostr-sdk": "0.32.0"
    }
}

WASM

This library to work require to load the WASM code.

Load in async context

const { loadWasmAsync } = require("@rust-nostr/nostr-sdk");

async function main() {
    // Load WASM
    await loadWasmAsync();

    // ...
}

main();

Load in sync context

const { loadWasmSync } = require("@rust-nostr/nostr-sdk");

function main() {
    // Load WASM
    loadWasmSync();

    // ...
}

main();
Kotlin

To use the Kotlin language bindings for nostr-sdk in your Android project add the following to your gradle dependencies:

repositories {
    mavenCentral()
}

dependencies { 
    implementation("org.rust-nostr:nostr-sdk:0.32.2")
}

Import the library in your code:

import rust.nostr.protocol.*
import rust.nostr.sdk.*

Known issues

JNA dependency

Depending on the JVM version you use, you might not have the JNA dependency on your classpath. The exception thrown will be

class file for com.sun.jna.Pointer not found

The solution is to add JNA as a dependency like so:

dependencies {
    // ...
    implementation("net.java.dev.jna:jna:5.12.0@aar")
}
Swift

Xcode

Via File > Add Packages..., add

https://github.com/rust-nostr/nostr-sdk-swift.git

as a package dependency in Xcode.

Swift Package

Add the following to the dependencies array in your Package.swift:

.package(url: "https://github.com/rust-nostr/nostr-sdk-swift.git", from: "0.32.2"),

Quickstart

Rust

Create a client and connect to some relays.

    let my_keys: Keys = Keys::generate();

    let client = Client::new(&my_keys);
    let proxy = Some(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9050)));

    client.add_relay("wss://relay.damus.io").await?;
    client
        .add_relay_with_opts(
            "wss://relay.nostr.info",
            RelayOptions::new().proxy(proxy).flags(RelayServiceFlags::default().remove(RelayServiceFlags::WRITE)),
        )
        .await?;
    client
        .add_relay_with_opts(
            "ws://jgqaglhautb4k6e6i2g34jakxiemqp6z4wynlirltuukgkft2xuglmqd.onion",
            RelayOptions::new().proxy(proxy),
        )
        .await?;

    client.connect().await;

Add metadata for the keys in the existing client.

    let metadata = Metadata::new()
        .name("username")
        .display_name("My Username")
        .about("Description")
        .picture(Url::parse("https://example.com/avatar.png")?)
        .banner(Url::parse("https://example.com/banner.png")?)
        .nip05("username@example.com")
        .lud16("yuki@getalby.com")
        .custom_field("custom_field", "my value");

    client.set_metadata(&metadata).await?;

Create a filter and notify the relays of the subscription.

    let filter = Filter::new().kind(Kind::Metadata);
    let sub_id: SubscriptionId = client.subscribe(vec![filter], None).await;

For more supported filters, view the documentation.

Listen for notifications from the relays based on the subscribed filters and process them some way.

    let mut notifications = client.notifications();
    while let Ok(notification) = notifications.recv().await {
        if let RelayPoolNotification::Event { subscription_id, event, .. } = notification {
            if subscription_id == sub_id && event.kind == Kind::Metadata {
                // handle the event
                break; // Exit
            }
        }
    }
Python

TODO

JavaScript

TODO

Kotlin

TODO

Swift

TODO

Options

TODO

Proxy

TODO

Changelog

Unreleased

Summary

Changed

  • pool: use per-purpose dedicated relay channels (Yuki Kishimoto)
  • ffi(sdk): convert RelayPool::handle_notifications method to async/future (Yuki Kishimoto)

Added

Fixed

Removed

v0.32.0

Summary

Added async/future support to Python, Kotlin and Swift, added automatic authentication to relays (NIP-42, can be deactivated in client options), improvements to relay limits, many bug fixes (relays not auto reconnect, wrong query order for SQLite, tokio panic when using SQLite database in bindings) and more!

Note for kotlin devs: from this release the packages will be published at org.rust-nostr instead of io.github.rust-nostr.

Changed

Added

Fixed

  • nostr: fix re-serialization of events that contains unknown keys during deserialization (Yuki Kishimoto)
  • nostr: fix Nip21::to_nostr_uri serialization (Yuki Kishimoto)
  • pool: fix relay doesn't auto reconnect in certain cases (Yuki Kishimoto)
  • nostr: add missing TagStandard::PublicKeyLiveEvent variant to Event::public_keys (Yuki Kishimoto)
  • sqlite: fix SQLite database panics when used outside the client context in bindings (Yuki Kishimoto)
  • sqlite: fix wrong event order when querying (Yuki Kishimoto)

Removed

  • nostr: remove verify_blocking and get_profile_blocking functions (Yuki Kishimoto)
  • nostr: remove RelayInformationDocument::get_blocking (Yuki Kishimoto)
  • nostr: remove blocking feature (Yuki Kishimoto)
  • sqlite: removed deadpool-sqlite dep (Yuki Kishimoto)
  • ffi(nostr): remove Keys::from_mnemonic_with_account and Keys::from_mnemonic_advanced (Yuki Kishimoto)

v0.31.0

Summary

Reworked Tag, added TagStandard enum, simplified the way to subscribe and/or reconcile to subset of relays (respectively, client.subscribe_to and client.reconcile_with), added blacklist support to mute public keys or event IDs, removed zap split from client.zap method, many improvements and more!

Changed

Added

Fixed

Removed

v0.30.0

Summary

Adapted NIP46 to last changes, added NostrConnectRemoteSigner to easily build remote signers (just construct it and call serve method), improved proxy options (allow to specify the proxy target: all relays or only .onion ones), improvements to NWC client, fixed equality operator for bindings (Python, Kotlin and Swift), added nostrdb storage backend, added NIP32 and completed NIP51 support and more!

Changed

Added

Fixed

Removed

v0.29.4

  • pool: fix InternalRelay::get_events_of_with_callback timeout (Yuki Kishimoto)

v0.29.3

  • pool: check filter limit in InternalRelayPool::get_events_from (Yuki Kishimoto)

v0.29.2

Fixed

v0.29.1

Fixed

  • nostr: fix deserialization issues for events with non-standard k and x tags (Yuki Kishimoto)
  • pool: fix spurious send_event timeout error (DanConwayDev in https://github.com/rust-nostr/nostr/pull/375)

Donate to Rust Nostr 💜

Your donation directly supports the continued development of rust-nostr!

Give a one-time donation

Bitcoin

Altcoins

We not accept coins different from bitcoin. If you are interested to support rust-nostr with these ones, are available some swap services to convert them directly into bitcoin:

Disclaimer: rust-nostr is not affiliated in any way with these services!

Recurring donations

Please consider becoming a sustaining supporter of the rust-nostr project by giving a recurring monthly donation. If we know how much funding to expect every month, we can better plan our efforts and the use of available resources.

You can setup an automatically recurring donation here:

Verify donation details

The donation details can be verified via the rust-nostr/nostr repository, specifically in the fund directory.