diff --git a/Cargo.toml b/Cargo.toml index 1329b95..867c066 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "carbonado" -version = "0.4.0" edition = "2021" +version = "0.5.0" license = "MIT" description = "An apocalypse-resistant data storage format for the truly paranoid." documentation = "https://docs.rs/carbonado" @@ -22,6 +22,8 @@ hex = "0.4.3" libsecp256k1 = { version = "0.7.1", features = ["std"] } log = "0.4.19" nom = "7.1.3" +nostr = "0.28.1" +nostr-sdk = "0.28.0" pretty_env_logger = "0.5.0" secp256k1 = { version = "0.29.0", features = [ "global-context", diff --git a/src/error.rs b/src/error.rs index 1c37535..eb359f4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -26,9 +26,9 @@ pub enum CarbonadoError { #[error(transparent)] Bech32DecodeError(#[from] bech32::DecodeError), - /// Bech32 human readable part error + /// Bech32 hrp error #[error(transparent)] - InvalidHrp(#[from] bech32::primitives::hrp::Error), + Bech32HrpError(#[from] bech32::primitives::hrp::Error), /// snap error #[error(transparent)] @@ -50,6 +50,14 @@ pub enum CarbonadoError { #[error(transparent)] ZfecError(#[from] zfec_rs::Error), + /// nostr secp256k1 error + #[error(transparent)] + NostrSecp256k1Error(#[from] nostr::secp256k1::Error), + + /// nostr NIP-19 / Bech32 error + #[error(transparent)] + NostrNip19Error(#[from] nostr::nips::nip19::Error), + /// An uneven number of input bytes were provided for zfec chunks #[error("Input bytes must divide evenly over number of zfec chunks.")] UnevenZfecChunks, @@ -109,4 +117,8 @@ pub enum CarbonadoError { /// Invalid header length calculation #[error("Invalid header length calculation")] InvalidHeaderLength, + + /// Invalid header length calculation + #[error("Incorrect public key format")] + IncorrectPubKeyFormat, } diff --git a/src/lib.rs b/src/lib.rs index 166cc7c..0e7d492 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,3 +27,6 @@ pub use decoding::extract_slice; pub use decoding::verify_slice; pub use decoding::scrub; + +pub use bao; +pub use secp256k1; diff --git a/src/structs.rs b/src/structs.rs index cc39608..dba78a5 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -1,5 +1,13 @@ +use std::{ + fmt::{self, Display}, + str::FromStr, +}; + +use nostr::{FromBech32, PublicKey}; use serde::{Deserialize, Serialize}; +use crate::error::CarbonadoError; + /// Information from the encoding step, some of which is needed for decoding. #[derive(Serialize, Deserialize, Clone, Debug)] pub struct EncodeInfo { @@ -38,3 +46,92 @@ pub struct EncodeInfo { /// Tuple of verifiable bytes, bao hash, and encode info struct /// i.e., Encoded(encoded_bytes, bao_hash, encode_info) pub struct Encoded(pub Vec, pub bao::Hash, pub EncodeInfo); + +pub struct Secp256k1PubKey { + pub pk: secp256k1::PublicKey, + pub x_only_pk: [u8; 32], +} + +impl TryFrom<&str> for Secp256k1PubKey { + type Error = CarbonadoError; + + fn try_from(value: &str) -> Result { + let pk = match value.get(0..2).expect("key is at least 2 characters long") { + "+n" => secp256k1::PublicKey::from_x_only_public_key( + secp256k1::XOnlyPublicKey::from_slice( + &PublicKey::from_bech32(value.get(1..).unwrap())?.to_bytes(), + )?, + secp256k1::Parity::Even, + ), + "-n" => secp256k1::PublicKey::from_x_only_public_key( + secp256k1::XOnlyPublicKey::from_slice( + &PublicKey::from_bech32(value.get(1..).unwrap())?.to_bytes(), + )?, + secp256k1::Parity::Odd, + ), + "02" => secp256k1::PublicKey::from_str(value)?, + "03" => secp256k1::PublicKey::from_str(value)?, + _ => return Err(CarbonadoError::IncorrectPubKeyFormat), + }; + + let (x_only_pk, _) = pk.x_only_public_key(); + let x_only_pk = x_only_pk.serialize(); + + Ok(Self { pk, x_only_pk }) + } +} + +impl Display for Secp256k1PubKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { pk, .. } = self; + + f.write_str(&pk.to_string()) + } +} + +impl AsRef<[u8; 32]> for Secp256k1PubKey { + fn as_ref(&self) -> &[u8; 32] { + &self.x_only_pk + } +} + +impl Secp256k1PubKey { + pub fn new(pk: secp256k1::PublicKey) -> Self { + let (x_only_pk, _) = pk.x_only_public_key(); + let x_only_pk = x_only_pk.serialize(); + + Self { pk, x_only_pk } + } + + pub fn to_bytes(&self) -> Vec { + let Self { pk, .. } = self; + + pk.serialize().to_vec() + } + + pub fn into_inner(&self) -> secp256k1::PublicKey { + let Self { pk, .. } = self; + + pk.to_owned() + } +} + +#[test] +fn test_pubkey_decode() { + let result = Secp256k1PubKey::try_from( + "+npub14rnkcwkw0q5lnmjye7ffxvy7yxscyjl3u4mrr5qxsks76zctmz3qvuftjz", + ); + assert!(result.is_ok()); + assert_eq!( + result.unwrap().to_string(), + "02a8e76c3ace7829f9ee44cf9293309e21a1824bf1e57631d00685a1ed0b0bd8a2" + ); + let result = Secp256k1PubKey::try_from( + "-npub14rnkcwkw0q5lnmjye7ffxvy7yxscyjl3u4mrr5qxsks76zctmz3qvuftjz", + ); + assert!(result.is_ok()); + assert_eq!( + result.unwrap().to_string(), + "03a8e76c3ace7829f9ee44cf9293309e21a1824bf1e57631d00685a1ed0b0bd8a2" + ); +} diff --git a/src/utils.rs b/src/utils.rs index 85e3292..025ede3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -5,7 +5,7 @@ use std::{ }; use bao::{encode::Encoder, Hash}; -use bech32::{decode, encode_to_fmt, Bech32m, Hrp}; +use bech32::{decode, encode, Bech32m, Hrp}; use log::trace; use crate::{ @@ -58,18 +58,14 @@ pub fn calc_padding_len(input_len: usize) -> (u32, u32) { } /// Helper for encoding data to bech32m. -pub fn bech32m_encode(hrp_str: &str, bytes: &[u8]) -> Result { - let hrp = Hrp::parse(hrp_str).map_err(CarbonadoError::InvalidHrp)?; - let mut buf = String::new(); - encode_to_fmt::(&mut buf, hrp, bytes)?; - Ok(buf) +pub fn bech32m_encode(hrp: &str, bytes: &[u8]) -> Result { + Ok(encode::(Hrp::parse(hrp)?, bytes)?) } /// Helper for decoding bech32-encoded data. pub fn bech32_decode(bech32_str: &str) -> Result<(String, Vec), CarbonadoError> { - let (hrp, data) = decode(bech32_str).map_err(CarbonadoError::Bech32DecodeError)?; - let hrp_str = hrp.to_string(); - Ok((hrp_str, data)) + let (hrp, words) = decode(bech32_str)?; + Ok((hrp.to_string(), words)) } #[derive(Clone, Debug)]