From 81cc91122584ceb6b37e82aae9d2a55a5af886bd Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Fri, 1 Mar 2024 11:36:26 -0700 Subject: [PATCH 1/4] Update crates. Bump version to 0.4.0. --- Cargo.toml | 6 ++++-- src/decoding.rs | 2 +- src/encoding.rs | 2 +- src/error.rs | 12 ++++++++++-- src/file.rs | 18 +++++++++--------- src/utils.rs | 10 +++++----- tests/codec.rs | 2 +- tests/format.rs | 2 +- 8 files changed, 32 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e14df19..3b6ac97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "carbonado" -version = "0.3.6" +version = "0.4.0" edition = "2021" license = "MIT" description = "An apocalypse-resistant data storage format for the truly paranoid." @@ -11,7 +11,7 @@ include = ["src/**/*", "LICENSE", "README.md"] [dependencies] bao = "0.12.1" -bech32 = "0.9.1" +bech32 = "0.11.0" bitmask-enum = "2.1.0" bytes = "1.4.0" ecies = { version = "0.2.6", default-features = false, features = [ @@ -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.28.0", features = [ "global-context", diff --git a/src/decoding.rs b/src/decoding.rs index 1084bd7..9c089ca 100644 --- a/src/decoding.rs +++ b/src/decoding.rs @@ -84,7 +84,7 @@ pub fn decode( padding: u32, format: u8, ) -> Result, CarbonadoError> { - let format = Format::try_from(format)?; + let format = Format::from(format); let verified = if format.contains(Format::Bao) { bao(input, hash)? diff --git a/src/encoding.rs b/src/encoding.rs index b0fbf2e..31c0f99 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -85,7 +85,7 @@ pub fn zfec(input: &[u8]) -> Result<(Vec, u32, u32), CarbonadoError> { /// `snap -> ecies -> zfec -> bao` pub fn encode(pubkey: &[u8], input: &[u8], format: u8) -> Result { let input_len = input.len() as u32; - let format = Format::try_from(format)?; + let format = Format::from(format); let compressed; let encrypted; diff --git a/src/error.rs b/src/error.rs index a0e962d..74fee89 100644 --- a/src/error.rs +++ b/src/error.rs @@ -18,9 +18,17 @@ pub enum CarbonadoError { #[error(transparent)] HexDecodeError(#[from] hex::FromHexError), - /// Bech32 error + /// Bech32 encode error #[error(transparent)] - Bech32Error(#[from] bech32::Error), + Bech32EncodeError(#[from] bech32::EncodeError), + + /// Bech32 decode error + #[error(transparent)] + Bech32DecodeError(#[from] bech32::DecodeError), + + /// Bech32 hrp error + #[error(transparent)] + Bech32HrpError(#[from] bech32::primitives::hrp::Error), /// snap error #[error(transparent)] diff --git a/src/file.rs b/src/file.rs index 2160d5a..19045ac 100644 --- a/src/file.rs +++ b/src/file.rs @@ -80,9 +80,9 @@ impl TryFrom<&File> for Header { // Verify hash against signature signature.verify(&Message::from_digest_slice(&hash)?, &pubkey)?; - let hash = bao::Hash::try_from(hash)?; + let hash = bao::Hash::from(hash); - let format = Format::try_from(format[0])?; + let format = Format::from(format[0]); let chunk_index = u8::from_le_bytes(chunk_index); let encoded_len = u32::from_le_bytes(encoded_len); let padding_len = u32::from_le_bytes(padding_len); @@ -135,9 +135,9 @@ impl TryFrom<&[u8]> for Header { signature.verify(&Message::from_digest_slice(hash)?, &pubkey)?; let hash: [u8; 32] = hash[0..32].try_into()?; - let hash = bao::Hash::try_from(hash)?; + let hash = bao::Hash::from(hash); - let format = Format::try_from(format)?; + let format = Format::from(format); Ok(Header { pubkey, @@ -183,9 +183,9 @@ impl TryFrom for Header { signature.verify(&Message::from_digest_slice(hash)?, &pubkey)?; let hash: [u8; 32] = hash[0..32].try_into()?; - let hash = bao::Hash::try_from(hash)?; + let hash = bao::Hash::from(hash); - let format = Format::try_from(format)?; + let format = Format::from(format); Ok(Header { pubkey, @@ -231,9 +231,9 @@ impl TryFrom<&Bytes> for Header { signature.verify(&Message::from_digest_slice(hash)?, &pubkey)?; let hash: [u8; 32] = hash[0..32].try_into()?; - let hash = bao::Hash::try_from(hash)?; + let hash = bao::Hash::from(hash); - let format = Format::try_from(format)?; + let format = Format::from(format); Ok(Header { pubkey, @@ -416,7 +416,7 @@ pub fn encode( let Encoded(mut encoded, hash, encode_info) = encoding::encode(&pubkey, input, level)?; - let format = Format::try_from(level)?; + let format = Format::from(level); let header = Header::new( sk, &pubkey, diff --git a/src/utils.rs b/src/utils.rs index 99f93cf..6efd3b5 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,7 @@ use std::sync::Once; use bao::Hash; -use bech32::{decode, encode, FromBase32, ToBase32, Variant}; +use bech32::{decode, encode, Bech32m, Hrp}; use log::trace; use crate::{ @@ -55,11 +55,11 @@ pub fn calc_padding_len(input_len: usize) -> (u32, u32) { /// Helper for encoding data to bech32m. pub fn bech32m_encode(hrp: &str, bytes: &[u8]) -> Result { - Ok(encode(hrp, bytes.to_base32(), Variant::Bech32m)?) + Ok(encode::(Hrp::parse(hrp)?, bytes)?) } /// Helper for decoding bech32-encoded data. -pub fn bech32_decode(bech32_str: &str) -> Result<(String, Vec, Variant), CarbonadoError> { - let (hrp, words, variant) = decode(bech32_str)?; - Ok((hrp, Vec::::from_base32(&words)?, variant)) +pub fn bech32_decode(bech32_str: &str) -> Result<(String, Vec), CarbonadoError> { + let (hrp, words) = decode(bech32_str)?; + Ok((hrp.to_string(), words)) } diff --git a/tests/codec.rs b/tests/codec.rs index fcf5549..6f188c5 100644 --- a/tests/codec.rs +++ b/tests/codec.rs @@ -101,7 +101,7 @@ fn codec(path: &str) -> Result<()> { assert_eq!(decoded, input, "Decoded output is same as encoded input"); let carbonado_level = 15; - let format = Format::try_from(carbonado_level)?; + let format = Format::from(carbonado_level); let header = Header::new( &sk.secret_bytes(), &pk.serialize(), diff --git a/tests/format.rs b/tests/format.rs index 08f80cd..738bf2b 100644 --- a/tests/format.rs +++ b/tests/format.rs @@ -19,7 +19,7 @@ fn format() -> Result<()> { let input = "Hello world!".as_bytes(); let carbonado_level = 15; - let format = Format::try_from(carbonado_level)?; + let format = Format::from(carbonado_level); let (file_sk, file_pk) = generate_keypair(&mut thread_rng()); let (node_sk, node_pk) = generate_keypair(&mut thread_rng()); From 26077284ab9fc40ce24ba524cf387e6cae8f16a5 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Fri, 1 Mar 2024 11:56:30 -0700 Subject: [PATCH 2/4] Add Nostr key decoding. --- src/error.rs | 12 ++++++++ src/structs.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/src/error.rs b/src/error.rs index 74fee89..393fa3a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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/structs.rs b/src/structs.rs index cc39608..dbedffa 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,73 @@ 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 secp256k1::PublicKey); + +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), + }; + + Ok(Self(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 Secp256k1PubKey { + 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" + ); +} From e33f76b0a0cbe6700a6702fb3d937fc8e8e68eb5 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Sat, 2 Mar 2024 11:51:42 -0700 Subject: [PATCH 3/4] PubKey updates. --- Cargo.toml | 1 + src/structs.rs | 29 ++++++++++++++++++++++++----- src/utils.rs | 7 +++++++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3b6ac97..64f87d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ include = ["src/**/*", "LICENSE", "README.md"] [dependencies] bao = "0.12.1" bech32 = "0.11.0" +blake3 = "1.5.0" bitmask-enum = "2.1.0" bytes = "1.4.0" ecies = { version = "0.2.6", default-features = false, features = [ diff --git a/src/structs.rs b/src/structs.rs index dbedffa..dba78a5 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -47,7 +47,10 @@ pub struct EncodeInfo { /// i.e., Encoded(encoded_bytes, bao_hash, encode_info) pub struct Encoded(pub Vec, pub bao::Hash, pub EncodeInfo); -pub struct Secp256k1PubKey(pub secp256k1::PublicKey); +pub struct Secp256k1PubKey { + pub pk: secp256k1::PublicKey, + pub x_only_pk: [u8; 32], +} impl TryFrom<&str> for Secp256k1PubKey { type Error = CarbonadoError; @@ -71,27 +74,43 @@ impl TryFrom<&str> for Secp256k1PubKey { _ => return Err(CarbonadoError::IncorrectPubKeyFormat), }; - Ok(Self(pk)) + 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; + 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; + let Self { pk, .. } = self; pk.serialize().to_vec() } pub fn into_inner(&self) -> secp256k1::PublicKey { - let Self(pk) = self; + let Self { pk, .. } = self; pk.to_owned() } diff --git a/src/utils.rs b/src/utils.rs index 6efd3b5..14972b8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -63,3 +63,10 @@ pub fn bech32_decode(bech32_str: &str) -> Result<(String, Vec), CarbonadoErr let (hrp, words) = decode(bech32_str)?; Ok((hrp.to_string(), words)) } + +pub fn pub_keyed_hash(ss: &str, bytes: &[u8]) -> Result { + let key = hex::decode(ss)?; + let key_bytes: [u8; 32] = key[0..32].try_into()?; + let pub_keyed_hash = blake3::keyed_hash(&key_bytes, bytes).to_hex().to_string(); + Ok(pub_keyed_hash) +} From 95e903970c18af7a4b942c710b45fc5977bcb9cb Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Sat, 2 Mar 2024 12:17:14 -0700 Subject: [PATCH 4/4] Fix pub_keyed_hash utility fn. --- Cargo.toml | 2 +- src/utils.rs | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 64f87d5..1a0ab61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "carbonado" -version = "0.4.0" +version = "0.4.1" edition = "2021" license = "MIT" description = "An apocalypse-resistant data storage format for the truly paranoid." diff --git a/src/utils.rs b/src/utils.rs index 14972b8..b308094 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -67,6 +67,12 @@ pub fn bech32_decode(bech32_str: &str) -> Result<(String, Vec), CarbonadoErr pub fn pub_keyed_hash(ss: &str, bytes: &[u8]) -> Result { let key = hex::decode(ss)?; let key_bytes: [u8; 32] = key[0..32].try_into()?; - let pub_keyed_hash = blake3::keyed_hash(&key_bytes, bytes).to_hex().to_string(); + let secp = secp256k1::Secp256k1::new(); + let secret_key = secp256k1::SecretKey::from_slice(key_bytes.as_slice())?; + let pk = secret_key.public_key(&secp); + let (pk, _parity) = pk.x_only_public_key(); + let pub_keyed_hash = blake3::keyed_hash(&pk.serialize(), bytes) + .to_hex() + .to_string(); Ok(pub_keyed_hash) }