Skip to content

Commit

Permalink
ffi(nostr): expose NostrSigner
Browse files Browse the repository at this point in the history
Signed-off-by: Yuki Kishimoto <[email protected]>
  • Loading branch information
yukibtc committed Oct 31, 2024
1 parent b9b6309 commit ff49164
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 45 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bindings/nostr-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ name = "nostr_ffi"
crate-type = ["lib", "cdylib", "staticlib"]

[dependencies]
async-trait.workspace = true
nostr = { workspace = true, features = ["std", "all-nips"] }
uniffi = { workspace = true, features = ["tokio"] }

Expand Down
53 changes: 30 additions & 23 deletions bindings/nostr-ffi/bindings-python/examples/event_builder.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
from nostr_protocol import Keys, PublicKey, EventBuilder, Event, Tag, Kind
import asyncio
from nostr_protocol import Keys, EventBuilder, Kind

keys = Keys.generate()

# Build a text note
event = EventBuilder.text_note("New note from Rust Nostr python bindings", []).to_event(keys)
print(event.as_json())
async def main():
keys = Keys.generate()

# Build a DM
receiver_pk = PublicKey.from_bech32("npub14f8usejl26twx0dhuxjh9cas7keav9vr0v8nvtwtrjqx3vycc76qqh9nsy")
event = EventBuilder.encrypted_direct_msg(keys, receiver_pk, "New note from Rust Nostr python bindings", None).to_event(keys)
print(event.as_json())
# Build a text note
builder = EventBuilder.text_note("Note from rust-nostr python bindings", [])
event = await builder.sign(keys)
print(event.as_json())

# Build a custom event
kind = Kind(1234)
content = "My custom content"
tags = []
builder = EventBuilder(kind, content, tags)
# Build a custom event
kind = Kind(1234)
content = "My custom content"
tags = []
builder = EventBuilder(kind, content, tags)

# Normal
event = builder.to_event(keys)
print(f"Event: {event.as_json()}")
# Sign with generic signer
event = await builder.sign(keys)
print(f"Event: {event.as_json()}")

# POW
event = builder.to_pow_event(keys, 20)
print(f"POW event: {event.as_json()}")
# Sign specifically with keys
event = builder.sign_with_keys(keys)
print(f"Event: {event.as_json()}")

# Unsigned
event = builder.to_unsigned_event(keys.public_key())
print(f"Event: {event.as_json()}")
# POW
event = await builder.pow(24).sign(keys)
print(f"POW event: {event.as_json()}")

# Build unsigned event
event = builder.build(keys.public_key())
print(f"Event: {event.as_json()}")


if __name__ == '__main__':
asyncio.run(main())
6 changes: 6 additions & 0 deletions bindings/nostr-ffi/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ impl From<std::char::ParseCharError> for NostrError {
}
}

impl From<nostr::signer::SignerError> for NostrError {
fn from(e: nostr::signer::SignerError) -> NostrError {
Self::Generic(e.to_string())
}
}

impl From<nostr::key::Error> for NostrError {
fn from(e: nostr::key::Error) -> NostrError {
Self::Generic(e.to_string())
Expand Down
30 changes: 21 additions & 9 deletions bindings/nostr-ffi/src/event/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::nips::nip53::LiveEvent;
use crate::nips::nip57::ZapRequestData;
use crate::nips::nip90::JobFeedbackData;
use crate::nips::nip98::HttpData;
use crate::signer::{IntermediateNostrSigner, NostrSigner};
use crate::types::{Contact, Metadata};
use crate::{
FileMetadata, Image, ImageDimensions, NostrConnectMessage, PublicKey, RelayMetadata, Tag,
Expand All @@ -48,8 +49,11 @@ impl Deref for EventBuilder {
}
}

#[uniffi::export]
#[uniffi::export(async_runtime = "tokio")]
impl EventBuilder {
// `#[uniffi::export(async_runtime = "tokio")]` require an async method
async fn _none(&self) {}

#[uniffi::constructor]
pub fn new(kind: &Kind, content: &str, tags: &[Arc<Tag>]) -> Self {
let tags = tags.iter().map(|t| t.as_ref().deref().clone());
Expand Down Expand Up @@ -82,13 +86,19 @@ impl EventBuilder {
builder
}

pub fn to_event(&self, keys: &Keys) -> Result<Event> {
let event = self.inner.clone().to_event(keys.deref())?;
pub async fn sign(&self, signer: Arc<dyn NostrSigner>) -> Result<Event> {
let signer = IntermediateNostrSigner::new(signer);
let event = self.inner.clone().sign(&signer).await?;
Ok(event.into())
}

pub fn sign_with_keys(&self, keys: &Keys) -> Result<Event> {
let event = self.inner.clone().sign_with_keys(keys.deref())?;
Ok(event.into())
}

pub fn to_unsigned_event(&self, public_key: &PublicKey) -> UnsignedEvent {
self.inner.clone().to_unsigned_event(**public_key).into()
pub fn build(&self, public_key: &PublicKey) -> UnsignedEvent {
self.inner.clone().build(**public_key).into()
}

/// Profile metadata
Expand Down Expand Up @@ -549,17 +559,19 @@ impl EventBuilder {
/// <https://github.com/nostr-protocol/nips/blob/master/59.md>
#[inline]
#[uniffi::constructor]
pub fn seal(
sender_keys: &Keys,
pub async fn seal(
signer: Arc<dyn NostrSigner>,
receiver_public_key: &PublicKey,
rumor: &UnsignedEvent,
) -> Result<Self> {
let signer = IntermediateNostrSigner::new(signer);
Ok(Self {
inner: nostr::EventBuilder::seal(
sender_keys.deref(),
&signer,
receiver_public_key.deref(),
rumor.deref().clone(),
)?,
)
.await?,
})
}

Expand Down
54 changes: 53 additions & 1 deletion bindings/nostr-ffi/src/key/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
// Distributed under the MIT software license

use std::ops::Deref;
use std::sync::Arc;

use nostr::key;
use nostr::nips::nip06::FromMnemonic;
use nostr::secp256k1::Message;
use nostr::{key, NostrSigner as _};
use uniffi::Object;

mod public_key;
Expand All @@ -15,6 +16,8 @@ mod secret_key;
pub use self::public_key::PublicKey;
pub use self::secret_key::SecretKey;
use crate::error::Result;
use crate::signer::NostrSigner;
use crate::{Event, UnsignedEvent};

/// Nostr keys
#[derive(Debug, PartialEq, Eq, Object)]
Expand Down Expand Up @@ -110,3 +113,52 @@ impl Keys {
Ok(self.inner.sign_schnorr(&message).to_string())
}
}

#[uniffi::export]
#[async_trait::async_trait]
impl NostrSigner for Keys {
async fn get_public_key(&self) -> Result<Option<Arc<PublicKey>>> {
Ok(Some(Arc::new(self.inner.get_public_key().await?.into())))
}

async fn sign_event(&self, unsigned: Arc<UnsignedEvent>) -> Result<Option<Arc<Event>>> {
Ok(Some(Arc::new(
self.inner
.sign_event(unsigned.as_ref().deref().clone())
.await?
.into(),
)))
}

async fn nip04_encrypt(&self, public_key: Arc<PublicKey>, content: String) -> Result<String> {
Ok(self
.inner
.nip04_encrypt(public_key.as_ref().deref(), &content)
.await?)
}

async fn nip04_decrypt(
&self,
public_key: Arc<PublicKey>,
encrypted_content: String,
) -> Result<String> {
Ok(self
.inner
.nip04_decrypt(public_key.as_ref().deref(), &encrypted_content)
.await?)
}

async fn nip44_encrypt(&self, public_key: Arc<PublicKey>, content: String) -> Result<String> {
Ok(self
.inner
.nip44_encrypt(public_key.as_ref().deref(), &content)
.await?)
}

async fn nip44_decrypt(&self, public_key: Arc<PublicKey>, payload: String) -> Result<String> {
Ok(self
.inner
.nip44_decrypt(public_key.as_ref().deref(), &payload)
.await?)
}
}
1 change: 1 addition & 0 deletions bindings/nostr-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub mod helper;
pub mod key;
pub mod message;
pub mod nips;
pub mod signer;
pub mod types;
pub mod util;

Expand Down
27 changes: 17 additions & 10 deletions bindings/nostr-ffi/src/nips/nip59.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,27 @@ use nostr::EventBuilder;
use uniffi::Object;

use crate::error::Result;
use crate::{Event, Keys, PublicKey, Timestamp, UnsignedEvent};
use crate::signer::{IntermediateNostrSigner, NostrSigner};
use crate::{Event, PublicKey, Timestamp, UnsignedEvent};

/// Build Gift Wrap
///
/// <https://github.com/nostr-protocol/nips/blob/master/59.md>
#[uniffi::export(default(expiration = None))]
pub fn gift_wrap(
sender_keys: &Keys,
#[uniffi::export(async_runtime = "tokio", default(expiration = None))]
pub async fn gift_wrap(
signer: Arc<dyn NostrSigner>,
receiver_pubkey: &PublicKey,
rumor: &UnsignedEvent,
expiration: Option<Arc<Timestamp>>,
) -> Result<Event> {
let signer = IntermediateNostrSigner::new(signer);
Ok(EventBuilder::gift_wrap(
sender_keys.deref(),
&signer,
receiver_pubkey.deref(),
rumor.deref().clone(),
expiration.map(|t| **t),
)?
)
.await?
.into())
}

Expand Down Expand Up @@ -55,20 +58,24 @@ pub struct UnwrappedGift {
}

impl From<nip59::UnwrappedGift> for UnwrappedGift {
fn from(inner: nostr::prelude::UnwrappedGift) -> Self {
fn from(inner: nip59::UnwrappedGift) -> Self {
Self { inner }
}
}

#[uniffi::export]
#[uniffi::export(async_runtime = "tokio")]
impl UnwrappedGift {
// `#[uniffi::export(async_runtime = "tokio")]` require an async method
async fn _none(&self) {}

/// Unwrap Gift Wrap event
///
/// Internally verify the `seal` event
#[uniffi::constructor]
pub fn from_gift_wrap(receiver_keys: &Keys, gift_wrap: &Event) -> Result<Self> {
pub async fn from_gift_wrap(signer: Arc<dyn NostrSigner>, gift_wrap: &Event) -> Result<Self> {
let signer = IntermediateNostrSigner::new(signer);
Ok(Self {
inner: nip59::UnwrappedGift::from_gift_wrap(receiver_keys.deref(), gift_wrap.deref())?,
inner: nip59::UnwrappedGift::from_gift_wrap(&signer, gift_wrap.deref()).await?,
})
}

Expand Down
Loading

0 comments on commit ff49164

Please sign in to comment.