From 916d96d1e5566624cdddff4a86414b9e32b7b71a Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Sun, 21 Jul 2024 17:32:54 -0400 Subject: [PATCH 1/9] nostr: add `NostrSigner` trait * Add `sign`, `sign_with_keys`, `sign_with_ctx`, `build` and `build_with_supplier` methods to `EventBuilder` * Deprecate `to_event`, `to_event_with_ctx` and `to_unsigned_event` methods Signed-off-by: Yuki Kishimoto --- CHANGELOG.md | 3 + Cargo.lock | 1 + bindings/nostr-ffi/src/event/unsigned.rs | 6 +- .../src/protocol/event/unsigned.rs | 9 +- crates/nostr/Cargo.toml | 1 + crates/nostr/examples/nip09.rs | 8 +- crates/nostr/examples/nip13.rs | 12 +- crates/nostr/examples/nip15.rs | 9 +- crates/nostr/examples/nip57.rs | 20 +- crates/nostr/src/event/builder.rs | 156 ++++++++++---- crates/nostr/src/event/mod.rs | 8 +- crates/nostr/src/event/unsigned.rs | 6 +- crates/nostr/src/key/mod.rs | 60 +++++- crates/nostr/src/lib.rs | 3 + crates/nostr/src/nips/nip07.rs | 80 +++++-- crates/nostr/src/nips/nip47.rs | 2 +- crates/nostr/src/nips/nip57.rs | 6 +- crates/nostr/src/nips/nip59.rs | 111 +++++----- crates/nostr/src/prelude.rs | 1 + crates/nostr/src/signer.rs | 197 ++++++++++++++++++ 20 files changed, 527 insertions(+), 172 deletions(-) create mode 100644 crates/nostr/src/signer.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a2086934..89c5e5032 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ * nostr: change `impl Ord for Event` behaviour (descending order instead of ascending) ([Yuki Kishimoto]) * nostr: change `TagStandard::Relays` variant value from `Vec` to `Vec` ([Yuki Kishimoto]) * nostr: reserve capacity for tags when POW is enabled in `EventBuilder` ([Yuki Kishimoto]) +* nostr: add `sign`, `sign_with_keys`, `sign_with_ctx`, `build` and `build_with_supplier` methods to `EventBuilder` ([Yuki Kishimoto]) +* nostr: deprecate `to_event`, `to_event_with_ctx` and `to_unsigned_event` methods ([Yuki Kishimoto]) * relay-builder: refactor `Session::check_rate_limit` method ([Yuki Kishimoto]) * relay-builder: return error if event was deleted ([Yuki Kishimoto]) * pool: changes in `RelayPool::remove_relay` behavior ([Yuki Kishimoto]) @@ -88,6 +90,7 @@ * nostr: add `EventBuilder::git_patch` ([Yuki Kishimoto]) * nostr: add `Tag::reference` constructor ([Yuki Kishimoto]) * nostr: add `nip59::make_seal` function ([Yuki Kishimoto]) +* nostr: add `NostrSigner` trait ([Yuki Kishimoto]) * database: add `Backend::is_persistent` method ([Yuki Kishimoto]) * database: add `Events` struct ([Yuki Kishimoto]) * relay-builder: add `LocalRelay` and `RelayBuilder` ([Yuki Kishimoto]) diff --git a/Cargo.lock b/Cargo.lock index 35f517e9b..84623a56f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2750,6 +2750,7 @@ name = "nostr" version = "0.35.0" dependencies = [ "aes", + "async-trait", "base64 0.22.1", "bech32", "bip39", diff --git a/bindings/nostr-ffi/src/event/unsigned.rs b/bindings/nostr-ffi/src/event/unsigned.rs index 07a26902b..10a7ffc90 100644 --- a/bindings/nostr-ffi/src/event/unsigned.rs +++ b/bindings/nostr-ffi/src/event/unsigned.rs @@ -67,8 +67,10 @@ impl UnsignedEvent { /// Sign an unsigned event /// /// Internally: calculate event ID (if not set), sign it, compose and verify event. - pub fn sign(&self, keys: &Keys) -> Result { - Ok(Event::from(self.inner.clone().sign(keys.deref())?)) + pub fn sign_with_keys(&self, keys: &Keys) -> Result { + Ok(Event::from( + self.inner.clone().sign_with_keys(keys.deref())?, + )) } /// Add signature to unsigned event diff --git a/bindings/nostr-sdk-js/src/protocol/event/unsigned.rs b/bindings/nostr-sdk-js/src/protocol/event/unsigned.rs index 5333f8b98..62d0ae1b6 100644 --- a/bindings/nostr-sdk-js/src/protocol/event/unsigned.rs +++ b/bindings/nostr-sdk-js/src/protocol/event/unsigned.rs @@ -93,8 +93,13 @@ impl JsUnsignedEvent { /// Sign an unsigned event /// /// Internally: calculate event ID (if not set), sign it, compose and verify event. - pub fn sign(self, keys: &JsKeys) -> Result { - Ok(self.inner.sign(keys.deref()).map_err(into_err)?.into()) + #[wasm_bindgen(js_name = signWithKeys)] + pub fn sign_with_keys(self, keys: &JsKeys) -> Result { + Ok(self + .inner + .sign_with_keys(keys.deref()) + .map_err(into_err)? + .into()) } /// Add signature to unsigned event diff --git a/crates/nostr/Cargo.toml b/crates/nostr/Cargo.toml index 4dac0dcdd..ff789c896 100644 --- a/crates/nostr/Cargo.toml +++ b/crates/nostr/Cargo.toml @@ -62,6 +62,7 @@ nip59 = ["nip44"] [dependencies] aes = { version = "0.8", optional = true } +async-trait.workspace = true base64 = { version = "0.22", default-features = false, optional = true } bech32 = { version = "0.11", default-features = false } # bitcoin uses v0.11 bip39 = { version = "2.0", default-features = false, optional = true } diff --git a/crates/nostr/examples/nip09.rs b/crates/nostr/examples/nip09.rs index 3ae9dc8bd..1a0cc38de 100644 --- a/crates/nostr/examples/nip09.rs +++ b/crates/nostr/examples/nip09.rs @@ -4,19 +4,15 @@ use nostr::prelude::*; -const MY_BECH32_SK: &str = "nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85"; - fn main() -> Result<()> { - let secret_key = SecretKey::from_bech32(MY_BECH32_SK)?; - let my_keys = Keys::new(secret_key); + let keys = Keys::parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; let event_id = EventId::from_hex("7469af3be8c8e06e1b50ef1caceba30392ddc0b6614507398b7d7daa4c218e96")?; let event: Event = EventBuilder::delete_with_reason(vec![event_id], "these posts were published by accident") - .to_event(&my_keys)?; - + .sign_with_keys(&keys)?; println!("{}", event.as_json()); Ok(()) diff --git a/crates/nostr/examples/nip13.rs b/crates/nostr/examples/nip13.rs index 471a04b9f..29532dc83 100644 --- a/crates/nostr/examples/nip13.rs +++ b/crates/nostr/examples/nip13.rs @@ -2,15 +2,10 @@ // Copyright (c) 2023-2024 Rust Nostr Developers // Distributed under the MIT software license -use std::str::FromStr; - use nostr::prelude::*; -const ALICE_SK: &str = "6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e"; - fn main() -> Result<()> { - let secret_key = SecretKey::from_str(ALICE_SK)?; - let alice_keys = Keys::new(secret_key); + let keys = Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")?; let difficulty = 20; // leading zero bits let msg_content = "This is a Nostr message with embedded proof-of-work"; @@ -19,9 +14,8 @@ fn main() -> Result<()> { // or // let builder = EventBuilder::new(Kind::TextNote, msg_content, &[]); - let event: Event = builder.pow(difficulty).to_event(&alice_keys)?; - - println!("{:#?}", event); + let event: Event = builder.pow(difficulty).sign_with_keys(&keys)?; + println!("{}", event.as_json()); Ok(()) } diff --git a/crates/nostr/examples/nip15.rs b/crates/nostr/examples/nip15.rs index c14967c17..a91dcdc9e 100644 --- a/crates/nostr/examples/nip15.rs +++ b/crates/nostr/examples/nip15.rs @@ -4,17 +4,16 @@ use nostr::prelude::*; -const ALICE_SK: &str = "6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e"; - fn main() -> Result<()> { - let alice_keys = Keys::parse(ALICE_SK)?; + let keys = Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")?; + let shipping = ShippingMethod::new("123", 5.50).name("DHL"); let stall = StallData::new("123", "my test stall", "USD") .description("this is a test stall") .shipping(vec![shipping.clone()]); - let stall_event = EventBuilder::stall_data(stall).to_event(&alice_keys)?; + let stall_event = EventBuilder::stall_data(stall).sign_with_keys(&keys)?; println!("{}", stall_event.as_json()); let product = ProductData::new("1", "123", "my test product", "USD") @@ -24,7 +23,7 @@ fn main() -> Result<()> { .images(vec!["https://example.com/image.png".into()]) .categories(vec!["test".into()]); - let product_event = EventBuilder::product_data(product).to_event(&alice_keys)?; + let product_event = EventBuilder::product_data(product).sign_with_keys(&keys)?; println!("{}", product_event.as_json()); Ok(()) diff --git a/crates/nostr/examples/nip57.rs b/crates/nostr/examples/nip57.rs index 08df385d7..4d084fbd5 100644 --- a/crates/nostr/examples/nip57.rs +++ b/crates/nostr/examples/nip57.rs @@ -1,30 +1,24 @@ // Copyright (c) 2022-2023 Yuki Kishimoto // Distributed under the MIT software license -use std::str::FromStr; - use nostr::prelude::*; -const ALICE_SK: &str = "6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e"; - fn main() -> Result<()> { - let secret_key = SecretKey::from_str(ALICE_SK)?; - let alice_keys = Keys::new(secret_key); + let keys = Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e")?; let public_key = PublicKey::from_bech32("npub14f8usejl26twx0dhuxjh9cas7keav9vr0v8nvtwtrjqx3vycc76qqh9nsy")?; let relays = [Url::parse("wss://relay.damus.io").unwrap()]; - let msg = "Zap!"; - let data = ZapRequestData::new(public_key, relays).message(msg); + let data = ZapRequestData::new(public_key, relays).message("Zap!"); - let public_zap: Event = EventBuilder::public_zap_request(data.clone()).to_event(&alice_keys)?; - println!("Public zap request: {public_zap:#?}"); + let public_zap: Event = EventBuilder::public_zap_request(data.clone()).sign_with_keys(&keys)?; + println!("Public zap request: {}", public_zap.as_json()); let anon_zap: Event = nip57::anonymous_zap_request(data.clone())?; - println!("Anonymous zap request: {anon_zap:#?}"); + println!("Anonymous zap request: {}", anon_zap.as_json()); - let private_zap: Event = nip57::private_zap_request(data, &alice_keys)?; - println!("Private zap request: {private_zap:#?}"); + let private_zap: Event = nip57::private_zap_request(data, &keys)?; + println!("Private zap request: {}", private_zap.as_json()); Ok(()) } diff --git a/crates/nostr/src/event/builder.rs b/crates/nostr/src/event/builder.rs index 7224dc047..12c70b679 100644 --- a/crates/nostr/src/event/builder.rs +++ b/crates/nostr/src/event/builder.rs @@ -46,6 +46,8 @@ pub enum Error { Json(serde_json::Error), /// Secp256k1 error Secp256k1(secp256k1::Error), + /// Signer error + Signer(SignerError), /// Unsigned event error Unsigned(super::unsigned::Error), /// OpenTimestamps error @@ -80,6 +82,7 @@ impl fmt::Display for Error { Self::Key(e) => write!(f, "Key: {e}"), Self::Json(e) => write!(f, "Json: {e}"), Self::Secp256k1(e) => write!(f, "Secp256k1: {e}"), + Self::Signer(e) => write!(f, "{e}"), Self::Unsigned(e) => write!(f, "Unsigned event: {e}"), #[cfg(feature = "nip03")] Self::OpenTimestamps(e) => write!(f, "NIP03: {e}"), @@ -115,6 +118,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: SignerError) -> Self { + Self::Signer(e) + } +} + impl From for Error { fn from(e: super::unsigned::Error) -> Self { Self::Unsigned(e) @@ -211,32 +220,8 @@ impl EventBuilder { self } - /// Build event - #[inline] - pub fn to_event_with_ctx( - self, - secp: &Secp256k1, - rng: &mut R, - supplier: &T, - keys: &Keys, - ) -> Result - where - C: Signing + Verification, - R: Rng + CryptoRng, - T: TimeSupplier, - { - let pubkey: PublicKey = keys.public_key(); - Ok(self - .to_unsigned_event_with_supplier(supplier, pubkey) - .sign_with_ctx(secp, rng, keys)?) - } - /// Build unsigned event - pub fn to_unsigned_event_with_supplier( - self, - supplier: &T, - pubkey: PublicKey, - ) -> UnsignedEvent + pub fn build_with_ctx(self, supplier: &T, pubkey: PublicKey) -> UnsignedEvent where T: TimeSupplier, { @@ -291,18 +276,87 @@ impl EventBuilder { } } + /// Build unsigned event + #[inline] + #[cfg(feature = "std")] + #[deprecated(since = "0.36.0", note = "Use `build` method instead")] + pub fn to_unsigned_event(self, pubkey: PublicKey) -> UnsignedEvent { + self.build_with_ctx(&Instant::now(), pubkey) + } + + /// Build unsigned event + #[inline] + #[cfg(feature = "std")] + pub fn build(self, pubkey: PublicKey) -> UnsignedEvent { + self.build_with_ctx(&Instant::now(), pubkey) + } + + /// Build, sign and return [`Event`] + #[inline] + #[cfg(feature = "std")] + #[deprecated(since = "0.36.0", note = "Use `sign_with_ctx` method instead")] + pub fn to_event_with_ctx( + self, + secp: &Secp256k1, + rng: &mut R, + supplier: &T, + keys: &Keys, + ) -> Result + where + C: Signing + Verification, + R: Rng + CryptoRng, + T: TimeSupplier, + { + self.sign_with_ctx(secp, rng, supplier, keys) + } + /// Build event #[inline] #[cfg(feature = "std")] + #[deprecated( + since = "0.36.0", + note = "Use `sign` or `sign_with_keys` method instead" + )] pub fn to_event(self, keys: &Keys) -> Result { - self.to_event_with_ctx(&SECP256K1, &mut OsRng, &Instant::now(), keys) + self.sign_with_keys(keys) } - /// Build unsigned event + /// Build, sign and return [`Event`] #[inline] #[cfg(feature = "std")] - pub fn to_unsigned_event(self, pubkey: PublicKey) -> UnsignedEvent { - self.to_unsigned_event_with_supplier(&Instant::now(), pubkey) + pub async fn sign(self, signer: &T) -> Result + where + T: NostrSigner, + { + let public_key: PublicKey = signer.get_public_key().await?; + let unsigned: UnsignedEvent = self.build(public_key); + Ok(signer.sign_event(unsigned).await?) + } + + /// Build, sign and return [`Event`] using [`Keys`] signer + #[inline] + #[cfg(feature = "std")] + pub fn sign_with_keys(self, keys: &Keys) -> Result { + self.sign_with_ctx(&SECP256K1, &mut OsRng, &Instant::now(), keys) + } + + /// Build, sign and return [`Event`] using [`Keys`] signer + pub fn sign_with_ctx( + self, + secp: &Secp256k1, + rng: &mut R, + supplier: &T, + keys: &Keys, + ) -> Result + where + C: Signing + Verification, + R: Rng + CryptoRng, + T: TimeSupplier, + { + let pubkey: PublicKey = keys.public_key(); + Ok(self + .build_with_ctx(supplier, pubkey) + .sign_with_ctx(secp, rng, keys)?) } /// Profile metadata @@ -1225,14 +1279,18 @@ impl EventBuilder { /// #[inline] #[cfg(all(feature = "std", feature = "nip59"))] - pub fn seal( - sender_keys: &Keys, + pub async fn seal( + signer: &T, receiver_pubkey: &PublicKey, rumor: UnsignedEvent, - ) -> Result { - Ok(nip59::make_seal(sender_keys, receiver_pubkey, rumor)?) + ) -> Result + where + T: NostrSigner, + { + Ok(nip59::make_seal(signer, receiver_pubkey, rumor).await?) } + // TODO: remove expiration arg and return event builder (this will allow to build POW events and add custom tags) /// Gift Wrap from seal /// /// @@ -1266,7 +1324,7 @@ impl EventBuilder { Self::new(Kind::GiftWrap, content, tags) .custom_created_at(Timestamp::tweaked(nip59::RANGE_RANDOM_TIMESTAMP_TWEAK)) - .to_event(&keys) + .sign_with_keys(&keys) } /// Gift Wrap @@ -1274,13 +1332,19 @@ impl EventBuilder { /// #[inline] #[cfg(all(feature = "std", feature = "nip59"))] - pub fn gift_wrap( - sender_keys: &Keys, + pub async fn gift_wrap( + signer: &T, receiver: &PublicKey, rumor: UnsignedEvent, expiration: Option, - ) -> Result { - let seal: Event = Self::seal(sender_keys, receiver, rumor)?.to_event(sender_keys)?; + ) -> Result + where + T: NostrSigner, + { + let seal: Event = Self::seal(signer, receiver, rumor) + .await? + .sign(signer) + .await?; Self::gift_wrap_from_seal(receiver, &seal, expiration) } @@ -1592,7 +1656,7 @@ mod tests { ); let event = EventBuilder::text_note("hello", []) - .to_event(&keys) + .sign_with_keys(&keys) .unwrap(); let serialized = event.as_json(); @@ -1734,7 +1798,7 @@ mod tests { let event_builder: Event = EventBuilder::award_badge(&badge_definition_event, awarded_pubkeys) .unwrap() - .to_event(&keys) + .sign_with_keys(&keys) .unwrap(); assert_eq!(event_builder.kind, Kind::BadgeAward); @@ -1760,12 +1824,12 @@ mod tests { ]; let bravery_badge_event = EventBuilder::define_badge("bravery", None, None, None, None, Vec::new()) - .to_event(&badge_one_keys) + .sign_with_keys(&badge_one_keys) .unwrap(); let bravery_badge_award = EventBuilder::award_badge(&bravery_badge_event, awarded_pubkeys.clone()) .unwrap() - .to_event(&badge_one_keys) + .sign_with_keys(&badge_one_keys) .unwrap(); // Badge 2 @@ -1774,12 +1838,12 @@ mod tests { let honor_badge_event = EventBuilder::define_badge("honor", None, None, None, None, Vec::new()) - .to_event(&badge_two_keys) + .sign_with_keys(&badge_two_keys) .unwrap(); let honor_badge_award = EventBuilder::award_badge(&honor_badge_event, awarded_pubkeys.clone()) .unwrap() - .to_event(&badge_two_keys) + .sign_with_keys(&badge_two_keys) .unwrap(); let example_event_json = format!( @@ -1807,7 +1871,7 @@ mod tests { let profile_badges = EventBuilder::profile_badges(badge_definitions, badge_awards, &pub_key) .unwrap() - .to_event(&keys) + .sign_with_keys(&keys) .unwrap(); assert_eq!(profile_badges.kind, Kind::ProfileBadges); @@ -1825,7 +1889,7 @@ mod benches { pub fn builder_to_event(bh: &mut Bencher) { let keys = Keys::generate(); bh.iter(|| { - black_box(EventBuilder::text_note("hello", []).to_event(&keys)).unwrap(); + black_box(EventBuilder::text_note("hello", []).sign_with_keys(&keys)).unwrap(); }); } } diff --git a/crates/nostr/src/event/mod.rs b/crates/nostr/src/event/mod.rs index 22abb8ca1..fcf874397 100644 --- a/crates/nostr/src/event/mod.rs +++ b/crates/nostr/src/event/mod.rs @@ -524,7 +524,7 @@ mod tests { fn test_custom_kind() { let keys = Keys::generate(); let e: Event = EventBuilder::new(Kind::Custom(123), "my content", []) - .to_event(&keys) + .sign_with_keys(&keys) .unwrap(); let serialized = e.as_json(); @@ -541,7 +541,7 @@ mod tests { let my_keys = Keys::generate(); let event = EventBuilder::text_note("my content", [Tag::expiration(Timestamp::from(1600000000))]) - .to_event(&my_keys) + .sign_with_keys(&my_keys) .unwrap(); assert!(&event.is_expired()); @@ -558,7 +558,7 @@ mod tests { "my content", [Tag::expiration(Timestamp::from(expiry_date))], ) - .to_event(&my_keys) + .sign_with_keys(&my_keys) .unwrap(); assert!(!&event.is_expired()); @@ -569,7 +569,7 @@ mod tests { fn test_event_without_expiration_tag() { let my_keys = Keys::generate(); let event = EventBuilder::text_note("my content", []) - .to_event(&my_keys) + .sign_with_keys(&my_keys) .unwrap(); assert!(!&event.is_expired()); } diff --git a/crates/nostr/src/event/unsigned.rs b/crates/nostr/src/event/unsigned.rs index 2276b63f3..d95bdb5f6 100644 --- a/crates/nostr/src/event/unsigned.rs +++ b/crates/nostr/src/event/unsigned.rs @@ -143,16 +143,16 @@ impl UnsignedEvent { Ok(()) } - /// Sign an unsigned event + /// Sign an unsigned event with [`Keys`] signer /// /// Internally: calculate [EventId] (if not set), sign it, compose and verify [Event]. #[inline] #[cfg(feature = "std")] - pub fn sign(self, keys: &Keys) -> Result { + pub fn sign_with_keys(self, keys: &Keys) -> Result { self.sign_with_ctx(&SECP256K1, &mut OsRng, keys) } - /// Sign an unsigned event + /// Sign an unsigned event with [`Keys`] signer /// /// Internally: calculate [EventId] (if not set), sign it, compose and verify [Event]. pub fn sign_with_ctx( diff --git a/crates/nostr/src/key/mod.rs b/crates/nostr/src/key/mod.rs index 7493fa35b..0f2314355 100644 --- a/crates/nostr/src/key/mod.rs +++ b/crates/nostr/src/key/mod.rs @@ -15,6 +15,8 @@ use core::str::FromStr; #[cfg(feature = "std")] use std::sync::OnceLock as OnceCell; +#[cfg(feature = "std")] +use async_trait::async_trait; #[cfg(feature = "std")] use bitcoin::secp256k1::rand::rngs::OsRng; use bitcoin::secp256k1::rand::{CryptoRng, Rng}; @@ -29,7 +31,7 @@ pub mod vanity; pub use self::public_key::PublicKey; pub use self::secret_key::SecretKey; #[cfg(feature = "std")] -use crate::SECP256K1; +use crate::{Event, NostrSigner, SignerError, UnsignedEvent, SECP256K1}; /// [`Keys`] error #[derive(Debug, PartialEq, Eq)] @@ -258,6 +260,62 @@ impl FromStr for Keys { } } +#[cfg(feature = "std")] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl NostrSigner for Keys { + async fn get_public_key(&self) -> Result { + Ok(self.public_key) + } + + async fn sign_event(&self, unsigned: UnsignedEvent) -> Result { + unsigned.sign_with_keys(self).map_err(SignerError::backend) + } + + #[cfg(feature = "nip04")] + async fn nip04_encrypt( + &self, + public_key: &PublicKey, + content: &str, + ) -> Result { + let secret_key: &SecretKey = self.secret_key(); + crate::nips::nip04::encrypt(secret_key, public_key, content).map_err(SignerError::backend) + } + + #[cfg(feature = "nip04")] + async fn nip04_decrypt( + &self, + public_key: &PublicKey, + encrypted_content: &str, + ) -> Result { + let secret_key: &SecretKey = self.secret_key(); + crate::nips::nip04::decrypt(secret_key, public_key, encrypted_content) + .map_err(SignerError::backend) + } + + #[cfg(feature = "nip44")] + async fn nip44_encrypt( + &self, + public_key: &PublicKey, + content: &str, + ) -> Result { + use crate::nips::nip44::{self, Version}; + let secret_key: &SecretKey = self.secret_key(); + nip44::encrypt(secret_key, public_key, content, Version::default()) + .map_err(SignerError::backend) + } + + #[cfg(feature = "nip44")] + async fn nip44_decrypt( + &self, + public_key: &PublicKey, + payload: &str, + ) -> Result { + let secret_key: &SecretKey = self.secret_key(); + crate::nips::nip44::decrypt(secret_key, public_key, payload).map_err(SignerError::backend) + } +} + #[cfg(test)] #[cfg(feature = "std")] mod tests { diff --git a/crates/nostr/src/lib.rs b/crates/nostr/src/lib.rs index 7d043a711..ef7ed5dee 100644 --- a/crates/nostr/src/lib.rs +++ b/crates/nostr/src/lib.rs @@ -46,6 +46,7 @@ pub mod key; pub mod message; pub mod nips; pub mod prelude; +pub mod signer; pub mod types; pub mod util; @@ -62,6 +63,8 @@ pub use self::message::{ClientMessage, RawRelayMessage, RelayMessage, Subscripti #[doc(hidden)] pub use self::nips::nip19::{FromBech32, ToBech32}; #[doc(hidden)] +pub use self::signer::{NostrSigner, SignerError}; +#[doc(hidden)] pub use self::types::{ Alphabet, Contact, Filter, ImageDimensions, Metadata, SingleLetterTag, Timestamp, TryIntoUrl, UncheckedUrl, Url, diff --git a/crates/nostr/src/nips/nip07.rs b/crates/nostr/src/nips/nip07.rs index 356183b3d..b10f7777c 100644 --- a/crates/nostr/src/nips/nip07.rs +++ b/crates/nostr/src/nips/nip07.rs @@ -10,6 +10,7 @@ use alloc::string::{String, ToString}; use core::fmt; use core::str::FromStr; +use async_trait::async_trait; use bitcoin::secp256k1; use bitcoin::secp256k1::schnorr::Signature; use js_sys::{Array, Function, Object, Promise, Reflect}; @@ -18,6 +19,7 @@ use wasm_bindgen_futures::JsFuture; use web_sys::Window; use crate::event::{self, unsigned}; +use crate::signer::{NostrSigner, SignerError}; use crate::{key, Event, PublicKey, UnsignedEvent}; /// NIP07 error @@ -139,7 +141,7 @@ impl Nip07Signer { } /// Get Public Key - pub async fn get_public_key(&self) -> Result { + async fn _get_public_key(&self) -> Result { let func: Function = self.get_func(&self.nostr_obj, "getPublicKey")?; let promise: Promise = Promise::resolve(&func.call0(&self.nostr_obj)?); let result: JsValue = JsFuture::from(promise).await?; @@ -149,8 +151,7 @@ impl Nip07Signer { Ok(PublicKey::from_hex(public_key)?) } - /// Sign event - pub async fn sign_event(&self, unsigned: UnsignedEvent) -> Result { + async fn _sign_event(&self, unsigned: UnsignedEvent) -> Result { let func: Function = self.get_func(&self.nostr_obj, "signEvent")?; let tags: Array = unsigned @@ -221,12 +222,7 @@ impl Nip07Signer { .map_err(|_| Error::NamespaceNotFound(String::from("nip04"))) } - /// NIP04 encrypt - pub async fn nip04_encrypt( - &self, - public_key: &PublicKey, - content: T, - ) -> Result + async fn _nip04_encrypt(&self, public_key: &PublicKey, content: T) -> Result where T: AsRef<[u8]>, { @@ -245,8 +241,7 @@ impl Nip07Signer { .ok_or_else(|| Error::TypeMismatch(String::from("expected a string"))) } - /// NIP04 decrypt - pub async fn nip04_decrypt( + async fn _nip04_decrypt( &self, public_key: &PublicKey, ciphertext: S, @@ -275,12 +270,7 @@ impl Nip07Signer { .map_err(|_| Error::NamespaceNotFound(String::from("nip44"))) } - /// NIP44 encrypt - pub async fn nip44_encrypt( - &self, - public_key: &PublicKey, - content: T, - ) -> Result + async fn _nip44_encrypt(&self, public_key: &PublicKey, content: T) -> Result where T: AsRef<[u8]>, { @@ -299,8 +289,7 @@ impl Nip07Signer { .ok_or_else(|| Error::TypeMismatch(String::from("expected a string"))) } - /// NIP44 decrypt - pub async fn nip44_decrypt( + async fn _nip44_decrypt( &self, public_key: &PublicKey, ciphertext: T, @@ -323,3 +312,56 @@ impl Nip07Signer { .ok_or_else(|| Error::TypeMismatch(String::from("expected a string"))) } } + +#[async_trait(?Send)] +impl NostrSigner for Nip07Signer { + async fn get_public_key(&self) -> Result { + self._get_public_key().await.map_err(SignerError::backend) + } + + async fn sign_event(&self, unsigned: UnsignedEvent) -> Result { + self._sign_event(unsigned) + .await + .map_err(SignerError::backend) + } + + async fn nip04_encrypt( + &self, + public_key: &PublicKey, + content: &str, + ) -> Result { + self._nip04_encrypt(public_key, content) + .await + .map_err(SignerError::backend) + } + + async fn nip04_decrypt( + &self, + public_key: &PublicKey, + content: &str, + ) -> Result { + self._nip04_decrypt(public_key, content) + .await + .map_err(SignerError::backend) + } + + async fn nip44_encrypt( + &self, + public_key: &PublicKey, + content: &str, + ) -> Result { + self._nip44_encrypt(public_key, content) + .await + .map_err(SignerError::backend) + } + + async fn nip44_decrypt( + &self, + public_key: &PublicKey, + content: &str, + ) -> Result { + self._nip44_decrypt(public_key, content) + .await + .map_err(SignerError::backend) + } +} diff --git a/crates/nostr/src/nips/nip47.rs b/crates/nostr/src/nips/nip47.rs index 2e0695bb2..6d23fe089 100644 --- a/crates/nostr/src/nips/nip47.rs +++ b/crates/nostr/src/nips/nip47.rs @@ -526,7 +526,7 @@ impl Request { encrypted, [Tag::public_key(uri.public_key)], ) - .to_event(&keys)?) + .sign_with_keys(&keys)?) } } diff --git a/crates/nostr/src/nips/nip57.rs b/crates/nostr/src/nips/nip57.rs index 6b22ad43d..c31bd2d29 100644 --- a/crates/nostr/src/nips/nip57.rs +++ b/crates/nostr/src/nips/nip57.rs @@ -273,7 +273,7 @@ pub fn anonymous_zap_request(data: ZapRequestData) -> Result { tags.push(Tag::from_standardized_without_cell(TagStandard::Anon { msg: None, })); - Ok(EventBuilder::new(Kind::ZapRequest, message, tags).to_event(&keys)?) + Ok(EventBuilder::new(Kind::ZapRequest, message, tags).sign_with_keys(&keys)?) } /// Create **private** zap request @@ -308,7 +308,7 @@ where tags.push(Tag::event(event_id)); } let msg: String = EventBuilder::new(Kind::ZapPrivateMessage, &data.message, tags) - .to_event_with_ctx(secp, rng, supplier, keys)? + .sign_with_ctx(secp, rng, supplier, keys)? .as_json(); let msg: String = encrypt_private_zap_message(rng, &secret_key, &data.public_key, msg)?; @@ -320,7 +320,7 @@ where let private_zap_keys: Keys = Keys::new_with_ctx(secp, secret_key); Ok(EventBuilder::new(Kind::ZapRequest, "", tags) .custom_created_at(created_at) - .to_event_with_ctx(secp, rng, supplier, &private_zap_keys)?) + .sign_with_ctx(secp, rng, supplier, &private_zap_keys)?) } /// Create NIP57 encryption key for **private** zap diff --git a/crates/nostr/src/nips/nip59.rs b/crates/nostr/src/nips/nip59.rs index 8ccf190aa..9302946fe 100644 --- a/crates/nostr/src/nips/nip59.rs +++ b/crates/nostr/src/nips/nip59.rs @@ -11,28 +11,25 @@ use core::ops::Range; use bitcoin::secp256k1::{Secp256k1, Verification}; -use super::nip44; use crate::event::unsigned::{self, UnsignedEvent}; use crate::event::{self, Event}; -use crate::key::{self, Keys, SecretKey}; +use crate::signer::SignerError; #[cfg(feature = "std")] use crate::{EventBuilder, Timestamp, SECP256K1}; -use crate::{JsonUtil, Kind, PublicKey}; +use crate::{JsonUtil, Kind, NostrSigner, PublicKey}; /// Range for random timestamp tweak (up to 2 days) pub const RANGE_RANDOM_TIMESTAMP_TWEAK: Range = 0..172800; // From 0 secs to 2 days /// NIP59 error -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug)] pub enum Error { - /// Key error - Key(key::Error), + /// Signer error + Signer(SignerError), /// Event error Event(event::Error), /// Unsigned event error Unsigned(unsigned::Error), - /// NIP44 error - NIP44(nip44::Error), /// Not Gift Wrap event NotGiftWrap, } @@ -43,18 +40,17 @@ impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Key(e) => write!(f, "Key: {e}"), + Self::Signer(e) => write!(f, "{e}"), Self::Event(e) => write!(f, "Event: {e}"), Self::Unsigned(e) => write!(f, "Unsigned event: {e}"), - Self::NIP44(e) => write!(f, "NIP44: {e}"), Self::NotGiftWrap => write!(f, "Not Gift Wrap event"), } } } -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Key(e) +impl From for Error { + fn from(e: SignerError) -> Self { + Self::Signer(e) } } @@ -70,12 +66,6 @@ impl From for Error { } } -impl From for Error { - fn from(e: nip44::Error) -> Self { - Self::NIP44(e) - } -} - /// Unwrapped Gift Wrap (NIP59) #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct UnwrappedGift { @@ -91,35 +81,39 @@ impl UnwrappedGift { /// Internally verify the `seal` event #[inline] #[cfg(feature = "std")] - pub fn from_gift_wrap(receiver_keys: &Keys, gift_wrap: &Event) -> Result { - Self::from_gift_wrap_with_ctx(&SECP256K1, receiver_keys, gift_wrap) + pub async fn from_gift_wrap(signer: &T, gift_wrap: &Event) -> Result + where + T: NostrSigner, + { + Self::from_gift_wrap_with_ctx(&SECP256K1, signer, gift_wrap).await } /// Unwrap Gift Wrap event /// /// Internally verify the `seal` event - pub fn from_gift_wrap_with_ctx( + pub async fn from_gift_wrap_with_ctx( secp: &Secp256k1, - receiver_keys: &Keys, + signer: &T, gift_wrap: &Event, ) -> Result where C: Verification, + T: NostrSigner, { // Check event kind if gift_wrap.kind != Kind::GiftWrap { return Err(Error::NotGiftWrap); } - let secret_key: &SecretKey = receiver_keys.secret_key(); - // Decrypt and verify seal - let seal: String = nip44::decrypt(secret_key, &gift_wrap.pubkey, &gift_wrap.content)?; + let seal: String = signer + .nip44_decrypt(&gift_wrap.pubkey, &gift_wrap.content) + .await?; let seal: Event = Event::from_json(seal)?; seal.verify_with_ctx(secp)?; // Decrypt rumor - let rumor: String = nip44::decrypt(secret_key, &seal.pubkey, &seal.content)?; + let rumor: String = signer.nip44_decrypt(&seal.pubkey, &seal.content).await?; Ok(UnwrappedGift { sender: seal.pubkey, @@ -131,27 +125,30 @@ impl UnwrappedGift { /// Extract `rumor` from Gift Wrap event #[inline] #[cfg(feature = "std")] -pub fn extract_rumor(receiver_keys: &Keys, gift_wrap: &Event) -> Result { - UnwrappedGift::from_gift_wrap(receiver_keys, gift_wrap) +pub async fn extract_rumor(signer: &T, gift_wrap: &Event) -> Result +where + T: NostrSigner, +{ + UnwrappedGift::from_gift_wrap(signer, gift_wrap).await } /// Create seal #[cfg(feature = "std")] -pub fn make_seal( - sender_keys: &Keys, +pub async fn make_seal( + signer: &T, receiver_pubkey: &PublicKey, mut rumor: UnsignedEvent, -) -> Result { +) -> Result +where + T: NostrSigner, +{ // Make sure that rumor has event ID rumor.ensure_id(); // Encrypt content - let content: String = nip44::encrypt( - sender_keys.secret_key(), - receiver_pubkey, - rumor.as_json(), - nip44::Version::default(), - )?; + let content: String = signer + .nip44_encrypt(receiver_pubkey, &rumor.as_json()) + .await?; // Compose builder Ok(EventBuilder::new(Kind::Seal, content, []) @@ -161,47 +158,45 @@ pub fn make_seal( #[cfg(feature = "std")] #[cfg(test)] mod tests { - use core::str::FromStr; - use super::*; - use crate::EventBuilder; + use crate::{EventBuilder, Keys}; - #[test] - fn test_extract_rumor() { - let sender_keys = Keys::new( - SecretKey::from_str("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e") - .unwrap(), - ); - let receiver_keys = Keys::new( - SecretKey::from_str("7b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e") - .unwrap(), - ); + #[tokio::test] + async fn test_extract_rumor() { + let sender_keys = + Keys::parse("6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e") + .unwrap(); + let receiver_keys = + Keys::parse("7b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e") + .unwrap(); // Compose Gift Wrap event let rumor: UnsignedEvent = - EventBuilder::text_note("Test", []).to_unsigned_event(sender_keys.public_key()); + EventBuilder::text_note("Test", []).build(sender_keys.public_key()); let event: Event = EventBuilder::gift_wrap( &sender_keys, &receiver_keys.public_key(), rumor.clone(), None, ) + .await .unwrap(); assert_eq!( - extract_rumor(&receiver_keys, &event).unwrap(), + extract_rumor(&receiver_keys, &event).await.unwrap(), UnwrappedGift { sender: sender_keys.public_key(), rumor, } ); - assert!(extract_rumor(&sender_keys, &event).is_err()); + assert!(extract_rumor(&sender_keys, &event).await.is_err()); let event: Event = EventBuilder::text_note("", []) - .to_event(&sender_keys) + .sign(&sender_keys) + .await .unwrap(); - assert_eq!( - extract_rumor(&receiver_keys, &event).unwrap_err(), + assert!(matches!( + extract_rumor(&receiver_keys, &event).await.unwrap_err(), Error::NotGiftWrap - ); + )); } } diff --git a/crates/nostr/src/prelude.rs b/crates/nostr/src/prelude.rs index 0ae3f1ba5..866cabb58 100644 --- a/crates/nostr/src/prelude.rs +++ b/crates/nostr/src/prelude.rs @@ -66,6 +66,7 @@ pub use crate::nips::nip65::{self, *}; pub use crate::nips::nip90::{self, *}; pub use crate::nips::nip94::{self, *}; pub use crate::nips::nip98::{self, *}; +pub use crate::signer::{self, *}; pub use crate::types::*; pub use crate::util::{self, *}; #[cfg(feature = "std")] diff --git a/crates/nostr/src/signer.rs b/crates/nostr/src/signer.rs new file mode 100644 index 000000000..287ac676f --- /dev/null +++ b/crates/nostr/src/signer.rs @@ -0,0 +1,197 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2024 Rust Nostr Developers +// Distributed under the MIT software license + +//! Nostr Signer + +use alloc::boxed::Box; +#[cfg(any(not(feature = "std"), feature = "nip04", feature = "nip44"))] +use alloc::string::String; +use alloc::sync::Arc; +use core::fmt; + +use async_trait::async_trait; + +use crate::{Event, PublicKey, UnsignedEvent}; + +#[cfg(feature = "std")] +type InnerError = Box; +#[cfg(not(feature = "std"))] +type InnerError = String; // TODO: remove core::error::Error will be stable for MSRV + +/// Nostr Signer error +#[derive(Debug)] +pub struct SignerError(InnerError); + +impl fmt::Display for SignerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for SignerError {} + +impl SignerError { + /// New signer error + #[inline] + #[cfg(feature = "std")] + pub fn backend(error: E) -> Self + where + E: std::error::Error + Send + Sync + 'static, + { + Self(Box::new(error)) + } + + /// New signer error + #[inline] + #[cfg(not(feature = "std"))] + pub fn backend(error: E) -> Self + where + E: Into, + { + Self(error.into()) + } +} + +#[doc(hidden)] +pub trait IntoNostrSigner { + fn into_nostr_signer(self) -> Arc; +} + +impl IntoNostrSigner for T +where + T: NostrSigner + 'static, +{ + fn into_nostr_signer(self) -> Arc { + Arc::new(self) + } +} + +/// Nostr signer abstraction +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +pub trait NostrSigner: AsyncTraitDeps { + // TODO: use `Cow<'a, PublicKey>`? + /// Get signer public key + async fn get_public_key(&self) -> Result; + + /// Sign an unsigned event + async fn sign_event(&self, unsigned: UnsignedEvent) -> Result; + + /// NIP04 encrypt (deprecate and unsecure) + #[cfg(feature = "nip04")] + async fn nip04_encrypt( + &self, + public_key: &PublicKey, + content: &str, + ) -> Result; + + /// NIP04 decrypt + #[cfg(feature = "nip04")] + async fn nip04_decrypt( + &self, + public_key: &PublicKey, + encrypted_content: &str, + ) -> Result; + + /// NIP44 encrypt + #[cfg(feature = "nip44")] + async fn nip44_encrypt( + &self, + public_key: &PublicKey, + content: &str, + ) -> Result; + + /// NIP44 decrypt + #[cfg(feature = "nip44")] + async fn nip44_decrypt( + &self, + public_key: &PublicKey, + payload: &str, + ) -> Result; +} + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl NostrSigner for Arc { + async fn get_public_key(&self) -> Result { + self.as_ref().get_public_key().await + } + + async fn sign_event(&self, unsigned: UnsignedEvent) -> Result { + self.as_ref().sign_event(unsigned).await + } + + #[cfg(feature = "nip04")] + async fn nip04_encrypt( + &self, + public_key: &PublicKey, + content: &str, + ) -> Result { + self.as_ref().nip04_encrypt(public_key, content).await + } + + #[cfg(feature = "nip04")] + async fn nip04_decrypt( + &self, + public_key: &PublicKey, + encrypted_content: &str, + ) -> Result { + self.as_ref() + .nip04_decrypt(public_key, encrypted_content) + .await + } + + #[cfg(feature = "nip44")] + async fn nip44_encrypt( + &self, + public_key: &PublicKey, + content: &str, + ) -> Result { + self.as_ref().nip44_encrypt(public_key, content).await + } + + #[cfg(feature = "nip44")] + async fn nip44_decrypt( + &self, + public_key: &PublicKey, + payload: &str, + ) -> Result { + self.as_ref().nip44_decrypt(public_key, payload).await + } +} + +/// Alias for `Send` on non-wasm, empty trait (implemented by everything) on +/// wasm. +#[cfg(not(target_arch = "wasm32"))] +pub trait SendOutsideWasm: Send {} +#[cfg(not(target_arch = "wasm32"))] +impl SendOutsideWasm for T {} + +/// Alias for `Send` on non-wasm, empty trait (implemented by everything) on +/// wasm. +#[cfg(target_arch = "wasm32")] +pub trait SendOutsideWasm {} +#[cfg(target_arch = "wasm32")] +impl SendOutsideWasm for T {} + +/// Alias for `Sync` on non-wasm, empty trait (implemented by everything) on +/// wasm. +#[cfg(not(target_arch = "wasm32"))] +pub trait SyncOutsideWasm: Sync {} +#[cfg(not(target_arch = "wasm32"))] +impl SyncOutsideWasm for T {} + +/// Alias for `Sync` on non-wasm, empty trait (implemented by everything) on +/// wasm. +#[cfg(target_arch = "wasm32")] +pub trait SyncOutsideWasm {} +#[cfg(target_arch = "wasm32")] +impl SyncOutsideWasm for T {} + +/// Super trait that is used for our store traits, this trait will differ if +/// it's used on WASM. WASM targets will not require `Send` and `Sync` to have +/// implemented, while other targets will. +pub trait AsyncTraitDeps: fmt::Debug + SendOutsideWasm + SyncOutsideWasm {} +impl AsyncTraitDeps for T {} From 1b60489e42914fa9f176112aacfd65440f162716 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Thu, 17 Oct 2024 17:39:14 +0200 Subject: [PATCH 2/9] Convert `nostr-signer` crate to `nostr-connect` Signed-off-by: Yuki Kishimoto --- CHANGELOG.md | 1 + Cargo.lock | 32 +- Cargo.toml | 2 +- README.md | 4 +- contrib/scripts/check-crates.sh | 1 + contrib/scripts/check-docs.sh | 5 +- crates/nostr-cli/Cargo.toml | 1 + crates/nostr-cli/src/main.rs | 1 + crates/nostr-connect/Cargo.toml | 29 ++ crates/nostr-connect/README.md | 13 + .../examples/nostr-connect-signer.rs | 2 +- .../src/nip46 => nostr-connect/src}/client.rs | 114 ++++-- .../src/nip46 => nostr-connect/src}/error.rs | 0 .../nip46/mod.rs => nostr-connect/src/lib.rs} | 6 + .../src/prelude.rs | 2 - .../src/nip46 => nostr-connect/src}/signer.rs | 6 +- crates/nostr-sdk/Cargo.toml | 15 +- crates/nostr-sdk/examples/nostr-connect.rs | 5 +- crates/nostr-sdk/src/lib.rs | 2 - crates/nostr-sdk/src/prelude.rs | 1 - crates/nostr-signer/Cargo.toml | 32 -- crates/nostr-signer/README.md | 27 -- crates/nostr-signer/src/lib.rs | 324 ------------------ crates/nostr/README.md | 6 +- crates/nwc/src/lib.rs | 3 +- 25 files changed, 174 insertions(+), 460 deletions(-) create mode 100644 crates/nostr-connect/Cargo.toml create mode 100644 crates/nostr-connect/README.md rename crates/{nostr-signer => nostr-connect}/examples/nostr-connect-signer.rs (97%) rename crates/{nostr-signer/src/nip46 => nostr-connect/src}/client.rs (80%) rename crates/{nostr-signer/src/nip46 => nostr-connect/src}/error.rs (100%) rename crates/{nostr-signer/src/nip46/mod.rs => nostr-connect/src/lib.rs} (76%) rename crates/{nostr-signer => nostr-connect}/src/prelude.rs (84%) rename crates/{nostr-signer/src/nip46 => nostr-connect/src}/signer.rs (98%) delete mode 100644 crates/nostr-signer/Cargo.toml delete mode 100644 crates/nostr-signer/README.md delete mode 100644 crates/nostr-signer/src/lib.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 89c5e5032..71d23c62b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ ### Changed * Bump toolchain channel to `1.82.0` +* Convert `nostr-signer` crate to `nostr-connect` ([Yuki Kishimoto]) * nostr: move `TagsIndexes` into `Tags` struct ([Yuki Kishimoto]) * nostr: use `OnceCell` implementation from `std` lib instead of `once_cell` ([Yuki Kishimoto]) * nostr: remove redundant public key from repost events ([Yuki Kishimoto]) diff --git a/Cargo.lock b/Cargo.lock index 84623a56f..9ada81fbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2788,6 +2788,7 @@ dependencies = [ "dialoguer", "dirs", "indicatif", + "nostr-connect", "nostr-relay-builder", "nostr-sdk", "once_cell", @@ -2798,6 +2799,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "nostr-connect" +version = "0.35.0" +dependencies = [ + "async-trait", + "async-utility", + "dialoguer", + "nostr", + "nostr-relay-pool", + "thiserror", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "nostr-database" version = "0.35.0" @@ -2914,12 +2930,12 @@ dependencies = [ "atomic-destructor", "lnurl-pay", "nostr", + "nostr-connect", "nostr-database", "nostr-indexeddb", "nostr-lmdb", "nostr-ndb", "nostr-relay-pool", - "nostr-signer", "nostr-zapper", "nwc", "thiserror", @@ -2956,20 +2972,6 @@ dependencies = [ "wasm-bindgen-futures", ] -[[package]] -name = "nostr-signer" -version = "0.35.0" -dependencies = [ - "async-utility", - "dialoguer", - "nostr", - "nostr-relay-pool", - "thiserror", - "tokio", - "tracing", - "tracing-subscriber", -] - [[package]] name = "nostr-zapper" version = "0.35.0" diff --git a/Cargo.toml b/Cargo.toml index 3ca2d10d1..8beafcb46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ js-sys = "0.3" negentropy = { version = "0.4", default-features = false } negentropy-deprecated = { package = "negentropy", version = "0.3", default-features = false } nostr = { version = "0.35", path = "./crates/nostr", default-features = false } +nostr-connect = { version = "0.35", path = "./crates/nostr-connect", default-features = false } nostr-database = { version = "0.35", path = "./crates/nostr-database", default-features = false } nostr-indexeddb = { version = "0.35", path = "./crates/nostr-indexeddb", default-features = false } nostr-lmdb = { version = "0.35", path = "./crates/nostr-lmdb", default-features = false } @@ -35,7 +36,6 @@ nostr-ndb = { version = "0.35", path = "./crates/nostr-ndb", default-features = nostr-relay-builder = { version = "0.35", path = "./crates/nostr-relay-builder", default-features = false } nostr-relay-pool = { version = "0.35", path = "./crates/nostr-relay-pool", default-features = false } nostr-sdk = { version = "0.35", path = "./crates/nostr-sdk", default-features = false } -nostr-signer = { version = "0.35", path = "./crates/nostr-signer", default-features = false } nostr-zapper = { version = "0.35", path = "./crates/nostr-zapper", default-features = false } nwc = { version = "0.35", path = "./crates/nwc", default-features = false } once_cell = { version = "1.20", default-features = false } diff --git a/README.md b/README.md index 09b70e090..d10958cf6 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,15 @@ The project is split up into several crates in the `crates/` directory: * Libraries: * [**nostr**](./crates/nostr): Rust implementation of Nostr protocol + * [**nostr-connect**](./crates/nostr-connect): Nostr Connect (NIP46) * [**nostr-database**](./crates/nostr-database): Database for Nostr apps * [**nostr-lmdb**](./crates/nostr-lmdb): LMDB storage backend * [**nostr-ndb**](./crates/nostr-ndb): [nostrdb](https://github.com/damus-io/nostrdb) storage backend * [**nostr-indexeddb**](./crates/nostr-indexeddb): IndexedDB storage backend * [**nostr-relay-pool**](./crates/nostr-relay-pool): Nostr Relay Pool - * [**nostr-signer**](./crates/nostr-signer): Signer for Nostr apps * [**nostr-zapper**](./crates/nostr-zapper): Zapper abstraction for Nostr apps * [**nostr-sdk**](./crates/nostr-sdk): High level client library - * [**nwc**](./crates/nwc): `NWC` client and zapper backend for Nostr apps + * [**nwc**](./crates/nwc): Nostr Wallet Connect (NWC) client * Binaries (tools): * [**nostr-cli**](./crates/nostr-cli): Nostr CLI diff --git a/contrib/scripts/check-crates.sh b/contrib/scripts/check-crates.sh index 65ece3b62..b9049d8c5 100755 --- a/contrib/scripts/check-crates.sh +++ b/contrib/scripts/check-crates.sh @@ -38,6 +38,7 @@ buildargs=( "-p nostr-database" "-p nostr-lmdb" "-p nostr-relay-builder" + "-p nostr-connect" "-p nostr-sdk" "-p nostr-sdk --no-default-features" "-p nostr-sdk --features nip47,nip57" diff --git a/contrib/scripts/check-docs.sh b/contrib/scripts/check-docs.sh index 62a96893b..3a699cf7f 100755 --- a/contrib/scripts/check-docs.sh +++ b/contrib/scripts/check-docs.sh @@ -6,8 +6,7 @@ buildargs=( "-p nostr" "-p nostr-database" "-p nostr-relay-pool" - "-p nostr-signer" - "-p nostr-zapper" + "-p nostr-connect" "-p nwc" "-p nostr-sdk" ) @@ -16,4 +15,4 @@ for arg in "${buildargs[@]}"; do echo "Checking '$arg' docs" cargo doc $arg --all-features echo -done \ No newline at end of file +done diff --git a/crates/nostr-cli/Cargo.toml b/crates/nostr-cli/Cargo.toml index 456e590be..c4da526d9 100644 --- a/crates/nostr-cli/Cargo.toml +++ b/crates/nostr-cli/Cargo.toml @@ -16,6 +16,7 @@ console = "0.15" dialoguer = "0.11" dirs = "5.0" indicatif = "0.17" +nostr-connect.workspace = true nostr-relay-builder.workspace = true nostr-sdk = { workspace = true, features = ["all-nips", "lmdb", "tor"] } once_cell.workspace = true diff --git a/crates/nostr-cli/src/main.rs b/crates/nostr-cli/src/main.rs index 39421ea78..6a36a7d33 100644 --- a/crates/nostr-cli/src/main.rs +++ b/crates/nostr-cli/src/main.rs @@ -11,6 +11,7 @@ use std::time::Duration; use clap::Parser; use console::Term; use indicatif::{ProgressBar, ProgressState, ProgressStyle}; +use nostr_connect::prelude::*; use nostr_relay_builder::prelude::*; use nostr_sdk::prelude::*; use rustyline::error::ReadlineError; diff --git a/crates/nostr-connect/Cargo.toml b/crates/nostr-connect/Cargo.toml new file mode 100644 index 000000000..61876e61e --- /dev/null +++ b/crates/nostr-connect/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "nostr-connect" +version = "0.35.0" +edition = "2021" +description = "Nostr Connect (NIP46)" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +readme = "README.md" +rust-version.workspace = true +keywords = ["nostr", "signer", "nip46", "nostr-connect"] + +[features] +default = [] +tor = ["nostr-relay-pool/tor"] + +[dependencies] +async-trait.workspace = true +async-utility.workspace = true +nostr = { workspace = true, features = ["std", "nip04", "nip44", "nip46"] } +nostr-relay-pool.workspace = true +thiserror.workspace = true +tokio = { workspace = true, features = ["sync"] } +tracing = { workspace = true, features = ["std", "attributes"] } + +[dev-dependencies] +dialoguer = "0.11" +tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/crates/nostr-connect/README.md b/crates/nostr-connect/README.md new file mode 100644 index 000000000..9eb1cd1e6 --- /dev/null +++ b/crates/nostr-connect/README.md @@ -0,0 +1,13 @@ +# Nostr Connect (NIP46) + +## State + +**This library is in an 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](https://rust-nostr.org/donate). + +## License + +This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details diff --git a/crates/nostr-signer/examples/nostr-connect-signer.rs b/crates/nostr-connect/examples/nostr-connect-signer.rs similarity index 97% rename from crates/nostr-signer/examples/nostr-connect-signer.rs rename to crates/nostr-connect/examples/nostr-connect-signer.rs index 9812a119c..803b09de5 100644 --- a/crates/nostr-signer/examples/nostr-connect-signer.rs +++ b/crates/nostr-connect/examples/nostr-connect-signer.rs @@ -4,7 +4,7 @@ use dialoguer::Confirm; use nostr::nips::nip46::Request; -use nostr_signer::prelude::*; +use nostr_connect::prelude::*; const USER_SECRET_KEY: &str = "nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85"; diff --git a/crates/nostr-signer/src/nip46/client.rs b/crates/nostr-connect/src/client.rs similarity index 80% rename from crates/nostr-signer/src/nip46/client.rs rename to crates/nostr-connect/src/client.rs index 880f948f3..11fee407c 100644 --- a/crates/nostr-signer/src/nip46/client.rs +++ b/crates/nostr-connect/src/client.rs @@ -9,6 +9,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; +use async_trait::async_trait; use async_utility::time; use nostr::nips::nip46::{Message, NostrConnectURI, Request, ResponseResult}; use nostr::prelude::*; @@ -172,7 +173,7 @@ impl Nip46Signer { let req_id = msg.id().to_string(); let event: Event = EventBuilder::nostr_connect(&self.app_keys, signer_public_key, msg)? - .to_event(&self.app_keys)?; + .sign_with_keys(&self.app_keys)?; let mut notifications = self.pool.notifications(); @@ -233,66 +234,59 @@ impl Nip46Signer { } /// Sign an [UnsignedEvent] - pub async fn sign_event(&self, unsigned: UnsignedEvent) -> Result { + async fn _sign_event(&self, unsigned: UnsignedEvent) -> Result { let req = Request::SignEvent(unsigned); let res = self.send_request(req).await?; Ok(res.to_sign_event()?) } - /// NIP04 encrypt - pub async fn nip04_encrypt(&self, public_key: PublicKey, content: T) -> Result - where - T: AsRef<[u8]>, - { - let content: &[u8] = content.as_ref(); + async fn _nip04_encrypt( + &self, + public_key: PublicKey, + content: String, + ) -> Result { let req = Request::Nip04Encrypt { public_key, - text: String::from_utf8_lossy(content).to_string(), + text: content, }; let res = self.send_request(req).await?; Ok(res.to_encrypt_decrypt()?) } - /// NIP04 decrypt - pub async fn nip04_decrypt( + async fn _nip04_decrypt( &self, public_key: PublicKey, - ciphertext: S, - ) -> Result - where - S: Into, - { + ciphertext: String, + ) -> Result { let req = Request::Nip04Decrypt { public_key, - ciphertext: ciphertext.into(), + ciphertext, }; let res = self.send_request(req).await?; Ok(res.to_encrypt_decrypt()?) } - /// NIP44 encrypt - pub async fn nip44_encrypt(&self, public_key: PublicKey, content: T) -> Result - where - T: AsRef<[u8]>, - { - let content: &[u8] = content.as_ref(); + async fn _nip44_encrypt( + &self, + public_key: PublicKey, + content: String, + ) -> Result { let req = Request::Nip44Encrypt { public_key, - text: String::from_utf8_lossy(content).to_string(), + text: content, }; let res = self.send_request(req).await?; Ok(res.to_encrypt_decrypt()?) } - /// NIP44 decrypt - pub async fn nip44_decrypt(&self, public_key: PublicKey, payload: T) -> Result - where - T: AsRef<[u8]>, - { - let payload: &[u8] = payload.as_ref(); + async fn _nip44_decrypt( + &self, + public_key: PublicKey, + payload: String, + ) -> Result { let req = Request::Nip44Decrypt { public_key, - ciphertext: String::from_utf8_lossy(payload).to_string(), + ciphertext: payload, }; let res = self.send_request(req).await?; Ok(res.to_encrypt_decrypt()?) @@ -346,3 +340,61 @@ async fn get_signer_public_key( .await .ok_or(Error::Timeout)? } + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl NostrSigner for Nip46Signer { + async fn get_public_key(&self) -> Result { + // TODO: avoid copied? + self.signer_public_key() + .await + .map_err(SignerError::backend) + .copied() + } + + async fn sign_event(&self, unsigned: UnsignedEvent) -> Result { + self._sign_event(unsigned) + .await + .map_err(SignerError::backend) + } + + async fn nip04_encrypt( + &self, + public_key: &PublicKey, + content: &str, + ) -> Result { + self._nip04_encrypt(*public_key, content.to_string()) + .await + .map_err(SignerError::backend) + } + + async fn nip04_decrypt( + &self, + public_key: &PublicKey, + content: &str, + ) -> Result { + self._nip04_decrypt(*public_key, content.to_string()) + .await + .map_err(SignerError::backend) + } + + async fn nip44_encrypt( + &self, + public_key: &PublicKey, + content: &str, + ) -> Result { + self._nip44_encrypt(*public_key, content.to_string()) + .await + .map_err(SignerError::backend) + } + + async fn nip44_decrypt( + &self, + public_key: &PublicKey, + content: &str, + ) -> Result { + self._nip44_decrypt(*public_key, content.to_string()) + .await + .map_err(SignerError::backend) + } +} diff --git a/crates/nostr-signer/src/nip46/error.rs b/crates/nostr-connect/src/error.rs similarity index 100% rename from crates/nostr-signer/src/nip46/error.rs rename to crates/nostr-connect/src/error.rs diff --git a/crates/nostr-signer/src/nip46/mod.rs b/crates/nostr-connect/src/lib.rs similarity index 76% rename from crates/nostr-signer/src/nip46/mod.rs rename to crates/nostr-connect/src/lib.rs index a6b4b3a07..8c6ed80d2 100644 --- a/crates/nostr-signer/src/nip46/mod.rs +++ b/crates/nostr-connect/src/lib.rs @@ -6,8 +6,14 @@ //! //! +#![forbid(unsafe_code)] +#![warn(missing_docs)] +#![warn(rustdoc::bare_urls)] +#![warn(clippy::large_futures)] + pub mod client; pub mod error; +pub mod prelude; pub mod signer; pub use self::client::Nip46Signer; diff --git a/crates/nostr-signer/src/prelude.rs b/crates/nostr-connect/src/prelude.rs similarity index 84% rename from crates/nostr-signer/src/prelude.rs rename to crates/nostr-connect/src/prelude.rs index 8218aeb99..9a7bb9ca9 100644 --- a/crates/nostr-signer/src/prelude.rs +++ b/crates/nostr-connect/src/prelude.rs @@ -10,6 +10,4 @@ pub use nostr::prelude::*; -#[cfg(feature = "nip46")] -pub use crate::nip46::*; pub use crate::*; diff --git a/crates/nostr-signer/src/nip46/signer.rs b/crates/nostr-connect/src/signer.rs similarity index 98% rename from crates/nostr-signer/src/nip46/signer.rs rename to crates/nostr-connect/src/signer.rs index b74b4242a..9186eaa52 100644 --- a/crates/nostr-signer/src/nip46/signer.rs +++ b/crates/nostr-connect/src/signer.rs @@ -95,7 +95,7 @@ impl NostrConnectRemoteSigner { secret: None, }); let event = - EventBuilder::nostr_connect(&self.keys, public_key, msg)?.to_event(&self.keys)?; + EventBuilder::nostr_connect(&self.keys, public_key, msg)?.sign_with_keys(&self.keys)?; self.pool .send_event(event, RelaySendOptions::default()) .await?; @@ -226,7 +226,7 @@ impl NostrConnectRemoteSigner { } } Request::SignEvent(unsigned) => { - match unsigned.sign(&self.keys) { + match unsigned.sign_with_keys(&self.keys) { Ok(event) => ( Some(ResponseResult::SignEvent(Box::new( event, @@ -248,7 +248,7 @@ impl NostrConnectRemoteSigner { // Compose and publish event let event = EventBuilder::nostr_connect(&self.keys, event.pubkey, msg)? - .to_event(&self.keys)?; + .sign_with_keys(&self.keys)?; self.pool.send_event(event, RelaySendOptions::new()).await?; } } else { diff --git a/crates/nostr-sdk/Cargo.toml b/crates/nostr-sdk/Cargo.toml index fe4380197..1a168bf35 100644 --- a/crates/nostr-sdk/Cargo.toml +++ b/crates/nostr-sdk/Cargo.toml @@ -22,19 +22,18 @@ lmdb = ["dep:nostr-lmdb"] ndb = ["dep:nostr-ndb"] indexeddb = ["dep:nostr-indexeddb"] webln = ["nip57", "nostr-zapper?/webln"] -all-nips = ["nip04", "nip05", "nip06", "nip07", "nip11", "nip44", "nip46", "nip47", "nip49", "nip57", "nip59"] +all-nips = ["nip04", "nip05", "nip06", "nip07", "nip11", "nip44", "nip47", "nip49", "nip57", "nip59"] nip03 = ["nostr/nip03"] -nip04 = ["nostr/nip04", "nostr-signer/nip04"] +nip04 = ["nostr/nip04"] nip05 = ["nostr/nip05"] nip06 = ["nostr/nip06"] -nip07 = ["nostr/nip07", "nostr-signer/nip07"] +nip07 = ["nostr/nip07"] nip11 = ["nostr/nip11", "nostr-relay-pool/nip11"] -nip44 = ["nostr/nip44", "nostr-signer/nip44"] -nip46 = ["nostr/nip46", "nostr-signer/nip46"] +nip44 = ["nostr/nip44"] nip47 = ["nostr/nip47", "dep:nwc"] nip49 = ["nostr/nip49"] nip57 = ["nostr/nip57", "dep:nostr-zapper", "dep:lnurl-pay"] -nip59 = ["nostr/nip59", "nostr-signer/nip59"] +nip59 = ["nostr/nip59"] [dependencies] async-utility.workspace = true @@ -43,7 +42,6 @@ lnurl-pay = { version = "0.6", features = ["api"], optional = true } nostr = { workspace = true, features = ["std"] } nostr-database.workspace = true nostr-relay-pool.workspace = true -nostr-signer.workspace = true nostr-zapper = { workspace = true, optional = true } nwc = { workspace = true, optional = true } thiserror.workspace = true @@ -58,6 +56,7 @@ nostr-ndb = { workspace = true, optional = true } nostr-indexeddb = { workspace = true, optional = true } [dev-dependencies] +nostr-connect.workspace = true tokio = { workspace = true, features = ["macros"] } tracing-subscriber = { workspace = true, features = ["env-filter"] } @@ -87,7 +86,7 @@ required-features = ["all-nips"] [[example]] name = "nostr-connect" -required-features = ["nip46"] +required-features = ["nip59"] [[example]] name = "bot" diff --git a/crates/nostr-sdk/examples/nostr-connect.rs b/crates/nostr-sdk/examples/nostr-connect.rs index 2d57f24fb..e888f7b16 100644 --- a/crates/nostr-sdk/examples/nostr-connect.rs +++ b/crates/nostr-sdk/examples/nostr-connect.rs @@ -4,15 +4,14 @@ use std::time::Duration; +use nostr_connect::prelude::*; use nostr_sdk::prelude::*; -const APP_SECRET_KEY: &str = "nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99"; - #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::fmt::init(); - let app_keys = Keys::parse(APP_SECRET_KEY)?; + let app_keys = Keys::parse("nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99")?; // Compose signer from bunker URI let uri = NostrConnectURI::parse("bunker://79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3?relay=wss%3A%2F%2Frelay.nsec.app")?; diff --git a/crates/nostr-sdk/src/lib.rs b/crates/nostr-sdk/src/lib.rs index bf1db5f90..fbca30877 100644 --- a/crates/nostr-sdk/src/lib.rs +++ b/crates/nostr-sdk/src/lib.rs @@ -37,8 +37,6 @@ pub use nostr_relay_pool::{ SyncOptions, }; #[doc(hidden)] -pub use nostr_signer::{self as signer, NostrSigner, NostrSignerType}; -#[doc(hidden)] #[cfg(feature = "nip57")] pub use nostr_zapper::{self as zapper, *}; #[doc(hidden)] diff --git a/crates/nostr-sdk/src/prelude.rs b/crates/nostr-sdk/src/prelude.rs index 0b36afd18..76afa2ac0 100644 --- a/crates/nostr-sdk/src/prelude.rs +++ b/crates/nostr-sdk/src/prelude.rs @@ -12,7 +12,6 @@ pub use nostr::prelude::*; pub use nostr_database::*; pub use nostr_relay_pool::prelude::*; -pub use nostr_signer::prelude::*; #[cfg(feature = "nip57")] pub use nostr_zapper::prelude::*; diff --git a/crates/nostr-signer/Cargo.toml b/crates/nostr-signer/Cargo.toml deleted file mode 100644 index 342d03dd1..000000000 --- a/crates/nostr-signer/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "nostr-signer" -version = "0.35.0" -edition = "2021" -description = "Signer for Nostr apps" -authors.workspace = true -homepage.workspace = true -repository.workspace = true -license.workspace = true -readme = "README.md" -rust-version.workspace = true -keywords = ["nostr", "signer"] - -[features] -default = ["nip04", "nip07", "nip44", "nip46", "nip59"] -nip04 = ["nostr/nip04"] -nip07 = ["nostr/nip07"] -nip44 = ["nostr/nip44"] -nip46 = ["nostr/nip46", "dep:nostr-relay-pool", "dep:tracing"] -nip59 = ["nip44", "nostr/nip59"] - -[dependencies] -async-utility.workspace = true -nostr = { workspace = true, features = ["std"] } -nostr-relay-pool = { workspace = true, optional = true } -thiserror.workspace = true -tokio = { workspace = true, features = ["sync"] } -tracing = { workspace = true, features = ["std", "attributes"], optional = true } - -[dev-dependencies] -dialoguer = "0.11" -tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/crates/nostr-signer/README.md b/crates/nostr-signer/README.md deleted file mode 100644 index 4eda0187a..000000000 --- a/crates/nostr-signer/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Nostr Signer - -Signer for Nostr apps - -## Crate Feature Flags - -The following crate feature flags are available: - -| Feature | Default | Description | -|---------|:-------:|----------------------------------------------------------------------------------------------| -| `nip04` | Yes | Enable NIP-04: Encrypted Direct Message | -| `nip07` | Yes | Enable NIP-07: `window.nostr` capability for web browsers (**available only for `wasm32`!**) | -| `nip44` | Yes | Enable NIP-44: Encrypted Payloads (Versioned) | -| `nip46` | Yes | Enable NIP-46: Nostr Connect | -| `nip59` | Yes | Enable NIP-59: Gift Wrap | - -## State - -**This library is in an 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](https://rust-nostr.org/donate). - -## License - -This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details \ No newline at end of file diff --git a/crates/nostr-signer/src/lib.rs b/crates/nostr-signer/src/lib.rs deleted file mode 100644 index 1ce180023..000000000 --- a/crates/nostr-signer/src/lib.rs +++ /dev/null @@ -1,324 +0,0 @@ -// Copyright (c) 2022-2023 Yuki Kishimoto -// Copyright (c) 2023-2024 Rust Nostr Developers -// Distributed under the MIT software license - -//! Nostr Signer - -#![forbid(unsafe_code)] -#![warn(missing_docs)] -#![warn(rustdoc::bare_urls)] -#![warn(clippy::large_futures)] - -use std::fmt; - -use nostr::prelude::*; -use thiserror::Error; - -#[cfg(feature = "nip46")] -pub mod nip46; -pub mod prelude; - -#[cfg(feature = "nip46")] -pub use self::nip46::{Nip46Signer, NostrConnectRemoteSigner, NostrConnectSignerActions}; - -/// Nostr Signer error -#[derive(Debug, Error)] -pub enum Error { - /// Keys error - #[error(transparent)] - Keys(#[from] key::Error), - /// Unsigned event error - #[error(transparent)] - Unsigned(#[from] unsigned::Error), - /// Builder event error - #[error(transparent)] - Builder(#[from] builder::Error), - /// NIP04 error - #[cfg(feature = "nip04")] - #[error(transparent)] - NIP04(#[from] nip04::Error), - /// NIP07 error - #[cfg(all(feature = "nip07", target_arch = "wasm32"))] - #[error(transparent)] - NIP07(#[from] nip07::Error), - /// NIP44 error - #[cfg(feature = "nip44")] - #[error(transparent)] - NIP44(#[from] nip44::Error), - /// NIP46 error - #[cfg(feature = "nip46")] - #[error(transparent)] - NIP46(#[from] nip46::Error), - /// NIP59 error - #[cfg(feature = "nip59")] - #[error(transparent)] - NIP59(#[from] nip59::Error), - /// Event error - #[cfg(feature = "nip59")] - #[error(transparent)] - Event(#[from] event::Error), -} - -/// Nostr Signer Type -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum NostrSignerType { - /// Keys - Keys, - /// NIP07 - #[cfg(all(feature = "nip07", target_arch = "wasm32"))] - NIP07, - /// NIP46 - #[cfg(feature = "nip46")] - NIP46, -} - -// TODO: better display -impl fmt::Display for NostrSignerType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Keys => write!(f, "Keys"), - #[cfg(all(feature = "nip07", target_arch = "wasm32"))] - Self::NIP07 => write!(f, "Nostr Browser Extension"), - #[cfg(feature = "nip46")] - Self::NIP46 => write!(f, "Nostr Connect"), - } - } -} - -/// Nostr signer -#[derive(Debug, Clone)] -pub enum NostrSigner { - /// Private Keys - Keys(Keys), - /// NIP07 signer - #[cfg(all(feature = "nip07", target_arch = "wasm32"))] - NIP07(Nip07Signer), - /// NIP46 signer - #[cfg(feature = "nip46")] - NIP46(Box), -} - -impl NostrSigner { - /// Create a new [NIP07] instance and compose [NostrSigner] - #[cfg(all(feature = "nip07", target_arch = "wasm32"))] - pub fn nip07() -> Result { - let instance = Nip07Signer::new()?; - Ok(Self::NIP07(instance)) - } - - /// Compose [NostrSigner] with [Nip46Signer] - #[cfg(feature = "nip46")] - pub fn nip46(signer: Nip46Signer) -> Self { - Self::NIP46(Box::new(signer)) - } - - /// Get Nostr Signer Type - pub fn r#type(&self) -> NostrSignerType { - match self { - Self::Keys(..) => NostrSignerType::Keys, - #[cfg(all(feature = "nip07", target_arch = "wasm32"))] - Self::NIP07(..) => NostrSignerType::NIP07, - #[cfg(feature = "nip46")] - Self::NIP46(..) => NostrSignerType::NIP46, - } - } - - /// Get signer public key - pub async fn public_key(&self) -> Result { - match self { - Self::Keys(keys) => Ok(keys.public_key()), - #[cfg(all(feature = "nip07", target_arch = "wasm32"))] - Self::NIP07(s) => Ok(s.get_public_key().await?), - #[cfg(feature = "nip46")] - Self::NIP46(s) => Ok(*s.signer_public_key().await?), - } - } - - /// Sign an [EventBuilder] - pub async fn sign_event_builder(&self, builder: EventBuilder) -> Result { - let public_key: PublicKey = self.public_key().await?; - let unsigned: UnsignedEvent = builder.to_unsigned_event(public_key); - self.sign_event(unsigned).await - } - - /// Sign an [UnsignedEvent] - pub async fn sign_event(&self, unsigned: UnsignedEvent) -> Result { - match self { - Self::Keys(keys) => Ok(unsigned.sign(keys)?), - #[cfg(all(feature = "nip07", target_arch = "wasm32"))] - Self::NIP07(nip07) => Ok(nip07.sign_event(unsigned).await?), - #[cfg(feature = "nip46")] - Self::NIP46(nip46) => Ok(nip46.sign_event(unsigned).await?), - } - } - - /// NIP04 encrypt - #[cfg(feature = "nip04")] - pub async fn nip04_encrypt( - &self, - public_key: &PublicKey, - content: T, - ) -> Result - where - T: AsRef<[u8]>, - { - let content: &[u8] = content.as_ref(); - match self { - Self::Keys(keys) => Ok(nip04::encrypt(keys.secret_key(), public_key, content)?), - #[cfg(all(feature = "nip07", target_arch = "wasm32"))] - Self::NIP07(signer) => Ok(signer.nip04_encrypt(public_key, content).await?), - #[cfg(feature = "nip46")] - Self::NIP46(signer) => Ok(signer.nip04_encrypt(*public_key, content).await?), - } - } - - /// NIP04 decrypt - #[cfg(feature = "nip04")] - pub async fn nip04_decrypt( - &self, - public_key: &PublicKey, - encrypted_content: T, - ) -> Result - where - T: AsRef, - { - let encrypted_content: &str = encrypted_content.as_ref(); - match self { - Self::Keys(keys) => Ok(nip04::decrypt( - keys.secret_key(), - public_key, - encrypted_content, - )?), - #[cfg(all(feature = "nip07", target_arch = "wasm32"))] - Self::NIP07(signer) => Ok(signer.nip04_decrypt(public_key, encrypted_content).await?), - #[cfg(feature = "nip46")] - Self::NIP46(signer) => Ok(signer.nip04_decrypt(*public_key, encrypted_content).await?), - } - } - - /// NIP44 encryption with [NostrSigner] - #[cfg(feature = "nip44")] - pub async fn nip44_encrypt( - &self, - public_key: &PublicKey, - content: T, - ) -> Result - where - T: AsRef<[u8]>, - { - let content: &[u8] = content.as_ref(); - match self { - Self::Keys(keys) => Ok(nip44::encrypt( - keys.secret_key(), - public_key, - content, - nip44::Version::default(), - )?), - #[cfg(all(feature = "nip07", target_arch = "wasm32"))] - Self::NIP07(signer) => Ok(signer.nip44_encrypt(public_key, content).await?), - #[cfg(feature = "nip46")] - Self::NIP46(signer) => Ok(signer.nip44_encrypt(*public_key, content).await?), - } - } - - /// NIP44 decryption with [NostrSigner] - #[cfg(feature = "nip44")] - pub async fn nip44_decrypt( - &self, - public_key: &PublicKey, - payload: T, - ) -> Result - where - T: AsRef<[u8]>, - { - let payload: &[u8] = payload.as_ref(); - match self { - Self::Keys(keys) => Ok(nip44::decrypt(keys.secret_key(), public_key, payload)?), - #[cfg(all(feature = "nip07", target_arch = "wasm32"))] - Self::NIP07(signer) => Ok(signer.nip44_decrypt(public_key, payload).await?), - #[cfg(feature = "nip46")] - Self::NIP46(signer) => Ok(signer.nip44_decrypt(*public_key, payload).await?), - } - } - - /// Build gift wrap event - // TODO: find a way to merge this with the `Keys` implementation in `nostr` crate - #[cfg(feature = "nip59")] - pub async fn gift_wrap( - &self, - receiver: &PublicKey, - rumor: EventBuilder, - expiration: Option, - ) -> Result { - // Compose rumor - let public_key: PublicKey = self.public_key().await?; - let rumor = rumor.to_unsigned_event(public_key); - - // Compose seal - // TODO: use directly the `EventBuilder::seal` constructor - let content: String = self.nip44_encrypt(receiver, rumor.as_json()).await?; - let seal: EventBuilder = EventBuilder::new(Kind::Seal, content, []) - .custom_created_at(Timestamp::tweaked(nip59::RANGE_RANDOM_TIMESTAMP_TWEAK)); - let seal: Event = self.sign_event_builder(seal).await?; - - // Compose gift wrap - Ok(EventBuilder::gift_wrap_from_seal( - receiver, &seal, expiration, - )?) - } - - /// Unwrap Gift Wrap event - /// - /// Internally verify the `seal` event - /// - /// - // TODO: find a way to merge this with the `Keys` implementation in `nostr` crate - #[cfg(feature = "nip59")] - pub async fn unwrap_gift_wrap(&self, gift_wrap: &Event) -> Result { - // Check event kind - if gift_wrap.kind != Kind::GiftWrap { - return Err(Error::NIP59(nip59::Error::NotGiftWrap)); - } - - // Decrypt and verify seal - let seal: String = self - .nip44_decrypt(&gift_wrap.pubkey, &gift_wrap.content) - .await?; - let seal: Event = Event::from_json(seal)?; - seal.verify()?; - - // Decrypt rumor - let rumor: String = self.nip44_decrypt(&seal.pubkey, &seal.content).await?; - - Ok(UnwrappedGift { - sender: seal.pubkey, - rumor: UnsignedEvent::from_json(rumor)?, - }) - } -} - -impl From for NostrSigner { - fn from(keys: Keys) -> Self { - Self::Keys(keys) - } -} - -impl From<&Keys> for NostrSigner { - fn from(keys: &Keys) -> Self { - Self::Keys(keys.clone()) - } -} - -#[cfg(all(feature = "nip07", target_arch = "wasm32"))] -impl From for NostrSigner { - fn from(nip07: Nip07Signer) -> Self { - Self::NIP07(nip07) - } -} - -#[cfg(feature = "nip46")] -impl From for NostrSigner { - fn from(nip46: Nip46Signer) -> Self { - Self::nip46(nip46) - } -} diff --git a/crates/nostr/README.md b/crates/nostr/README.md index 40ca5f148..1beb641e0 100644 --- a/crates/nostr/README.md +++ b/crates/nostr/README.md @@ -12,11 +12,9 @@ Rust implementation of Nostr protocol. You may be interested in: * [`nostr-sdk`](https://crates.io/crates/nostr-sdk) if you want to write a typical Nostr client or bot -* [`nostr-database`](https://crates.io/crates/nostr-database): Database for Nostr apps * [`nostr-relay-pool`](https://crates.io/crates/nostr-relay-pool): Nostr Relay Pool -* [`nostr-signer`](https://crates.io/crates/nostr-signer): Signer for Nostr apps -* [`nostr-zapper`](https://crates.io/crates/nostr-zapper): Zapper abstraction for Nostr apps -* [`nwc`](https://crates.io/crates/nwc): `NWC` client and zapper backend for Nostr apps +* [`nostr-connect`](https://crates.io/crates/nostr-connect): Nostr Connect (NIP46) +* [`nwc`](https://crates.io/crates/nwc): Nostr Wallet Connect (NWC) client ## Getting started diff --git a/crates/nwc/src/lib.rs b/crates/nwc/src/lib.rs index ca49eb78f..02b8e4db2 100644 --- a/crates/nwc/src/lib.rs +++ b/crates/nwc/src/lib.rs @@ -18,6 +18,7 @@ use std::time::Duration; pub extern crate nostr; pub extern crate nostr_zapper as zapper; +use async_trait::async_trait; use async_utility::time; use nostr::nips::nip47::{ GetBalanceResponseResult, GetInfoResponseResult, ListTransactionsRequestParams, @@ -27,7 +28,7 @@ use nostr::nips::nip47::{ }; use nostr::{Event, EventId, Filter, Kind, SubscriptionId, Timestamp}; use nostr_relay_pool::{Relay, RelayNotification, RelaySendOptions, SubscribeOptions}; -use nostr_zapper::{async_trait, NostrZapper, ZapperBackend, ZapperError}; +use nostr_zapper::{NostrZapper, ZapperBackend, ZapperError}; pub mod error; pub mod options; From ab36336121e142d0c71c4c3e97366bb36c8c08eb Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Thu, 17 Oct 2024 17:40:44 +0200 Subject: [PATCH 3/9] zapper: small adj. Signed-off-by: Yuki Kishimoto --- Cargo.lock | 1 + crates/nostr-zapper/src/lib.rs | 36 +--------------------------------- crates/nwc/Cargo.toml | 1 + 3 files changed, 3 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ada81fbe..f91bd37c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3128,6 +3128,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" name = "nwc" version = "0.35.0" dependencies = [ + "async-trait", "async-utility", "nostr", "nostr-relay-pool", diff --git a/crates/nostr-zapper/src/lib.rs b/crates/nostr-zapper/src/lib.rs index 668656140..fe6323112 100644 --- a/crates/nostr-zapper/src/lib.rs +++ b/crates/nostr-zapper/src/lib.rs @@ -14,7 +14,7 @@ use std::sync::Arc; pub extern crate nostr; -pub use async_trait::async_trait; +use async_trait::async_trait; use nostr::prelude::*; pub mod error; @@ -80,37 +80,3 @@ pub trait NostrZapper: AsyncTraitDeps { /// Pay invoice async fn pay(&self, invoice: String) -> Result<(), ZapperError>; } - -/// Alias for `Send` on non-wasm, empty trait (implemented by everything) on -/// wasm. -#[cfg(not(target_arch = "wasm32"))] -pub trait SendOutsideWasm: Send {} -#[cfg(not(target_arch = "wasm32"))] -impl SendOutsideWasm for T {} - -/// Alias for `Send` on non-wasm, empty trait (implemented by everything) on -/// wasm. -#[cfg(target_arch = "wasm32")] -pub trait SendOutsideWasm {} -#[cfg(target_arch = "wasm32")] -impl SendOutsideWasm for T {} - -/// Alias for `Sync` on non-wasm, empty trait (implemented by everything) on -/// wasm. -#[cfg(not(target_arch = "wasm32"))] -pub trait SyncOutsideWasm: Sync {} -#[cfg(not(target_arch = "wasm32"))] -impl SyncOutsideWasm for T {} - -/// Alias for `Sync` on non-wasm, empty trait (implemented by everything) on -/// wasm. -#[cfg(target_arch = "wasm32")] -pub trait SyncOutsideWasm {} -#[cfg(target_arch = "wasm32")] -impl SyncOutsideWasm for T {} - -/// Super trait that is used for our store traits, this trait will differ if -/// it's used on WASM. WASM targets will not require `Send` and `Sync` to have -/// implemented, while other targets will. -pub trait AsyncTraitDeps: std::fmt::Debug + SendOutsideWasm + SyncOutsideWasm {} -impl AsyncTraitDeps for T {} diff --git a/crates/nwc/Cargo.toml b/crates/nwc/Cargo.toml index d5b0ff21b..6392242c8 100644 --- a/crates/nwc/Cargo.toml +++ b/crates/nwc/Cargo.toml @@ -16,6 +16,7 @@ default = [] tor = ["nostr-relay-pool/tor"] [dependencies] +async-trait.workspace = true async-utility.workspace = true nostr = { workspace = true, features = ["std", "nip47"] } nostr-relay-pool.workspace = true From 50d93627bd2a780ed99d8a98040ab050e8a3b2fa Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Thu, 17 Oct 2024 17:43:09 +0200 Subject: [PATCH 4/9] sdk: adapt to use `NostrSigner` trait Signed-off-by: Yuki Kishimoto --- crates/nostr-database/examples/helper.rs | 24 +- crates/nostr-database/examples/memory.rs | 10 +- crates/nostr-lmdb/src/lib.rs | 16 +- crates/nostr-sdk/README.md | 11 +- crates/nostr-sdk/examples/bot.rs | 7 +- crates/nostr-sdk/examples/client-with-opts.rs | 8 +- crates/nostr-sdk/examples/client.rs | 29 +- crates/nostr-sdk/examples/gossip.rs | 2 +- crates/nostr-sdk/examples/lmdb.rs | 8 +- crates/nostr-sdk/examples/nostrdb.rs | 8 +- crates/nostr-sdk/examples/shutdown-on-drop.rs | 8 +- crates/nostr-sdk/examples/subscriptions.rs | 5 +- crates/nostr-sdk/examples/tor.rs | 8 +- crates/nostr-sdk/examples/zapper.rs | 6 +- crates/nostr-sdk/src/client/builder.rs | 10 +- crates/nostr-sdk/src/client/mod.rs | 256 +++++------------- crates/nostr-sdk/src/gossip/graph.rs | 4 +- 17 files changed, 146 insertions(+), 274 deletions(-) diff --git a/crates/nostr-database/examples/helper.rs b/crates/nostr-database/examples/helper.rs index e3fef25ff..dbecc29f9 100644 --- a/crates/nostr-database/examples/helper.rs +++ b/crates/nostr-database/examples/helper.rs @@ -24,27 +24,29 @@ async fn main() { .unwrap(); let keys_b = Keys::new(secret_key); - let index = DatabaseHelper::unbounded(); + let helper = DatabaseHelper::unbounded(); for i in 0..100_000 { let event = EventBuilder::text_note(format!("Event #{i}"), []) - .to_event(&keys_a) + .sign_with_keys(&keys_a) .unwrap(); - index.index_event(&event).await; + helper.index_event(&event).await; let event = EventBuilder::text_note( format!("Reply to event #{i}"), [Tag::event(event.id), Tag::public_key(event.pubkey)], ) - .to_event(&keys_b) + .sign_with_keys(&keys_b) .unwrap(); - index.index_event(&event).await; + helper.index_event(&event).await; } for i in 0..1000 { let metadata = Metadata::new().name(format!("Name #{i}")); - let event = EventBuilder::metadata(&metadata).to_event(&keys_a).unwrap(); - index.index_event(&event).await; + let event = EventBuilder::metadata(&metadata) + .sign_with_keys(&keys_a) + .unwrap(); + helper.index_event(&event).await; } for i in 0..500_000 { @@ -53,12 +55,12 @@ async fn main() { "Custom with d tag", [Tag::identifier(format!("myid{i}"))], ) - .to_event(&keys_a) + .sign_with_keys(&keys_a) .unwrap(); - index.index_event(&event).await; + helper.index_event(&event).await; } - let ids = index + let events = helper .query(vec![Filter::new() .kinds(vec![Kind::Metadata, Kind::Custom(123), Kind::TextNote]) .limit(20) @@ -66,7 +68,7 @@ async fn main() { //.identifier("myid5000") .author(keys_a.public_key())]) .await; - println!("Got {} ids", ids.len()); + println!("Got {} events", events.len()); loop { tokio::time::sleep(Duration::from_secs(60)).await; diff --git a/crates/nostr-database/examples/memory.rs b/crates/nostr-database/examples/memory.rs index 3ddf6b9fa..0ff69287d 100644 --- a/crates/nostr-database/examples/memory.rs +++ b/crates/nostr-database/examples/memory.rs @@ -34,7 +34,7 @@ async fn main() { for i in 0..100_000 { let event = EventBuilder::text_note(format!("Event #{i}"), []) - .to_event(&keys_a) + .sign_with_keys(&keys_a) .unwrap(); database.save_event(&event).await.unwrap(); @@ -42,14 +42,16 @@ async fn main() { format!("Reply to event #{i}"), [Tag::event(event.id), Tag::public_key(event.pubkey)], ) - .to_event(&keys_b) + .sign_with_keys(&keys_b) .unwrap(); database.save_event(&event).await.unwrap(); } for i in 0..10 { let metadata = Metadata::new().name(format!("Name #{i}")); - let event = EventBuilder::metadata(&metadata).to_event(&keys_a).unwrap(); + let event = EventBuilder::metadata(&metadata) + .sign_with_keys(&keys_a) + .unwrap(); database.save_event(&event).await.unwrap(); } @@ -59,7 +61,7 @@ async fn main() { "Custom with d tag", [Tag::identifier(format!("myid{i}"))], ) - .to_event(&keys_a) + .sign_with_keys(&keys_a) .unwrap(); database.save_event(&event).await.unwrap(); } diff --git a/crates/nostr-lmdb/src/lib.rs b/crates/nostr-lmdb/src/lib.rs index 60dc5e850..fb62ec445 100644 --- a/crates/nostr-lmdb/src/lib.rs +++ b/crates/nostr-lmdb/src/lib.rs @@ -212,12 +212,12 @@ mod tests { // Add some text notes events.push( EventBuilder::text_note("Text Note A", []) - .to_event(&keys_a) + .sign_with_keys(&keys_a) .unwrap(), ); events.push( EventBuilder::text_note("Text Note B", []) - .to_event(&keys_b) + .sign_with_keys(&keys_b) .unwrap(), ); @@ -226,14 +226,14 @@ mod tests { EventBuilder::metadata( &Metadata::new().name("account-a").display_name("Account A"), ) - .to_event(&keys_a) + .sign_with_keys(&keys_a) .unwrap(), ); events.push( EventBuilder::metadata( &Metadata::new().name("account-b").display_name("Account B"), ) - .to_event(&keys_b) + .sign_with_keys(&keys_b) .unwrap(), ); @@ -244,7 +244,7 @@ mod tests { "", [Tag::identifier("my-id-a")], ) - .to_event(&keys_a) + .sign_with_keys(&keys_a) .unwrap(), ); events.push( @@ -253,7 +253,7 @@ mod tests { "", [Tag::identifier("my-id-b")], ) - .to_event(&keys_b) + .sign_with_keys(&keys_b) .unwrap(), ); @@ -267,13 +267,13 @@ mod tests { async fn add_event(&self, builder: EventBuilder) -> (Keys, Event) { let keys = Keys::generate(); - let event = builder.to_event(&keys).unwrap(); + let event = builder.sign_with_keys(&keys).unwrap(); self.db.save_event(&event).await.unwrap(); (keys, event) } async fn add_event_with_keys(&self, builder: EventBuilder, keys: &Keys) -> (Event, bool) { - let event = builder.to_event(&keys).unwrap(); + let event = builder.sign_with_keys(&keys).unwrap(); let stored = self.db.save_event(&event).await.unwrap(); (event, stored) } diff --git a/crates/nostr-sdk/README.md b/crates/nostr-sdk/README.md index 6271162ea..55ad79766 100644 --- a/crates/nostr-sdk/README.md +++ b/crates/nostr-sdk/README.md @@ -26,13 +26,13 @@ use nostr_sdk::prelude::*; #[tokio::main] async fn main() -> Result<()> { // Generate new random keys - let my_keys = Keys::generate(); + let keys = Keys::generate(); // Or use your already existing (from hex or bech32) - let my_keys = Keys::parse("hex-or-bech32-secret-key")?; + let keys = Keys::parse("hex-or-bech32-secret-key")?; // Show bech32 public key - let bech32_pubkey: String = my_keys.public_key().to_bech32()?; + let bech32_pubkey: String = keys.public_key().to_bech32()?; println!("Bech32 PubKey: {}", bech32_pubkey); // Configure client to use proxy for `.onion` relays @@ -45,7 +45,7 @@ async fn main() -> Result<()> { // Create new client with custom options. // Use `Client::new(signer)` to construct the client with a custom signer and default options // or `Client::default()` to create one without signer and with default options. - let client = Client::with_opts(&my_keys, opts); + let client = Client::with_opts(keys.clone(), opts); // Add relays client.add_relay("wss://relay.damus.io").await?; @@ -74,7 +74,7 @@ async fn main() -> Result<()> { client.publish_text_note("My first text note from rust-nostr!", []).await?; // Create a POW text note - let event: Event = EventBuilder::text_note("POW text note from nostr-sdk", []).pow(20).to_event(&my_keys)?; + let event: Event = EventBuilder::text_note("POW text note from nostr-sdk", []).pow(20).sign(&keys).await?; client.send_event(event).await?; // Send to all relays // client.send_event_to(["wss://relay.damus.io"], event).await?; // Send to specific relay @@ -141,7 +141,6 @@ The following crate feature flags are available: | `nip07` | Yes | Enable NIP-07: `window.nostr` capability for web browsers (**available only for `wasm32`!**) | | `nip11` | Yes | Enable NIP-11: Relay Information Document | | `nip44` | Yes | Enable NIP-44: Encrypted Payloads (Versioned) | -| `nip46` | Yes | Enable NIP-46: Nostr Connect | | `nip47` | Yes | Enable NIP-47: Nostr Wallet Connect | | `nip49` | Yes | Enable NIP-49: Private Key Encryption | | `nip57` | Yes | Enable NIP-57: Zaps | diff --git a/crates/nostr-sdk/examples/bot.rs b/crates/nostr-sdk/examples/bot.rs index dfe0640a9..2508dac28 100644 --- a/crates/nostr-sdk/examples/bot.rs +++ b/crates/nostr-sdk/examples/bot.rs @@ -6,18 +6,15 @@ use std::time::Duration; use nostr_sdk::prelude::*; -const BECH32_SK: &str = "nsec12kcgs78l06p30jz7z7h3n2x2cy99nw2z6zspjdp7qc206887mwvs95lnkx"; - #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::fmt::init(); - let secret_key = SecretKey::from_bech32(BECH32_SK)?; - let keys = Keys::new(secret_key); + let keys = Keys::parse("nsec12kcgs78l06p30jz7z7h3n2x2cy99nw2z6zspjdp7qc206887mwvs95lnkx")?; let opts = Options::new() .connection_timeout(Some(Duration::from_secs(10))) .send_timeout(Some(Duration::from_secs(5))); - let client = Client::with_opts(&keys, opts); + let client = Client::with_opts(keys.clone(), opts); println!("Bot public key: {}", keys.public_key().to_bech32()?); diff --git a/crates/nostr-sdk/examples/client-with-opts.rs b/crates/nostr-sdk/examples/client-with-opts.rs index e3afda4b4..cbe60d019 100644 --- a/crates/nostr-sdk/examples/client-with-opts.rs +++ b/crates/nostr-sdk/examples/client-with-opts.rs @@ -6,14 +6,12 @@ use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use nostr_sdk::prelude::*; -const BECH32_SK: &str = "nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85"; - #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::fmt::init(); // Parse keys - let my_keys = Keys::parse(BECH32_SK)?; + let keys = Keys::parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; // Configure client to use proxy for `.onion` relays let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9050)); @@ -21,7 +19,7 @@ async fn main() -> Result<()> { .proxy(addr) .target(ConnectionTarget::Onion); let opts = Options::new().connection(connection); - let client = Client::with_opts(&my_keys, opts); + let client = Client::with_opts(keys.clone(), opts); // Add relays client.add_relay("wss://relay.damus.io").await?; @@ -37,7 +35,7 @@ async fn main() -> Result<()> { client.connect().await; - let subscription = Filter::new().pubkey(my_keys.public_key()).limit(0); + let subscription = Filter::new().pubkey(keys.public_key()).limit(0); client.subscribe(vec![subscription], None).await?; diff --git a/crates/nostr-sdk/examples/client.rs b/crates/nostr-sdk/examples/client.rs index 7e8f07445..db62fa5dd 100644 --- a/crates/nostr-sdk/examples/client.rs +++ b/crates/nostr-sdk/examples/client.rs @@ -4,16 +4,13 @@ use nostr_sdk::prelude::*; -const BECH32_SK: &str = "nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85"; - #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::fmt::init(); - let secret_key = SecretKey::from_bech32(BECH32_SK)?; - let my_keys = Keys::new(secret_key); + let keys = Keys::parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; + let client = Client::new(keys); - let client = Client::new(&my_keys); client.add_relay("wss://relay.damus.io").await?; client.add_relay("wss://nostr.wine").await?; client.add_relay("wss://relay.rip").await?; @@ -26,26 +23,14 @@ async fn main() -> Result<()> { println!("Sent to: {:?}", output.success); println!("Not sent to: {:?}", output.failed); - // Create a text note POW event and broadcast to all connected relays - let event: Event = EventBuilder::text_note("POW text note from rust-nostr", []) - .pow(20) - .to_event(&my_keys)?; - client.send_event(event).await?; - - // Send multiple events at once (to all relays) - let mut events: Vec = Vec::new(); - for i in 0..10 { - events.push(EventBuilder::text_note(format!("Event #{i}"), []).to_event(&my_keys)?); - } - let opts = RelaySendOptions::default(); - client.batch_event(events, opts).await?; + // Create a text note POW event and broadcast to relays + let builder = EventBuilder::text_note("POW text note from rust-nostr", []).pow(20); + client.send_event_builder(builder).await?; // Send event to specific relays - let event: Event = EventBuilder::text_note("POW text note from rust-nostr 16", []) - .pow(16) - .to_event(&my_keys)?; + let builder = EventBuilder::text_note("POW text note from rust-nostr 16", []).pow(16); client - .send_event_to(["wss://relay.damus.io", "wss://relay.rip"], event) + .send_event_builder_to(["wss://relay.damus.io", "wss://relay.rip"], builder) .await?; Ok(()) diff --git a/crates/nostr-sdk/examples/gossip.rs b/crates/nostr-sdk/examples/gossip.rs index c132e31db..a1d65e95c 100644 --- a/crates/nostr-sdk/examples/gossip.rs +++ b/crates/nostr-sdk/examples/gossip.rs @@ -15,7 +15,7 @@ async fn main() -> Result<()> { let my_keys = Keys::parse(BECH32_SK)?; let opts = Options::new().gossip(true); - let client = Client::with_opts(&my_keys, opts); + let client = Client::with_opts(my_keys, opts); client.add_discovery_relay("wss://relay.damus.io").await?; client.add_discovery_relay("wss://purplepag.es").await?; diff --git a/crates/nostr-sdk/examples/lmdb.rs b/crates/nostr-sdk/examples/lmdb.rs index 13cf87042..337a12523 100644 --- a/crates/nostr-sdk/examples/lmdb.rs +++ b/crates/nostr-sdk/examples/lmdb.rs @@ -8,11 +8,11 @@ use nostr_sdk::prelude::*; async fn main() -> Result<()> { tracing_subscriber::fmt::init(); - let my_keys = Keys::parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; + let keys = Keys::parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; let database = NostrLMDB::open("./db/nostr-lmdb")?; let client: Client = ClientBuilder::default() - .signer(&my_keys) + .signer(keys.clone()) .database(database) .build(); @@ -26,7 +26,7 @@ async fn main() -> Result<()> { client.publish_text_note("Hello world", []).await?; // Negentropy reconcile - let filter = Filter::new().author(my_keys.public_key()); + let filter = Filter::new().author(keys.public_key()); let output = client.sync(filter, &SyncOptions::default()).await?; println!("Local: {}", output.local.len()); @@ -42,7 +42,7 @@ async fn main() -> Result<()> { } // Query events from database - let filter = Filter::new().author(my_keys.public_key()).limit(10); + let filter = Filter::new().author(keys.public_key()).limit(10); let events = client.database().query(vec![filter]).await?; println!("Events: {events:?}"); diff --git a/crates/nostr-sdk/examples/nostrdb.rs b/crates/nostr-sdk/examples/nostrdb.rs index 69aa927ef..2e9e5a2d4 100644 --- a/crates/nostr-sdk/examples/nostrdb.rs +++ b/crates/nostr-sdk/examples/nostrdb.rs @@ -10,11 +10,11 @@ const BECH32_SK: &str = "nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9 async fn main() -> Result<()> { tracing_subscriber::fmt::init(); - let my_keys = Keys::parse(BECH32_SK)?; + let keys = Keys::parse(BECH32_SK)?; let database = NdbDatabase::open("./db/ndb")?; let client: Client = Client::builder() - .signer(&my_keys) + .signer(keys.clone()) .database(database) .build(); @@ -26,11 +26,11 @@ async fn main() -> Result<()> { client.publish_text_note("Hello world", []).await?; // Negentropy reconcile - let filter = Filter::new().author(my_keys.public_key()); + let filter = Filter::new().author(keys.public_key()); client.sync(filter, &SyncOptions::default()).await?; // Query events from database - let filter = Filter::new().author(my_keys.public_key()).limit(10); + let filter = Filter::new().author(keys.public_key()).limit(10); let events = client.database().query(vec![filter]).await?; println!("Events: {events:?}"); diff --git a/crates/nostr-sdk/examples/shutdown-on-drop.rs b/crates/nostr-sdk/examples/shutdown-on-drop.rs index a0c2353ee..4471863ab 100644 --- a/crates/nostr-sdk/examples/shutdown-on-drop.rs +++ b/crates/nostr-sdk/examples/shutdown-on-drop.rs @@ -12,17 +12,17 @@ async fn main() -> Result<()> { tracing_subscriber::fmt::init(); let keys = Keys::generate(); - let client = Client::new(keys); // Ref countert set to 1 + let client = Client::new(keys); // Counter set to 1 client.add_relay("wss://relay.rip").await?; client.add_relay("wss://relay.damus.io").await?; client.connect().await; - let c = client.clone(); // Clone, ref counter set to 2 + let c = client.clone(); // Clone, counter set to 2 let _ = thread::spawn(async move { thread::sleep(Duration::from_secs(3)).await; c.relays().await; - // First drop, decrease ref counter to 1... + // First drop, decrease counter to 1... }); thread::sleep(Duration::from_secs(5)).await; @@ -34,4 +34,4 @@ async fn main() -> Result<()> { Ok(()) } -// Client dropped, ref counter set to 0: auto shutdown relay pool. +// Client dropped, counter set to 0: auto shutdown relay pool. diff --git a/crates/nostr-sdk/examples/subscriptions.rs b/crates/nostr-sdk/examples/subscriptions.rs index 0d6b8d37d..7c3a2a79c 100644 --- a/crates/nostr-sdk/examples/subscriptions.rs +++ b/crates/nostr-sdk/examples/subscriptions.rs @@ -4,14 +4,11 @@ use nostr_sdk::prelude::*; -const BECH32_SK: &str = "nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85"; - #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::fmt::init(); - let secret_key = SecretKey::from_bech32(BECH32_SK)?; - let keys = Keys::new(secret_key); + let keys = Keys::parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; let public_key = keys.public_key(); let opts = Options::new().wait_for_send(false); diff --git a/crates/nostr-sdk/examples/tor.rs b/crates/nostr-sdk/examples/tor.rs index 3102171e3..a5e401591 100644 --- a/crates/nostr-sdk/examples/tor.rs +++ b/crates/nostr-sdk/examples/tor.rs @@ -4,21 +4,19 @@ use nostr_sdk::prelude::*; -const BECH32_SK: &str = "nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85"; - #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::fmt::init(); // Parse keys - let my_keys = Keys::parse(BECH32_SK)?; + let keys = Keys::parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; // Configure client to use embedded tor for `.onion` relays let connection: Connection = Connection::new() .embedded_tor() .target(ConnectionTarget::Onion); let opts = Options::new().connection(connection); - let client = Client::with_opts(&my_keys, opts); + let client = Client::with_opts(keys.clone(), opts); // Add relays client.add_relay("wss://relay.damus.io").await?; @@ -31,7 +29,7 @@ async fn main() -> Result<()> { client.connect().await; - let filter: Filter = Filter::new().pubkey(my_keys.public_key()).limit(0); + let filter: Filter = Filter::new().pubkey(keys.public_key()).limit(0); client.subscribe(vec![filter], None).await?; // Handle subscription notifications with `handle_notifications` method diff --git a/crates/nostr-sdk/examples/zapper.rs b/crates/nostr-sdk/examples/zapper.rs index 674632b65..ea6a80d9c 100644 --- a/crates/nostr-sdk/examples/zapper.rs +++ b/crates/nostr-sdk/examples/zapper.rs @@ -22,9 +22,7 @@ async fn main() -> Result<()> { let nwc = NWC::new(uri); // Compose client - let secret_key = - SecretKey::from_bech32("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; - let keys = Keys::new(secret_key); + let keys = Keys::parse("nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85")?; let client = Client::builder().signer(keys).zapper(nwc).build(); client.add_relay("wss://relay.nostr.band").await?; @@ -35,7 +33,7 @@ async fn main() -> Result<()> { PublicKey::from_bech32("npub1drvpzev3syqt0kjrls50050uzf25gehpz9vgdw08hvex7e0vgfeq0eseet") .unwrap(); - // Send sats without zap event + // Send SAT without zap event client.zap(public_key, 1000, None).await?; // Zap profile diff --git a/crates/nostr-sdk/src/client/builder.rs b/crates/nostr-sdk/src/client/builder.rs index f013d8fb9..3e9b43c9f 100644 --- a/crates/nostr-sdk/src/client/builder.rs +++ b/crates/nostr-sdk/src/client/builder.rs @@ -6,9 +6,9 @@ use std::sync::Arc; +use nostr::signer::{IntoNostrSigner, NostrSigner}; use nostr_database::memory::MemoryDatabase; use nostr_database::{DynNostrDatabase, IntoNostrDatabase}; -use nostr_signer::NostrSigner; #[cfg(feature = "nip57")] use nostr_zapper::{DynNostrZapper, IntoNostrZapper}; @@ -18,7 +18,7 @@ use crate::{Client, Options}; #[derive(Debug, Clone)] pub struct ClientBuilder { /// Nostr Signer - pub signer: Option, + pub signer: Option>, /// Nostr Zapper #[cfg(feature = "nip57")] pub zapper: Option>, @@ -58,11 +58,11 @@ impl ClientBuilder { /// let client = ClientBuilder::new().signer(keys).build(); /// ``` #[inline] - pub fn signer(mut self, signer: S) -> Self + pub fn signer(mut self, signer: T) -> Self where - S: Into, + T: IntoNostrSigner, { - self.signer = Some(signer.into()); + self.signer = Some(signer.into_nostr_signer()); self } diff --git a/crates/nostr-sdk/src/client/mod.rs b/crates/nostr-sdk/src/client/mod.rs index 50e75a6cd..fed49f18e 100644 --- a/crates/nostr-sdk/src/client/mod.rs +++ b/crates/nostr-sdk/src/client/mod.rs @@ -14,7 +14,6 @@ use atomic_destructor::StealthClone; use nostr::prelude::*; use nostr_database::DynNostrDatabase; use nostr_relay_pool::prelude::*; -use nostr_signer::prelude::*; #[cfg(feature = "nip57")] use nostr_zapper::{DynNostrZapper, IntoNostrZapper, ZapperError}; use thiserror::Error; @@ -48,7 +47,7 @@ pub enum Error { Database(#[from] DatabaseError), /// Signer error #[error(transparent)] - Signer(#[from] nostr_signer::Error), + Signer(#[from] SignerError), /// Zapper error #[cfg(feature = "nip57")] #[error(transparent)] @@ -74,6 +73,10 @@ pub enum Error { #[cfg(feature = "nip57")] #[error(transparent)] LnUrlPay(#[from] lnurl_pay::Error), + /// NIP59 + #[cfg(feature = "nip59")] + #[error(transparent)] + NIP59(#[from] nip59::Error), /// Event not found #[error("event not found: {0}")] EventNotFound(EventId), @@ -89,7 +92,7 @@ pub enum Error { #[derive(Debug, Clone)] pub struct Client { pool: RelayPool, - signer: Arc>>, + signer: Arc>>>, #[cfg(feature = "nip57")] zapper: Arc>>>, gossip_graph: GossipGraph, @@ -117,46 +120,46 @@ impl StealthClone for Client { } impl Client { - /// Create a new [`Client`] with signer + /// Construct client with signer /// - /// To create a [`Client`] without any signer use `Client::default()`. + /// To construct one without signer use [`Client::default()`]. /// /// # Example /// ```rust,no_run /// use nostr_sdk::prelude::*; /// - /// let my_keys = Keys::generate(); - /// let client = Client::new(&my_keys); + /// let keys = Keys::generate(); + /// let client = Client::new(keys); /// ``` #[inline] - pub fn new(signer: S) -> Self + pub fn new(signer: T) -> Self where - S: Into, + T: IntoNostrSigner, { Self::builder().signer(signer).build() } - /// Create a new [`Client`] with [`Options`] + /// Construct client with signer and options /// - /// To create a [`Client`] with custom [`Options`] and without any signer use `Client::builder().opts(opts).build()`. + /// Check [`ClientBuilder`] to construct more customized clients (i.e. with persistent database). /// /// # Example /// ```rust,no_run /// use nostr_sdk::prelude::*; /// - /// let my_keys = Keys::generate(); + /// let keys = Keys::generate(); /// let opts = Options::new().wait_for_send(true); - /// let client = Client::with_opts(&my_keys, opts); + /// let client = Client::with_opts(keys, opts); /// ``` #[inline] - pub fn with_opts(signer: S, opts: Options) -> Self + pub fn with_opts(signer: T, opts: Options) -> Self where - S: Into, + T: IntoNostrSigner, { Self::builder().signer(signer).opts(opts).build() } - /// Construct [ClientBuilder] + /// Construct client builder /// /// # Example /// ```rust,no_run @@ -214,15 +217,24 @@ impl Client { /// Get current nostr signer /// /// Rise error if it not set. - pub async fn signer(&self) -> Result { + pub async fn signer(&self) -> Result, Error> { let signer = self.signer.read().await; signer.clone().ok_or(Error::SignerNotConfigured) } /// Set nostr signer - pub async fn set_signer(&self, signer: Option) { + pub async fn set_signer(&self, signer: T) + where + T: IntoNostrSigner, + { let mut s = self.signer.write().await; - *s = signer; + *s = Some(signer.into_nostr_signer()); + } + + /// Unset nostr signer + pub async fn unset_signer(&self) { + let mut s = self.signer.write().await; + *s = None; } /// Check if `zapper` is configured @@ -404,21 +416,6 @@ impl Client { /// To use custom [`RelayOptions`] use [`RelayPool::add_relay`]. /// /// Connection is **NOT** automatically started with relay, remember to call `client.connect()`! - /// - /// # Example - /// ```rust,no_run - /// use nostr_sdk::prelude::*; - /// - /// # #[tokio::main] - /// # async fn main() { - /// # let my_keys = Keys::generate(); - /// # let client = Client::new(&my_keys); - /// client.add_relay("wss://relay.nostr.info").await.unwrap(); - /// client.add_relay("wss://relay.damus.io").await.unwrap(); - /// - /// client.connect().await; - /// # } - /// ``` #[inline] pub async fn add_relay(&self, url: U) -> Result where @@ -545,21 +542,6 @@ impl Client { } /// Connect to a previously added relay - /// - /// # Example - /// ```rust,no_run - /// use nostr_sdk::prelude::*; - /// - /// # #[tokio::main] - /// # async fn main() { - /// # let my_keys = Keys::generate(); - /// # let client = Client::new(&my_keys); - /// client - /// .connect_relay("wss://relay.nostr.info") - /// .await - /// .unwrap(); - /// # } - /// ``` #[inline] pub async fn connect_relay(&self, url: U) -> Result<(), Error> where @@ -573,21 +555,6 @@ impl Client { } /// Disconnect relay - /// - /// # Example - /// ```rust,no_run - /// use nostr_sdk::prelude::*; - /// - /// # #[tokio::main] - /// # async fn main() { - /// # let my_keys = Keys::generate(); - /// # let client = Client::new(&my_keys); - /// client - /// .disconnect_relay("wss://relay.nostr.info") - /// .await - /// .unwrap(); - /// # } - /// ``` #[inline] pub async fn disconnect_relay(&self, url: U) -> Result<(), Error> where @@ -598,18 +565,6 @@ impl Client { } /// Connect to all added relays - /// - /// # Example - /// ```rust,no_run - /// use nostr_sdk::prelude::*; - /// - /// # #[tokio::main] - /// # async fn main() { - /// # let my_keys = Keys::generate(); - /// # let client = Client::new(&my_keys); - /// client.connect().await; - /// # } - /// ``` #[inline] pub async fn connect(&self) { self.pool.connect(self.opts.connection_timeout).await; @@ -625,18 +580,6 @@ impl Client { } /// Disconnect from all relays - /// - /// # Example - /// ```rust,no_run - /// use nostr_sdk::prelude::*; - /// - /// # #[tokio::main] - /// # async fn main() { - /// # let my_keys = Keys::generate(); - /// # let client = Client::new(&my_keys); - /// client.disconnect().await.unwrap(); - /// # } - /// ``` #[inline] pub async fn disconnect(&self) -> Result<(), Error> { Ok(self.pool.disconnect().await?) @@ -671,14 +614,14 @@ impl Client { /// /// # Example /// ```rust,no_run - /// use nostr_sdk::prelude::*; - /// + /// # use nostr_sdk::prelude::*; /// # #[tokio::main] /// # async fn main() -> Result<()> { - /// # let my_keys = Keys::generate(); - /// # let client = Client::new(&my_keys); + /// # let keys = Keys::generate(); + /// # let client = Client::new(keys.clone()); + /// // Compose filter /// let subscription = Filter::new() - /// .pubkeys(vec![my_keys.public_key()]) + /// .pubkeys(vec![keys.public_key()]) /// .since(Timestamp::now()); /// /// // Subscribe @@ -866,16 +809,14 @@ impl Client { /// /// # Example /// ```rust,no_run - /// use std::time::Duration; - /// - /// use nostr_sdk::prelude::*; - /// + /// # use std::time::Duration; + /// # use nostr_sdk::prelude::*; /// # #[tokio::main] /// # async fn main() { - /// # let my_keys = Keys::generate(); - /// # let client = Client::new(&my_keys); + /// # let keys = Keys::generate(); + /// # let client = Client::new(keys.clone()); /// let subscription = Filter::new() - /// .pubkeys(vec![my_keys.public_key()]) + /// .pubkeys(vec![keys.public_key()]) /// .since(Timestamp::now()); /// /// let _events = client @@ -1159,9 +1100,9 @@ impl Client { pub async fn sign_event_builder(&self, builder: EventBuilder) -> Result { let signer = self.signer().await?; - let public_key: PublicKey = signer.public_key().await?; + let public_key: PublicKey = signer.get_public_key().await?; let difficulty: u8 = self.opts.get_difficulty(); - let unsigned: UnsignedEvent = builder.pow(difficulty).to_unsigned_event(public_key); + let unsigned: UnsignedEvent = builder.pow(difficulty).build(public_key); Ok(signer.sign_event(unsigned).await?) } @@ -1225,12 +1166,11 @@ impl Client { /// /// # Example /// ```rust,no_run - /// use nostr_sdk::prelude::*; - /// + /// # use nostr_sdk::prelude::*; /// # #[tokio::main] /// # async fn main() { - /// # let my_keys = Keys::generate(); - /// # let client = Client::new(&my_keys); + /// # let keys = Keys::generate(); + /// # let client = Client::new(keys); /// let metadata = Metadata::new() /// .name("username") /// .display_name("My Username") @@ -1262,21 +1202,6 @@ impl Client { /// Publish text note /// /// - /// - /// # Example - /// ```rust,no_run - /// use nostr_sdk::prelude::*; - /// - /// # #[tokio::main] - /// # async fn main() { - /// # let my_keys = Keys::generate(); - /// # let client = Client::new(&my_keys); - /// client - /// .publish_text_note("My first text note from rust-nostr!", []) - /// .await - /// .unwrap(); - /// # } - /// ``` #[inline] pub async fn publish_text_note( &self, @@ -1305,7 +1230,7 @@ impl Client { async fn get_contact_list_filters(&self) -> Result, Error> { let signer = self.signer().await?; - let public_key = signer.public_key().await?; + let public_key = signer.get_public_key().await?; let filter: Filter = Filter::new() .author(public_key) .kind(Kind::ContactList) @@ -1316,21 +1241,6 @@ impl Client { /// Get contact list from relays. /// /// - /// - /// # Example - /// ```rust,no_run - /// use std::time::Duration; - /// - /// use nostr_sdk::prelude::*; - /// - /// # #[tokio::main] - /// # async fn main() { - /// # let my_keys = Keys::generate(); - /// # let client = Client::new(&my_keys); - /// let timeout = Duration::from_secs(10); - /// let _list = client.get_contact_list(Some(timeout)).await.unwrap(); - /// # } - /// ``` pub async fn get_contact_list(&self, timeout: Option) -> Result, Error> { let mut contact_list: Vec = Vec::new(); let filters: Vec = self.get_contact_list_filters().await?; @@ -1470,24 +1380,6 @@ impl Client { /// Like event /// /// - /// - /// # Example - /// ```rust,no_run - /// use std::str::FromStr; - /// - /// use nostr_sdk::prelude::*; - /// - /// # #[tokio::main] - /// # async fn main() { - /// # let my_keys = Keys::generate(); - /// # let client = Client::new(&my_keys); - /// let event = - /// Event::from_json(r#"{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","sig":"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}"#) - /// .unwrap(); - /// - /// client.like(&event).await.unwrap(); - /// # } - /// ``` #[inline] pub async fn like(&self, event: &Event) -> Result, Error> { self.reaction(event, "+").await @@ -1496,24 +1388,6 @@ impl Client { /// Disike event /// /// - /// - /// # Example - /// ```rust,no_run - /// use std::str::FromStr; - /// - /// use nostr_sdk::prelude::*; - /// - /// # #[tokio::main] - /// # async fn main() { - /// # let my_keys = Keys::generate(); - /// # let client = Client::new(&my_keys); - /// let event = - /// Event::from_json(r#"{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","sig":"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}"#) - /// .unwrap(); - /// - /// client.dislike(&event).await.unwrap(); - /// # } - /// ``` #[inline] pub async fn dislike(&self, event: &Event) -> Result, Error> { self.reaction(event, "-").await @@ -1531,8 +1405,8 @@ impl Client { /// /// # #[tokio::main] /// # async fn main() { - /// # let my_keys = Keys::generate(); - /// # let client = Client::new(&my_keys); + /// # let keys = Keys::generate(); + /// # let client = Client::new(keys); /// let event = /// Event::from_json(r#"{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","sig":"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}"#) /// .unwrap(); @@ -1690,8 +1564,18 @@ impl Client { rumor: EventBuilder, expiration: Option, ) -> Result, Error> { - let signer: NostrSigner = self.signer().await?; - let gift_wrap: Event = signer.gift_wrap(receiver, rumor, expiration).await?; + // Acquire signer + let signer = self.signer().await?; + + // Compose rumor + let public_key: PublicKey = signer.get_public_key().await?; + let rumor: UnsignedEvent = rumor.build(public_key); + + // Build gift wrap + let gift_wrap: Event = + EventBuilder::gift_wrap(&signer, receiver, rumor, expiration).await?; + + // Send self.send_event(gift_wrap).await } @@ -1712,8 +1596,18 @@ impl Client { U: TryIntoUrl, pool::Error: From<::Err>, { - let signer: NostrSigner = self.signer().await?; - let gift_wrap: Event = signer.gift_wrap(receiver, rumor, expiration).await?; + // Acquire signer + let signer = self.signer().await?; + + // Compose rumor + let public_key: PublicKey = signer.get_public_key().await?; + let rumor: UnsignedEvent = rumor.build(public_key); + + // Build gift wrap + let gift_wrap: Event = + EventBuilder::gift_wrap(&signer, receiver, rumor, expiration).await?; + + // Send self.send_event_to(urls, gift_wrap).await } @@ -1725,8 +1619,8 @@ impl Client { #[inline] #[cfg(feature = "nip59")] pub async fn unwrap_gift_wrap(&self, gift_wrap: &Event) -> Result { - let signer: NostrSigner = self.signer().await?; - Ok(signer.unwrap_gift_wrap(gift_wrap).await?) + let signer = self.signer().await?; + Ok(UnwrappedGift::from_gift_wrap(&signer, gift_wrap).await?) } /// File metadata diff --git a/crates/nostr-sdk/src/gossip/graph.rs b/crates/nostr-sdk/src/gossip/graph.rs index 43ab5a9ec..cdddf3b75 100644 --- a/crates/nostr-sdk/src/gossip/graph.rs +++ b/crates/nostr-sdk/src/gossip/graph.rs @@ -349,7 +349,9 @@ mod tests { let list = relays .into_iter() .filter_map(|(url, m)| Some((Url::parse(url).ok()?, m))); - EventBuilder::relay_list(list).to_event(&keys).unwrap() + EventBuilder::relay_list(list) + .sign_with_keys(&keys) + .unwrap() } async fn setup_graph() -> GossipGraph { From b9b6309715874c7ce38e4c93fb9b0cce1a2a4804 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Wed, 30 Oct 2024 12:02:02 +0100 Subject: [PATCH 5/9] connect: rename `Nip46Signer` to `NostrConnect` Signed-off-by: Yuki Kishimoto --- CHANGELOG.md | 1 + Cargo.lock | 2 ++ bindings/nostr-sdk-ffi/Cargo.toml | 1 + .../nostr-sdk-ffi/bindings-python/README.md | 4 ++-- .../bindings-python/examples/client.py | 4 ++-- .../nostr-sdk-ffi/src/client/signer/mod.rs | 4 ++-- .../nostr-sdk-ffi/src/client/signer/nip46.rs | 18 +++++++------- bindings/nostr-sdk-ffi/src/error.rs | 8 +++---- bindings/nostr-sdk-js/Cargo.toml | 1 + .../nostr-sdk-js/examples/nostr-connect.js | 4 ++-- .../nostr-sdk-js/src/client/signer/mod.rs | 4 ++-- .../nostr-sdk-js/src/client/signer/nip46.rs | 24 +++++++++---------- crates/nostr-connect/src/client.rs | 12 ++++++---- crates/nostr-connect/src/lib.rs | 4 ---- crates/nostr-connect/src/prelude.rs | 4 +++- crates/nostr-connect/src/signer.rs | 2 +- crates/nostr-sdk/examples/nostr-connect.rs | 4 ++-- 17 files changed, 54 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71d23c62b..cd319f0a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ * sdk: deprecate `Client::reconcile` and `Client::reconcile_with` ([Yuki Kishimoto]) * sdk: use by default tor for onion relays if `tor` feature is enabled on non-mobile targets ([Yuki Kishimoto]) * signer: auto enable `nip44` feature if `nip59` is enabled ([Yuki Kishimoto]) +* connect: rename `Nip46Signer` to `NostrConnect` ([Yuki Kishimoto]) * database: improve `BTreeCappedSet` ([Yuki Kishimoto]) * database: not save invalid event deletion ([Yuki Kishimoto]) * lmdb: not save event deletion ([Yuki Kishimoto]) diff --git a/Cargo.lock b/Cargo.lock index f91bd37c8..ffbe5c838 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2950,6 +2950,7 @@ version = "0.1.0" dependencies = [ "async-trait", "async-utility", + "nostr-connect", "nostr-ffi", "nostr-relay-builder", "nostr-sdk", @@ -2965,6 +2966,7 @@ version = "0.1.0" dependencies = [ "console_error_panic_hook", "js-sys", + "nostr-connect", "nostr-sdk", "tracing", "tracing-subscriber", diff --git a/bindings/nostr-sdk-ffi/Cargo.toml b/bindings/nostr-sdk-ffi/Cargo.toml index 8f08b3231..890c8604e 100644 --- a/bindings/nostr-sdk-ffi/Cargo.toml +++ b/bindings/nostr-sdk-ffi/Cargo.toml @@ -16,6 +16,7 @@ ndb = ["nostr-sdk/ndb"] [dependencies] async-trait.workspace = true async-utility.workspace = true +nostr-connect.workspace = true nostr-ffi = { path = "../nostr-ffi" } nostr-relay-builder.workspace = true nostr-sdk = { workspace = true, default-features = false, features = ["all-nips", "tor"] } diff --git a/bindings/nostr-sdk-ffi/bindings-python/README.md b/bindings/nostr-sdk-ffi/bindings-python/README.md index efc2008e6..b8049b7e7 100644 --- a/bindings/nostr-sdk-ffi/bindings-python/README.md +++ b/bindings/nostr-sdk-ffi/bindings-python/README.md @@ -19,7 +19,7 @@ pip install nostr-sdk ```python import asyncio from datetime import timedelta -from nostr_sdk import Keys, Client, NostrSigner, EventBuilder, Filter, Metadata, init_logger, LogLevel +from nostr_sdk import * async def main(): @@ -36,7 +36,7 @@ async def main(): # Or, initialize with NIP46 signer # app_keys = Keys.parse("..") # uri = NostrConnectUri.parse("bunker://.. or nostrconnect://..") - # nip46 = Nip46Signer(uri, app_keys, timedelta(seconds=60), None) + # nip46 = NostrConnect(uri, app_keys, timedelta(seconds=60), None) # signer = NostrSigner.nip46(nip46) client = Client(signer) diff --git a/bindings/nostr-sdk-ffi/bindings-python/examples/client.py b/bindings/nostr-sdk-ffi/bindings-python/examples/client.py index d5e5bd6b3..ccc7e33af 100644 --- a/bindings/nostr-sdk-ffi/bindings-python/examples/client.py +++ b/bindings/nostr-sdk-ffi/bindings-python/examples/client.py @@ -1,6 +1,6 @@ import asyncio from datetime import timedelta -from nostr_sdk import Keys, Client, NostrSigner, EventBuilder, Filter, Metadata, init_logger, LogLevel +from nostr_sdk import * async def main(): @@ -17,7 +17,7 @@ async def main(): # Or, initialize with NIP46 signer # app_keys = Keys.parse("..") # uri = NostrConnectUri.parse("bunker://.. or nostrconnect://..") - # nip46 = Nip46Signer(uri, app_keys, timedelta(seconds=60), None) + # nip46 = NostrConnect(uri, app_keys, timedelta(seconds=60), None) # signer = NostrSigner.nip46(nip46) client = Client(signer) diff --git a/bindings/nostr-sdk-ffi/src/client/signer/mod.rs b/bindings/nostr-sdk-ffi/src/client/signer/mod.rs index e970ae0e3..7319d8cab 100644 --- a/bindings/nostr-sdk-ffi/src/client/signer/mod.rs +++ b/bindings/nostr-sdk-ffi/src/client/signer/mod.rs @@ -11,7 +11,7 @@ use uniffi::Object; pub mod nip46; -use self::nip46::Nip46Signer; +use self::nip46::NostrConnect; use crate::error::Result; #[derive(Object)] @@ -43,7 +43,7 @@ impl NostrSigner { } #[uniffi::constructor] - pub fn nip46(nip46: &Nip46Signer) -> Self { + pub fn nip46(nip46: &NostrConnect) -> Self { Self { inner: signer::NostrSigner::nip46(nip46.deref().clone()), } diff --git a/bindings/nostr-sdk-ffi/src/client/signer/nip46.rs b/bindings/nostr-sdk-ffi/src/client/signer/nip46.rs index aabe2bcb0..61c918701 100644 --- a/bindings/nostr-sdk-ffi/src/client/signer/nip46.rs +++ b/bindings/nostr-sdk-ffi/src/client/signer/nip46.rs @@ -6,36 +6,36 @@ use std::ops::Deref; use std::sync::Arc; use std::time::Duration; +use nostr_connect::{client, signer}; use nostr_ffi::nips::nip46::{Nip46Request, NostrConnectURI}; use nostr_ffi::{Keys, PublicKey, SecretKey}; use nostr_sdk::nostr::nips::nip46::Request; -use nostr_sdk::signer; use uniffi::Object; use crate::error::Result; use crate::relay::RelayOptions; #[derive(Object)] -pub struct Nip46Signer { - inner: signer::Nip46Signer, +pub struct NostrConnect { + inner: client::NostrConnect, } -impl Deref for Nip46Signer { - type Target = signer::Nip46Signer; +impl Deref for NostrConnect { + type Target = client::NostrConnect; fn deref(&self) -> &Self::Target { &self.inner } } -impl From for Nip46Signer { - fn from(inner: signer::Nip46Signer) -> Self { +impl From for NostrConnect { + fn from(inner: client::NostrConnect) -> Self { Self { inner } } } #[uniffi::export(async_runtime = "tokio")] -impl Nip46Signer { +impl NostrConnect { /// Construct Nostr Connect client #[uniffi::constructor] pub fn new( @@ -45,7 +45,7 @@ impl Nip46Signer { opts: Option>, ) -> Result { Ok(Self { - inner: signer::Nip46Signer::new( + inner: client::NostrConnect::new( uri.deref().clone(), app_keys.deref().clone(), timeout, diff --git a/bindings/nostr-sdk-ffi/src/error.rs b/bindings/nostr-sdk-ffi/src/error.rs index 3ef72a47c..095ddb406 100644 --- a/bindings/nostr-sdk-ffi/src/error.rs +++ b/bindings/nostr-sdk-ffi/src/error.rs @@ -74,14 +74,14 @@ impl From for NostrSdkError { } } -impl From for NostrSdkError { - fn from(e: nostr_sdk::signer::Error) -> NostrSdkError { +impl From for NostrSdkError { + fn from(e: nostr_sdk::signer::SignerError) -> NostrSdkError { Self::Generic(e.to_string()) } } -impl From for NostrSdkError { - fn from(e: nostr_sdk::signer::nip46::Error) -> NostrSdkError { +impl From for NostrSdkError { + fn from(e: nostr_connect::error::Error) -> NostrSdkError { Self::Generic(e.to_string()) } } diff --git a/bindings/nostr-sdk-js/Cargo.toml b/bindings/nostr-sdk-js/Cargo.toml index c6988e68a..b608c5a75 100644 --- a/bindings/nostr-sdk-js/Cargo.toml +++ b/bindings/nostr-sdk-js/Cargo.toml @@ -10,6 +10,7 @@ crate-type = ["lib", "cdylib"] [dependencies] console_error_panic_hook = "0.1" js-sys.workspace = true +nostr-connect.workspace = true nostr-sdk = { workspace = true, default-features = false, features = ["all-nips", "indexeddb", "webln"] } tracing.workspace = true tracing-subscriber.workspace = true diff --git a/bindings/nostr-sdk-js/examples/nostr-connect.js b/bindings/nostr-sdk-js/examples/nostr-connect.js index 77346222d..9be37b834 100644 --- a/bindings/nostr-sdk-js/examples/nostr-connect.js +++ b/bindings/nostr-sdk-js/examples/nostr-connect.js @@ -1,4 +1,4 @@ -const { Keys, Client, NostrSigner, Nip46Signer, NostrConnectURI, loadWasmAsync, initLogger, LogLevel, Duration } = require("../"); +const { Keys, Client, NostrSigner, NostrConnect, NostrConnectURI, loadWasmAsync, initLogger, LogLevel, Duration } = require("../"); async function main() { await loadWasmAsync(); @@ -11,7 +11,7 @@ async function main() { // Remote signer (NIP46) let uri = NostrConnectURI.parse("bunker://.."); let timeout = Duration.fromSecs(60); - let nip46 = new Nip46Signer(uri, appKeys, timeout); + let nip46 = new NostrConnect(uri, appKeys, timeout); let signer = NostrSigner.nip46(nip46); // Compose client and add relays diff --git a/bindings/nostr-sdk-js/src/client/signer/mod.rs b/bindings/nostr-sdk-js/src/client/signer/mod.rs index 72faa6b18..179f3df97 100644 --- a/bindings/nostr-sdk-js/src/client/signer/mod.rs +++ b/bindings/nostr-sdk-js/src/client/signer/mod.rs @@ -9,7 +9,7 @@ use wasm_bindgen::prelude::*; pub mod nip46; -use self::nip46::JsNip46Signer; +use self::nip46::JsNostrConnect; use crate::error::{into_err, Result}; use crate::protocol::event::{JsEvent, JsEventBuilder, JsUnsignedEvent}; use crate::protocol::key::{JsKeys, JsPublicKey}; @@ -52,7 +52,7 @@ impl JsNostrSigner { } /// NIP46 - pub fn nip46(signer: &JsNip46Signer) -> Self { + pub fn nip46(signer: &JsNostrConnect) -> Self { Self { inner: NostrSigner::nip46(signer.deref().clone()), } diff --git a/bindings/nostr-sdk-js/src/client/signer/nip46.rs b/bindings/nostr-sdk-js/src/client/signer/nip46.rs index b0c2568ad..fbf56b6a7 100644 --- a/bindings/nostr-sdk-js/src/client/signer/nip46.rs +++ b/bindings/nostr-sdk-js/src/client/signer/nip46.rs @@ -5,7 +5,7 @@ use core::ops::Deref; use js_sys::Array; -use nostr_sdk::signer::Nip46Signer; +use nostr_connect::prelude::*; use wasm_bindgen::prelude::*; use crate::duration::JsDuration; @@ -14,36 +14,36 @@ use crate::protocol::key::{JsKeys, JsPublicKey}; use crate::protocol::nips::nip46::JsNostrConnectURI; use crate::JsStringArray; -#[wasm_bindgen(js_name = Nip46Signer)] -pub struct JsNip46Signer { - inner: Nip46Signer, +#[wasm_bindgen(js_name = NostrConnect)] +pub struct JsNostrConnect { + inner: NostrConnect, } -impl Deref for JsNip46Signer { - type Target = Nip46Signer; +impl Deref for JsNostrConnect { + type Target = NostrConnect; fn deref(&self) -> &Self::Target { &self.inner } } -impl From for JsNip46Signer { - fn from(inner: Nip46Signer) -> Self { +impl From for JsNostrConnect { + fn from(inner: NostrConnect) -> Self { Self { inner } } } -#[wasm_bindgen(js_class = Nip46Signer)] -impl JsNip46Signer { +#[wasm_bindgen(js_class = NostrConnect)] +impl JsNostrConnect { /// Construct Nostr Connect client #[wasm_bindgen] pub fn new( uri: &JsNostrConnectURI, app_keys: &JsKeys, timeout: &JsDuration, - ) -> Result { + ) -> Result { Ok(Self { - inner: Nip46Signer::new( + inner: NostrConnect::new( uri.deref().clone(), app_keys.deref().clone(), **timeout, diff --git a/crates/nostr-connect/src/client.rs b/crates/nostr-connect/src/client.rs index 11fee407c..dbe018b4f 100644 --- a/crates/nostr-connect/src/client.rs +++ b/crates/nostr-connect/src/client.rs @@ -19,13 +19,17 @@ use nostr_relay_pool::{ use tokio::sync::broadcast::Receiver; use tokio::sync::OnceCell; -use super::Error; +use crate::error::Error; + +#[allow(missing_docs)] +#[deprecated(since = "0.36.0", note = "Use `NostrConnect` instead")] +pub type Nip46Signer = NostrConnect; /// Nostr Connect Client /// /// #[derive(Debug, Clone)] -pub struct Nip46Signer { +pub struct NostrConnect { app_keys: Keys, uri: NostrConnectURI, signer_public_key: OnceCell, @@ -36,7 +40,7 @@ pub struct Nip46Signer { bootstrapped: Arc, } -impl Nip46Signer { +impl NostrConnect { /// Construct Nostr Connect client pub fn new( uri: NostrConnectURI, @@ -343,7 +347,7 @@ async fn get_signer_public_key( #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl NostrSigner for Nip46Signer { +impl NostrSigner for NostrConnect { async fn get_public_key(&self) -> Result { // TODO: avoid copied? self.signer_public_key() diff --git a/crates/nostr-connect/src/lib.rs b/crates/nostr-connect/src/lib.rs index 8c6ed80d2..0cbdded0a 100644 --- a/crates/nostr-connect/src/lib.rs +++ b/crates/nostr-connect/src/lib.rs @@ -15,7 +15,3 @@ pub mod client; pub mod error; pub mod prelude; pub mod signer; - -pub use self::client::Nip46Signer; -pub use self::error::Error; -pub use self::signer::{NostrConnectRemoteSigner, NostrConnectSignerActions}; diff --git a/crates/nostr-connect/src/prelude.rs b/crates/nostr-connect/src/prelude.rs index 9a7bb9ca9..3b25f52d7 100644 --- a/crates/nostr-connect/src/prelude.rs +++ b/crates/nostr-connect/src/prelude.rs @@ -10,4 +10,6 @@ pub use nostr::prelude::*; -pub use crate::*; +pub use crate::client::*; +pub use crate::error::*; +pub use crate::signer::*; diff --git a/crates/nostr-connect/src/signer.rs b/crates/nostr-connect/src/signer.rs index 9186eaa52..33d341c03 100644 --- a/crates/nostr-connect/src/signer.rs +++ b/crates/nostr-connect/src/signer.rs @@ -12,7 +12,7 @@ use nostr_relay_pool::{ pool, RelayOptions, RelayPool, RelayPoolNotification, RelaySendOptions, SubscribeOptions, }; -use super::Error; +use crate::error::Error; /// Nostr Connect Signer /// diff --git a/crates/nostr-sdk/examples/nostr-connect.rs b/crates/nostr-sdk/examples/nostr-connect.rs index e888f7b16..e419927ae 100644 --- a/crates/nostr-sdk/examples/nostr-connect.rs +++ b/crates/nostr-sdk/examples/nostr-connect.rs @@ -15,7 +15,7 @@ async fn main() -> Result<()> { // Compose signer from bunker URI let uri = NostrConnectURI::parse("bunker://79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3?relay=wss%3A%2F%2Frelay.nsec.app")?; - let signer = Nip46Signer::new(uri, app_keys, Duration::from_secs(60), None)?; + let signer = NostrConnect::new(uri, app_keys, Duration::from_secs(60), None)?; // Compose signer /* let uri = NostrConnectURI::client( @@ -24,7 +24,7 @@ async fn main() -> Result<()> { "Test app", ); println!("\n{uri}\n"); - let signer = Nip46Signer::new(uri, app_keys, Duration::from_secs(60), None).await?; */ + let signer = NostrConnect::new(uri, app_keys, Duration::from_secs(60), None).await?; */ // Get bunker URI for future connections let bunker_uri: NostrConnectURI = signer.bunker_uri().await?; From ff49164f47a6d2a5c64d85df74ef973328f2ecf3 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Wed, 30 Oct 2024 15:00:45 +0100 Subject: [PATCH 6/9] ffi(nostr): expose `NostrSigner` Signed-off-by: Yuki Kishimoto --- Cargo.lock | 5 +- bindings/nostr-ffi/Cargo.toml | 1 + .../bindings-python/examples/event_builder.py | 53 ++++--- bindings/nostr-ffi/src/error.rs | 6 + bindings/nostr-ffi/src/event/builder.rs | 30 ++-- bindings/nostr-ffi/src/key/mod.rs | 54 ++++++- bindings/nostr-ffi/src/lib.rs | 1 + bindings/nostr-ffi/src/nips/nip59.rs | 27 ++-- bindings/nostr-ffi/src/signer.rs | 145 ++++++++++++++++++ 9 files changed, 277 insertions(+), 45 deletions(-) create mode 100644 bindings/nostr-ffi/src/signer.rs diff --git a/Cargo.lock b/Cargo.lock index ffbe5c838..226fe4968 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2832,6 +2832,7 @@ dependencies = [ name = "nostr-ffi" version = "0.1.0" dependencies = [ + "async-trait", "nostr", "uniffi", ] @@ -5905,9 +5906,9 @@ dependencies = [ [[package]] name = "uniffi_checksum_derive" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a22dbe67c1c957ac6e7611bdf605a6218aa86b0eebeb8be58b70ae85ad7d73dc" +checksum = "d2c801f0f05b06df456a2da4c41b9c2c4fdccc6b9916643c6c67275c4c9e4d07" dependencies = [ "quote", "syn 2.0.77", diff --git a/bindings/nostr-ffi/Cargo.toml b/bindings/nostr-ffi/Cargo.toml index 72b119cd3..029d19c18 100644 --- a/bindings/nostr-ffi/Cargo.toml +++ b/bindings/nostr-ffi/Cargo.toml @@ -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"] } diff --git a/bindings/nostr-ffi/bindings-python/examples/event_builder.py b/bindings/nostr-ffi/bindings-python/examples/event_builder.py index b2398d93e..58232a09f 100644 --- a/bindings/nostr-ffi/bindings-python/examples/event_builder.py +++ b/bindings/nostr-ffi/bindings-python/examples/event_builder.py @@ -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()}") \ No newline at end of file + # 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()) diff --git a/bindings/nostr-ffi/src/error.rs b/bindings/nostr-ffi/src/error.rs index abf8a3793..046174ed6 100644 --- a/bindings/nostr-ffi/src/error.rs +++ b/bindings/nostr-ffi/src/error.rs @@ -38,6 +38,12 @@ impl From for NostrError { } } +impl From for NostrError { + fn from(e: nostr::signer::SignerError) -> NostrError { + Self::Generic(e.to_string()) + } +} + impl From for NostrError { fn from(e: nostr::key::Error) -> NostrError { Self::Generic(e.to_string()) diff --git a/bindings/nostr-ffi/src/event/builder.rs b/bindings/nostr-ffi/src/event/builder.rs index 8f2a46cf3..ae3d59c73 100644 --- a/bindings/nostr-ffi/src/event/builder.rs +++ b/bindings/nostr-ffi/src/event/builder.rs @@ -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, @@ -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]) -> Self { let tags = tags.iter().map(|t| t.as_ref().deref().clone()); @@ -82,13 +86,19 @@ impl EventBuilder { builder } - pub fn to_event(&self, keys: &Keys) -> Result { - let event = self.inner.clone().to_event(keys.deref())?; + pub async fn sign(&self, signer: Arc) -> Result { + 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 { + 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 @@ -549,17 +559,19 @@ impl EventBuilder { /// #[inline] #[uniffi::constructor] - pub fn seal( - sender_keys: &Keys, + pub async fn seal( + signer: Arc, receiver_public_key: &PublicKey, rumor: &UnsignedEvent, ) -> Result { + let signer = IntermediateNostrSigner::new(signer); Ok(Self { inner: nostr::EventBuilder::seal( - sender_keys.deref(), + &signer, receiver_public_key.deref(), rumor.deref().clone(), - )?, + ) + .await?, }) } diff --git a/bindings/nostr-ffi/src/key/mod.rs b/bindings/nostr-ffi/src/key/mod.rs index 2b5f45990..e08d9c7e2 100644 --- a/bindings/nostr-ffi/src/key/mod.rs +++ b/bindings/nostr-ffi/src/key/mod.rs @@ -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; @@ -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)] @@ -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>> { + Ok(Some(Arc::new(self.inner.get_public_key().await?.into()))) + } + + async fn sign_event(&self, unsigned: Arc) -> Result>> { + Ok(Some(Arc::new( + self.inner + .sign_event(unsigned.as_ref().deref().clone()) + .await? + .into(), + ))) + } + + async fn nip04_encrypt(&self, public_key: Arc, content: String) -> Result { + Ok(self + .inner + .nip04_encrypt(public_key.as_ref().deref(), &content) + .await?) + } + + async fn nip04_decrypt( + &self, + public_key: Arc, + encrypted_content: String, + ) -> Result { + Ok(self + .inner + .nip04_decrypt(public_key.as_ref().deref(), &encrypted_content) + .await?) + } + + async fn nip44_encrypt(&self, public_key: Arc, content: String) -> Result { + Ok(self + .inner + .nip44_encrypt(public_key.as_ref().deref(), &content) + .await?) + } + + async fn nip44_decrypt(&self, public_key: Arc, payload: String) -> Result { + Ok(self + .inner + .nip44_decrypt(public_key.as_ref().deref(), &payload) + .await?) + } +} diff --git a/bindings/nostr-ffi/src/lib.rs b/bindings/nostr-ffi/src/lib.rs index e9316757a..0db018c58 100644 --- a/bindings/nostr-ffi/src/lib.rs +++ b/bindings/nostr-ffi/src/lib.rs @@ -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; diff --git a/bindings/nostr-ffi/src/nips/nip59.rs b/bindings/nostr-ffi/src/nips/nip59.rs index ffa6bb6af..fda303b01 100644 --- a/bindings/nostr-ffi/src/nips/nip59.rs +++ b/bindings/nostr-ffi/src/nips/nip59.rs @@ -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 /// /// -#[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, receiver_pubkey: &PublicKey, rumor: &UnsignedEvent, expiration: Option>, ) -> Result { + 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()) } @@ -55,20 +58,24 @@ pub struct UnwrappedGift { } impl From 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 { + pub async fn from_gift_wrap(signer: Arc, gift_wrap: &Event) -> Result { + 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?, }) } diff --git a/bindings/nostr-ffi/src/signer.rs b/bindings/nostr-ffi/src/signer.rs new file mode 100644 index 000000000..8913dd795 --- /dev/null +++ b/bindings/nostr-ffi/src/signer.rs @@ -0,0 +1,145 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2024 Rust Nostr Developers +// Distributed under the MIT software license + +use std::fmt; +use std::sync::Arc; + +use crate::error::Result; +use crate::event::{Event, UnsignedEvent}; +use crate::key::PublicKey; + +// NOTE: for some weird reason the `Arc` as output must be wrapped inside a `Vec` or an `Option` +// otherwise compilation will fail. +#[uniffi::export(with_foreign)] +#[async_trait::async_trait] +pub trait NostrSigner: Send + Sync { + /// Get signer public key + async fn get_public_key(&self) -> Result>>; + + /// Sign an unsigned event + async fn sign_event(&self, unsigned: Arc) -> Result>>; + + /// NIP04 encrypt (deprecate and unsecure) + async fn nip04_encrypt(&self, public_key: Arc, content: String) -> Result; + + /// NIP04 decrypt + async fn nip04_decrypt( + &self, + public_key: Arc, + encrypted_content: String, + ) -> Result; + + /// NIP44 encrypt + async fn nip44_encrypt(&self, public_key: Arc, content: String) -> Result; + + /// NIP44 decrypt + async fn nip44_decrypt(&self, public_key: Arc, payload: String) -> Result; +} + +pub struct IntermediateNostrSigner { + pub(super) inner: Arc, +} + +impl fmt::Debug for IntermediateNostrSigner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("IntermediateNostrSigner").finish() + } +} + +impl IntermediateNostrSigner { + pub fn new(inner: Arc) -> Self { + Self { inner } + } +} + +mod inner { + use std::ops::Deref; + use std::sync::Arc; + + use async_trait::async_trait; + use nostr::prelude::*; + + use super::IntermediateNostrSigner; + use crate::NostrError; + + #[async_trait] + impl NostrSigner for IntermediateNostrSigner { + async fn get_public_key(&self) -> Result { + let public_key = self + .inner + .get_public_key() + .await + .map_err(SignerError::backend)? + .ok_or_else(|| { + SignerError::backend(NostrError::Generic(String::from( + "Received None instead of public key", + ))) + })?; + Ok(**public_key) + } + + async fn sign_event(&self, unsigned: UnsignedEvent) -> Result { + let unsigned = Arc::new(unsigned.into()); + let event = self + .inner + .sign_event(unsigned) + .await + .map_err(SignerError::backend)? + .ok_or_else(|| { + SignerError::backend(NostrError::Generic(String::from( + "Received None instead of event", + ))) + })?; + Ok(event.as_ref().deref().clone()) + } + + async fn nip04_encrypt( + &self, + public_key: &PublicKey, + content: &str, + ) -> Result { + let public_key = Arc::new((*public_key).into()); + self.inner + .nip04_encrypt(public_key, content.to_string()) + .await + .map_err(SignerError::backend) + } + + async fn nip04_decrypt( + &self, + public_key: &PublicKey, + encrypted_content: &str, + ) -> Result { + let public_key = Arc::new((*public_key).into()); + self.inner + .nip04_decrypt(public_key, encrypted_content.to_string()) + .await + .map_err(SignerError::backend) + } + + async fn nip44_encrypt( + &self, + public_key: &PublicKey, + content: &str, + ) -> Result { + let public_key = Arc::new((*public_key).into()); + self.inner + .nip44_encrypt(public_key, content.to_string()) + .await + .map_err(SignerError::backend) + } + + async fn nip44_decrypt( + &self, + public_key: &PublicKey, + payload: &str, + ) -> Result { + let public_key = Arc::new((*public_key).into()); + self.inner + .nip44_decrypt(public_key, payload.to_string()) + .await + .map_err(SignerError::backend) + } + } +} From 1a4169975b4b6f4b68bf2e251def3591e83d9478 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Wed, 30 Oct 2024 15:42:32 +0100 Subject: [PATCH 7/9] ffi(sdk): adj. for new `NostrSigner` trait Signed-off-by: Yuki Kishimoto --- bindings/nostr-ffi/src/event/builder.rs | 6 +- bindings/nostr-ffi/src/nips/nip59.rs | 6 +- bindings/nostr-ffi/src/signer.rs | 71 ++++++++++- bindings/nostr-sdk-ffi/src/client/builder.rs | 7 +- bindings/nostr-sdk-ffi/src/client/mod.rs | 15 +-- .../nostr-sdk-ffi/src/client/signer/mod.rs | 113 ------------------ .../{client/signer/nip46.rs => connect.rs} | 68 ++++++++++- bindings/nostr-sdk-ffi/src/error.rs | 6 + bindings/nostr-sdk-ffi/src/lib.rs | 1 + 9 files changed, 157 insertions(+), 136 deletions(-) delete mode 100644 bindings/nostr-sdk-ffi/src/client/signer/mod.rs rename bindings/nostr-sdk-ffi/src/{client/signer/nip46.rs => connect.rs} (70%) diff --git a/bindings/nostr-ffi/src/event/builder.rs b/bindings/nostr-ffi/src/event/builder.rs index ae3d59c73..249dc8e3c 100644 --- a/bindings/nostr-ffi/src/event/builder.rs +++ b/bindings/nostr-ffi/src/event/builder.rs @@ -22,7 +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::signer::{NostrSigner, NostrSignerFFI2Rust}; use crate::types::{Contact, Metadata}; use crate::{ FileMetadata, Image, ImageDimensions, NostrConnectMessage, PublicKey, RelayMetadata, Tag, @@ -87,7 +87,7 @@ impl EventBuilder { } pub async fn sign(&self, signer: Arc) -> Result { - let signer = IntermediateNostrSigner::new(signer); + let signer = NostrSignerFFI2Rust::new(signer); let event = self.inner.clone().sign(&signer).await?; Ok(event.into()) } @@ -564,7 +564,7 @@ impl EventBuilder { receiver_public_key: &PublicKey, rumor: &UnsignedEvent, ) -> Result { - let signer = IntermediateNostrSigner::new(signer); + let signer = NostrSignerFFI2Rust::new(signer); Ok(Self { inner: nostr::EventBuilder::seal( &signer, diff --git a/bindings/nostr-ffi/src/nips/nip59.rs b/bindings/nostr-ffi/src/nips/nip59.rs index fda303b01..ecb69866a 100644 --- a/bindings/nostr-ffi/src/nips/nip59.rs +++ b/bindings/nostr-ffi/src/nips/nip59.rs @@ -9,7 +9,7 @@ use nostr::EventBuilder; use uniffi::Object; use crate::error::Result; -use crate::signer::{IntermediateNostrSigner, NostrSigner}; +use crate::signer::{NostrSigner, NostrSignerFFI2Rust}; use crate::{Event, PublicKey, Timestamp, UnsignedEvent}; /// Build Gift Wrap @@ -22,7 +22,7 @@ pub async fn gift_wrap( rumor: &UnsignedEvent, expiration: Option>, ) -> Result { - let signer = IntermediateNostrSigner::new(signer); + let signer = NostrSignerFFI2Rust::new(signer); Ok(EventBuilder::gift_wrap( &signer, receiver_pubkey.deref(), @@ -73,7 +73,7 @@ impl UnwrappedGift { /// Internally verify the `seal` event #[uniffi::constructor] pub async fn from_gift_wrap(signer: Arc, gift_wrap: &Event) -> Result { - let signer = IntermediateNostrSigner::new(signer); + let signer = NostrSignerFFI2Rust::new(signer); Ok(Self { inner: nip59::UnwrappedGift::from_gift_wrap(&signer, gift_wrap.deref()).await?, }) diff --git a/bindings/nostr-ffi/src/signer.rs b/bindings/nostr-ffi/src/signer.rs index 8913dd795..2e0fae188 100644 --- a/bindings/nostr-ffi/src/signer.rs +++ b/bindings/nostr-ffi/src/signer.rs @@ -3,6 +3,7 @@ // Distributed under the MIT software license use std::fmt; +use std::ops::Deref; use std::sync::Arc; use crate::error::Result; @@ -37,22 +38,80 @@ pub trait NostrSigner: Send + Sync { async fn nip44_decrypt(&self, public_key: Arc, payload: String) -> Result; } -pub struct IntermediateNostrSigner { +pub struct NostrSignerFFI2Rust { pub(super) inner: Arc, } -impl fmt::Debug for IntermediateNostrSigner { +impl fmt::Debug for NostrSignerFFI2Rust { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("IntermediateNostrSigner").finish() + f.debug_struct("NostrSignerFFI2Rust").finish() } } -impl IntermediateNostrSigner { +impl NostrSignerFFI2Rust { pub fn new(inner: Arc) -> Self { Self { inner } } } +pub struct NostrSignerRust2FFI { + pub(super) inner: Arc, +} + +impl NostrSignerRust2FFI { + pub fn new(inner: Arc) -> Self { + Self { inner } + } +} + +#[async_trait::async_trait] +impl NostrSigner for NostrSignerRust2FFI { + async fn get_public_key(&self) -> Result>> { + Ok(Some(Arc::new(self.inner.get_public_key().await?.into()))) + } + + async fn sign_event(&self, unsigned: Arc) -> Result>> { + Ok(Some(Arc::new( + self.inner + .sign_event(unsigned.as_ref().deref().clone()) + .await? + .into(), + ))) + } + + async fn nip04_encrypt(&self, public_key: Arc, content: String) -> Result { + Ok(self + .inner + .nip04_encrypt(public_key.as_ref().deref(), &content) + .await?) + } + + async fn nip04_decrypt( + &self, + public_key: Arc, + encrypted_content: String, + ) -> Result { + Ok(self + .inner + .nip04_decrypt(public_key.as_ref().deref(), &encrypted_content) + .await?) + } + + async fn nip44_encrypt(&self, public_key: Arc, content: String) -> Result { + Ok(self + .inner + .nip44_encrypt(public_key.as_ref().deref(), &content) + .await?) + } + + async fn nip44_decrypt(&self, public_key: Arc, payload: String) -> Result { + Ok(self + .inner + .nip44_decrypt(public_key.as_ref().deref(), &payload) + .await?) + } +} + mod inner { use std::ops::Deref; use std::sync::Arc; @@ -60,11 +119,11 @@ mod inner { use async_trait::async_trait; use nostr::prelude::*; - use super::IntermediateNostrSigner; + use super::NostrSignerFFI2Rust; use crate::NostrError; #[async_trait] - impl NostrSigner for IntermediateNostrSigner { + impl NostrSigner for NostrSignerFFI2Rust { async fn get_public_key(&self) -> Result { let public_key = self .inner diff --git a/bindings/nostr-sdk-ffi/src/client/builder.rs b/bindings/nostr-sdk-ffi/src/client/builder.rs index 5af7d4e64..00484044c 100644 --- a/bindings/nostr-sdk-ffi/src/client/builder.rs +++ b/bindings/nostr-sdk-ffi/src/client/builder.rs @@ -6,12 +6,13 @@ use std::ops::Deref; use std::sync::Arc; use nostr_ffi::helper::unwrap_or_clone_arc; +use nostr_ffi::signer::{NostrSigner, NostrSignerFFI2Rust}; use nostr_sdk::database::DynNostrDatabase; use nostr_sdk::zapper::DynNostrZapper; use uniffi::Object; use super::zapper::NostrZapper; -use super::{Client, ClientSdk, NostrSigner, Options}; +use super::{Client, ClientSdk, Options}; use crate::database::NostrDatabase; #[derive(Clone, Default, Object)] @@ -34,8 +35,8 @@ impl ClientBuilder { Self::default() } - pub fn signer(self: Arc, signer: Arc) -> Self { - let signer: nostr_sdk::NostrSigner = signer.as_ref().deref().clone(); + pub fn signer(self: Arc, signer: Arc) -> Self { + let signer = NostrSignerFFI2Rust::new(signer); let mut builder = unwrap_or_clone_arc(self); builder.inner = builder.inner.signer(signer); builder diff --git a/bindings/nostr-sdk-ffi/src/client/mod.rs b/bindings/nostr-sdk-ffi/src/client/mod.rs index b557d6de1..8c1eceabd 100644 --- a/bindings/nostr-sdk-ffi/src/client/mod.rs +++ b/bindings/nostr-sdk-ffi/src/client/mod.rs @@ -8,6 +8,7 @@ use std::sync::Arc; use std::time::Duration; use nostr_ffi::nips::nip59::UnwrappedGift; +use nostr_ffi::signer::{NostrSigner, NostrSignerFFI2Rust, NostrSignerRust2FFI}; use nostr_ffi::{ ClientMessage, Event, EventBuilder, EventId, FileMetadata, Filter, Metadata, PublicKey, Timestamp, @@ -19,12 +20,10 @@ use uniffi::Object; mod builder; mod options; -pub mod signer; pub mod zapper; pub use self::builder::ClientBuilder; pub use self::options::{EventSource, Options}; -pub use self::signer::NostrSigner; use self::zapper::{ZapDetails, ZapEntity}; use crate::database::events::Events; use crate::error::Result; @@ -48,16 +47,16 @@ impl From for Client { #[uniffi::export(async_runtime = "tokio")] impl Client { #[uniffi::constructor(default(signer = None))] - pub fn new(signer: Option>) -> Self { + pub fn new(signer: Option>) -> Self { Self::with_opts(signer, Arc::new(Options::new())) } #[uniffi::constructor] - pub fn with_opts(signer: Option>, opts: Arc) -> Self { + pub fn with_opts(signer: Option>, opts: Arc) -> Self { Self { inner: match signer { Some(signer) => ClientSdk::with_opts( - signer.as_ref().deref().clone(), + NostrSignerFFI2Rust::new(signer), opts.as_ref().deref().clone(), ), None => nostr_sdk::Client::builder() @@ -89,8 +88,10 @@ impl Client { self.inner.automatic_authentication(enable); } - pub async fn signer(&self) -> Result { - Ok(self.inner.signer().await?.into()) + pub async fn signer(&self) -> Result> { + let signer = self.inner.signer().await?; + let intermediate = NostrSignerRust2FFI::new(signer); + Ok(Arc::new(intermediate) as Arc) } /// Get relay pool diff --git a/bindings/nostr-sdk-ffi/src/client/signer/mod.rs b/bindings/nostr-sdk-ffi/src/client/signer/mod.rs deleted file mode 100644 index 7319d8cab..000000000 --- a/bindings/nostr-sdk-ffi/src/client/signer/mod.rs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) 2022-2023 Yuki Kishimoto -// Copyright (c) 2023-2024 Rust Nostr Developers -// Distributed under the MIT software license - -use std::ops::Deref; - -use nostr_ffi::nips::nip59::UnwrappedGift; -use nostr_ffi::{Event, EventBuilder, Keys, PublicKey, UnsignedEvent}; -use nostr_sdk::signer; -use uniffi::Object; - -pub mod nip46; - -use self::nip46::NostrConnect; -use crate::error::Result; - -#[derive(Object)] -pub struct NostrSigner { - inner: signer::NostrSigner, -} - -impl Deref for NostrSigner { - type Target = signer::NostrSigner; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl From for NostrSigner { - fn from(inner: signer::NostrSigner) -> Self { - Self { inner } - } -} - -#[uniffi::export(async_runtime = "tokio")] -impl NostrSigner { - #[uniffi::constructor] - pub fn keys(keys: &Keys) -> Self { - Self { - inner: signer::NostrSigner::Keys(keys.deref().clone()), - } - } - - #[uniffi::constructor] - pub fn nip46(nip46: &NostrConnect) -> Self { - Self { - inner: signer::NostrSigner::nip46(nip46.deref().clone()), - } - } - - /// Get signer public key - pub async fn public_key(&self) -> Result { - Ok(self.inner.public_key().await?.into()) - } - - pub async fn sign_event_builder(&self, builder: &EventBuilder) -> Result { - Ok(self - .inner - .sign_event_builder(builder.deref().clone()) - .await? - .into()) - } - - pub async fn sign_event(&self, unsigned_event: &UnsignedEvent) -> Result { - Ok(self - .inner - .sign_event(unsigned_event.deref().clone()) - .await? - .into()) - } - - pub async fn nip04_encrypt(&self, public_key: &PublicKey, content: String) -> Result { - Ok(self - .inner - .nip04_encrypt(public_key.deref(), content) - .await?) - } - - pub async fn nip04_decrypt( - &self, - public_key: &PublicKey, - encrypted_content: String, - ) -> Result { - Ok(self - .inner - .nip04_decrypt(public_key.deref(), encrypted_content) - .await?) - } - - pub async fn nip44_encrypt(&self, public_key: &PublicKey, content: String) -> Result { - Ok(self - .inner - .nip44_encrypt(public_key.deref(), content) - .await?) - } - - pub async fn nip44_decrypt(&self, public_key: &PublicKey, content: String) -> Result { - Ok(self - .inner - .nip44_decrypt(public_key.deref(), content) - .await?) - } - - /// Unwrap Gift Wrap event - /// - /// Internally verify the `seal` event - /// - /// - pub async fn unwrap_gift_wrap(&self, gift_wrap: &Event) -> Result { - Ok(self.inner.unwrap_gift_wrap(gift_wrap.deref()).await?.into()) - } -} diff --git a/bindings/nostr-sdk-ffi/src/client/signer/nip46.rs b/bindings/nostr-sdk-ffi/src/connect.rs similarity index 70% rename from bindings/nostr-sdk-ffi/src/client/signer/nip46.rs rename to bindings/nostr-sdk-ffi/src/connect.rs index 61c918701..33cb34a0f 100644 --- a/bindings/nostr-sdk-ffi/src/client/signer/nip46.rs +++ b/bindings/nostr-sdk-ffi/src/connect.rs @@ -8,8 +8,10 @@ use std::time::Duration; use nostr_connect::{client, signer}; use nostr_ffi::nips::nip46::{Nip46Request, NostrConnectURI}; -use nostr_ffi::{Keys, PublicKey, SecretKey}; +use nostr_ffi::signer::NostrSigner; +use nostr_ffi::{Event, Keys, NostrError, PublicKey, SecretKey, UnsignedEvent}; use nostr_sdk::nostr::nips::nip46::Request; +use nostr_sdk::signer::NostrSigner as _; use uniffi::Object; use crate::error::Result; @@ -74,6 +76,70 @@ impl NostrConnect { } } +#[uniffi::export] +#[async_trait::async_trait] +impl NostrSigner for NostrConnect { + async fn get_public_key(&self) -> Result>, NostrError> { + Ok(Some(Arc::new(self.inner.get_public_key().await?.into()))) + } + + async fn sign_event( + &self, + unsigned: Arc, + ) -> Result>, NostrError> { + Ok(Some(Arc::new( + self.inner + .sign_event(unsigned.as_ref().deref().clone()) + .await? + .into(), + ))) + } + + async fn nip04_encrypt( + &self, + public_key: Arc, + content: String, + ) -> Result { + Ok(self + .inner + .nip04_encrypt(public_key.as_ref().deref(), &content) + .await?) + } + + async fn nip04_decrypt( + &self, + public_key: Arc, + encrypted_content: String, + ) -> Result { + Ok(self + .inner + .nip04_decrypt(public_key.as_ref().deref(), &encrypted_content) + .await?) + } + + async fn nip44_encrypt( + &self, + public_key: Arc, + content: String, + ) -> Result { + Ok(self + .inner + .nip44_encrypt(public_key.as_ref().deref(), &content) + .await?) + } + + async fn nip44_decrypt( + &self, + public_key: Arc, + payload: String, + ) -> Result { + Ok(self + .inner + .nip44_decrypt(public_key.as_ref().deref(), &payload) + .await?) + } +} + /// Nostr Connect Signer /// /// Signer that listen for requests from client, handle them and send the response. diff --git a/bindings/nostr-sdk-ffi/src/error.rs b/bindings/nostr-sdk-ffi/src/error.rs index 095ddb406..a6db995fe 100644 --- a/bindings/nostr-sdk-ffi/src/error.rs +++ b/bindings/nostr-sdk-ffi/src/error.rs @@ -32,6 +32,12 @@ impl From for NostrSdkError { } } +impl From for NostrSdkError { + fn from(e: nostr_sdk::nips::nip59::Error) -> NostrSdkError { + Self::Generic(e.to_string()) + } +} + impl From for NostrSdkError { fn from(e: SetGlobalDefaultError) -> NostrSdkError { Self::Generic(e.to_string()) diff --git a/bindings/nostr-sdk-ffi/src/lib.rs b/bindings/nostr-sdk-ffi/src/lib.rs index 665cb30da..6c63749b1 100644 --- a/bindings/nostr-sdk-ffi/src/lib.rs +++ b/bindings/nostr-sdk-ffi/src/lib.rs @@ -7,6 +7,7 @@ nostr_ffi::uniffi_reexport_scaffolding!(); pub mod client; +pub mod connect; pub mod database; pub mod error; pub mod logger; From aa5a3b5ec7eeccb9bc5454916166d950ce87d085 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Wed, 30 Oct 2024 15:54:40 +0100 Subject: [PATCH 8/9] js: adj. for new `NostrSigner` trait Signed-off-by: Yuki Kishimoto --- bindings/nostr-sdk-js/src/client/mod.rs | 3 +- .../{client/signer/nip46.rs => connect.rs} | 0 bindings/nostr-sdk-js/src/lib.rs | 2 + .../src/protocol/event/builder.rs | 40 +++++++----- .../nostr-sdk-js/src/protocol/nips/nip59.rs | 11 +++- .../src/{client/signer/mod.rs => signer.rs} | 61 ++++++------------- 6 files changed, 54 insertions(+), 63 deletions(-) rename bindings/nostr-sdk-js/src/{client/signer/nip46.rs => connect.rs} (100%) rename bindings/nostr-sdk-js/src/{client/signer/mod.rs => signer.rs} (58%) diff --git a/bindings/nostr-sdk-js/src/client/mod.rs b/bindings/nostr-sdk-js/src/client/mod.rs index 99eae9ca4..b96198510 100644 --- a/bindings/nostr-sdk-js/src/client/mod.rs +++ b/bindings/nostr-sdk-js/src/client/mod.rs @@ -12,12 +12,10 @@ use wasm_bindgen::prelude::*; pub mod builder; pub mod options; -pub mod signer; pub mod zapper; pub use self::builder::JsClientBuilder; use self::options::JsOptions; -pub use self::signer::JsNostrSigner; use self::zapper::{JsZapDetails, JsZapEntity}; use crate::abortable::JsAbortHandle; use crate::database::{JsEvents, JsNostrDatabase}; @@ -33,6 +31,7 @@ use crate::protocol::types::{JsContact, JsFilter, JsMetadata, JsTimestamp}; use crate::relay::filtering::JsRelayFiltering; use crate::relay::options::{JsSubscribeAutoCloseOptions, JsSyncOptions}; use crate::relay::{JsRelay, JsRelayArray}; +use crate::signer::JsNostrSigner; #[wasm_bindgen(js_name = Client)] pub struct JsClient { diff --git a/bindings/nostr-sdk-js/src/client/signer/nip46.rs b/bindings/nostr-sdk-js/src/connect.rs similarity index 100% rename from bindings/nostr-sdk-js/src/client/signer/nip46.rs rename to bindings/nostr-sdk-js/src/connect.rs diff --git a/bindings/nostr-sdk-js/src/lib.rs b/bindings/nostr-sdk-js/src/lib.rs index 4dffe8865..c3c731c59 100644 --- a/bindings/nostr-sdk-js/src/lib.rs +++ b/bindings/nostr-sdk-js/src/lib.rs @@ -14,6 +14,7 @@ use wasm_bindgen::prelude::*; pub mod abortable; pub mod client; +pub mod connect; pub mod database; pub mod duration; pub mod error; @@ -23,6 +24,7 @@ pub mod pool; pub mod profile; pub mod protocol; pub mod relay; +pub mod signer; #[wasm_bindgen] extern "C" { diff --git a/bindings/nostr-sdk-js/src/protocol/event/builder.rs b/bindings/nostr-sdk-js/src/protocol/event/builder.rs index 09cd7f5ad..73de7b254 100644 --- a/bindings/nostr-sdk-js/src/protocol/event/builder.rs +++ b/bindings/nostr-sdk-js/src/protocol/event/builder.rs @@ -25,6 +25,7 @@ use crate::protocol::nips::nip94::JsFileMetadata; use crate::protocol::nips::nip98::JsHttpData; use crate::protocol::types::image::{JsImageDimensions, JsThumbnails}; use crate::protocol::types::{JsContact, JsMetadata, JsTimestamp}; +use crate::signer::JsNostrSigner; #[wasm_bindgen(js_name = EventBuilder)] pub struct JsEventBuilder { @@ -76,21 +77,30 @@ impl JsEventBuilder { self.inner.pow(difficulty).into() } - /// Build event + /// Build, sign and return event /// /// **This method consume the builder, so it will no longer be usable!** - #[wasm_bindgen(js_name = toEvent)] - pub fn to_event(self, keys: &JsKeys) -> Result { - let event = self.inner.to_event(keys.deref()).map_err(into_err)?; + #[wasm_bindgen(js_name = sign)] + pub async fn sign(self, signer: &JsNostrSigner) -> Result { + let event = self.inner.sign(signer.deref()).await.map_err(into_err)?; + Ok(event.into()) + } + + /// Build, sign and return event using keys signer + /// + /// **This method consume the builder, so it will no longer be usable!** + #[wasm_bindgen(js_name = signWithKeys)] + pub fn sign_with_keys(self, keys: &JsKeys) -> Result { + let event = self.inner.sign_with_keys(keys.deref()).map_err(into_err)?; Ok(event.into()) } /// Build unsigned event /// /// **This method consume the builder, so it will no longer be usable!** - #[wasm_bindgen(js_name = toUnsignedEvent)] - pub fn to_unsigned_event(self, public_key: &JsPublicKey) -> JsUnsignedEvent { - self.inner.to_unsigned_event(**public_key).into() + #[wasm_bindgen(js_name = build)] + pub fn build(self, public_key: &JsPublicKey) -> JsUnsignedEvent { + self.inner.build(**public_key).into() } /// Profile metadata @@ -520,17 +530,18 @@ impl JsEventBuilder { /// #[inline] #[wasm_bindgen] - pub fn seal( - sender_keys: &JsKeys, + pub async fn seal( + signer: &JsNostrSigner, receiver_public_key: &JsPublicKey, rumor: &JsUnsignedEvent, ) -> Result { Ok(Self { - inner: nostr::EventBuilder::seal( - sender_keys.deref(), + inner: EventBuilder::seal( + signer.deref(), receiver_public_key.deref(), rumor.deref().clone(), ) + .await .map_err(into_err)?, }) } @@ -557,18 +568,19 @@ impl JsEventBuilder { /// /// #[wasm_bindgen(js_name = giftWrap)] - pub fn gift_wrap( - sender_keys: &JsKeys, + pub async fn gift_wrap( + signer: &JsNostrSigner, receiver: &JsPublicKey, rumor: &JsUnsignedEvent, expiration: Option, ) -> Result { Ok(EventBuilder::gift_wrap( - sender_keys.deref(), + signer.deref(), receiver.deref(), rumor.deref().clone(), expiration.map(|t| *t), ) + .await .map_err(into_err)? .into()) } diff --git a/bindings/nostr-sdk-js/src/protocol/nips/nip59.rs b/bindings/nostr-sdk-js/src/protocol/nips/nip59.rs index 72b574ee4..99bdddc0f 100644 --- a/bindings/nostr-sdk-js/src/protocol/nips/nip59.rs +++ b/bindings/nostr-sdk-js/src/protocol/nips/nip59.rs @@ -9,7 +9,8 @@ use wasm_bindgen::prelude::*; use crate::error::{into_err, Result}; use crate::protocol::event::{JsEvent, JsUnsignedEvent}; -use crate::protocol::key::{JsKeys, JsPublicKey}; +use crate::protocol::key::JsPublicKey; +use crate::signer::JsNostrSigner; /// Unwrapped Gift Wrap /// @@ -31,9 +32,13 @@ impl JsUnwrappedGift { /// /// Internally verify the `seal` event #[wasm_bindgen(js_name = fromGiftWrap)] - pub fn from_gift_wrap(receiver_keys: &JsKeys, gift_wrap: &JsEvent) -> Result { + pub async fn from_gift_wrap( + signer: &JsNostrSigner, + gift_wrap: &JsEvent, + ) -> Result { Ok(Self { - inner: UnwrappedGift::from_gift_wrap(receiver_keys.deref(), gift_wrap.deref()) + inner: UnwrappedGift::from_gift_wrap(signer.deref(), gift_wrap.deref()) + .await .map_err(into_err)?, }) } diff --git a/bindings/nostr-sdk-js/src/client/signer/mod.rs b/bindings/nostr-sdk-js/src/signer.rs similarity index 58% rename from bindings/nostr-sdk-js/src/client/signer/mod.rs rename to bindings/nostr-sdk-js/src/signer.rs index 179f3df97..c747f4919 100644 --- a/bindings/nostr-sdk-js/src/client/signer/mod.rs +++ b/bindings/nostr-sdk-js/src/signer.rs @@ -3,34 +3,32 @@ // Distributed under the MIT software license use std::ops::Deref; +use std::sync::Arc; -use nostr_sdk::NostrSigner; +use nostr_sdk::prelude::*; use wasm_bindgen::prelude::*; -pub mod nip46; - -use self::nip46::JsNostrConnect; +use crate::connect::JsNostrConnect; use crate::error::{into_err, Result}; -use crate::protocol::event::{JsEvent, JsEventBuilder, JsUnsignedEvent}; +use crate::protocol::event::{JsEvent, JsUnsignedEvent}; use crate::protocol::key::{JsKeys, JsPublicKey}; use crate::protocol::nips::nip07::JsNip07Signer; -use crate::protocol::nips::nip59::JsUnwrappedGift; #[wasm_bindgen(js_name = NostrSigner)] pub struct JsNostrSigner { - inner: nostr_sdk::NostrSigner, + inner: Arc, } impl Deref for JsNostrSigner { - type Target = NostrSigner; + type Target = Arc; fn deref(&self) -> &Self::Target { &self.inner } } -impl From for JsNostrSigner { - fn from(inner: NostrSigner) -> Self { +impl From> for JsNostrSigner { + fn from(inner: Arc) -> Self { Self { inner } } } @@ -40,38 +38,28 @@ impl JsNostrSigner { /// Private keys pub fn keys(keys: &JsKeys) -> Self { Self { - inner: NostrSigner::Keys(keys.deref().clone()), + inner: keys.deref().clone().into_nostr_signer(), } } /// NIP07 pub fn nip07(signer: &JsNip07Signer) -> Self { Self { - inner: NostrSigner::NIP07(signer.deref().clone()), + inner: signer.deref().clone().into_nostr_signer(), } } /// NIP46 pub fn nip46(signer: &JsNostrConnect) -> Self { Self { - inner: NostrSigner::nip46(signer.deref().clone()), + inner: signer.deref().clone().into_nostr_signer(), } } /// Get signer public key #[wasm_bindgen(js_name = publicKey)] - pub async fn public_key(&self) -> Result { - Ok(self.inner.public_key().await.map_err(into_err)?.into()) - } - - #[wasm_bindgen(js_name = signEventBuilder)] - pub async fn sign_event_builder(&self, builder: &JsEventBuilder) -> Result { - Ok(self - .inner - .sign_event_builder(builder.deref().clone()) - .await - .map_err(into_err)? - .into()) + pub async fn get_public_key(&self) -> Result { + Ok(self.inner.get_public_key().await.map_err(into_err)?.into()) } #[wasm_bindgen(js_name = signEvent)] @@ -85,7 +73,7 @@ impl JsNostrSigner { } #[wasm_bindgen(js_name = nip04Encrypt)] - pub async fn nip04_encrypt(&self, public_key: &JsPublicKey, content: String) -> Result { + pub async fn nip04_encrypt(&self, public_key: &JsPublicKey, content: &str) -> Result { self.inner .nip04_encrypt(public_key.deref(), content) .await @@ -96,7 +84,7 @@ impl JsNostrSigner { pub async fn nip04_decrypt( &self, public_key: &JsPublicKey, - encrypted_content: String, + encrypted_content: &str, ) -> Result { self.inner .nip04_decrypt(public_key.deref(), encrypted_content) @@ -105,7 +93,7 @@ impl JsNostrSigner { } #[wasm_bindgen(js_name = nip44Encrypt)] - pub async fn nip44_encrypt(&self, public_key: &JsPublicKey, content: String) -> Result { + pub async fn nip44_encrypt(&self, public_key: &JsPublicKey, content: &str) -> Result { self.inner .nip44_encrypt(public_key.deref(), content) .await @@ -113,25 +101,10 @@ impl JsNostrSigner { } #[wasm_bindgen(js_name = nip44Decrypt)] - pub async fn nip44_decrypt(&self, public_key: &JsPublicKey, content: String) -> Result { + pub async fn nip44_decrypt(&self, public_key: &JsPublicKey, content: &str) -> Result { self.inner .nip44_decrypt(public_key.deref(), content) .await .map_err(into_err) } - - /// Unwrap Gift Wrap event - /// - /// Internally verify the `seal` event - /// - /// - #[wasm_bindgen(js_name = unwrapGiftWrap)] - pub async fn unwrap_gift_wrap(&self, gift_wrap: &JsEvent) -> Result { - Ok(self - .inner - .unwrap_gift_wrap(gift_wrap.deref()) - .await - .map_err(into_err)? - .into()) - } } From 0b31464dd78c4a807397920ba52a88750626c23d Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Thu, 31 Oct 2024 11:43:07 +0100 Subject: [PATCH 9/9] nostr: add `UnsignedEvent::sign` Signed-off-by: Yuki Kishimoto --- bindings/nostr-ffi/src/event/unsigned.rs | 7 ++++ .../src/protocol/event/unsigned.rs | 12 ++++++ crates/nostr/src/event/builder.rs | 5 ++- crates/nostr/src/event/unsigned.rs | 41 ++++++++++--------- 4 files changed, 43 insertions(+), 22 deletions(-) diff --git a/bindings/nostr-ffi/src/event/unsigned.rs b/bindings/nostr-ffi/src/event/unsigned.rs index 10a7ffc90..d0390f14e 100644 --- a/bindings/nostr-ffi/src/event/unsigned.rs +++ b/bindings/nostr-ffi/src/event/unsigned.rs @@ -12,6 +12,7 @@ use uniffi::Object; use super::EventId; use crate::error::Result; +use crate::signer::{NostrSigner, NostrSignerFFI2Rust}; use crate::{Event, Keys, Kind, PublicKey, Tag, Timestamp}; #[derive(Debug, PartialEq, Eq, Hash, Object)] @@ -65,6 +66,12 @@ impl UnsignedEvent { } /// Sign an unsigned event + pub async fn sign(&self, signer: Arc) -> Result { + let signer = NostrSignerFFI2Rust::new(signer); + Ok(self.inner.clone().sign(&signer).await?.into()) + } + + /// Sign an unsigned event with keys signer /// /// Internally: calculate event ID (if not set), sign it, compose and verify event. pub fn sign_with_keys(&self, keys: &Keys) -> Result { diff --git a/bindings/nostr-sdk-js/src/protocol/event/unsigned.rs b/bindings/nostr-sdk-js/src/protocol/event/unsigned.rs index 62d0ae1b6..26a205603 100644 --- a/bindings/nostr-sdk-js/src/protocol/event/unsigned.rs +++ b/bindings/nostr-sdk-js/src/protocol/event/unsigned.rs @@ -14,6 +14,7 @@ use crate::error::{into_err, Result}; use crate::protocol::event::{JsEvent, JsEventId, JsKind}; use crate::protocol::key::{JsKeys, JsPublicKey}; use crate::protocol::types::JsTimestamp; +use crate::signer::JsNostrSigner; #[wasm_bindgen(js_name = UnsignedEvent)] pub struct JsUnsignedEvent { @@ -91,6 +92,17 @@ impl JsUnsignedEvent { } /// Sign an unsigned event + #[wasm_bindgen] + pub async fn sign(self, signer: &JsNostrSigner) -> Result { + Ok(self + .inner + .sign(signer.deref()) + .await + .map_err(into_err)? + .into()) + } + + /// Sign an unsigned event with keys signer /// /// Internally: calculate event ID (if not set), sign it, compose and verify event. #[wasm_bindgen(js_name = signWithKeys)] diff --git a/crates/nostr/src/event/builder.rs b/crates/nostr/src/event/builder.rs index 12c70b679..631d833a8 100644 --- a/crates/nostr/src/event/builder.rs +++ b/crates/nostr/src/event/builder.rs @@ -322,6 +322,8 @@ impl EventBuilder { } /// Build, sign and return [`Event`] + /// + /// Shortcut for `builder.build(public_key).sign(signer)`. #[inline] #[cfg(feature = "std")] pub async fn sign(self, signer: &T) -> Result @@ -329,8 +331,7 @@ impl EventBuilder { T: NostrSigner, { let public_key: PublicKey = signer.get_public_key().await?; - let unsigned: UnsignedEvent = self.build(public_key); - Ok(signer.sign_event(unsigned).await?) + Ok(self.build(public_key).sign(signer).await?) } /// Build, sign and return [`Event`] using [`Keys`] signer diff --git a/crates/nostr/src/event/unsigned.rs b/crates/nostr/src/event/unsigned.rs index d95bdb5f6..482b74b92 100644 --- a/crates/nostr/src/event/unsigned.rs +++ b/crates/nostr/src/event/unsigned.rs @@ -11,21 +11,19 @@ use core::fmt; use bitcoin::secp256k1::rand::rngs::OsRng; use bitcoin::secp256k1::rand::{CryptoRng, Rng}; use bitcoin::secp256k1::schnorr::Signature; -use bitcoin::secp256k1::{self, Message, Secp256k1, Signing, Verification}; +use bitcoin::secp256k1::{Message, Secp256k1, Signing, Verification}; +use crate::{Event, EventId, JsonUtil, Keys, Kind, PublicKey, SignerError, Tag, Tags, Timestamp}; #[cfg(feature = "std")] -use crate::SECP256K1; -use crate::{Event, EventId, JsonUtil, Keys, Kind, PublicKey, Tag, Tags, Timestamp}; +use crate::{NostrSigner, SECP256K1}; -/// [`UnsignedEvent`] error -#[derive(Debug, PartialEq, Eq)] +/// Unsigned event error +#[derive(Debug)] pub enum Error { - /// Key error - Key(crate::key::Error), + /// Signer error + Signer(SignerError), /// Error serializing or deserializing JSON data Json(String), - /// Secp256k1 error - Secp256k1(secp256k1::Error), /// Event error Event(super::Error), } @@ -36,17 +34,16 @@ impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Key(e) => write!(f, "Key: {e}"), + Self::Signer(e) => write!(f, "{e}"), Self::Json(e) => write!(f, "Json: {e}"), - Self::Secp256k1(e) => write!(f, "Secp256k1: {e}"), Self::Event(e) => write!(f, "Event: {e}"), } } } -impl From for Error { - fn from(e: crate::key::Error) -> Self { - Self::Key(e) +impl From for Error { + fn from(e: SignerError) -> Self { + Self::Signer(e) } } @@ -56,12 +53,6 @@ impl From for Error { } } -impl From for Error { - fn from(e: secp256k1::Error) -> Self { - Self::Secp256k1(e) - } -} - impl From for Error { fn from(e: super::Error) -> Self { Self::Event(e) @@ -143,6 +134,16 @@ impl UnsignedEvent { Ok(()) } + /// Sign an unsigned event + #[inline] + #[cfg(feature = "std")] + pub async fn sign(self, signer: &T) -> Result + where + T: NostrSigner, + { + Ok(signer.sign_event(self).await?) + } + /// Sign an unsigned event with [`Keys`] signer /// /// Internally: calculate [EventId] (if not set), sign it, compose and verify [Event].