From fa6afe509e3f7ec94b5d7ee9d1b15f7d6a848c94 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Fri, 23 Aug 2024 12:33:33 -0400 Subject: [PATCH] nostr: update `Keys` struct * Impl custom `Debug`, `PartialEq` and `Eq` * Impl `PartialOrd`, `Ord` and `Hash` * Change `Keys::secret_key` and `Keys::sign_schnorr` methods fingerprint * Deprecate `Keys::generate_without_keypair` * Remove `Keys::from_public_key` Signed-off-by: Yuki Kishimoto --- CHANGELOG.md | 7 ++ bindings/nostr-ffi/src/key/mod.rs | 13 +- bindings/nostr-ffi/src/nips/nip26.rs | 4 +- bindings/nostr-js/src/key/mod.rs | 18 +-- bindings/nostr-js/src/nips/nip26.rs | 6 +- crates/nostr-sdk/examples/subscriptions.rs | 2 +- crates/nostr-signer/src/lib.rs | 8 +- crates/nostr-signer/src/nip46/client.rs | 6 +- crates/nostr-signer/src/nip46/signer.rs | 10 +- crates/nostr/examples/embedded/src/main.rs | 4 +- crates/nostr/examples/keys.rs | 9 +- crates/nostr/examples/nip06.rs | 2 +- crates/nostr/examples/vanity.rs | 2 +- crates/nostr/src/event/builder.rs | 6 +- crates/nostr/src/event/mod.rs | 4 +- crates/nostr/src/event/unsigned.rs | 2 +- crates/nostr/src/key/mod.rs | 139 +++++++++++++-------- crates/nostr/src/key/vanity.rs | 2 +- crates/nostr/src/nips/nip04.rs | 18 +-- crates/nostr/src/nips/nip06.rs | 2 +- crates/nostr/src/nips/nip26.rs | 35 +++--- crates/nostr/src/nips/nip44/mod.rs | 11 +- crates/nostr/src/nips/nip57.rs | 7 +- crates/nostr/src/nips/nip59.rs | 2 +- 24 files changed, 159 insertions(+), 160 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71c4786e5..460a902b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,11 @@ * nostr: deprecate `Event::is_*` kind related methods ([Yuki Kishimoto]) * nostr: change `TryIntoUrl::Err` to `Infallible` for `Url` ([Yuki Kishimoto]) * nostr: change `Event::verify_id` and `Event::verify_signature` fingerprint ([Yuki Kishimoto]) +* nostr: impl custom `Debug`, `PartialEq` and `Eq` for `Keys` ([Yuki Kishimoto]) +* nostr: impl `PartialOrd`, `Ord` and `Hash` for `Keys` ([Yuki Kishimoto]) +* nostr: change `Keys::secret_key` and `Keys::sign_schnorr` methods fingerprint ([Yuki Kishimoto]) +* nostr: deprecate `Keys::generate_without_keypair` ([Yuki Kishimoto]) +* nostr: change NIP-26 functions fingerprint ([Yuki Kishimoto]) * signer: update NIP-04 and NIP-44 methods signature ([Yuki Kishimoto]) * webln: bump `webln` to `v0.3` ([Yuki Kishimoto]) * sdk: bump `lnurl-pay` to `v0.6` ([Yuki Kishimoto]) @@ -59,6 +64,8 @@ ### Removed * nostr: remove `bech32` from the public API ([Yuki Kishimoto]) +* nostr: remove `Keys::from_public_key` ([Yuki Kishimoto]) +* js(nostr): remove `Keys::vanity` ([Yuki Kishimoto]) ## [v0.34.0] diff --git a/bindings/nostr-ffi/src/key/mod.rs b/bindings/nostr-ffi/src/key/mod.rs index 12f69602f..be2896fe9 100644 --- a/bindings/nostr-ffi/src/key/mod.rs +++ b/bindings/nostr-ffi/src/key/mod.rs @@ -53,13 +53,6 @@ impl Keys { }) } - #[uniffi::constructor] - pub fn from_public_key(public_key: &PublicKey) -> Self { - Self { - inner: key::Keys::from_public_key(**public_key), - } - } - /// Generate random `Keys` #[uniffi::constructor] pub fn generate() -> Self { @@ -95,12 +88,12 @@ impl Keys { self.inner.public_key().into() } - pub fn secret_key(&self) -> Result { - Ok(self.inner.secret_key()?.clone().into()) + pub fn secret_key(&self) -> SecretKey { + self.inner.secret_key().clone().into() } pub fn sign_schnorr(&self, message: &[u8]) -> Result { let message: Message = Message::from_digest_slice(message)?; - Ok(self.inner.sign_schnorr(&message)?.to_string()) + Ok(self.inner.sign_schnorr(&message).to_string()) } } diff --git a/bindings/nostr-ffi/src/nips/nip26.rs b/bindings/nostr-ffi/src/nips/nip26.rs index ea9816afb..8e0d97d9e 100644 --- a/bindings/nostr-ffi/src/nips/nip26.rs +++ b/bindings/nostr-ffi/src/nips/nip26.rs @@ -21,7 +21,7 @@ pub fn create_delegation_tag( conditions: &str, ) -> Result { let conditions = Conditions::from_str(conditions)?; - let tag = DelegationTag::new(delegator_keys.deref(), delegatee_pubkey.deref(), conditions)?; + let tag = DelegationTag::new(delegator_keys.deref(), delegatee_pubkey.deref(), conditions); Ok(tag.to_string()) } @@ -57,7 +57,7 @@ pub fn sign_delegation( ) -> Result { let conditions = Conditions::from_str(conditions)?; Ok( - nip26::sign_delegation(delegator_keys.deref(), delegatee_pk.deref(), &conditions)? + nip26::sign_delegation(delegator_keys.deref(), delegatee_pk.deref(), &conditions) .to_string(), ) } diff --git a/bindings/nostr-js/src/key/mod.rs b/bindings/nostr-js/src/key/mod.rs index f2b80339c..8f7c5971c 100644 --- a/bindings/nostr-js/src/key/mod.rs +++ b/bindings/nostr-js/src/key/mod.rs @@ -50,14 +50,6 @@ impl JsKeys { }) } - /// Initialize with public key only (no secret key). - #[wasm_bindgen(js_name = fromPublicKey)] - pub fn from_public_key(public_key: &JsPublicKey) -> JsKeys { - Self { - inner: Keys::from_public_key(**public_key), - } - } - /// Generate new random keys pub fn generate() -> JsKeys { Self { @@ -65,12 +57,6 @@ impl JsKeys { } } - pub fn vanity(prefixes: Vec, bech32: bool, num_cores: u8) -> Result { - Ok(Self { - inner: Keys::vanity(prefixes, bech32, num_cores as usize).map_err(into_err)?, - }) - } - /// Derive keys from BIP-39 mnemonics (ENGLISH wordlist). /// /// @@ -102,7 +88,7 @@ impl JsKeys { /// Get secret key #[wasm_bindgen(js_name = secretKey, getter)] - pub fn secret_key(&self) -> Result { - Ok(self.inner.secret_key().cloned().map_err(into_err)?.into()) + pub fn secret_key(&self) -> JsSecretKey { + self.inner.secret_key().clone().into() } } diff --git a/bindings/nostr-js/src/nips/nip26.rs b/bindings/nostr-js/src/nips/nip26.rs index 5756855f6..b362bd1f5 100644 --- a/bindings/nostr-js/src/nips/nip26.rs +++ b/bindings/nostr-js/src/nips/nip26.rs @@ -25,8 +25,7 @@ pub fn create_delegation_tag( conditions: &str, ) -> Result { let conditions = Conditions::from_str(conditions).map_err(into_err)?; - let tag = DelegationTag::new(delegator_keys.deref(), delegatee_pubkey.deref(), conditions) - .map_err(into_err)?; + let tag = DelegationTag::new(delegator_keys.deref(), delegatee_pubkey.deref(), conditions); Ok(tag.to_string()) } @@ -61,8 +60,7 @@ pub fn sign_delegation( ) -> Result { let conditions = Conditions::from_str(conditions).map_err(into_err)?; let signature: Signature = - nip26::sign_delegation(keys.deref(), delegatee_pk.deref(), &conditions) - .map_err(into_err)?; + nip26::sign_delegation(keys.deref(), delegatee_pk.deref(), &conditions); Ok(signature.to_string()) } diff --git a/crates/nostr-sdk/examples/subscriptions.rs b/crates/nostr-sdk/examples/subscriptions.rs index a68ce47a9..0d6b8d37d 100644 --- a/crates/nostr-sdk/examples/subscriptions.rs +++ b/crates/nostr-sdk/examples/subscriptions.rs @@ -67,7 +67,7 @@ async fn main() -> Result<()> { // Check kind if event.kind == Kind::EncryptedDirectMessage { if let Ok(msg) = - nip04::decrypt(keys.secret_key()?, &event.pubkey, &event.content) + nip04::decrypt(keys.secret_key(), &event.pubkey, &event.content) { println!("DM: {msg}"); } else { diff --git a/crates/nostr-signer/src/lib.rs b/crates/nostr-signer/src/lib.rs index 5e9c6ce53..dbea39d3c 100644 --- a/crates/nostr-signer/src/lib.rs +++ b/crates/nostr-signer/src/lib.rs @@ -160,7 +160,7 @@ impl NostrSigner { { let content: &[u8] = content.as_ref(); match self { - Self::Keys(keys) => Ok(nip04::encrypt(keys.secret_key()?, public_key, content)?), + 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")] @@ -181,7 +181,7 @@ impl NostrSigner { let encrypted_content: &str = encrypted_content.as_ref(); match self { Self::Keys(keys) => Ok(nip04::decrypt( - keys.secret_key()?, + keys.secret_key(), public_key, encrypted_content, )?), @@ -205,7 +205,7 @@ impl NostrSigner { let content: &[u8] = content.as_ref(); match self { Self::Keys(keys) => Ok(nip44::encrypt( - keys.secret_key()?, + keys.secret_key(), public_key, content, nip44::Version::default(), @@ -229,7 +229,7 @@ impl NostrSigner { { let payload: &[u8] = payload.as_ref(); match self { - Self::Keys(keys) => Ok(nip44::decrypt(keys.secret_key()?, public_key, payload)?), + 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")] diff --git a/crates/nostr-signer/src/nip46/client.rs b/crates/nostr-signer/src/nip46/client.rs index 6f47bb531..61957c485 100644 --- a/crates/nostr-signer/src/nip46/client.rs +++ b/crates/nostr-signer/src/nip46/client.rs @@ -109,7 +109,7 @@ impl Nip46Signer { } async fn send_request(&self, req: Request) -> Result { - let secret_key: &SecretKey = self.app_keys.secret_key()?; + let secret_key: &SecretKey = self.app_keys.secret_key(); let signer_public_key: PublicKey = self.signer_public_key(); // Convert request to event @@ -275,13 +275,13 @@ async fn get_signer_public_key( mut notifications: Receiver, timeout: Duration, ) -> Result { - let secret_key = app_keys.secret_key()?; time::timeout(Some(timeout), async { while let Ok(notification) = notifications.recv().await { if let RelayPoolNotification::Event { event, .. } = notification { if event.kind == Kind::NostrConnect { // Decrypt content - let msg: String = nip04::decrypt(secret_key, &event.pubkey, event.content)?; + let msg: String = + nip04::decrypt(app_keys.secret_key(), &event.pubkey, event.content)?; tracing::debug!("Received Nostr Connect message: '{msg}'"); diff --git a/crates/nostr-signer/src/nip46/signer.rs b/crates/nostr-signer/src/nip46/signer.rs index 2d04d03a3..b74b4242a 100644 --- a/crates/nostr-signer/src/nip46/signer.rs +++ b/crates/nostr-signer/src/nip46/signer.rs @@ -130,7 +130,7 @@ impl NostrConnectRemoteSigner { if let RelayPoolNotification::Event { event, .. } = notification { if event.kind == Kind::NostrConnect { if let Ok(msg) = - nip04::decrypt(self.keys.secret_key()?, &event.pubkey, event.content) + nip04::decrypt(self.keys.secret_key(), &event.pubkey, event.content) { tracing::debug!("New Nostr Connect message received: {msg}"); @@ -160,7 +160,7 @@ impl NostrConnectRemoteSigner { } Request::Nip04Encrypt { public_key, text } => { match nip04::encrypt( - self.keys.secret_key()?, + self.keys.secret_key(), &public_key, text, ) { @@ -178,7 +178,7 @@ impl NostrConnectRemoteSigner { ciphertext, } => { match nip04::decrypt( - self.keys.secret_key()?, + self.keys.secret_key(), &public_key, ciphertext, ) { @@ -193,7 +193,7 @@ impl NostrConnectRemoteSigner { } Request::Nip44Encrypt { public_key, text } => { match nip44::encrypt( - self.keys.secret_key()?, + self.keys.secret_key(), &public_key, text, nip44::Version::default(), @@ -212,7 +212,7 @@ impl NostrConnectRemoteSigner { ciphertext, } => { match nip44::decrypt( - self.keys.secret_key()?, + self.keys.secret_key(), &public_key, ciphertext, ) { diff --git a/crates/nostr/examples/embedded/src/main.rs b/crates/nostr/examples/embedded/src/main.rs index f69664aa4..5dd0e6046 100644 --- a/crates/nostr/examples/embedded/src/main.rs +++ b/crates/nostr/examples/embedded/src/main.rs @@ -86,13 +86,13 @@ fn main() -> ! { fn print_keys(keys: &Keys) { hprintln!( "- Secret Key (hex): {}", - keys.secret_key().unwrap().to_secret_hex() + keys.secret_key().to_secret_hex() ) .unwrap(); hprintln!("- Public Key (hex): {}", keys.public_key()).unwrap(); hprintln!( "- Secret Key (bech32): {}", - keys.secret_key().unwrap().to_bech32().unwrap() + keys.secret_key().to_bech32().unwrap() ) .unwrap(); hprintln!( diff --git a/crates/nostr/examples/keys.rs b/crates/nostr/examples/keys.rs index ea1d2c488..bd22bd1fc 100644 --- a/crates/nostr/examples/keys.rs +++ b/crates/nostr/examples/keys.rs @@ -8,11 +8,11 @@ fn main() -> Result<()> { // Random keys let keys = Keys::generate(); let public_key = keys.public_key(); - let secret_key = keys.secret_key()?; + let secret_key = keys.secret_key(); println!("Public key: {}", public_key); println!("Public key bech32: {}", public_key.to_bech32()?); - println!("Secret key: {}", keys.secret_key()?.to_secret_hex()); + println!("Secret key: {}", secret_key.to_secret_hex()); println!("Secret key bech32: {}", secret_key.to_bech32()?); // Bech32 keys @@ -21,10 +21,5 @@ fn main() -> Result<()> { let keys = Keys::new(secret_key); println!("Public key: {}", keys.public_key()); - let public_key = - PublicKey::from_bech32("npub14f8usejl26twx0dhuxjh9cas7keav9vr0v8nvtwtrjqx3vycc76qqh9nsy")?; - let keys = Keys::from_public_key(public_key); - println!("Public key: {}", keys.public_key()); - Ok(()) } diff --git a/crates/nostr/examples/nip06.rs b/crates/nostr/examples/nip06.rs index b92246424..229cf0bfa 100644 --- a/crates/nostr/examples/nip06.rs +++ b/crates/nostr/examples/nip06.rs @@ -10,7 +10,7 @@ const MNEMONIC_PHRASE: &str = "equal dragon fabric refuse stable cherry smoke al fn main() -> Result<()> { let keys = Keys::from_mnemonic(MNEMONIC_PHRASE, Some("mypassphrase"))?; - println!("{}", keys.secret_key()?.to_bech32()?); + println!("{}", keys.secret_key().to_bech32()?); Ok(()) } diff --git a/crates/nostr/examples/vanity.rs b/crates/nostr/examples/vanity.rs index 1f8c6945c..319404ea6 100644 --- a/crates/nostr/examples/vanity.rs +++ b/crates/nostr/examples/vanity.rs @@ -7,7 +7,7 @@ use nostr::prelude::*; fn main() -> Result<()> { let num_cores = num_cpus::get(); let keys = Keys::vanity(vec!["0000", "yuk", "yuk0"], true, num_cores)?; - println!("Secret key: {}", keys.secret_key()?.to_bech32()?); + println!("Secret key: {}", keys.secret_key().to_bech32()?); println!("Public key: {}", keys.public_key().to_bech32()?); Ok(()) } diff --git a/crates/nostr/src/event/builder.rs b/crates/nostr/src/event/builder.rs index f4a7f51f5..effa80374 100644 --- a/crates/nostr/src/event/builder.rs +++ b/crates/nostr/src/event/builder.rs @@ -782,7 +782,7 @@ impl EventBuilder { ) -> Result { Ok(Self::new( Kind::NostrConnect, - nip04::encrypt(sender_keys.secret_key()?, &receiver_pubkey, msg.as_json())?, + nip04::encrypt(sender_keys.secret_key(), &receiver_pubkey, msg.as_json())?, [Tag::public_key(receiver_pubkey)], )) } @@ -1288,7 +1288,7 @@ impl EventBuilder { // Encrypt content let content: String = nip44::encrypt( - sender_keys.secret_key()?, + sender_keys.secret_key(), receiver_pubkey, rumor.as_json(), Version::default(), @@ -1317,7 +1317,7 @@ impl EventBuilder { let keys: Keys = Keys::generate(); let content: String = nip44::encrypt( - keys.secret_key()?, + keys.secret_key(), receiver, seal.as_json(), Version::default(), diff --git a/crates/nostr/src/event/mod.rs b/crates/nostr/src/event/mod.rs index c7bee07f1..c5e27aeff 100644 --- a/crates/nostr/src/event/mod.rs +++ b/crates/nostr/src/event/mod.rs @@ -881,7 +881,7 @@ mod benches { let json = r#"{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","sig":"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}"#; let event = Event::from_json(json).unwrap(); bh.iter(|| { - black_box(event.verify_id()).unwrap(); + black_box(event.verify_id()); }); } @@ -890,7 +890,7 @@ mod benches { let json = r#"{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","sig":"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}"#; let event = Event::from_json(json).unwrap(); bh.iter(|| { - black_box(event.verify_signature()).unwrap(); + black_box(event.verify_signature()); }); } } diff --git a/crates/nostr/src/event/unsigned.rs b/crates/nostr/src/event/unsigned.rs index eea4f71c9..d5760a697 100644 --- a/crates/nostr/src/event/unsigned.rs +++ b/crates/nostr/src/event/unsigned.rs @@ -169,7 +169,7 @@ impl UnsignedEvent { let verify_id: bool = self.id.is_some(); let id: EventId = self.id.unwrap_or_else(|| self.compute_id()); let message: Message = Message::from_digest(id.to_bytes()); - let sig: Signature = keys.sign_schnorr_with_ctx(secp, &message, rng)?; + let sig: Signature = keys.sign_schnorr_with_ctx(secp, &message, rng); self.internal_add_signature(secp, id, sig, verify_id, false) } diff --git a/crates/nostr/src/key/mod.rs b/crates/nostr/src/key/mod.rs index 647ae17f7..92eda9b76 100644 --- a/crates/nostr/src/key/mod.rs +++ b/crates/nostr/src/key/mod.rs @@ -5,7 +5,9 @@ //! Keys +use core::cmp::Ordering; use core::fmt; +use core::hash::{Hash, Hasher}; #[cfg(feature = "std")] use core::str::FromStr; @@ -14,6 +16,10 @@ use bitcoin::secp256k1::rand::rngs::OsRng; use bitcoin::secp256k1::rand::{CryptoRng, Rng}; use bitcoin::secp256k1::schnorr::Signature; use bitcoin::secp256k1::{self, Keypair, Message, Secp256k1, Signing, XOnlyPublicKey}; +#[cfg(feature = "std")] +use once_cell::sync::OnceCell; // TODO: when MSRV will be >= 1.70.0, use `std::cell::OnceLock` instead and remove `once_cell` dep. +#[cfg(not(feature = "std"))] +use once_cell::unsync::OnceCell; // TODO: when MSRV will be >= 1.70.0, use `core::cell::OnceCell` instead and remove `once_cell` dep. pub mod public_key; pub mod secret_key; @@ -32,8 +38,6 @@ pub enum Error { InvalidSecretKey, /// Invalid public key InvalidPublicKey, - /// Secret key missing - SkMissing, /// Unsupported char InvalidChar(char), /// Secp256k1 error @@ -48,7 +52,6 @@ impl fmt::Display for Error { match self { Self::InvalidSecretKey => write!(f, "Invalid secret key"), Self::InvalidPublicKey => write!(f, "Invalid public key"), - Self::SkMissing => write!(f, "Secret key missing"), Self::InvalidChar(c) => write!(f, "Unsupported char: {c}"), Self::Secp256k1(e) => write!(f, "Secp256k1: {e}"), } @@ -62,11 +65,45 @@ impl From for Error { } /// Keys -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone)] pub struct Keys { public_key: PublicKey, - key_pair: Option, - secret_key: Option, + secret_key: SecretKey, + key_pair: OnceCell, +} + +impl fmt::Debug for Keys { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Keys") + .field("public_key", &self.public_key) + .finish() + } +} + +impl PartialEq for Keys { + fn eq(&self, other: &Self) -> bool { + self.public_key == other.public_key + } +} + +impl Eq for Keys {} + +impl PartialOrd for Keys { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Keys { + fn cmp(&self, other: &Self) -> Ordering { + self.public_key.cmp(&other.public_key) + } +} + +impl Hash for Keys { + fn hash(&self, state: &mut H) { + self.public_key.hash(state) + } } #[cfg(feature = "std")] @@ -77,7 +114,7 @@ impl Keys { Self::new_with_ctx(&SECP256K1, secret_key) } - /// Try to parse [Keys] from **secret key** `hex` or `bech32` + /// Parse secret key from `hex` or `bech32` and compose keys #[inline] pub fn parse(secret_key: S) -> Result where @@ -104,17 +141,20 @@ impl Keys { /// Generate random [`Keys`] with custom [`Rng`] and without [`Keypair`] /// /// Useful for faster [`Keys`] generation (ex. vanity pubkey mining) - #[inline] + #[deprecated( + since = "0.35.0", + note = "Use `generate` or `generate_with_rng` instead" + )] pub fn generate_without_keypair(rng: &mut R) -> Self where R: Rng + ?Sized, { - Self::generate_without_keypair_with_ctx(&SECP256K1, rng) + Self::generate_with_rng(rng) } /// Sign schnorr [`Message`] #[inline] - pub fn sign_schnorr(&self, message: &Message) -> Result { + pub fn sign_schnorr(&self, message: &Message) -> Signature { self.sign_schnorr_with_ctx(&SECP256K1, message, &mut OsRng) } } @@ -125,13 +165,13 @@ impl Keys { where C: Signing, { - let key_pair = Keypair::from_secret_key(secp, &secret_key); - let public_key = XOnlyPublicKey::from_keypair(&key_pair).0; + let key_pair: Keypair = Keypair::from_secret_key(secp, &secret_key); + let public_key: XOnlyPublicKey = XOnlyPublicKey::from_keypair(&key_pair).0; Self { public_key: PublicKey::from(public_key), - key_pair: Some(key_pair), - secret_key: Some(secret_key), + secret_key, + key_pair: OnceCell::from(key_pair), } } @@ -146,16 +186,6 @@ impl Keys { Ok(Self::new_with_ctx(secp, secret_key)) } - /// Initialize with public key only (no secret key). - #[inline] - pub fn from_public_key(public_key: PublicKey) -> Self { - Self { - public_key, - key_pair: None, - secret_key: None, - } - } - /// Generate random [`Keys`] with custom [`Rng`] #[inline] pub fn generate_with_ctx(secp: &Secp256k1, rng: &mut R) -> Self @@ -163,24 +193,24 @@ impl Keys { C: Signing, R: Rng + ?Sized, { - let secret_key: SecretKey = SecretKey::generate_with_ctx(secp, rng); - Self::new_with_ctx(secp, secret_key) + let (secret_key, public_key) = secp.generate_keypair(rng); + let (public_key, _) = public_key.x_only_public_key(); + Self { + public_key: PublicKey::from(public_key), + secret_key: SecretKey::from(secret_key), + key_pair: OnceCell::new(), + } } /// Generate random [`Keys`] with custom [`Rng`] and without [`Keypair`] /// Useful for faster [`Keys`] generation (ex. vanity pubkey mining) + #[deprecated(since = "0.35.0", note = "Use `generate_with_ctx` instead")] pub fn generate_without_keypair_with_ctx(secp: &Secp256k1, rng: &mut R) -> Self where C: Signing, R: Rng + ?Sized, { - let (secret_key, public_key) = secp.generate_keypair(rng); - let (public_key, _) = public_key.x_only_public_key(); - Self { - public_key: PublicKey::from(public_key), - key_pair: None, - secret_key: Some(SecretKey::from(secret_key)), - } + Self::generate_with_ctx(secp, rng) } /// Get public key @@ -197,27 +227,18 @@ impl Keys { /// Get secret key #[inline] - pub fn secret_key(&self) -> Result<&SecretKey, Error> { - if let Some(secret_key) = &self.secret_key { - Ok(secret_key) - } else { - Err(Error::SkMissing) - } + pub fn secret_key(&self) -> &SecretKey { + &self.secret_key } /// Get keypair - /// - /// If not exists, will be created - pub fn key_pair(&self, secp: &Secp256k1) -> Result + #[inline] + pub fn key_pair(&self, secp: &Secp256k1) -> &Keypair where C: Signing, { - if let Some(key_pair) = self.key_pair { - Ok(key_pair) - } else { - let secret_key = self.secret_key()?; - Ok(Keypair::from_secret_key(secp, secret_key)) - } + self.key_pair + .get_or_init(|| Keypair::from_secret_key(secp, &self.secret_key)) } /// Sign schnorr [`Message`] @@ -226,13 +247,13 @@ impl Keys { secp: &Secp256k1, message: &Message, rng: &mut R, - ) -> Result + ) -> Signature where C: Signing, R: Rng + CryptoRng, { - let keypair: &Keypair = &self.key_pair(secp)?; - Ok(secp.sign_schnorr_with_rng(message, keypair, rng)) + let keypair: &Keypair = self.key_pair(secp); + secp.sign_schnorr_with_rng(message, keypair, rng) } } @@ -249,6 +270,20 @@ impl FromStr for Keys { impl Drop for Keys { fn drop(&mut self) { - self.secret_key = None; + self.secret_key.non_secure_erase(); + } +} + +#[cfg(bench)] +mod benches { + use test::{black_box, Bencher}; + + use super::*; + + #[bench] + pub fn generate_keys(bh: &mut Bencher) { + bh.iter(|| { + black_box(Keys::generate()); + }); } } diff --git a/crates/nostr/src/key/vanity.rs b/crates/nostr/src/key/vanity.rs index 245bb44b3..411bcc25d 100644 --- a/crates/nostr/src/key/vanity.rs +++ b/crates/nostr/src/key/vanity.rs @@ -89,7 +89,7 @@ impl Keys { break; } - let keys = Keys::generate_without_keypair(&mut rng); + let keys: Keys = Keys::generate_with_rng(&mut rng); if bech32 { let bech32_key = keys diff --git a/crates/nostr/src/nips/nip04.rs b/crates/nostr/src/nips/nip04.rs index f3748643c..08b0ac7d5 100644 --- a/crates/nostr/src/nips/nip04.rs +++ b/crates/nostr/src/nips/nip04.rs @@ -194,22 +194,16 @@ mod tests { let content = String::from("Saturn, bringer of old age"); - let encrypted_content = - encrypt(sender_keys.secret_key().unwrap(), &receiver_pk, &content).unwrap(); + let encrypted_content = encrypt(sender_keys.secret_key(), &receiver_pk, &content).unwrap(); assert_eq!( - decrypt( - receiver_keys.secret_key().unwrap(), - &sender_pk, - encrypted_content - ) - .unwrap(), + decrypt(receiver_keys.secret_key(), &sender_pk, encrypted_content).unwrap(), content ); assert_eq!( decrypt( - receiver_keys.secret_key().unwrap(), + receiver_keys.secret_key(), &sender_pk, encrypted_content_from_outside ) @@ -219,7 +213,7 @@ mod tests { assert_eq!( decrypt( - sender_keys.secret_key().unwrap(), + sender_keys.secret_key(), &receiver_pk, "invalidcontentformat" ) @@ -228,7 +222,7 @@ mod tests { ); assert_eq!( decrypt( - sender_keys.secret_key().unwrap(), + sender_keys.secret_key(), &receiver_pk, "badbase64?iv=encode" ) @@ -239,7 +233,7 @@ mod tests { // Content encrypted with aes256 using GCM mode assert_eq!( decrypt( - sender_keys.secret_key().unwrap(), + sender_keys.secret_key(), &receiver_pk, "nseh0cQPEFID5C0CxYdcPwp091NhRQ==?iv=8PHy8/T19vf4+fr7/P3+/w==" ) diff --git a/crates/nostr/src/nips/nip06.rs b/crates/nostr/src/nips/nip06.rs index c3c56ee52..9f056add0 100644 --- a/crates/nostr/src/nips/nip06.rs +++ b/crates/nostr/src/nips/nip06.rs @@ -194,7 +194,7 @@ mod tests { let keys = Keys::from_mnemonic_with_ctx(&secp, mnemonic, None, None, None, None).unwrap(); assert_eq!( - keys.secret_key().unwrap(), + keys.secret_key(), &SecretKey::from_str(expected_secret_key).unwrap() ); } diff --git a/crates/nostr/src/nips/nip26.rs b/crates/nostr/src/nips/nip26.rs index 00026afa6..49ee3d964 100644 --- a/crates/nostr/src/nips/nip26.rs +++ b/crates/nostr/src/nips/nip26.rs @@ -128,7 +128,7 @@ pub fn sign_delegation( delegator_keys: &Keys, delegatee_pk: &PublicKey, conditions: &Conditions, -) -> Result { +) -> Signature { sign_delegation_with_ctx( &SECP256K1, &mut rand::thread_rng(), @@ -146,15 +146,15 @@ pub fn sign_delegation_with_ctx( delegator_keys: &Keys, delegatee_pk: &PublicKey, conditions: &Conditions, -) -> Result +) -> Signature where C: Signing, R: Rng + CryptoRng, { let unhashed_token = DelegationToken::new(delegatee_pk, conditions); let hashed_token = Sha256Hash::hash(unhashed_token.as_bytes()); - let message = Message::from_digest_slice(hashed_token.as_byte_array())?; - Ok(delegator_keys.sign_schnorr_with_ctx(secp, &message, rng)?) + let message: Message = Message::from_digest(hashed_token.to_byte_array()); + delegator_keys.sign_schnorr_with_ctx(secp, &message, rng) } /// Verify delegation signature @@ -188,7 +188,7 @@ where { let unhashed_token = DelegationToken::new(delegatee_public_key, conditions); let hashed_token = Sha256Hash::hash(unhashed_token.as_bytes()); - let message = Message::from_digest_slice(hashed_token.as_byte_array())?; + let message = Message::from_digest(hashed_token.to_byte_array()); secp.verify_schnorr(&signature, &message, delegator_public_key)?; Ok(()) } @@ -236,7 +236,7 @@ impl DelegationTag { delegator_keys: &Keys, delegatee_pubkey: &PublicKey, conditions: Conditions, - ) -> Result { + ) -> Self { Self::new_with_ctx( &SECP256K1, &mut rand::thread_rng(), @@ -253,18 +253,18 @@ impl DelegationTag { delegator_keys: &Keys, delegatee_pubkey: &PublicKey, conditions: Conditions, - ) -> Result + ) -> Self where C: Signing, R: Rng + CryptoRng, { - let signature = - sign_delegation_with_ctx(secp, rng, delegator_keys, delegatee_pubkey, &conditions)?; - Ok(Self { + let signature: Signature = + sign_delegation_with_ctx(secp, rng, delegator_keys, delegatee_pubkey, &conditions); + Self { delegator_pubkey: delegator_keys.public_key(), conditions, signature, - }) + } } /// Get delegator public key @@ -602,8 +602,7 @@ mod tests { let conditions = Conditions::from_str("kind=1&created_at>1676067553&created_at<1678659553").unwrap(); - let tag = - DelegationTag::new(&delegator_keys, &delegatee_pubkey, conditions.clone()).unwrap(); + let tag = DelegationTag::new(&delegator_keys, &delegatee_pubkey, conditions.clone()); // Verify signature (it's variable) let verify_result = verify_delegation_signature( @@ -634,7 +633,7 @@ mod tests { let conditions = Conditions::from_str("kind=1&created_at>1676067553&created_at<1678659553").unwrap(); - let tag = DelegationTag::new(&delegator_keys, &delegatee_pubkey, conditions).unwrap(); + let tag = DelegationTag::new(&delegator_keys, &delegatee_pubkey, conditions); assert!(tag .validate(&delegatee_pubkey, &EventProperties::new(1, 1677000000)) @@ -694,8 +693,7 @@ mod tests { let conditions = Conditions::from_str("kind=1&created_at>1674834236&created_at<1677426236").unwrap(); - let signature = - sign_delegation(&delegator_keys, &delegatee_public_key, &conditions).unwrap(); + let signature = sign_delegation(&delegator_keys, &delegatee_public_key, &conditions); // Signature is changing, validate by verify method let verify_result = verify_delegation_signature( @@ -720,8 +718,7 @@ mod tests { let conditions = Conditions::from_str("kind=1&created_at>1674834236&created_at<1677426236").unwrap(); - let signature = - sign_delegation(&delegator_keys, &delegatee_public_key, &conditions).unwrap(); + let signature = sign_delegation(&delegator_keys, &delegatee_public_key, &conditions); // Signature is changing, validate by lowlevel verify let unhashed_token: String = @@ -824,7 +821,7 @@ mod tests { let conditions = Conditions::from_str("kind=1&created_at>1676067553&created_at<1678659553").unwrap(); - let tag = DelegationTag::new(&delegator_keys, &delegatee_pubkey, conditions).unwrap(); + let tag = DelegationTag::new(&delegator_keys, &delegatee_pubkey, conditions); // Positive assert!(tag diff --git a/crates/nostr/src/nips/nip44/mod.rs b/crates/nostr/src/nips/nip44/mod.rs index 1c1e70576..6a6e515c9 100644 --- a/crates/nostr/src/nips/nip44/mod.rs +++ b/crates/nostr/src/nips/nip44/mod.rs @@ -194,15 +194,10 @@ mod tests { let bob_pk = bob_keys.public_key(); let content = String::from("hello"); - let encrypted_content = encrypt( - alice_keys.secret_key().unwrap(), - &bob_pk, - &content, - Version::V2, - ) - .unwrap(); + let encrypted_content = + encrypt(alice_keys.secret_key(), &bob_pk, &content, Version::V2).unwrap(); assert_eq!( - decrypt(bob_keys.secret_key().unwrap(), &alice_pk, encrypted_content).unwrap(), + decrypt(bob_keys.secret_key(), &alice_pk, encrypted_content).unwrap(), content ); } diff --git a/crates/nostr/src/nips/nip57.rs b/crates/nostr/src/nips/nip57.rs index 66d92dc8a..f62e286e7 100644 --- a/crates/nostr/src/nips/nip57.rs +++ b/crates/nostr/src/nips/nip57.rs @@ -300,7 +300,7 @@ where // Create encryption key let secret_key: SecretKey = - create_encryption_key(keys.secret_key()?, &data.public_key, created_at)?; + create_encryption_key(keys.secret_key(), &data.public_key, created_at)?; // Compose encrypted message let mut tags: Vec = vec![Tag::public_key(data.public_key)]; @@ -443,7 +443,7 @@ mod tests { let private_zap = private_zap_request(data, &alice_keys).unwrap(); let private_zap_msg = decrypt_sent_private_zap_message( - alice_keys.secret_key().unwrap(), + alice_keys.secret_key(), &bob_keys.public_key(), &private_zap, ) @@ -452,8 +452,7 @@ mod tests { assert_eq!(msg, &private_zap_msg.content); let private_zap_msg = - decrypt_received_private_zap_message(bob_keys.secret_key().unwrap(), &private_zap) - .unwrap(); + decrypt_received_private_zap_message(bob_keys.secret_key(), &private_zap).unwrap(); assert_eq!(msg, &private_zap_msg.content) } diff --git a/crates/nostr/src/nips/nip59.rs b/crates/nostr/src/nips/nip59.rs index d201f6b18..c367d59a0 100644 --- a/crates/nostr/src/nips/nip59.rs +++ b/crates/nostr/src/nips/nip59.rs @@ -111,7 +111,7 @@ impl UnwrappedGift { return Err(Error::NotGiftWrap); } - let secret_key: &SecretKey = receiver_keys.secret_key()?; + 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)?;