From a5f07187609576e3bcec32e7c596bac8f37a1098 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Mon, 29 Jul 2024 15:45:16 -0500 Subject: [PATCH] Breaking change. Bump Carbonado header version. Use Schnorr signatures. Support x-only public keys. --- Cargo.toml | 8 ++++---- src/constants.rs | 2 +- src/error.rs | 4 ---- src/file.rs | 27 ++++++++++++++++----------- src/structs.rs | 41 +++++++++++++++++++++++++++++++++++------ 5 files changed, 56 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 867c066..029e8e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "carbonado" edition = "2021" -version = "0.5.0" +version = "0.6.0" license = "MIT" description = "An apocalypse-resistant data storage format for the truly paranoid." documentation = "https://docs.rs/carbonado" @@ -22,10 +22,10 @@ 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" +nostr = "0.33.0" +nostr-sdk = "0.33.0" pretty_env_logger = "0.5.0" -secp256k1 = { version = "0.29.0", features = [ +secp256k1 = { version = "0.28.2", features = [ "global-context", "rand-std", "serde", diff --git a/src/constants.rs b/src/constants.rs index aec9024..161fc6b 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -2,7 +2,7 @@ use bitmask_enum::bitmask; use serde::{Deserialize, Serialize}; /// "Magic number" used by the Carbonado file format. 12 bytes: "CARBONADO", and a version, 00, plus a newline character -pub const MAGICNO: &[u8; 12] = b"CARBONADO00\n"; +pub const MAGICNO: &[u8; 12] = b"CARBONADO01\n"; /// Bao slice length pub const SLICE_LEN: u16 = 1024; diff --git a/src/error.rs b/src/error.rs index eb359f4..a3ce421 100644 --- a/src/error.rs +++ b/src/error.rs @@ -94,10 +94,6 @@ pub enum CarbonadoError { #[error("Verifiable slice count should be evenly divisible by 8. Remainder was {0}.")] InvalidVerifiableSliceCount(u16), - /// secp256k1 error - #[error(transparent)] - Secp256k1Error(#[from] secp256k1::Error), - /// Invalid magic number #[error("File header lacks Carbonado magic number and may not be a proper Carbonado file. Magic number found was {0}.")] InvalidMagicNumber(String), diff --git a/src/file.rs b/src/file.rs index 19045ac..3ab1796 100644 --- a/src/file.rs +++ b/src/file.rs @@ -11,7 +11,7 @@ use nom::{ number::complete::{le_u32, le_u8}, IResult, }; -use secp256k1::{ecdsa::Signature, Message, PublicKey, SecretKey}; +use secp256k1::{schnorr::Signature, Keypair, Message, PublicKey, Secp256k1, SecretKey}; use crate::{ constants::{Format, MAGICNO}, @@ -75,10 +75,11 @@ impl TryFrom<&File> for Header { } let pubkey = PublicKey::from_slice(&pubkey)?; - let signature = Signature::from_compact(&signature)?; + let signature = Signature::from_slice(&signature)?; // Verify hash against signature - signature.verify(&Message::from_digest_slice(&hash)?, &pubkey)?; + let (x_only_pubkey, _) = pubkey.x_only_public_key(); + signature.verify(&Message::from_digest_slice(&hash)?, &x_only_pubkey)?; let hash = bao::Hash::from(hash); @@ -129,10 +130,11 @@ impl TryFrom<&[u8]> for Header { } let pubkey = PublicKey::from_slice(pubkey)?; - let signature = Signature::from_compact(signature)?; + let signature = Signature::from_slice(signature)?; // Verify hash against signature - signature.verify(&Message::from_digest_slice(hash)?, &pubkey)?; + let (x_only_pubkey, _) = pubkey.x_only_public_key(); + signature.verify(&Message::from_digest_slice(hash)?, &x_only_pubkey)?; let hash: [u8; 32] = hash[0..32].try_into()?; let hash = bao::Hash::from(hash); @@ -177,10 +179,11 @@ impl TryFrom for Header { } let pubkey = PublicKey::from_slice(pubkey)?; - let signature = Signature::from_compact(signature)?; + let signature = Signature::from_slice(signature)?; // Verify hash against signature - signature.verify(&Message::from_digest_slice(hash)?, &pubkey)?; + let (x_only_pubkey, _) = pubkey.x_only_public_key(); + signature.verify(&Message::from_digest_slice(hash)?, &x_only_pubkey)?; let hash: [u8; 32] = hash[0..32].try_into()?; let hash = bao::Hash::from(hash); @@ -225,10 +228,11 @@ impl TryFrom<&Bytes> for Header { } let pubkey = PublicKey::from_slice(pubkey)?; - let signature = Signature::from_compact(signature)?; + let signature = Signature::from_slice(signature)?; // Verify hash against signature - signature.verify(&Message::from_digest_slice(hash)?, &pubkey)?; + let (x_only_pubkey, _) = pubkey.x_only_public_key(); + signature.verify(&Message::from_digest_slice(hash)?, &x_only_pubkey)?; let hash: [u8; 32] = hash[0..32].try_into()?; let hash = bao::Hash::from(hash); @@ -268,7 +272,8 @@ impl Header { ) -> Result { let msg = Message::from_digest_slice(hash)?; let pubkey = PublicKey::from_slice(pk)?; - let signature = SecretKey::from_slice(sk)?.sign_ecdsa(msg); + let secp = Secp256k1::new(); + let signature = Keypair::from_seckey_slice(&secp, sk)?.sign_schnorr(msg); let hash = decode_bao_hash(hash)?; Ok(Header { @@ -293,7 +298,7 @@ impl Header { if hash_bytes.len() != 32 { return Err(CarbonadoError::HashBytesLengthError); } - let mut signature_bytes = self.signature.serialize_compact().to_vec(); // 64 bytes + let mut signature_bytes = self.signature.serialize().to_vec(); // 64 bytes if signature_bytes.len() != 64 { return Err(CarbonadoError::UnexpectedSignatureBytesLength( signature_bytes.len(), diff --git a/src/structs.rs b/src/structs.rs index dba78a5..329a2da 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -56,22 +56,35 @@ 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( + let pk = match ( + value.get(0..2).expect("key is at least 2 characters long"), + value.len(), + ) { + ("+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( + ("-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), + ("np", _) => secp256k1::PublicKey::from_x_only_public_key( + secp256k1::XOnlyPublicKey::from_slice(&PublicKey::from_bech32(value)?.to_bytes())?, + secp256k1::Parity::Odd, + ), + ("02", 66) => secp256k1::PublicKey::from_str(value)?, + ("03", 66) => secp256k1::PublicKey::from_str(value)?, + (_, 64) => { + let value = "03".to_owned() + value; + secp256k1::PublicKey::from_str(&value)? + } + _ => { + return Err(CarbonadoError::IncorrectPubKeyFormat); + } }; let (x_only_pk, _) = pk.x_only_public_key(); @@ -134,4 +147,20 @@ fn test_pubkey_decode() { result.unwrap().to_string(), "03a8e76c3ace7829f9ee44cf9293309e21a1824bf1e57631d00685a1ed0b0bd8a2" ); + let result = Secp256k1PubKey::try_from( + "a8e76c3ace7829f9ee44cf9293309e21a1824bf1e57631d00685a1ed0b0bd8a2", + ); + assert!(result.is_ok()); + assert_eq!( + result.unwrap().to_string(), + "03a8e76c3ace7829f9ee44cf9293309e21a1824bf1e57631d00685a1ed0b0bd8a2" + ); + let result = Secp256k1PubKey::try_from( + "npub1qqqqqqqrxtrcx8vut2vlrqa0c2qn5mmf59hdmflkls8dsyg9vmnqsclxwk", + ); + assert!(result.is_ok()); + assert_eq!( + result.unwrap().to_string(), + "03000000000332c7831d9c5a99f183afc2813a6f69a16edda7f6fc0ed8110566e6" + ); }