diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3e309a5f..dc5e5a4d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,9 +28,9 @@ jobs: build-args: [ -p nostr, - -p nostr --no-default-features, - -p nostr --no-default-features --features all-nips, - -p nostr --no-default-features --features vanity, + -p nostr --no-default-features --features std, + -p nostr --no-default-features --features 'std all-nips', + -p nostr --no-default-features --features 'std vanity', -p nostr --features blocking, -p nostr-sdk, -p nostr-sdk --no-default-features, @@ -95,4 +95,39 @@ jobs: run: cargo build ${{ matrix.build-args }} --target wasm32-unknown-unknown - name: Clippy run: cargo clippy ${{ matrix.build-args }} --target wasm32-unknown-unknown -- -D warnings + + buld-no-std: + name: Build the `no_std` crate (ensure_no_std) + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - version: nightly + build-args: + [ + --no-default-features, + ] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-wasm32-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }} + - name: Set default toolchain + run: rustup default ${{ matrix.rust.version }} + - name: Add WASM + run: rustup target add wasm32-unknown-unknown + - name: Set profile + run: rustup set profile minimal && rustup component add clippy + - name: Build + run: cargo build ${{ matrix.build-args }} + working-directory: ./crates/nostr/examples/ensure_no_std + - name: Clippy + run: cargo clippy ${{ matrix.build-args }} -- -D warnings + working-directory: ./crates/nostr/examples/ensure_no_std \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 95e2da963..93a14a4a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ "crates/nostr-sdk", "crates/nostr-sdk-net", ] -default-members = ["crates/*"] +default-members = ["crates/nostr*"] resolver = "2" [workspace.package] @@ -19,4 +19,8 @@ rust-version = "1.64.0" [profile.release] lto = true -codegen-units = 1 \ No newline at end of file +codegen-units = 1 +panic = "abort" + +[profile.dev] +panic = "abort" diff --git a/bindings/nostr-js/Cargo.toml b/bindings/nostr-js/Cargo.toml index 115ad3c39..24a72f458 100644 --- a/bindings/nostr-js/Cargo.toml +++ b/bindings/nostr-js/Cargo.toml @@ -12,7 +12,7 @@ crate-type = ["lib", "cdylib"] [dependencies] console_error_panic_hook = "0.1" js-sys = "0.3" -nostr = { path = "../../crates/nostr" } +nostr = { path = "../../crates/nostr", features = ["std"]} serde-wasm-bindgen = "0.5" wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" diff --git a/crates/nostr-sdk/Cargo.toml b/crates/nostr-sdk/Cargo.toml index 064cab788..137e1b9fe 100644 --- a/crates/nostr-sdk/Cargo.toml +++ b/crates/nostr-sdk/Cargo.toml @@ -26,7 +26,7 @@ nip46 = ["nostr/nip46"] [dependencies] log = "0.4" -nostr = { version = "0.21", path = "../nostr", default-features = false } +nostr = { version = "0.21", path = "../nostr", default-features = false, features = ["std"]} nostr-sdk-net = { version = "0.21", path = "../nostr-sdk-net" } once_cell = { version = "1.17", optional = true } thiserror = "1.0" diff --git a/crates/nostr-sdk/src/client/mod.rs b/crates/nostr-sdk/src/client/mod.rs index d17799762..d437c309d 100644 --- a/crates/nostr-sdk/src/client/mod.rs +++ b/crates/nostr-sdk/src/client/mod.rs @@ -133,6 +133,7 @@ impl Client { /// /// # Example /// ```rust,no_run + /// use nostr_sdk::prelude::*; /// /// let my_keys = Keys::generate(); @@ -222,6 +223,7 @@ impl Client { /// /// # Example /// ```rust,no_run + /// use nostr_sdk::prelude::*; /// /// # #[tokio::main] @@ -260,6 +262,7 @@ impl Client { /// /// # Example /// ```rust,no_run + /// use nostr_sdk::prelude::*; /// /// # #[tokio::main] @@ -305,8 +308,8 @@ impl Client { /// /// # Example /// ```rust,no_run + /// use nostr_sdk::prelude::*; - /// /// # #[tokio::main] /// # async fn main() { /// # let my_keys = Keys::generate(); @@ -351,6 +354,7 @@ impl Client { /// /// # Example /// ```rust,no_run + /// use nostr_sdk::prelude::*; /// /// # #[tokio::main] @@ -381,6 +385,7 @@ impl Client { /// /// # Example /// ```rust,no_run + /// use nostr_sdk::prelude::*; /// /// # #[tokio::main] @@ -409,8 +414,8 @@ impl Client { /// /// # Example /// ```rust,no_run + /// use nostr_sdk::prelude::*; - /// /// # #[tokio::main] /// # async fn main() { /// # let my_keys = Keys::generate(); @@ -426,8 +431,8 @@ impl Client { /// /// # Example /// ```rust,no_run + /// use nostr_sdk::prelude::*; - /// /// # #[tokio::main] /// # async fn main() { /// # let my_keys = Keys::generate(); @@ -443,8 +448,8 @@ impl Client { /// /// # Example /// ```rust,no_run + /// use nostr_sdk::prelude::*; - /// /// # #[tokio::main] /// # async fn main() { /// # let my_keys = Keys::generate(); @@ -472,9 +477,8 @@ impl Client { /// # Example /// ```rust,no_run /// use std::time::Duration; - /// + /// use nostr_sdk::prelude::*; - /// /// # #[tokio::main] /// # async fn main() { /// # let my_keys = Keys::generate(); @@ -605,8 +609,8 @@ impl Client { /// /// # Example /// ```rust,no_run + /// use nostr_sdk::prelude::*; - /// /// # #[tokio::main] /// # async fn main() { /// # let my_keys = Keys::generate(); @@ -632,8 +636,8 @@ impl Client { /// /// # Example /// ```rust,no_run + /// use nostr_sdk::prelude::*; - /// /// # #[tokio::main] /// # async fn main() { /// # let my_keys = Keys::generate(); @@ -658,8 +662,8 @@ impl Client { /// /// # Example /// ```rust,no_run + /// use nostr_sdk::prelude::*; - /// /// # #[tokio::main] /// # async fn main() { /// # let my_keys = Keys::generate(); @@ -722,9 +726,8 @@ impl Client { /// # Example /// ```rust,no_run /// use std::time::Duration; - /// + /// use nostr_sdk::prelude::*; - /// /// # #[tokio::main] /// # async fn main() { /// # let my_keys = Keys::generate(); @@ -821,8 +824,8 @@ impl Client { /// /// # Example /// ```rust,no_run + /// use nostr_sdk::prelude::*; - /// /// # #[tokio::main] /// # async fn main() { /// # let my_keys = Keys::generate(); @@ -907,9 +910,8 @@ impl Client { /// # Example /// ```rust,no_run /// use std::str::FromStr; - /// + /// use nostr_sdk::prelude::*; - /// /// # #[tokio::main] /// # async fn main() { /// # let my_keys = Keys::generate(); @@ -941,9 +943,8 @@ impl Client { /// # Example /// ```rust,no_run /// use std::str::FromStr; - /// + /// use nostr_sdk::prelude::*; - /// /// # #[tokio::main] /// # async fn main() { /// # let my_keys = Keys::generate(); @@ -975,9 +976,8 @@ impl Client { /// # Example /// ```rust,no_run /// use std::str::FromStr; - /// + /// use nostr_sdk::prelude::*; - /// /// # #[tokio::main] /// # async fn main() { /// # let my_keys = Keys::generate(); diff --git a/crates/nostr/Cargo.toml b/crates/nostr/Cargo.toml index e6ca33221..b252ee946 100644 --- a/crates/nostr/Cargo.toml +++ b/crates/nostr/Cargo.toml @@ -12,38 +12,76 @@ rust-version.workspace = true keywords = ["nostr", "protocol", "sdk"] [features] -default = ["all-nips"] +default = ["all-nips", "std"] +std = [ + "bitcoin_hashes/std", + "serde/std", + "serde_json/std", + "url", + "secp256k1/global-context", + "secp256k1/rand-std", +] +alloc = [ + "bitcoin_hashes/alloc", + "serde/alloc", + "serde_json/alloc", + "url_no_std", + "secp256k1/alloc", + "secp256k1/rand", + "rand/alloc", + "rand/getrandom", +] blocking = ["reqwest?/blocking"] -vanity = ["nip19"] +vanity = ["nip19-std"] all-nips = ["nip04", "nip05", "nip06", "nip11", "nip19", "nip21", "nip46"] nip03 = ["dep:nostr-ots"] nip04 = ["dep:aes", "dep:base64", "dep:cbc"] nip05 = ["dep:reqwest"] nip06 = ["dep:bip39", "dep:bitcoin"] nip11 = ["dep:reqwest"] -nip19 = ["dep:bech32"] +nip19 = ["bech32/alloc"] +nip19-std = ["bech32/std"] nip21 = ["nip19"] nip46 = ["nip04"] [dependencies] aes = { version = "0.8", optional = true } base64 = { version = "0.21", optional = true } -bech32 = { version = "0.9", optional = true } +bech32 = { git = "https://github.com/rust-bitcoin/rust-bech32", rev = "360af7e0647fa94bce892fa69f31c0ef02452b63", optional = true, default-features = false } bip39 = { version = "2.0", optional = true } bitcoin = { version = "0.30", optional = true } -bitcoin_hashes = { version = "0.12", features = ["serde"] } +bitcoin_hashes = { version = "0.12", default-features = false, features = [ + "serde", +] } cbc = { version = "0.1", features = ["alloc"], optional = true } -log = "0.4" +log = "0.4" # no_std compatible by-default nostr-ots = { version = "0.2", optional = true } -reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls-webpki-roots", "socks"], optional = true } -secp256k1 = { version = "0.27", features = ["global-context", "rand-std", "serde"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = { version = "1.0" } -url = { version = "2", features = ["serde"] } +reqwest = { version = "0.11", default-features = false, features = [ + "json", + "rustls-tls-webpki-roots", + "socks", +], optional = true } +secp256k1 = { version = "0.27", default-features = false, features = ["serde"] } + +serde = { version = "1.0", features = [ + "derive", +], default-features = false, optional = true } +serde_json = { version = "1.0", optional = true, default-features = false } + +url = { version = "2", features = ["serde"], optional = true } +url_no_std = { package = "url", features = [ + "alloc", +], git = "https://github.com/OverOrion/rust-url", branch = "no_std_net", optional = true, default-features = false } + +rand = { version = "0.8", features = [ + "alloc", + "getrandom", +], default-features = false, optional = true } + [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2", features = ["js"] } -instant = { version = "0.1", features = [ "wasm-bindgen", "inaccurate" ] } +instant = { version = "0.1", features = ["wasm-bindgen", "inaccurate"] } [dev-dependencies] csv = "1.1.5" diff --git a/crates/nostr/examples/ensure_no_std/Cargo.toml b/crates/nostr/examples/ensure_no_std/Cargo.toml new file mode 100644 index 000000000..6ddd520df --- /dev/null +++ b/crates/nostr/examples/ensure_no_std/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "ensure_no_std" +version = "0.7.0" +authors = ["Supercomputing Systems AG "] +license = "Apache-2.0" +edition = "2021" + +[dependencies] +libc = { version = "0.2.119", default-features = false } + +nostr = { path = "../../../nostr", default-features = false, features = [ + "alloc", +] } + +[workspace] diff --git a/crates/nostr/examples/ensure_no_std/src/main.rs b/crates/nostr/examples/ensure_no_std/src/main.rs new file mode 100644 index 000000000..4d1190222 --- /dev/null +++ b/crates/nostr/examples/ensure_no_std/src/main.rs @@ -0,0 +1,102 @@ +#![feature(start, libc, lang_items)] +#![feature(alloc_error_handler)] +#![allow(unused_imports)] +#![no_std] +#![no_main] + +// The libc crate allows importing functions from C. +extern crate libc; +use core::{ + alloc::{GlobalAlloc, Layout}, + cell::UnsafeCell, + panic::PanicInfo, + ptr::null_mut, + sync::atomic::{AtomicUsize, Ordering::SeqCst}, +}; + +// A list of C functions that are being imported +extern "C" { + pub fn printf(format: *const u8, ...) -> i32; +} + +#[allow(clippy::single_component_path_imports)] +use nostr; + +#[no_mangle] +// The main function, with its input arguments ignored, and an exit status is returned +pub extern "C" fn main(_nargs: i32, _args: *const *const u8) -> i32 { + // Print "Hello, World" to stdout using printf + unsafe { + printf(b"Hello, World!\n" as *const u8); + } + + // Exit with a return status of 0. + 0 +} + +#[lang = "eh_personality"] +extern "C" fn eh_personality() {} + +#[panic_handler] +fn panic(_panic: &PanicInfo<'_>) -> ! { + loop {} +} + +#[alloc_error_handler] +fn foo(_: core::alloc::Layout) -> ! { + extern "C" { + fn abort() -> !; + } + unsafe { abort() } +} + +const ARENA_SIZE: usize = 128 * 1024; +const MAX_SUPPORTED_ALIGN: usize = 4096; +#[repr(C, align(4096))] // 4096 == MAX_SUPPORTED_ALIGN +struct SimpleAllocator { + arena: UnsafeCell<[u8; ARENA_SIZE]>, + remaining: AtomicUsize, // we allocate from the top, counting down +} + +#[global_allocator] +static ALLOCATOR: SimpleAllocator = SimpleAllocator { + arena: UnsafeCell::new([0x55; ARENA_SIZE]), + remaining: AtomicUsize::new(ARENA_SIZE), +}; +unsafe impl Sync for SimpleAllocator {} + +// From https://doc.rust-lang.org/core/alloc/trait.GlobalAlloc.html +#[allow(clippy::blocks_in_if_conditions)] +unsafe impl GlobalAlloc for SimpleAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let size = layout.size(); + let align = layout.align(); + + // `Layout` contract forbids making a `Layout` with align=0, or align not power of 2. + // So we can safely use a mask to ensure alignment without worrying about UB. + let align_mask_to_round_down = !(align - 1); + + if align > MAX_SUPPORTED_ALIGN { + return null_mut(); + } + + let mut allocated = 0; + if self + .remaining + .fetch_update(SeqCst, SeqCst, |mut remaining| { + if size > remaining { + return None; + } + remaining -= size; + remaining &= align_mask_to_round_down; + allocated = remaining; + Some(remaining) + }) + .is_err() + { + return null_mut(); + }; + (self.arena.get() as *mut u8).add(allocated) + } + unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {} +} diff --git a/crates/nostr/examples/nip19.rs b/crates/nostr/examples/nip19.rs index 49cdbdf1b..66bbf26b0 100644 --- a/crates/nostr/examples/nip19.rs +++ b/crates/nostr/examples/nip19.rs @@ -3,7 +3,10 @@ use std::str::FromStr; +use nostr::key::XOnlyPublicKey; +use nostr::nips::nip19::ToBech32; use nostr::prelude::*; +use nostr::Profile; fn main() -> Result<()> { env_logger::init(); diff --git a/crates/nostr/examples/vanity.rs b/crates/nostr/examples/vanity.rs index 66b418770..77177c0f1 100644 --- a/crates/nostr/examples/vanity.rs +++ b/crates/nostr/examples/vanity.rs @@ -1,6 +1,7 @@ // Copyright (c) 2022-2023 Yuki Kishimoto // Distributed under the MIT software license +use nostr::nips::nip19::ToBech32; use nostr::prelude::*; fn main() -> Result<()> { diff --git a/crates/nostr/src/event/builder.rs b/crates/nostr/src/event/builder.rs index 80d396f5b..ebf810c25 100644 --- a/crates/nostr/src/event/builder.rs +++ b/crates/nostr/src/event/builder.rs @@ -2,14 +2,22 @@ // Distributed under the MIT software license //! Event builder - use core::fmt; -#[cfg(not(target_arch = "wasm32"))] -use std::time::Instant; -#[cfg(target_arch = "wasm32")] -use instant::Instant; -use secp256k1::XOnlyPublicKey; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::string::{String, ToString}; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::{vec, vec::Vec}; + +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + +use secp256k1::schnorr::Signature; +use secp256k1::{Message, XOnlyPublicKey}; +use secp256k1::{Secp256k1, Signing}; use serde_json::{json, Value}; use url::Url; @@ -22,6 +30,7 @@ use crate::nips::nip04; use crate::nips::nip13; #[cfg(feature = "nip46")] use crate::nips::nip46::Message as NostrConnectMessage; +use crate::types::time::TimeSupplier; use crate::types::{ChannelId, Contact, Metadata, Timestamp}; /// [`EventBuilder`] error @@ -40,7 +49,7 @@ pub enum Error { NIP04(nip04::Error), } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -108,67 +117,240 @@ impl EventBuilder { } /// Build [`Event`] + #[cfg(feature = "std")] pub fn to_event(self, keys: &Keys) -> Result { let pubkey: XOnlyPublicKey = keys.public_key(); Ok(self.to_unsigned_event(pubkey).sign(keys)?) } - /// Build [`UnsignedEvent`] - pub fn to_unsigned_event(self, pubkey: XOnlyPublicKey) -> UnsignedEvent { - let created_at: Timestamp = Timestamp::now(); + /// Build [`Event`] with a `timestamp` + #[cfg(not(feature = "std"))] + pub fn to_event_with_timestamp_with_secp( + self, + keys: &Keys, + created_at: Timestamp, + secp: &Secp256k1, + ) -> Result { + let pubkey: XOnlyPublicKey = keys.public_key(); let id = EventId::new(&pubkey, created_at, &self.kind, &self.tags, &self.content); - UnsignedEvent { + let message = Message::from_slice(id.as_bytes())?; + let signature = keys.sign_schnorr_with_secp(&message, secp)?; + + Self::into_event_internal(self, keys, created_at, id, signature) + } + + fn into_event_internal( + self, + keys: &Keys, + created_at: Timestamp, + id: EventId, + sig: Signature, + ) -> Result { + let pubkey: XOnlyPublicKey = keys.public_key(); + + Ok(Event { id, pubkey, created_at, kind: self.kind, tags: self.tags, content: self.content, - } + sig, + }) } /// Build POW [`Event`] + #[cfg(feature = "std")] pub fn to_pow_event(self, keys: &Keys, difficulty: u8) -> Result { let pubkey: XOnlyPublicKey = keys.public_key(); Ok(self.to_unsigned_pow_event(pubkey, difficulty).sign(keys)?) } /// Build unsigned POW [`Event`] + #[cfg(feature = "std")] pub fn to_unsigned_pow_event(self, pubkey: XOnlyPublicKey, difficulty: u8) -> UnsignedEvent { - let mut nonce: u128 = 0; - let mut tags: Vec = self.tags; + #[cfg(target_arch = "wasm32")] + use instant::Instant; + #[cfg(all(feature = "std", not(target_arch = "wasm32")))] + use std::time::Instant; let now = Instant::now(); + use secp256k1::SECP256K1; + self.to_unsigned_pow_event_with_time_supplier_with_secp::( + pubkey, difficulty, &now, SECP256K1, + ) + } + + /// Build POW [`Event`] using the given time supplier + pub fn to_pow_event_with_time_supplier_with_secp( + self, + keys: &Keys, + difficulty: u8, + time_supplier: &impl TimeSupplier, + secp: &Secp256k1, + ) -> Result + where + T: TimeSupplier, + { + self.into_pow_event_internal(keys, difficulty, time_supplier, secp) + } + + fn into_pow_event_internal( + self, + keys: &Keys, + difficulty: u8, + time_supplier: &T, + _secp: &Secp256k1, + ) -> Result + where + T: TimeSupplier, + { + #[cfg(feature = "std")] + use std::cmp; + + #[cfg(all(feature = "alloc", not(feature = "std")))] + use core::cmp; + + let mut nonce: u128 = 0; + let mut tags: Vec = self.tags.clone(); + + let pubkey = keys.public_key(); + + let now = time_supplier.now(); + + loop { + nonce += 1; + + tags.push(Tag::POW { nonce, difficulty }); + + let new_now = time_supplier.now(); + let created_at = time_supplier.duration_since_starting_point(now.clone()); + let created_at = time_supplier.to_timestamp(created_at); + let id = EventId::new(&pubkey, created_at, &self.kind, &tags, &self.content); + + if nip13::get_leading_zero_bits(id.inner()) >= difficulty { + log::debug!( + "{} iterations in {} ms. Avg rate {} hashes/second", + nonce, + time_supplier + .elapsed_since(now.clone(), new_now.clone()) + .as_millis(), + nonce * 1000 + / cmp::max(1, time_supplier.elapsed_since(now, new_now).as_millis()) + ); + + let message = Message::from_slice(id.as_bytes())?; + + #[cfg(all(feature = "alloc", not(feature = "std")))] + let sig = keys.sign_schnorr_with_secp(&message, &_secp)?; + + #[cfg(feature = "std")] + let sig = keys.sign_schnorr(&message)?; + + return self.into_event_internal(keys, created_at, id, sig); + } + + tags.pop(); + } + } + /// Build POW [`Event`] using the given time supplier + pub fn to_unsigned_pow_event_with_time_supplier_with_secp( + self, + pubkey: XOnlyPublicKey, + difficulty: u8, + time_supplier: &impl TimeSupplier, + secp: &Secp256k1, + ) -> UnsignedEvent + where + T: TimeSupplier, + { + self.into_pow_unsigned_event_internal(pubkey, difficulty, time_supplier, secp) + } + + fn into_pow_unsigned_event_internal( + self, + pubkey: XOnlyPublicKey, + difficulty: u8, + time_supplier: &T, + _secp: &Secp256k1, + ) -> UnsignedEvent + where + T: TimeSupplier, + { + #[cfg(feature = "std")] + use std::cmp; + + #[cfg(all(feature = "alloc", not(feature = "std")))] + use core::cmp; + + let mut nonce: u128 = 0; + let mut tags: Vec = self.tags.clone(); + + let now = time_supplier.now(); + loop { nonce += 1; tags.push(Tag::POW { nonce, difficulty }); - let created_at: Timestamp = Timestamp::now(); + let new_now = time_supplier.now(); + let created_at = time_supplier.duration_since_starting_point(now.clone()); + let created_at = time_supplier.to_timestamp(created_at); let id = EventId::new(&pubkey, created_at, &self.kind, &tags, &self.content); if nip13::get_leading_zero_bits(id.inner()) >= difficulty { log::debug!( "{} iterations in {} ms. Avg rate {} hashes/second", nonce, - now.elapsed().as_millis(), - nonce * 1000 / std::cmp::max(1, now.elapsed().as_millis()) + time_supplier + .elapsed_since(now.clone(), new_now.clone()) + .as_millis(), + nonce * 1000 + / cmp::max(1, time_supplier.elapsed_since(now, new_now).as_millis()) ); - return UnsignedEvent { - id, - pubkey, - created_at, - kind: self.kind, - tags, - content: self.content, - }; + return self.to_unsigned_event_with_timestamp(pubkey, created_at); } tags.pop(); } } + + /// Build [`UnsignedEvent`] + #[cfg(feature = "std")] + pub fn to_unsigned_event(self, pubkey: XOnlyPublicKey) -> UnsignedEvent { + let created_at: Timestamp = Timestamp::now(); + + Self::into_unsigned_event_internal(self, pubkey, created_at) + } + + /// Build [`UnsignedEvent`] with the given `Timestamp` + /// Mostly useful for cases where the time source comes from the outside, not from builtin + /// functions + pub fn to_unsigned_event_with_timestamp( + self, + pubkey: XOnlyPublicKey, + created_at: Timestamp, + ) -> UnsignedEvent { + Self::into_unsigned_event_internal(self, pubkey, created_at) + } + + fn into_unsigned_event_internal( + self, + pubkey: XOnlyPublicKey, + created_at: Timestamp, + ) -> UnsignedEvent { + let id = EventId::new(&pubkey, created_at, &self.kind, &self.tags, &self.content); + UnsignedEvent { + id, + pubkey, + created_at, + kind: self.kind, + tags: self.tags, + content: self.content, + } + } } impl EventBuilder { diff --git a/crates/nostr/src/event/id.rs b/crates/nostr/src/event/id.rs index 1af00562c..12b7257f0 100644 --- a/crates/nostr/src/event/id.rs +++ b/crates/nostr/src/event/id.rs @@ -6,6 +6,17 @@ use core::fmt; use core::str::FromStr; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::string::{String, ToString}; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::vec; + +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + use bitcoin_hashes::sha256::Hash as Sha256Hash; use bitcoin_hashes::Hash; use secp256k1::XOnlyPublicKey; @@ -24,7 +35,7 @@ pub enum Error { Hash(bitcoin_hashes::Error), } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/nostr/src/event/mod.rs b/crates/nostr/src/event/mod.rs index 119d0616d..ea96953a9 100644 --- a/crates/nostr/src/event/mod.rs +++ b/crates/nostr/src/event/mod.rs @@ -7,8 +7,18 @@ use core::fmt; use core::str::FromStr; -use secp256k1::schnorr::Signature; -use secp256k1::{Message, XOnlyPublicKey}; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::string::{String, ToString}; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::vec::Vec; + +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + +use secp256k1::{schnorr::Signature, Message, Secp256k1, Verification, XOnlyPublicKey}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -23,7 +33,9 @@ pub use self::id::EventId; pub use self::kind::Kind; pub use self::tag::{Marker, Tag, TagKind}; pub use self::unsigned::UnsignedEvent; -use crate::{Timestamp, SECP256K1}; +use crate::Timestamp; +#[cfg(feature = "std")] +use crate::SECP256K1; /// [`Event`] error #[derive(Debug)] @@ -41,7 +53,7 @@ pub enum Error { OpenTimestamps(nostr_ots::Error), } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -105,8 +117,14 @@ pub struct Event { } impl Event { - /// Verify event + /// Verify Event + #[cfg(feature = "std")] pub fn verify(&self) -> Result<(), Error> { + self.verify_with_context(SECP256K1) + } + + /// Verify Event + pub fn verify_with_context(&self, secp: &Secp256k1) -> Result<(), Error> { let id = EventId::new( &self.pubkey, self.created_at, @@ -115,15 +133,13 @@ impl Event { &self.content, ); let message = Message::from_slice(id.as_bytes())?; - SECP256K1 - .verify_schnorr(&self.sig, &message, &self.pubkey) + secp.verify_schnorr(&self.sig, &message, &self.pubkey) .map_err(|_| Error::InvalidSignature) } /// New event from [`Value`] pub fn from_value(value: Value) -> Result { let event: Self = serde_json::from_value(value)?; - event.verify()?; Ok(event) } @@ -133,7 +149,6 @@ impl Event { S: Into, { let event: Self = serde_json::from_str(&json.into())?; - event.verify()?; Ok(event) } @@ -144,11 +159,18 @@ impl Event { /// Returns `true` if the event has an expiration tag that is expired. /// If an event has no `Expiration` tag, then it will return `false`. + #[cfg(feature = "std")] pub fn is_expired(&self) -> bool { let now = Timestamp::now(); + self.is_expired_since(now) + } + + /// Returns `true` if the event has an expiration tag that is expired `since`. + /// If an event has no `Expiration` tag, then it will return `false`. + pub fn is_expired_since(&self, since: Timestamp) -> bool { for tag in self.tags.iter() { if let Tag::Expiration(timestamp) = tag { - return timestamp < &now; + return timestamp < &since; } } false @@ -192,8 +214,6 @@ impl Event { ots: None, }; - event.verify()?; - Ok(event) } } diff --git a/crates/nostr/src/event/tag.rs b/crates/nostr/src/event/tag.rs index 15a94324e..fedda4894 100644 --- a/crates/nostr/src/event/tag.rs +++ b/crates/nostr/src/event/tag.rs @@ -2,11 +2,23 @@ // Distributed under the MIT software license //! Tag - use core::fmt; use core::num::ParseIntError; use core::str::FromStr; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::format; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::string::{String, ToString}; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::{vec, vec::Vec}; + +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + use secp256k1::schnorr::Signature; use secp256k1::XOnlyPublicKey; use serde::de::Error as DeserializerError; @@ -46,7 +58,7 @@ pub enum Error { Event(crate::event::Error), } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/nostr/src/event/unsigned.rs b/crates/nostr/src/event/unsigned.rs index fb3996292..58ec6747c 100644 --- a/crates/nostr/src/event/unsigned.rs +++ b/crates/nostr/src/event/unsigned.rs @@ -5,11 +5,26 @@ use core::fmt; -use secp256k1::schnorr::Signature; -use secp256k1::{Message, XOnlyPublicKey}; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::string::{String, ToString}; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + +use secp256k1::{schnorr::Signature, XOnlyPublicKey}; use serde::{Deserialize, Serialize}; -use crate::{Event, EventId, Keys, Kind, Tag, Timestamp}; +#[cfg(feature = "std")] +use secp256k1::Message; + +use crate::{Event, EventId, Kind, Tag, Timestamp}; + +#[cfg(feature = "std")] +use crate::Keys; /// [`UnsignedEvent`] error #[derive(Debug)] @@ -24,7 +39,7 @@ pub enum Error { Event(super::Error), } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -80,6 +95,7 @@ pub struct UnsignedEvent { impl UnsignedEvent { /// Sign an [`UnsignedEvent`] + #[cfg(feature = "std")] pub fn sign(self, keys: &Keys) -> Result { let message = Message::from_slice(self.id.as_bytes())?; Ok(Event { @@ -95,7 +111,24 @@ impl UnsignedEvent { }) } + /// Sign an [`UnsignedEvent`] with specified signature + #[cfg(not(feature = "std"))] + pub fn sign_with_signature(self, sig: Signature) -> Result { + Ok(Event { + id: self.id, + pubkey: self.pubkey, + created_at: self.created_at, + kind: self.kind, + tags: self.tags, + content: self.content, + sig, + #[cfg(feature = "nip03")] + ots: None, + }) + } + /// Add signature to [`UnsignedEvent`] + #[cfg(feature = "std")] pub fn add_signature(self, sig: Signature) -> Result { let event = Event { id: self.id, diff --git a/crates/nostr/src/key/mod.rs b/crates/nostr/src/key/mod.rs index c1d70946a..e3e76394a 100644 --- a/crates/nostr/src/key/mod.rs +++ b/crates/nostr/src/key/mod.rs @@ -10,12 +10,31 @@ use core::fmt; #[cfg(feature = "nip19")] use core::str::FromStr; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + +#[cfg(all(feature = "alloc", not(feature = "std")))] +use rand::rngs::OsRng; +#[cfg(feature = "std")] use secp256k1::rand::rngs::OsRng; + +#[cfg(all(feature = "alloc", not(feature = "std")))] +use secp256k1::Secp256k1; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use secp256k1::Signing; + +#[cfg(all(feature = "alloc", not(feature = "std")))] +use rand::Rng; +#[cfg(feature = "std")] use secp256k1::rand::Rng; use secp256k1::schnorr::Signature; use secp256k1::Message; pub use secp256k1::{KeyPair, PublicKey, SecretKey, XOnlyPublicKey}; +#[cfg(feature = "std")] use crate::SECP256K1; #[cfg(feature = "vanity")] @@ -39,7 +58,7 @@ pub enum Error { Secp256k1(secp256k1::Error), } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -63,8 +82,15 @@ impl From for Error { pub trait FromSkStr: Sized { /// Error type Err; + #[cfg(all(feature = "std", not(feature = "alloc")))] /// Init [`Keys`] from `hex` or `bech32` secret key string fn from_sk_str(secret_key: &str) -> Result; + #[cfg(all(feature = "alloc", not(feature = "std")))] + /// Init [`Keys`] from `hex` or `bech32` secret key string + fn from_sk_str( + secret_key: &str, + secp: &Secp256k1, + ) -> Result; } /// Trait for [`Keys`] @@ -85,6 +111,7 @@ pub struct Keys { impl Keys { /// Initialize from secret key. + #[cfg(feature = "std")] pub fn new(secret_key: SecretKey) -> Self { let key_pair = KeyPair::from_secret_key(SECP256K1, &secret_key); let public_key = XOnlyPublicKey::from_keypair(&key_pair).0; @@ -96,6 +123,22 @@ impl Keys { } } + /// Initialize from secret key. + #[cfg(not(feature = "std"))] + pub fn new_with_secp( + secret_key: SecretKey, + secp: &Secp256k1, + ) -> Self { + let key_pair = KeyPair::from_secret_key(secp, &secret_key); + let public_key = XOnlyPublicKey::from_keypair(&key_pair).0; + + Self { + public_key, + key_pair: Some(key_pair), + secret_key: Some(secret_key), + } + } + /// Initialize with public key only (no secret key). pub fn from_public_key(public_key: XOnlyPublicKey) -> Self { Self { @@ -106,13 +149,23 @@ impl Keys { } /// Generate new random [`Keys`] + #[cfg(feature = "std")] pub fn generate() -> Self { let mut rng = OsRng::default(); let (secret_key, _) = SECP256K1.generate_keypair(&mut rng); Self::new(secret_key) } + /// Generate new random [`Keys`] + #[cfg(not(feature = "std"))] + pub fn generate_with_secp(secp: &Secp256k1) -> Self { + let mut rng = OsRng::default(); + let (secret_key, _) = secp.generate_keypair(&mut rng); + Self::new_with_secp(secret_key, secp) + } + /// Generate random [`Keys`] with custom [`Rng`] + #[cfg(feature = "std")] pub fn generate_with_rng(rng: &mut R) -> Self where R: Rng + ?Sized, @@ -121,8 +174,19 @@ impl Keys { Self::new(secret_key) } + /// Generate random [`Keys`] with custom [`Rng`] and given [`Secp256k1`] + #[cfg(not(feature = "std"))] + pub fn generate_with_rng_with_secp(rng: &mut R, secp: &Secp256k1) -> Self + where + R: Rng + ?Sized, + { + let (secret_key, _) = secp.generate_keypair(rng); + Self::new_with_secp(secret_key, secp) + } + /// Generate random [`Keys`] with custom [`Rng`] and without [`KeyPair`] /// Useful for faster [`Keys`] generation (ex. vanity pubkey mining) + #[cfg(feature = "std")] pub fn generate_without_keypair(rng: &mut R) -> Self where R: Rng + ?Sized, @@ -136,7 +200,26 @@ impl Keys { } } - /// Get [`XOnlyPublicKey`] + /// Generate random [`Keys`] with custom [`Rng`] and without [`KeyPair`] + /// Useful for faster [`Keys`] generation (ex. vanity pubkey mining) + #[cfg(not(feature = "std"))] + pub fn generate_without_keypair_with_secp( + rng: &mut R, + secp: &Secp256k1, + ) -> Self + where + R: Rng + ?Sized, + { + let (secret_key, public_key) = secp.generate_keypair(rng); + let (public_key, _) = public_key.x_only_public_key(); + Self { + public_key, + key_pair: None, + secret_key: Some(secret_key), + } + } + + /// Get public key pub fn public_key(&self) -> XOnlyPublicKey { self.public_key } @@ -151,6 +234,7 @@ impl Keys { } /// Get [`PublicKey`] + #[cfg(feature = "std")] pub fn normalized_public_key(&self) -> Result { Ok(self.secret_key()?.public_key(SECP256K1)) } @@ -158,6 +242,7 @@ impl Keys { /// Get keypair /// /// If not exists, will be created + #[cfg(feature = "std")] pub fn key_pair(&self) -> Result { if let Some(key_pair) = self.key_pair { Ok(key_pair) @@ -167,14 +252,39 @@ impl Keys { } } + /// Get keypair + /// + /// If not exists, will be created + #[cfg(not(feature = "std"))] + pub fn key_pair_from_secp(&self, secp: &Secp256k1) -> Result { + if let Some(key_pair) = self.key_pair { + Ok(key_pair) + } else { + let sk = self.secret_key()?; + Ok(KeyPair::from_secret_key(secp, &sk)) + } + } + /// Sign schnorr [`Message`] + #[cfg(feature = "std")] pub fn sign_schnorr(&self, message: &Message) -> Result { let keypair: &KeyPair = &self.key_pair()?; Ok(SECP256K1.sign_schnorr(message, keypair)) } + + /// Sign schnorr [`Message`] + #[cfg(not(feature = "std"))] + pub fn sign_schnorr_with_secp( + &self, + message: &Message, + secp: &Secp256k1, + ) -> Result { + let keypair: &KeyPair = &self.key_pair_from_secp(&secp)?; + Ok(secp.sign_schnorr_no_aux_rand(message, keypair)) + } } -#[cfg(feature = "nip19")] +#[cfg(all(feature = "std", feature = "nip19", not(feature = "alloc")))] impl FromSkStr for Keys { type Err = Error; @@ -189,7 +299,24 @@ impl FromSkStr for Keys { } } } +#[cfg(all(feature = "alloc", feature = "nip19", not(feature = "std")))] +impl FromSkStr for Keys { + type Err = Error; + /// Init [`Keys`] from `hex` or `bech32` secret key + fn from_sk_str( + secret_key: &str, + secp: &Secp256k1, + ) -> Result { + match SecretKey::from_str(secret_key) { + Ok(secret_key) => Ok(Self::new_with_secp(secret_key, &secp)), + Err(_) => match SecretKey::from_bech32(secret_key) { + Ok(secret_key) => Ok(Self::new_with_secp(secret_key, &secp)), + Err(_) => Err(Error::InvalidSecretKey), + }, + } + } +} #[cfg(feature = "nip19")] impl FromPkStr for Keys { type Err = Error; diff --git a/crates/nostr/src/key/vanity.rs b/crates/nostr/src/key/vanity.rs index d42ace481..064abad68 100644 --- a/crates/nostr/src/key/vanity.rs +++ b/crates/nostr/src/key/vanity.rs @@ -9,6 +9,12 @@ use std::sync::mpsc::{sync_channel, RecvError}; use std::sync::Arc; use std::thread; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + use secp256k1::rand; use secp256k1::SecretKey; @@ -30,7 +36,7 @@ pub enum Error { JoinHandleError, } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/nostr/src/lib.rs b/crates/nostr/src/lib.rs index c48e752c9..76fa8a393 100644 --- a/crates/nostr/src/lib.rs +++ b/crates/nostr/src/lib.rs @@ -1,6 +1,7 @@ // Copyright (c) 2022-2023 Yuki Kishimoto // Distributed under the MIT software license +#![cfg_attr(all(not(feature = "std"), feature = "alloc"), feature(error_in_core))] #![warn(missing_docs)] #![warn(rustdoc::bare_urls)] @@ -10,6 +11,16 @@ feature = "default", doc = include_str!("../README.md") )] +#![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "alloc")] +extern crate alloc; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +#[macro_use] +/// Serde crate +pub extern crate serde; #[cfg(feature = "nip19")] pub use bech32; @@ -18,9 +29,17 @@ pub use bip39; #[cfg(feature = "nip06")] pub use bitcoin; pub use bitcoin_hashes as hashes; -pub use secp256k1::{self, SECP256K1}; +pub use secp256k1; +#[cfg(feature = "std")] +pub use secp256k1::SECP256K1; pub use serde_json; -pub use url::{self, Url}; + +#[cfg(feature = "std")] +pub use url; +#[cfg(all(feature = "alloc", not(feature = "std")))] +extern crate url_no_std as url; + +pub use url::Url; pub mod event; pub mod key; @@ -35,4 +54,8 @@ pub use self::message::{ClientMessage, Filter, RelayMessage, SubscriptionId}; pub use self::types::{ChannelId, Contact, Entity, Metadata, Profile, Timestamp, UncheckedUrl}; /// Result +#[cfg(feature = "std")] pub type Result> = std::result::Result; +/// Result +#[cfg(all(feature = "alloc", not(feature = "std")))] +pub type Result> = core::result::Result; diff --git a/crates/nostr/src/message/client.rs b/crates/nostr/src/message/client.rs index 06d32e4ff..fef725a3d 100644 --- a/crates/nostr/src/message/client.rs +++ b/crates/nostr/src/message/client.rs @@ -4,6 +4,13 @@ //! Client messages +#[cfg(feature = "alloc")] +use alloc::boxed::Box; +#[cfg(feature = "alloc")] +use alloc::string::{String, ToString}; +#[cfg(feature = "alloc")] +use alloc::{vec, vec::Vec}; + use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::{json, Value}; diff --git a/crates/nostr/src/message/mod.rs b/crates/nostr/src/message/mod.rs index e8dd87adf..16a34322e 100644 --- a/crates/nostr/src/message/mod.rs +++ b/crates/nostr/src/message/mod.rs @@ -5,6 +5,12 @@ use core::fmt; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + pub mod client; pub mod relay; pub mod subscription; @@ -24,7 +30,7 @@ pub enum MessageHandleError { Event(crate::event::Error), } -impl std::error::Error for MessageHandleError {} +impl StdError for MessageHandleError {} impl fmt::Display for MessageHandleError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/nostr/src/message/relay.rs b/crates/nostr/src/message/relay.rs index 903ec8292..6433f2b5e 100644 --- a/crates/nostr/src/message/relay.rs +++ b/crates/nostr/src/message/relay.rs @@ -4,6 +4,13 @@ //! Relay messages +#[cfg(feature = "alloc")] +use alloc::boxed::Box; +#[cfg(feature = "alloc")] +use alloc::string::{String, ToString}; +#[cfg(feature = "alloc")] +use alloc::vec; + use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::{json, Value}; diff --git a/crates/nostr/src/message/subscription.rs b/crates/nostr/src/message/subscription.rs index f86c6e12b..84ab313c1 100644 --- a/crates/nostr/src/message/subscription.rs +++ b/crates/nostr/src/message/subscription.rs @@ -4,12 +4,20 @@ //! Subscription filters +#![allow(missing_docs)] use core::fmt; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::string::{String, ToString}; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::{vec, vec::Vec}; + use bitcoin_hashes::sha256::Hash as Sha256Hash; use bitcoin_hashes::Hash; -use secp256k1::rand::rngs::OsRng; -use secp256k1::rand::RngCore; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use rand::{rngs::OsRng, RngCore}; +#[cfg(feature = "std")] +use secp256k1::rand::{rngs::OsRng, RngCore}; use secp256k1::XOnlyPublicKey; use serde::de::{self, Deserializer, MapAccess, Visitor}; use serde::ser::{SerializeMap, Serializer}; diff --git a/crates/nostr/src/nips/mod.rs b/crates/nostr/src/nips/mod.rs index 85fc534f1..c49ef7460 100644 --- a/crates/nostr/src/nips/mod.rs +++ b/crates/nostr/src/nips/mod.rs @@ -14,7 +14,7 @@ pub mod nip06; #[cfg(feature = "nip11")] pub mod nip11; pub mod nip13; -#[cfg(feature = "nip19")] +#[cfg(any(feature = "nip19", feature = "nip19-std"))] pub mod nip19; #[cfg(feature = "nip21")] pub mod nip21; diff --git a/crates/nostr/src/nips/nip04.rs b/crates/nostr/src/nips/nip04.rs index 454b99369..b1ccbae51 100644 --- a/crates/nostr/src/nips/nip04.rs +++ b/crates/nostr/src/nips/nip04.rs @@ -10,6 +10,12 @@ use core::convert::From; use core::fmt; use core::str::FromStr; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + use aes::cipher::block_padding::Pkcs7; use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit}; use aes::Aes256; @@ -35,7 +41,7 @@ pub enum Error { Secp256k1(secp256k1::Error), } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/nostr/src/nips/nip05.rs b/crates/nostr/src/nips/nip05.rs index 1c43903f7..7b9a48a43 100644 --- a/crates/nostr/src/nips/nip05.rs +++ b/crates/nostr/src/nips/nip05.rs @@ -7,6 +7,13 @@ use core::fmt; use core::str::FromStr; + +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + #[cfg(not(target_arch = "wasm32"))] use std::net::SocketAddr; @@ -32,7 +39,7 @@ pub enum Error { Secp256k1(secp256k1::Error), } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/nostr/src/nips/nip06.rs b/crates/nostr/src/nips/nip06.rs index ba03949cc..bac554f73 100644 --- a/crates/nostr/src/nips/nip06.rs +++ b/crates/nostr/src/nips/nip06.rs @@ -8,6 +8,12 @@ use core::fmt; use core::str::FromStr; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + use bip39::Mnemonic; use bitcoin::bip32::{DerivationPath, ExtendedPrivKey}; use bitcoin::Network; @@ -27,7 +33,7 @@ pub enum Error { BIP39(bip39::Error), } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/nostr/src/nips/nip11.rs b/crates/nostr/src/nips/nip11.rs index 0ccca2e2d..b78c3e83b 100644 --- a/crates/nostr/src/nips/nip11.rs +++ b/crates/nostr/src/nips/nip11.rs @@ -7,6 +7,13 @@ //! use core::fmt; + +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + #[cfg(not(target_arch = "wasm32"))] use std::net::SocketAddr; @@ -28,7 +35,7 @@ pub enum Error { Reqwest(reqwest::Error), } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/nostr/src/nips/nip13.rs b/crates/nostr/src/nips/nip13.rs index a4760ac25..b67da095b 100644 --- a/crates/nostr/src/nips/nip13.rs +++ b/crates/nostr/src/nips/nip13.rs @@ -6,6 +6,13 @@ //! //! +#[cfg(feature = "alloc")] +use alloc::format; +#[cfg(feature = "alloc")] +use alloc::string::String; +#[cfg(feature = "alloc")] +use alloc::{vec, vec::Vec}; + /// Gets the number of leading zero bits. Result is between 0 and 255. pub fn get_leading_zero_bits(h: T) -> u8 where diff --git a/crates/nostr/src/nips/nip19.rs b/crates/nostr/src/nips/nip19.rs index 3f4bc89ac..c8d0b5c30 100644 --- a/crates/nostr/src/nips/nip19.rs +++ b/crates/nostr/src/nips/nip19.rs @@ -7,7 +7,19 @@ #![allow(missing_docs)] +#[cfg(not(feature = "std"))] +use alloc::string::{FromUtf8Error, ToString}; +#[cfg(not(feature = "std"))] +use alloc::vec::{self, Vec}; use core::fmt; + +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + +#[cfg(feature = "std")] use std::string::FromUtf8Error; use bech32::{self, FromBase32, ToBase32, Variant}; @@ -54,7 +66,7 @@ pub enum Error { TryFromSlice, } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/nostr/src/nips/nip21.rs b/crates/nostr/src/nips/nip21.rs index ee0261809..2ac8daf98 100644 --- a/crates/nostr/src/nips/nip21.rs +++ b/crates/nostr/src/nips/nip21.rs @@ -7,6 +7,12 @@ use core::fmt; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + use secp256k1::XOnlyPublicKey; use super::nip19::{ @@ -27,7 +33,7 @@ pub enum Error { InvalidURI, } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/nostr/src/nips/nip26.rs b/crates/nostr/src/nips/nip26.rs index 192a046b0..b49e4c27f 100644 --- a/crates/nostr/src/nips/nip26.rs +++ b/crates/nostr/src/nips/nip26.rs @@ -9,6 +9,19 @@ use core::fmt; use core::num::ParseIntError; use core::str::FromStr; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::format; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::string::{String, ToString}; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::{vec, vec::Vec}; + +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + use bitcoin_hashes::sha256::Hash as Sha256Hash; use bitcoin_hashes::Hash; use secp256k1::schnorr::Signature; @@ -19,8 +32,13 @@ use serde_json::{json, Value}; use crate::event::Event; use crate::key::{self, Keys}; + +#[cfg(feature = "std")] use crate::SECP256K1; +#[cfg(not(feature = "std"))] +use secp256k1::{Secp256k1, Signing, Verification}; + const DELEGATION_KEYWORD: &str = "delegation"; /// `NIP26` error @@ -40,7 +58,7 @@ pub enum Error { DelegationTagParse, } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -96,7 +114,7 @@ pub enum ValidationError { CreatedTooLate, } -impl std::error::Error for ValidationError {} +impl StdError for ValidationError {} impl fmt::Display for ValidationError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -111,6 +129,7 @@ impl fmt::Display for ValidationError { /// Sign delegation. /// See `create_delegation_tag` for more complete functionality. +#[cfg(feature = "std")] pub fn sign_delegation( delegator_keys: &Keys, delegatee_pk: XOnlyPublicKey, @@ -122,7 +141,23 @@ pub fn sign_delegation( Ok(delegator_keys.sign_schnorr(&message)?) } +/// Sign delegation. +/// See `create_delegation_tag` for more complete functionality. +#[cfg(not(feature = "std"))] +pub fn sign_delegation_with_signer( + delegator_keys: &Keys, + delegatee_pk: XOnlyPublicKey, + conditions: Conditions, + secp: &Secp256k1, +) -> Result { + let unhashed_token = DelegationToken::new(delegatee_pk, conditions); + let hashed_token = Sha256Hash::hash(unhashed_token.as_bytes()); + let message = Message::from_slice(hashed_token.as_byte_array())?; + Ok(delegator_keys.sign_schnorr_with_secp(&message, secp)?) +} + /// Verify delegation signature +#[cfg(feature = "std")] pub fn verify_delegation_signature( delegator_public_key: XOnlyPublicKey, signature: Signature, @@ -136,6 +171,22 @@ pub fn verify_delegation_signature( Ok(()) } +/// Verify delegation signature +#[cfg(not(feature = "std"))] +pub fn verify_delegation_signature( + delegator_public_key: XOnlyPublicKey, + signature: Signature, + delegatee_public_key: XOnlyPublicKey, + conditions: Conditions, + secp: &Secp256k1, +) -> Result<(), Error> { + let unhashed_token = DelegationToken::new(delegatee_public_key, conditions); + let hashed_token = Sha256Hash::hash(unhashed_token.as_bytes()); + let message = Message::from_slice(hashed_token.as_byte_array())?; + secp.verify_schnorr(&signature, &message, &delegator_public_key)?; + Ok(()) +} + /// Delegation token #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct DelegationToken(String); @@ -171,6 +222,7 @@ pub struct DelegationTag { impl DelegationTag { /// Create a delegation tag (including the signature). /// See also validate(). + #[cfg(feature = "std")] pub fn new( delegator_keys: &Keys, delegatee_pubkey: XOnlyPublicKey, @@ -184,6 +236,28 @@ impl DelegationTag { }) } + /// Create a delegation tag (including the signature). + /// See also validate(). + #[cfg(not(feature = "std"))] + pub fn new( + delegator_keys: &Keys, + delegatee_pubkey: XOnlyPublicKey, + conditions: Conditions, + signer: Secp256k1, + ) -> Result { + let signature = sign_delegation_with_signer( + delegator_keys, + delegatee_pubkey, + conditions.clone(), + &signer, + )?; + Ok(Self { + delegator_pubkey: delegator_keys.public_key(), + conditions, + signature, + }) + } + /// Get delegator public key pub fn delegator_pubkey(&self) -> XOnlyPublicKey { self.delegator_pubkey @@ -200,6 +274,7 @@ impl DelegationTag { } /// Validate a delegation tag, check signature and conditions. + #[cfg(feature = "std")] pub fn validate( &self, delegatee_pubkey: XOnlyPublicKey, @@ -219,6 +294,30 @@ impl DelegationTag { Ok(()) } + /// Validate a delegation tag, check signature and conditions. + #[cfg(not(feature = "std"))] + pub fn validate( + &self, + delegatee_pubkey: XOnlyPublicKey, + event_properties: &EventProperties, + secp: &Secp256k1, + ) -> Result<(), Error> { + // verify signature + + verify_delegation_signature( + self.delegator_pubkey, + self.signature, + delegatee_pubkey, + self.conditions.clone(), + secp, + ) + .map_err(|_| Error::ConditionsValidation(ValidationError::InvalidSignature))?; + + // validate conditions + self.conditions.evaluate(event_properties)?; + + Ok(()) + } /// Convert to JSON string. pub fn as_json(&self) -> String { @@ -452,7 +551,7 @@ mod test { use std::str::FromStr; use super::*; - use crate::prelude::SecretKey; + use crate::key::SecretKey; #[test] fn test_serialize_conditions() { diff --git a/crates/nostr/src/nips/nip46.rs b/crates/nostr/src/nips/nip46.rs index 50b48ae5a..3430e491d 100644 --- a/crates/nostr/src/nips/nip46.rs +++ b/crates/nostr/src/nips/nip46.rs @@ -9,6 +9,12 @@ use core::fmt; use core::str::FromStr; use std::borrow::Cow; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + use bitcoin_hashes::sha256::Hash as Sha256Hash; use bitcoin_hashes::Hash; use secp256k1::schnorr::Signature; @@ -52,7 +58,7 @@ pub enum Error { InvalidURIScheme, } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/nostr/src/nips/nip58.rs b/crates/nostr/src/nips/nip58.rs index 6d765d30b..9f80e2940 100644 --- a/crates/nostr/src/nips/nip58.rs +++ b/crates/nostr/src/nips/nip58.rs @@ -4,6 +4,24 @@ use core::fmt; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use crate::alloc::borrow::ToOwned; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::string::String; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::{vec, vec::Vec}; + +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + +#[cfg(all(feature = "alloc", not(feature = "std")))] +use crate::prelude::{Secp256k1, Signing}; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use crate::types::Timestamp; + use secp256k1::XOnlyPublicKey; use crate::event::builder::Error as BuilderError; @@ -20,7 +38,7 @@ pub enum Error { EventBuilder(crate::event::builder::Error), } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -99,7 +117,29 @@ impl BadgeDefinitionBuilder { } /// Build [`Event`] + #[cfg(feature = "std")] pub fn build(self, keys: &Keys) -> Result { + let event_builder = self.build_internal()?; + let event = event_builder.to_event(keys)?; + + Ok(BadgeDefinition(event)) + } + + /// Build [`Event`] + #[cfg(all(feature = "alloc", not(feature = "std")))] + pub fn build( + self, + keys: &Keys, + created_at: Timestamp, + secp: &Secp256k1, + ) -> Result { + let event_builder = self.build_internal()?; + let event = event_builder.to_event_with_timestamp_with_secp(&keys, created_at, secp)?; + + Ok(BadgeDefinition(event)) + } + /// Build [`Event`] + fn build_internal(self) -> Result { let mut tags: Vec = Vec::new(); let badge_id = Tag::Identifier(self.badge_id); tags.push(badge_id); @@ -137,8 +177,7 @@ impl BadgeDefinitionBuilder { } let event_builder = EventBuilder::new(Kind::BadgeDefinition, String::new(), &tags); - let event = event_builder.to_event(keys)?; - Ok(BadgeDefinition(event)) + Ok(event_builder) } } @@ -151,12 +190,39 @@ pub struct BadgeDefinition(Event); pub struct BadgeAward(Event); impl BadgeAward { - /// + /// New [`BadgeAward`] + #[cfg(feature = "std")] pub fn new( badge_definition: &Event, awarded_pub_keys: Vec, keys: &Keys, ) -> Result { + let event_builder = BadgeAward::new_internal(badge_definition, awarded_pub_keys, keys)?; + let event = event_builder.to_event(keys)?; + + Ok(BadgeAward(event)) + } + + /// New [`BadgeAward`] + #[cfg(all(feature = "alloc", not(feature = "std")))] + pub fn new( + badge_definition: &Event, + awarded_pub_keys: Vec, + keys: &Keys, + created_at: Timestamp, + secp: &Secp256k1, + ) -> Result { + let event_builder = BadgeAward::new_internal(badge_definition, awarded_pub_keys, keys)?; + let event = event_builder.to_event_with_timestamp_with_secp(&keys, created_at, secp)?; + + Ok(BadgeAward(event)) + } + + fn new_internal( + badge_definition: &Event, + awarded_pub_keys: Vec, + keys: &Keys, + ) -> Result { let badge_id = match badge_definition.kind { Kind::BadgeDefinition => badge_definition.tags.iter().find_map(|t| match t { Tag::Identifier(id) => Some(id), @@ -185,9 +251,7 @@ impl BadgeAward { tags.extend(awarded_pub_keys); let event_builder = EventBuilder::new(Kind::BadgeAward, String::new(), &tags); - let event = event_builder.to_event(keys)?; - - Ok(BadgeAward(event)) + Ok(event_builder) } } @@ -214,7 +278,7 @@ pub enum ProfileBadgesEventError { EventBuilder(crate::event::builder::Error), } -impl std::error::Error for ProfileBadgesEventError {} +impl StdError for ProfileBadgesEventError {} impl fmt::Display for ProfileBadgesEventError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -269,6 +333,24 @@ impl ProfileBadgesEvent { }) } + #[cfg(all(feature = "alloc", not(feature = "std")))] + /// Create a new [`ProfileBadgesEvent`] from badge definition and awards events + /// [`badge_definitions`] and [`badge_awards`] must be ordered, so on the same position they refer to the same badge + pub fn new( + badge_definitions: Vec, + badge_awards: Vec, + pubkey_awarded: &XOnlyPublicKey, + keys: &Keys, + created_at: Timestamp, + secp: &Secp256k1, + ) -> Result { + let event_builder = + ProfileBadgesEvent::new_internal(badge_definitions, badge_awards, pubkey_awarded)?; + let event = event_builder.to_event_with_timestamp_with_secp(keys, created_at, &secp)?; + + Ok(ProfileBadgesEvent(event)) + } + #[cfg(feature = "std")] /// Create a new [`ProfileBadgesEvent`] from badge definition and awards events /// [`badge_definitions`] and [`badge_awards`] must be ordered, so on the same position they refer to the same badge pub fn new( @@ -277,6 +359,20 @@ impl ProfileBadgesEvent { pubkey_awarded: &XOnlyPublicKey, keys: &Keys, ) -> Result { + let event_builder = + ProfileBadgesEvent::new_internal(badge_definitions, badge_awards, pubkey_awarded)?; + let event = event_builder.to_event(keys)?; + + Ok(ProfileBadgesEvent(event)) + } + + /// Create a new [`ProfileBadgesEvent`] from badge definition and awards events + /// [`badge_definitions`] and [`badge_awards`] must be ordered, so on the same position they refer to the same badge + fn new_internal( + badge_definitions: Vec, + badge_awards: Vec, + pubkey_awarded: &XOnlyPublicKey, + ) -> Result { if badge_definitions.len() != badge_awards.len() { return Err(ProfileBadgesEventError::InvalidLength); } @@ -362,9 +458,7 @@ impl ProfileBadgesEvent { // Badge definitions and awards have been validated let event_builder = EventBuilder::new(Kind::ProfileBadges, String::new(), &tags); - let event = event_builder.to_event(keys)?; - - Ok(ProfileBadgesEvent(event)) + Ok(event_builder) } } diff --git a/crates/nostr/src/nips/nip65.rs b/crates/nostr/src/nips/nip65.rs index 8fb2082df..dd6ac76d5 100644 --- a/crates/nostr/src/nips/nip65.rs +++ b/crates/nostr/src/nips/nip65.rs @@ -2,6 +2,11 @@ //! //! +#[cfg(feature = "alloc")] +use alloc::string::String; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + use crate::Event; /// Extracts the relay info (url, optional read/write flag) from the event diff --git a/crates/nostr/src/prelude.rs b/crates/nostr/src/prelude.rs index 6dc142291..2a20b4473 100644 --- a/crates/nostr/src/prelude.rs +++ b/crates/nostr/src/prelude.rs @@ -2,11 +2,16 @@ // Distributed under the MIT software license //! Prelude +//#![allow(ambiguous_glob_reexports)] +#![cfg_attr( + all(not(feature = "std"), feature = "alloc"), + allow(ambiguous_glob_reexports) +)] // External crates -pub use ::url::*; #[cfg(feature = "nip19")] -pub use bech32::*; +pub use ::bech32::*; +pub use ::url::*; #[cfg(feature = "nip06")] pub use bip39::*; #[cfg(feature = "nip06")] @@ -25,7 +30,9 @@ pub use crate::event::*; pub use crate::key::*; pub use crate::message::*; pub use crate::types::*; -pub use crate::{Result, SECP256K1}; +pub use crate::Result; +#[cfg(feature = "std")] +pub use crate::SECP256K1; // NIPs #[cfg(feature = "nip04")] diff --git a/crates/nostr/src/types/channel_id.rs b/crates/nostr/src/types/channel_id.rs index a3cf1036a..cd0b57a25 100644 --- a/crates/nostr/src/types/channel_id.rs +++ b/crates/nostr/src/types/channel_id.rs @@ -6,6 +6,17 @@ use core::fmt; use core::str::FromStr; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::string::{String, ToString}; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::vec::Vec; + +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + #[cfg(feature = "nip19")] use bech32::{self, FromBase32, ToBase32, Variant}; use bitcoin_hashes::sha256::Hash as Sha256Hash; @@ -27,7 +38,7 @@ pub enum Error { Hash(bitcoin_hashes::Error), } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -154,6 +165,9 @@ impl FromBech32 for ChannelId { } } +#[cfg(all(feature = "nip19", feature = "alloc", not(feature = "std")))] +use alloc::vec; + #[cfg(feature = "nip19")] impl ToBech32 for ChannelId { type Err = Bech32Error; diff --git a/crates/nostr/src/types/contact.rs b/crates/nostr/src/types/contact.rs index ea3432698..151a2ab4a 100644 --- a/crates/nostr/src/types/contact.rs +++ b/crates/nostr/src/types/contact.rs @@ -3,6 +3,9 @@ //! Contact +#[cfg(feature = "alloc")] +use alloc::string::String; + use secp256k1::XOnlyPublicKey; use serde::{Deserialize, Serialize}; diff --git a/crates/nostr/src/types/metadata.rs b/crates/nostr/src/types/metadata.rs index 0fc0d404d..9cc539fc8 100644 --- a/crates/nostr/src/types/metadata.rs +++ b/crates/nostr/src/types/metadata.rs @@ -5,9 +5,18 @@ use core::fmt; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + use serde::{Deserialize, Serialize}; use url::Url; +#[cfg(feature = "alloc")] +use alloc::string::{String, ToString}; + /// [`Metadata`] error #[derive(Debug)] pub enum Error { @@ -15,7 +24,7 @@ pub enum Error { Json(serde_json::Error), } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/nostr/src/types/profile.rs b/crates/nostr/src/types/profile.rs index f1bc171d8..5776695b6 100644 --- a/crates/nostr/src/types/profile.rs +++ b/crates/nostr/src/types/profile.rs @@ -3,14 +3,20 @@ //! Profile +#[cfg(all(feature = "nip19", feature = "alloc"))] +use alloc::string::ToString; #[cfg(feature = "nip19")] use bech32::{self, FromBase32, ToBase32, Variant}; use secp256k1::XOnlyPublicKey; -use serde::{Deserialize, Serialize}; #[cfg(feature = "nip19")] use crate::nips::nip19::{Error, FromBech32, ToBech32, PREFIX_BECH32_PROFILE, RELAY, SPECIAL}; +#[cfg(feature = "alloc")] +use alloc::string::String; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + /// Profile #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Profile { @@ -80,6 +86,8 @@ impl FromBech32 for Profile { } } +#[cfg(all(feature = "nip19", feature = "alloc"))] +use alloc::vec; #[cfg(feature = "nip19")] impl ToBech32 for Profile { type Err = Error; diff --git a/crates/nostr/src/types/time.rs b/crates/nostr/src/types/time.rs index 008160008..6c21f92fb 100644 --- a/crates/nostr/src/types/time.rs +++ b/crates/nostr/src/types/time.rs @@ -3,22 +3,133 @@ //! Time -use std::fmt; +use core::ops::{Add, Sub}; +use core::str::FromStr; +use core::time::Duration; -use std::time::Duration; -#[cfg(not(target_arch = "wasm32"))] -use std::time::{SystemTime, UNIX_EPOCH}; +#[cfg(feature = "std")] use std::{ - ops::{Add, Sub}, - str::FromStr, + fmt, num, + time::{SystemTime, UNIX_EPOCH}, }; -#[cfg(target_arch = "wasm32")] -use instant::SystemTime; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::fmt; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::string::String; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::vec::Vec; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::num; + use serde::{Deserialize, Serialize}; +/// Helper trait for acquiring time in `no_std` environments. +pub trait TimeSupplier { + /// The current time from the specified `TimeSupplier` + type Now: Clone; + /// The starting point for the specified `TimeSupplier` + type StartingPoint: Clone; + + /// Get the current time as the associated `Now` type + fn instant_now(&self) -> Self::Now; + /// Get the current time as the associated `StartingPoint` type + fn now(&self) -> Self::StartingPoint; + /// Get a duration since the StartingPoint. + fn duration_since_starting_point(&self, now: Self::StartingPoint) -> Duration; + /// Get the starting point from the specified `TimeSupplier` + fn starting_point(&self) -> Self::StartingPoint; + /// Get the elapsed time as `Duration` starting from `since` to `now` + fn elapsed_instant_since(&self, now: Self::Now, since: Self::Now) -> Duration; + /// Get the elapsed time as `Duration` starting from `since` to `now` + fn elapsed_since(&self, now: Self::StartingPoint, since: Self::StartingPoint) -> Duration; + /// Convert the specified `Duration` to `i64` + fn as_i64(&self, duration: Duration) -> i64; + /// Convert the specified `Duration` to `Timestamp` + fn to_timestamp(&self, duration: Duration) -> Timestamp; +} + +#[cfg(target_arch = "wasm32")] +use instant::Instant as InstantWasm32; #[cfg(target_arch = "wasm32")] -const UNIX_EPOCH: SystemTime = SystemTime::UNIX_EPOCH; +impl TimeSupplier for InstantWasm32 { + type Now = InstantWasm32; + type StartingPoint = std::time::SystemTime; + + fn now(&self) -> Self::StartingPoint { + SystemTime::now() + } + + fn instant_now(&self) -> Self::Now { + InstantWasm32::now() + } + + fn duration_since_starting_point(&self, now: Self::StartingPoint) -> Duration { + now.duration_since(self.starting_point()) + .expect("duration_since panicked") + } + + fn starting_point(&self) -> Self::StartingPoint { + std::time::UNIX_EPOCH + } + + fn elapsed_instant_since(&self, now: Self::Now, since: Self::Now) -> Duration { + now - since + } + + fn elapsed_since(&self, now: Self::StartingPoint, since: Self::StartingPoint) -> Duration { + now.duration_since(since).expect("duration_since panicked") + } + + fn as_i64(&self, duration: Duration) -> i64 { + duration.as_millis() as i64 + } + + fn to_timestamp(&self, duration: Duration) -> Timestamp { + Timestamp(self.as_i64(duration)) + } +} + +#[cfg(all(not(target_arch = "wasm32"), feature = "std"))] +use std::time::Instant; +#[cfg(all(not(target_arch = "wasm32"), feature = "std"))] +impl TimeSupplier for Instant { + type Now = Instant; + type StartingPoint = std::time::SystemTime; + + fn now(&self) -> Self::StartingPoint { + SystemTime::now() + } + + fn instant_now(&self) -> Self::Now { + Instant::now() + } + + fn duration_since_starting_point(&self, now: Self::StartingPoint) -> Duration { + now.duration_since(self.starting_point()) + .expect("duration_since panicked") + } + + fn starting_point(&self) -> Self::StartingPoint { + std::time::UNIX_EPOCH + } + + fn elapsed_instant_since(&self, now: Self::Now, since: Self::Now) -> Duration { + now - since + } + + fn elapsed_since(&self, now: Self::StartingPoint, since: Self::StartingPoint) -> Duration { + now.duration_since(since).expect("duration_since panicked") + } + + fn as_i64(&self, duration: Duration) -> i64 { + duration.as_millis() as i64 + } + + fn to_timestamp(&self, duration: Duration) -> Timestamp { + Timestamp(self.as_i64(duration)) + } +} /// Unix timestamp in seconds #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] @@ -26,6 +137,7 @@ pub struct Timestamp(i64); impl Timestamp { /// Get UNIX timestamp + #[cfg(feature = "std")] pub fn now() -> Self { let ts: u64 = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -34,6 +146,19 @@ impl Timestamp { Self(ts as i64) } + /// Get UNIX timestamp from the specified `TimeSupplier` + #[cfg(not(feature = "std"))] + pub fn now_nostd(time_supplier: &T) -> Self + where + T: TimeSupplier, + { + let now = time_supplier.now(); + let starting_point = time_supplier.starting_point(); + let duration = time_supplier.elapsed_since(now, starting_point); + + time_supplier.to_timestamp(duration) + } + /// Get timestamp as [`u64`] pub fn as_u64(&self) -> u64 { if self.0 >= 0 { @@ -139,7 +264,7 @@ impl From for Timestamp { } impl FromStr for Timestamp { - type Err = std::num::ParseIntError; + type Err = num::ParseIntError; fn from_str(s: &str) -> Result { Ok(Self(s.parse::()?)) } diff --git a/crates/nostr/src/types/url.rs b/crates/nostr/src/types/url.rs index 205f556ae..56b2a93fa 100644 --- a/crates/nostr/src/types/url.rs +++ b/crates/nostr/src/types/url.rs @@ -9,6 +9,13 @@ use core::str::FromStr; use serde::{Deserialize, Serialize}; use url::{ParseError, Url}; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::string::String; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use core::error::Error as StdError; +#[cfg(feature = "std")] +use std::error::Error as StdError; + /// Url Error #[derive(Debug, PartialEq, Eq)] pub enum Error { @@ -16,7 +23,7 @@ pub enum Error { Url(ParseError), } -impl std::error::Error for Error {} +impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {