diff --git a/CHANGELOG.md b/CHANGELOG.md index 63decc028..c0dbb2324 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ ### Changed +* nostr: refactor `PublicKey` to use byte array internally ([Yuki Kishimoto]) * pool: update `Error::WebSocket` variant inner type ([Yuki Kishimoto]) ### Added diff --git a/bindings/nostr-sdk-ffi/src/protocol/util.rs b/bindings/nostr-sdk-ffi/src/protocol/util.rs index 6206a7e19..db9a41c42 100644 --- a/bindings/nostr-sdk-ffi/src/protocol/util.rs +++ b/bindings/nostr-sdk-ffi/src/protocol/util.rs @@ -17,8 +17,8 @@ use crate::error::{NostrSdkError, Result}; /// **Important: use of a strong cryptographic hash function may be critical to security! Do NOT use /// unless you understand cryptographical implications.** #[uniffi::export] -pub fn generate_shared_key(secret_key: &SecretKey, public_key: &PublicKey) -> Vec { - util::generate_shared_key(secret_key.deref(), public_key.deref()).to_vec() +pub fn generate_shared_key(secret_key: &SecretKey, public_key: &PublicKey) -> Result> { + Ok(util::generate_shared_key(secret_key.deref(), public_key.deref())?.to_vec()) } #[derive(Enum)] diff --git a/bindings/nostr-sdk-js/src/protocol/util.rs b/bindings/nostr-sdk-js/src/protocol/util.rs index e2287eba0..f0c573492 100644 --- a/bindings/nostr-sdk-js/src/protocol/util.rs +++ b/bindings/nostr-sdk-js/src/protocol/util.rs @@ -7,6 +7,7 @@ use std::ops::Deref; use nostr_sdk::prelude::*; use wasm_bindgen::prelude::*; +use crate::error::{into_err, Result}; use crate::protocol::key::{JsPublicKey, JsSecretKey}; /// Generate shared key @@ -14,6 +15,10 @@ use crate::protocol::key::{JsPublicKey, JsSecretKey}; /// **Important: use of a strong cryptographic hash function may be critical to security! Do NOT use /// unless you understand cryptographical implications.** #[wasm_bindgen(js_name = generateSharedKey)] -pub fn generate_shared_key(secret_key: &JsSecretKey, public_key: &JsPublicKey) -> Vec { - util::generate_shared_key(secret_key.deref(), public_key.deref()).to_vec() +pub fn generate_shared_key(secret_key: &JsSecretKey, public_key: &JsPublicKey) -> Result> { + Ok( + util::generate_shared_key(secret_key.deref(), public_key.deref()) + .map_err(into_err)? + .to_vec(), + ) } diff --git a/crates/nostr-database/src/flatbuffers/mod.rs b/crates/nostr-database/src/flatbuffers/mod.rs index c85de1b18..1d94adcae 100644 --- a/crates/nostr-database/src/flatbuffers/mod.rs +++ b/crates/nostr-database/src/flatbuffers/mod.rs @@ -28,8 +28,6 @@ pub enum Error { FlatBuffer(InvalidFlatbuffer), /// Tag error Tag(tag::Error), - /// Key error - Key(key::Error), /// Secp256k1 error Secp256k1(secp256k1::Error), /// Not found @@ -43,7 +41,6 @@ impl fmt::Display for Error { match self { Self::FlatBuffer(e) => write!(f, "{e}"), Self::Tag(e) => write!(f, "{e}"), - Self::Key(e) => write!(f, "{e}"), Self::Secp256k1(e) => write!(f, "{e}"), Self::NotFound => write!(f, "not found"), } @@ -62,12 +59,6 @@ impl From for Error { } } -impl From for Error { - fn from(e: key::Error) -> Self { - Self::Key(e) - } -} - impl From for Error { fn from(e: secp256k1::Error) -> Self { Self::Secp256k1(e) @@ -96,8 +87,8 @@ impl FlatBufferEncode for Event { fn encode<'a>(&self, fbb: &'a mut FlatBufferBuilder) -> &'a [u8] { fbb.reset(); - let id = event_fbs::Fixed32Bytes::new(&self.id.to_bytes()); - let pubkey = event_fbs::Fixed32Bytes::new(&self.pubkey.to_bytes()); + let id = event_fbs::Fixed32Bytes::new(self.id.as_bytes()); + let pubkey = event_fbs::Fixed32Bytes::new(self.pubkey.as_bytes()); let sig = event_fbs::Fixed64Bytes::new(self.sig.as_ref()); let tags = self .tags @@ -144,7 +135,7 @@ impl FlatBufferDecode for Event { Ok(Self::new( EventId::from_byte_array(ev.id().ok_or(Error::NotFound)?.0), - PublicKey::from_slice(&ev.pubkey().ok_or(Error::NotFound)?.0)?, + PublicKey::from_byte_array(ev.pubkey().ok_or(Error::NotFound)?.0), Timestamp::from(ev.created_at()), Kind::from(ev.kind() as u16), tags, diff --git a/crates/nostr-lmdb/src/store/lmdb/mod.rs b/crates/nostr-lmdb/src/store/lmdb/mod.rs index 1aa251309..d7a69e352 100644 --- a/crates/nostr-lmdb/src/store/lmdb/mod.rs +++ b/crates/nostr-lmdb/src/store/lmdb/mod.rs @@ -166,7 +166,7 @@ impl Lmdb { // Index by author and kind (with created_at and id) let akc_index_key: Vec = index::make_akc_index_key( - &event.pubkey.to_bytes(), + event.pubkey.as_bytes(), event.kind.as_u16(), &event.created_at, event.id.as_bytes(), @@ -175,7 +175,7 @@ impl Lmdb { // Index by author (with created_at and id) let ac_index_key: Vec = index::make_ac_index_key( - &event.pubkey.to_bytes(), + event.pubkey.as_bytes(), &event.created_at, event.id.as_bytes(), ); @@ -185,7 +185,7 @@ impl Lmdb { if let (Some(tag_name), Some(tag_value)) = (tag.single_letter_tag(), tag.content()) { // Index by author and tag (with created_at and id) let atc_index_key: Vec = index::make_atc_index_key( - &event.pubkey.to_bytes(), + event.pubkey.as_bytes(), &tag_name, tag_value, &event.created_at, @@ -574,7 +574,7 @@ impl Lmdb { let mut iter = self.akc_iter( txn, - &author.to_bytes(), + author.as_bytes(), kind.as_u16(), Timestamp::min(), Timestamp::max(), @@ -599,7 +599,7 @@ impl Lmdb { let iter = self.atc_iter( txn, - &addr.public_key.to_bytes(), + addr.public_key.as_bytes(), &SingleLetterTag::lowercase(Alphabet::D), &addr.identifier, &Timestamp::min(), @@ -636,7 +636,7 @@ impl Lmdb { let iter = self.akc_iter( read_txn, - &coordinate.public_key.to_bytes(), + coordinate.public_key.as_bytes(), coordinate.kind.as_u16(), Timestamp::zero(), until, @@ -668,7 +668,7 @@ impl Lmdb { let iter = self.atc_iter( read_txn, - &coordinate.public_key.to_bytes(), + coordinate.public_key.as_bytes(), &SingleLetterTag::lowercase(Alphabet::D), &coordinate.identifier, &Timestamp::min(), diff --git a/crates/nostr-lmdb/src/store/mod.rs b/crates/nostr-lmdb/src/store/mod.rs index 295988467..a9d938cd4 100644 --- a/crates/nostr-lmdb/src/store/mod.rs +++ b/crates/nostr-lmdb/src/store/mod.rs @@ -183,7 +183,7 @@ impl Store { for id in event.tags.event_ids() { if let Some(target) = db.get_event_by_id(read_txn, id.as_bytes())? { // Author must match - if target.author() != &event.pubkey.to_bytes() { + if target.author() != event.pubkey.as_bytes() { return Ok(true); } diff --git a/crates/nostr-lmdb/src/store/types/filter.rs b/crates/nostr-lmdb/src/store/types/filter.rs index 30551236a..21abb0963 100644 --- a/crates/nostr-lmdb/src/store/types/filter.rs +++ b/crates/nostr-lmdb/src/store/types/filter.rs @@ -111,7 +111,7 @@ impl From for DatabaseFilter { .map(|authors| { authors .into_iter() - .map(|pubkey| Fixed32Bytes::new(&pubkey.to_bytes())) + .map(|pubkey| Fixed32Bytes::new(pubkey.as_bytes())) .collect() }) .unwrap_or_default(), diff --git a/crates/nostr-ndb/src/lib.rs b/crates/nostr-ndb/src/lib.rs index 383bc8eab..b2ce96c2c 100644 --- a/crates/nostr-ndb/src/lib.rs +++ b/crates/nostr-ndb/src/lib.rs @@ -187,7 +187,7 @@ fn ndb_filter_conversion(f: Filter) -> nostrdb::Filter { if let Some(authors) = f.authors { if !authors.is_empty() { - let authors: Vec<[u8; 32]> = authors.into_iter().map(|p| p.serialize()).collect(); + let authors: Vec<[u8; 32]> = authors.into_iter().map(|p| p.to_bytes()).collect(); filter = filter.authors(authors.iter()); } } @@ -221,7 +221,7 @@ fn ndb_filter_conversion(f: Filter) -> nostrdb::Filter { fn ndb_note_to_event(note: Note) -> Result { let id = EventId::from_byte_array(*note.id()); - let public_key = PublicKey::from_slice(note.pubkey()).map_err(DatabaseError::backend)?; + let public_key = PublicKey::from_byte_array(*note.pubkey()); let sig = Signature::from_slice(note.sig()).map_err(DatabaseError::backend)?; let tags: Vec = ndb_note_to_tags(¬e)?; diff --git a/crates/nostr/src/event/mod.rs b/crates/nostr/src/event/mod.rs index 15dc7a195..3ee257ad9 100644 --- a/crates/nostr/src/event/mod.rs +++ b/crates/nostr/src/event/mod.rs @@ -242,8 +242,12 @@ impl Event { C: Verification, { let message: Message = Message::from_digest(self.id.to_bytes()); - secp.verify_schnorr(&self.sig, &message, &self.pubkey) - .is_ok() + match self.pubkey.xonly() { + Ok(public_key) => secp + .verify_schnorr(&self.sig, &message, &public_key) + .is_ok(), + Err(..) => false, // TODO: return error? + } } /// Check POW diff --git a/crates/nostr/src/key/mod.rs b/crates/nostr/src/key/mod.rs index e157ad14a..e4017879d 100644 --- a/crates/nostr/src/key/mod.rs +++ b/crates/nostr/src/key/mod.rs @@ -32,6 +32,7 @@ pub use self::public_key::PublicKey; pub use self::secret_key::SecretKey; #[cfg(feature = "std")] use crate::signer::{NostrSigner, SignerBackend, SignerError}; +use crate::util::hex; #[cfg(feature = "std")] use crate::{Event, UnsignedEvent, SECP256K1}; @@ -40,6 +41,8 @@ use crate::{Event, UnsignedEvent, SECP256K1}; pub enum Error { /// Secp256k1 error Secp256k1(secp256k1::Error), + /// Hex decode error + Hex(hex::Error), /// Invalid secret key InvalidSecretKey, /// Invalid public key @@ -55,6 +58,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Secp256k1(e) => write!(f, "{e}"), + Self::Hex(e) => write!(f, "{e}"), Self::InvalidSecretKey => write!(f, "Invalid secret key"), Self::InvalidPublicKey => write!(f, "Invalid public key"), Self::InvalidChar(c) => write!(f, "Unsupported char: {c}"), @@ -68,6 +72,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: hex::Error) -> Self { + Self::Hex(e) + } +} + /// Nostr keys #[derive(Clone)] pub struct Keys { diff --git a/crates/nostr/src/key/public_key.rs b/crates/nostr/src/key/public_key.rs index 7cb3e0918..7ed9806a2 100644 --- a/crates/nostr/src/key/public_key.rs +++ b/crates/nostr/src/key/public_key.rs @@ -5,8 +5,9 @@ //! Public key use alloc::string::String; +use core::cmp::Ordering; use core::fmt; -use core::ops::Deref; +use core::hash::{Hash, Hasher}; use core::str::FromStr; use bitcoin::secp256k1::XOnlyPublicKey; @@ -18,28 +19,40 @@ use crate::nips::nip21::NostrURI; use crate::util::hex; /// Public Key -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, Copy)] pub struct PublicKey { - inner: XOnlyPublicKey, + buf: [u8; 32], } -impl Deref for PublicKey { - type Target = XOnlyPublicKey; +impl fmt::Debug for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "PublicKey({})", self.to_hex()) + } +} - fn deref(&self) -> &Self::Target { - &self.inner +impl PartialEq for PublicKey { + fn eq(&self, other: &Self) -> bool { + self.buf == other.buf } } -impl From for PublicKey { - fn from(inner: XOnlyPublicKey) -> Self { - Self { inner } +impl Eq for PublicKey {} + +impl PartialOrd for PublicKey { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) } } -impl fmt::Debug for PublicKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "PublicKey({})", self.to_hex()) +impl Ord for PublicKey { + fn cmp(&self, other: &Self) -> Ordering { + self.buf.cmp(&other.buf) + } +} + +impl Hash for PublicKey { + fn hash(&self, state: &mut H) { + self.buf.hash(state); } } @@ -49,10 +62,24 @@ impl fmt::Display for PublicKey { } } +impl From for PublicKey { + fn from(inner: XOnlyPublicKey) -> Self { + Self { + buf: inner.serialize(), + } + } +} + impl PublicKey { /// Public Key len pub const LEN: usize = 32; + /// Construct from 32-byte array + #[inline] + pub const fn from_byte_array(bytes: [u8; Self::LEN]) -> Self { + Self { buf: bytes } + } + /// Parse from `hex`, `bech32` or [NIP21](https://github.com/nostr-protocol/nips/blob/master/21.md) uri pub fn parse(public_key: &str) -> Result { // Try from hex @@ -73,32 +100,50 @@ impl PublicKey { Err(Error::InvalidPublicKey) } - /// Parse from `bytes` - #[inline] - pub fn from_slice(slice: &[u8]) -> Result { - Ok(Self { - inner: XOnlyPublicKey::from_slice(slice)?, - }) + /// Parse from hex string + pub fn from_hex(hex: &str) -> Result { + let mut bytes: [u8; Self::LEN] = [0u8; Self::LEN]; + hex::decode_to_slice(hex, &mut bytes)?; + Ok(Self::from_byte_array(bytes)) } - /// Parse from `hex` string - #[inline] - pub fn from_hex(hex: &str) -> Result { - Ok(Self { - inner: XOnlyPublicKey::from_str(hex)?, - }) + /// Parse from bytes + pub fn from_slice(slice: &[u8]) -> Result { + // Check len + if slice.len() != Self::LEN { + return Err(Error::InvalidPublicKey); + } + + // Copy bytes + let mut bytes: [u8; Self::LEN] = [0u8; Self::LEN]; + bytes.copy_from_slice(slice); + + // Construct + Ok(Self::from_byte_array(bytes)) } /// Get public key as `hex` string #[inline] pub fn to_hex(&self) -> String { - hex::encode(self.to_bytes()) + hex::encode(self.as_bytes()) + } + + /// Get as bytes + #[inline] + pub fn as_bytes(&self) -> &[u8; Self::LEN] { + &self.buf } /// Get public key as `bytes` #[inline] - pub fn to_bytes(&self) -> [u8; Self::LEN] { - self.inner.serialize() + pub fn to_bytes(self) -> [u8; Self::LEN] { + self.buf + } + + /// Get the x-only public key + pub fn xonly(&self) -> Result { + // TODO: use a OnceCell + Ok(XOnlyPublicKey::from_slice(self.as_bytes())?) } } @@ -143,7 +188,7 @@ mod tests { use super::*; #[test] - pub fn test_public_key_parse() { + fn test_public_key_parse() { let public_key = PublicKey::parse( "nostr:npub14f8usejl26twx0dhuxjh9cas7keav9vr0v8nvtwtrjqx3vycc76qqh9nsy", ) @@ -153,6 +198,23 @@ mod tests { "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4" ); } + + #[test] + fn test_as_xonly() { + let hex_pk: &str = "aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4"; + + let public_key = PublicKey::from_hex(hex_pk).unwrap(); + + //assert!(public_key.xonly.is_null()); + + let expected = XOnlyPublicKey::from_str(hex_pk).unwrap(); + let xonly = public_key.xonly().unwrap(); + assert_eq!(&xonly, &expected); + + let public_key = PublicKey::from(expected); + let xonly = public_key.xonly().unwrap(); + assert_eq!(&xonly, &expected); + } } #[cfg(bench)] diff --git a/crates/nostr/src/nips/nip04.rs b/crates/nostr/src/nips/nip04.rs index d3b46d85e..2ceb33e0d 100644 --- a/crates/nostr/src/nips/nip04.rs +++ b/crates/nostr/src/nips/nip04.rs @@ -22,7 +22,7 @@ use bitcoin::secp256k1::rand; use bitcoin::secp256k1::rand::RngCore; use cbc::{Decryptor, Encryptor}; -use crate::{util, PublicKey, SecretKey}; +use crate::{key, util, PublicKey, SecretKey}; type Aes256CbcEnc = Encryptor; type Aes256CbcDec = Decryptor; @@ -30,6 +30,8 @@ type Aes256CbcDec = Decryptor; /// `NIP04` error #[derive(Debug, Eq, PartialEq)] pub enum Error { + /// Key error + Key(key::Error), /// Invalid content format InvalidContentFormat, /// Error while decoding from base64 @@ -46,6 +48,7 @@ impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::Key(e) => write!(f, "{e}"), Self::InvalidContentFormat => write!(f, "Invalid NIP04 content format"), Self::Base64Decode => write!(f, "Error while decoding NIP04 from base64"), Self::Utf8Encode => write!(f, "Error while encoding NIP04 to UTF-8"), @@ -57,6 +60,12 @@ impl fmt::Display for Error { } } +impl From for Error { + fn from(e: key::Error) -> Self { + Self::Key(e) + } +} + /// Encrypt /// ///
Unsecure! Deprecated in favor of NIP17!
@@ -87,7 +96,7 @@ where T: AsRef<[u8]>, { // Generate key - let key: [u8; 32] = util::generate_shared_key(secret_key, public_key); + let key: [u8; 32] = util::generate_shared_key(secret_key, public_key)?; // Generate iv let mut iv: [u8; 16] = [0u8; 16]; @@ -130,7 +139,7 @@ where let iv: Vec = general_purpose::STANDARD .decode(parsed_content[1]) .map_err(|_| Error::Base64Decode)?; - let key: [u8; 32] = util::generate_shared_key(secret_key, public_key); + let key: [u8; 32] = util::generate_shared_key(secret_key, public_key)?; let cipher = Aes256CbcDec::new(&key.into(), iv.as_slice().into()); let result = cipher diff --git a/crates/nostr/src/nips/nip19.rs b/crates/nostr/src/nips/nip19.rs index 2697e1e78..f00953d55 100644 --- a/crates/nostr/src/nips/nip19.rs +++ b/crates/nostr/src/nips/nip19.rs @@ -344,7 +344,7 @@ impl ToBech32 for PublicKey { type Err = Error; fn to_bech32(&self) -> Result { - Ok(bech32::encode::(HRP_PUBLIC_KEY, &self.serialize())?) + Ok(bech32::encode::(HRP_PUBLIC_KEY, self.as_bytes())?) } } @@ -597,7 +597,7 @@ impl ToBech32 for Nip19Profile { bytes.push(SPECIAL); // Type bytes.push(32); // Len - bytes.extend(self.public_key.to_bytes()); // Value + bytes.extend(self.public_key.as_bytes()); // Value for relay in self.relays.iter() { let url: &[u8] = relay.as_str().as_bytes(); @@ -713,7 +713,7 @@ impl ToBech32 for Coordinate { // Author bytes.push(AUTHOR); // Type bytes.push(32); // Len - bytes.extend(self.public_key.to_bytes()); // Value + bytes.extend(self.public_key.as_bytes()); // Value // Kind bytes.push(KIND); // Type diff --git a/crates/nostr/src/nips/nip26.rs b/crates/nostr/src/nips/nip26.rs index 8b36dd416..3a7dc3e95 100644 --- a/crates/nostr/src/nips/nip26.rs +++ b/crates/nostr/src/nips/nip26.rs @@ -18,7 +18,7 @@ use bitcoin::hashes::Hash; use bitcoin::secp256k1::rand::rngs::OsRng; use bitcoin::secp256k1::rand::{CryptoRng, Rng}; use bitcoin::secp256k1::schnorr::Signature; -use bitcoin::secp256k1::{self, Message, Secp256k1, Signing, Verification}; +use bitcoin::secp256k1::{self, Message, Secp256k1, Signing, Verification, XOnlyPublicKey}; use serde::de::Error as DeserializerError; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::{json, Value}; @@ -189,7 +189,8 @@ where let unhashed_token = DelegationToken::new(delegatee_public_key, conditions); let hashed_token = Sha256Hash::hash(unhashed_token.as_bytes()); let message = Message::from_digest(hashed_token.to_byte_array()); - secp.verify_schnorr(&signature, &message, delegator_public_key)?; + let public_key: XOnlyPublicKey = delegator_public_key.xonly()?; + secp.verify_schnorr(&signature, &message, &public_key)?; Ok(()) } @@ -726,8 +727,11 @@ mod tests { let hashed_token = Sha256Hash::hash(unhashed_token.as_bytes()); let message = Message::from_digest_slice(hashed_token.as_byte_array()).unwrap(); - let verify_result = - SECP256K1.verify_schnorr(&signature, &message, &delegator_keys.public_key()); + let verify_result = SECP256K1.verify_schnorr( + &signature, + &message, + &delegator_keys.public_key().xonly().unwrap(), + ); assert!(verify_result.is_ok()); } diff --git a/crates/nostr/src/nips/nip44/mod.rs b/crates/nostr/src/nips/nip44/mod.rs index c53ff9353..c97e687d0 100644 --- a/crates/nostr/src/nips/nip44/mod.rs +++ b/crates/nostr/src/nips/nip44/mod.rs @@ -18,11 +18,13 @@ use bitcoin::secp256k1::rand::RngCore; pub mod v2; use self::v2::ConversationKey; -use crate::{PublicKey, SecretKey}; +use crate::{key, PublicKey, SecretKey}; /// Error #[derive(Debug, PartialEq, Eq)] pub enum Error { + /// Key error + Key(key::Error), /// NIP44 V2 error V2(v2::ErrorV2), /// Error while decoding from base64 @@ -45,6 +47,7 @@ impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::Key(e) => write!(f, "{e}"), Self::V2(e) => write!(f, "{e}"), Self::Base64Decode(e) => write!(f, "Error while decoding from base64: {e}"), Self::InvalidLength => write!(f, "Invalid length"), @@ -56,6 +59,12 @@ impl fmt::Display for Error { } } +impl From for Error { + fn from(e: key::Error) -> Self { + Self::Key(e) + } +} + impl From for Error { fn from(e: v2::ErrorV2) -> Self { Self::V2(e) @@ -125,7 +134,8 @@ where { match version { Version::V2 => { - let conversation_key: ConversationKey = ConversationKey::derive(secret_key, public_key); + let conversation_key: ConversationKey = + ConversationKey::derive(secret_key, public_key)?; let payload: Vec = v2::encrypt_to_bytes_with_rng(rng, &conversation_key, content.as_ref())?; Ok(general_purpose::STANDARD.encode(payload)) @@ -164,7 +174,8 @@ where match Version::try_from(version)? { Version::V2 => { - let conversation_key: ConversationKey = ConversationKey::derive(secret_key, public_key); + let conversation_key: ConversationKey = + ConversationKey::derive(secret_key, public_key)?; v2::decrypt_to_bytes(&conversation_key, &payload) } } diff --git a/crates/nostr/src/nips/nip44/v2.rs b/crates/nostr/src/nips/nip44/v2.rs index f6b459f93..9cea8da40 100644 --- a/crates/nostr/src/nips/nip44/v2.rs +++ b/crates/nostr/src/nips/nip44/v2.rs @@ -143,9 +143,9 @@ impl ConversationKey { /// Derive Conversation Key #[inline] - pub fn derive(secret_key: &SecretKey, public_key: &PublicKey) -> Self { - let shared_key: [u8; 32] = util::generate_shared_key(secret_key, public_key); - Self(hkdf::extract(b"nip44-v2", &shared_key)) + pub fn derive(secret_key: &SecretKey, public_key: &PublicKey) -> Result { + let shared_key: [u8; 32] = util::generate_shared_key(secret_key, public_key)?; + Ok(Self(hkdf::extract(b"nip44-v2", &shared_key))) } /// Compose Conversation Key from bytes @@ -426,7 +426,7 @@ mod tests { }; let note = vector.get("note").unwrap().as_str().unwrap(); - let computed_conversation_key = ConversationKey::derive(&sec1, &pub2); + let computed_conversation_key = ConversationKey::derive(&sec1, &pub2).unwrap(); assert_eq!( conversation_key, @@ -508,7 +508,7 @@ mod tests { let ciphertext = vector.get("ciphertext").unwrap().as_str().unwrap(); // Test conversation key - let computed_conversation_key = ConversationKey::derive(&sec1, &pub2); + let computed_conversation_key = ConversationKey::derive(&sec1, &pub2).unwrap(); assert_eq!( computed_conversation_key, conversation_key, "Conversation key failure on ValidSec #{}", @@ -568,7 +568,7 @@ mod tests { }; let pub2result = { let pub2hex = vector.get("pub2").unwrap().as_str().unwrap(); - PublicKey::from_str(pub2hex) + PublicKey::from_str(pub2hex).unwrap().xonly() }; let note = vector.get("note").unwrap().as_str().unwrap(); diff --git a/crates/nostr/src/nips/nip57.rs b/crates/nostr/src/nips/nip57.rs index 253fdbe79..f8e552824 100644 --- a/crates/nostr/src/nips/nip57.rs +++ b/crates/nostr/src/nips/nip57.rs @@ -351,7 +351,7 @@ where R: RngCore, T: AsRef<[u8]>, { - let key: [u8; 32] = util::generate_shared_key(secret_key, public_key); + let key: [u8; 32] = util::generate_shared_key(secret_key, public_key)?; let mut iv: [u8; 16] = [0u8; 16]; rng.fill_bytes(&mut iv); @@ -386,7 +386,7 @@ pub fn decrypt_sent_private_zap_message( // Re-create our ephemeral encryption key let secret_key: SecretKey = create_encryption_key(secret_key, public_key, private_zap_event.created_at)?; - let key: [u8; 32] = util::generate_shared_key(&secret_key, public_key); + let key: [u8; 32] = util::generate_shared_key(&secret_key, public_key)?; // decrypt like normal decrypt_private_zap_message(key, private_zap_event) @@ -398,7 +398,7 @@ pub fn decrypt_received_private_zap_message( secret_key: &SecretKey, private_zap_event: &Event, ) -> Result { - let key: [u8; 32] = util::generate_shared_key(secret_key, &private_zap_event.pubkey); + let key: [u8; 32] = util::generate_shared_key(secret_key, &private_zap_event.pubkey)?; decrypt_private_zap_message(key, private_zap_event) } diff --git a/crates/nostr/src/util/mod.rs b/crates/nostr/src/util/mod.rs index 12d2ec60f..ebfcdf163 100644 --- a/crates/nostr/src/util/mod.rs +++ b/crates/nostr/src/util/mod.rs @@ -9,7 +9,7 @@ use core::fmt::Debug; #[cfg(feature = "std")] use bitcoin::secp256k1::rand::rngs::OsRng; -use bitcoin::secp256k1::{ecdh, Parity, PublicKey as NormalizedPublicKey}; +use bitcoin::secp256k1::{ecdh, Parity, PublicKey as NormalizedPublicKey, XOnlyPublicKey}; #[cfg(feature = "std")] use bitcoin::secp256k1::{All, Secp256k1}; #[cfg(feature = "std")] @@ -22,19 +22,23 @@ pub mod hex; pub mod hkdf; use crate::nips::nip01::Coordinate; -use crate::{EventBuilder, EventId, PublicKey, SecretKey, Tag, UnsignedEvent}; +use crate::{key, EventBuilder, EventId, PublicKey, SecretKey, Tag, UnsignedEvent}; /// Generate shared key /// /// **Important: use of a strong cryptographic hash function may be critical to security! Do NOT use /// unless you understand cryptographical implications.** -pub fn generate_shared_key(secret_key: &SecretKey, public_key: &PublicKey) -> [u8; 32] { +pub fn generate_shared_key( + secret_key: &SecretKey, + public_key: &PublicKey, +) -> Result<[u8; 32], key::Error> { + let pk: XOnlyPublicKey = public_key.xonly()?; let public_key_normalized: NormalizedPublicKey = - NormalizedPublicKey::from_x_only_public_key(**public_key, Parity::Even); + NormalizedPublicKey::from_x_only_public_key(pk, Parity::Even); let ssp: [u8; 64] = ecdh::shared_secret_point(&public_key_normalized, secret_key); let mut shared_key: [u8; 32] = [0u8; 32]; shared_key.copy_from_slice(&ssp[..32]); - shared_key + Ok(shared_key) } /// Secp256k1 global context