notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit e04c8821d54363e3930b270f4bd16b26b59cabbe
parent b8177459ab3c163d357dad1a1e396d15b6a66be6
Author: kernelkind <kernelkind@gmail.com>
Date:   Tue,  7 May 2024 19:57:40 -0400

Add keypair & update pubkey

Keypair & FullKeypair match structs in damus ios

Signed-off-by: kernelkind <kernelkind@gmail.com>
Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Menostr/src/error.rs | 5+++++
Aenostr/src/keypair.rs | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Menostr/src/lib.rs | 3+++
Menostr/src/pubkey.rs | 40++++++++++++++++++++++++++++++++++++++--
4 files changed, 119 insertions(+), 2 deletions(-)

diff --git a/enostr/src/error.rs b/enostr/src/error.rs @@ -8,8 +8,10 @@ pub enum Error { Empty, DecodeFailed, HexDecodeFailed, + InvalidBech32, InvalidByteSize, InvalidSignature, + InvalidPublicKey, // Secp(secp256k1::Error), Json(serde_json::Error), Generic(String), @@ -23,6 +25,7 @@ impl std::cmp::PartialEq for Error { (Error::HexDecodeFailed, Error::HexDecodeFailed) => true, (Error::InvalidSignature, Error::InvalidSignature) => true, (Error::InvalidByteSize, Error::InvalidByteSize) => true, + (Error::InvalidPublicKey, Error::InvalidPublicKey) => true, // This is slightly wrong but whatevs (Error::Json(..), Error::Json(..)) => true, (Error::Generic(left), Error::Generic(right)) => left == right, @@ -40,6 +43,8 @@ impl fmt::Display for Error { Self::InvalidSignature => write!(f, "invalid signature"), Self::HexDecodeFailed => write!(f, "hex decoding failed"), Self::InvalidByteSize => write!(f, "invalid byte size"), + Self::InvalidBech32 => write!(f, "invalid bech32 string"), + Self::InvalidPublicKey => write!(f, "invalid public key"), //Self::Secp(e) => write!(f, "{e}"), Self::Json(e) => write!(f, "{e}"), Self::Generic(e) => write!(f, "{e}"), diff --git a/enostr/src/keypair.rs b/enostr/src/keypair.rs @@ -0,0 +1,73 @@ +use crate::Pubkey; +use crate::SecretKey; + +#[derive(Debug, Eq, PartialEq)] +pub struct Keypair { + pub pubkey: Pubkey, + pub secret_key: Option<SecretKey>, +} + +impl Keypair { + pub fn new(secret_key: SecretKey) -> Self { + let cloned_secret_key = secret_key.clone(); + let nostr_keys = nostr::Keys::new(secret_key); + Keypair { + pubkey: Pubkey::new(&nostr_keys.public_key().to_bytes()), + secret_key: Some(cloned_secret_key), + } + } + + pub fn only_pubkey(pubkey: Pubkey) -> Self { + Keypair { + pubkey, + secret_key: None, + } + } + + pub fn to_full(self) -> Option<FullKeypair> { + if let Some(secret_key) = self.secret_key { + Some(FullKeypair { + pubkey: self.pubkey, + secret_key, + }) + } else { + None + } + } +} + +#[derive(Debug, Eq, PartialEq)] +pub struct FullKeypair { + pub pubkey: Pubkey, + pub secret_key: SecretKey, +} + +impl FullKeypair { + pub fn new(pubkey: Pubkey, secret_key: SecretKey) -> Self { + FullKeypair { pubkey, secret_key } + } +} + +impl std::fmt::Display for Keypair { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Keypair:\n\tpublic: {}\n\tsecret: {}", + self.pubkey, + match self.secret_key { + Some(_) => "Some(<hidden>)", + None => "None", + } + ) + } +} + +impl std::fmt::Display for FullKeypair { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Keypair:\n\tpublic: {}\n\tsecret: {}", + self.pubkey, "<hidden>" + ) + } +} diff --git a/enostr/src/lib.rs b/enostr/src/lib.rs @@ -2,6 +2,7 @@ mod client; mod error; mod event; mod filter; +mod keypair; mod profile; mod pubkey; mod relay; @@ -11,6 +12,8 @@ pub use error::Error; pub use event::{Event, EventId}; pub use ewebsock; pub use filter::Filter; +pub use keypair::{FullKeypair, Keypair}; +pub use nostr::SecretKey; pub use profile::Profile; pub use pubkey::Pubkey; pub use relay::message::{RelayEvent, RelayMessage}; diff --git a/enostr/src/pubkey.rs b/enostr/src/pubkey.rs @@ -1,14 +1,15 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::Error; -use hex; use log::debug; -//use nostr::key::XOnlyPublicKey; +use nostr::bech32::Hrp; use std::fmt; #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub struct Pubkey([u8; 32]); +static HRP_NPUB: Hrp = Hrp::parse_unchecked("npub"); + impl Pubkey { pub fn new(data: &[u8; 32]) -> Self { Self(*data) @@ -25,6 +26,41 @@ impl Pubkey { pub fn from_hex(hex_str: &str) -> Result<Self, Error> { Ok(Pubkey(hex::decode(hex_str)?.as_slice().try_into()?)) } + + pub fn try_from_hex_str_with_verify(hex_str: &str) -> Result<Self, Error> { + let vec: Vec<u8> = hex::decode(hex_str)?; + if vec.len() != 32 { + Err(Error::HexDecodeFailed) + } else { + let _ = match nostr::secp256k1::XOnlyPublicKey::from_slice(&vec) { + Ok(r) => Ok(r), + Err(_) => Err(Error::InvalidPublicKey), + }?; + + Ok(Pubkey(vec.try_into().unwrap())) + } + } + + pub fn try_from_bech32_string(s: &str, verify: bool) -> Result<Self, Error> { + let data = match nostr::bech32::decode(s) { + Ok(res) => Ok(res), + Err(_) => Err(Error::InvalidBech32), + }?; + + if data.0 != HRP_NPUB { + Err(Error::InvalidBech32) + } else if data.1.len() != 32 { + Err(Error::InvalidByteSize) + } else { + if verify { + let _ = match nostr::secp256k1::XOnlyPublicKey::from_slice(&data.1) { + Ok(r) => Ok(r), + Err(_) => Err(Error::InvalidPublicKey), + }?; + } + Ok(Pubkey(data.1.try_into().unwrap())) + } + } } impl fmt::Display for Pubkey {