diff --git a/Cargo.lock b/Cargo.lock index 326f26e17..58d9bd5c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,6 +278,7 @@ dependencies = [ "chia-consensus", "chia-protocol", "chia-puzzles", + "chia-secp", "chia-sha2", "chia-ssl", "chia-traits 0.15.0", @@ -444,6 +445,20 @@ dependencies = [ "libfuzzer-sys", ] +[[package]] +name = "chia-secp" +version = "0.16.0" +dependencies = [ + "anyhow", + "arbitrary", + "chia-sha2", + "hex", + "k256", + "p256", + "rand", + "rand_chacha", +] + [[package]] name = "chia-sha2" version = "0.15.0" @@ -641,6 +656,7 @@ name = "clvm-traits" version = "0.16.0" dependencies = [ "chia-bls 0.16.0", + "chia-secp", "clvm-derive", "clvmr", "hex", diff --git a/Cargo.toml b/Cargo.toml index 9246114f0..15def5a9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ implicit_hasher = "allow" [dependencies] chia-bls = { workspace = true, optional = true } +chia-secp = { workspace = true, optional = true } chia-client = { workspace = true, optional = true } chia-consensus = { workspace = true, optional = true } chia-protocol = { workspace = true, optional = true } @@ -71,6 +72,7 @@ ignored = ["clvmr"] [features] default = [ "bls", + "secp", "client", "consensus", "protocol", @@ -82,7 +84,8 @@ default = [ "clvm-utils" ] -bls = ["dep:chia-bls"] +bls = ["dep:chia-bls", "clvm-traits/chia-bls"] +secp = ["dep:chia-secp", "clvm-traits/chia-secp"] client = ["dep:chia-client"] consensus = ["dep:chia-consensus"] protocol = ["dep:chia-protocol"] @@ -105,6 +108,7 @@ chia-bls = { path = "./crates/chia-bls", version = "0.16.0" } chia-client = { path = "./crates/chia-client", version = "0.16.0" } chia-consensus = { path = "./crates/chia-consensus", version = "0.16.0" } chia-protocol = { path = "./crates/chia-protocol", version = "0.16.0" } +chia-secp = { path = "./crates/chia-secp", version = "0.16.0" } chia-ssl = { path = "./crates/chia-ssl", version = "0.11.0" } chia-traits = { path = "./crates/chia-traits", version = "0.15.0" } chia-puzzles = { path = "./crates/chia-puzzles", version = "0.16.0" } @@ -153,3 +157,6 @@ blocking-threadpool = "1.0.1" libfuzzer-sys = "0.4" wasm-bindgen = "0.2.95" openssl = "0.10.68" +k256 = "0.13.4" +p256 = "0.13.2" +rand_chacha = "0.3.1" diff --git a/crates/chia-secp/Cargo.toml b/crates/chia-secp/Cargo.toml new file mode 100644 index 000000000..484a8c757 --- /dev/null +++ b/crates/chia-secp/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "chia-secp" +version = "0.16.0" +edition = "2021" +license = "Apache-2.0" +description = "Secp256k1 and secp256r1 types for Chia" +authors = ["Brandon Haggstrom "] +homepage = "https://github.com/Chia-Network/chia_rs" +repository = "https://github.com/Chia-Network/chia_rs" + +[lints] +workspace = true + +[features] +arbitrary = ["dep:arbitrary"] + +[dependencies] +arbitrary = { workspace = true, optional = true } +k256 = { workspace = true } +p256 = { workspace = true } +hex = { workspace = true } +chia-sha2= { workspace = true } + +[dev-dependencies] +rand = { workspace = true } +rand_chacha = { workspace = true } +anyhow = { workspace = true } diff --git a/crates/chia-secp/src/lib.rs b/crates/chia-secp/src/lib.rs new file mode 100644 index 000000000..d37355c35 --- /dev/null +++ b/crates/chia-secp/src/lib.rs @@ -0,0 +1,103 @@ +mod secp256k1; +mod secp256r1; + +pub use secp256k1::*; +pub use secp256r1::*; + +#[cfg(test)] +mod tests { + use rand::{Rng, SeedableRng}; + use rand_chacha::ChaCha8Rng; + + use super::*; + + #[test] + fn test_secp256k1_key() -> anyhow::Result<()> { + let mut rng = ChaCha8Rng::seed_from_u64(1337); + + let sk = K1SecretKey::from_bytes(&rng.gen())?; + assert_eq!( + hex::encode(sk.to_bytes()), + "ae491886341a539a1ccfaffcc9c78650ad1adc6270620c882b8d29bf6b9bc4cd" + ); + assert_eq!(format!("{sk:?}"), "K1SecretKey(...)"); + + let pk = sk.public_key(); + assert_eq!( + hex::encode(pk.to_bytes()), + "02827cdbbed87e45683d448be2ea15fb72ba3732247bda18474868cf5456123fb4" + ); + assert_eq!( + format!("{pk:?}"), + "K1PublicKey(02827cdbbed87e45683d448be2ea15fb72ba3732247bda18474868cf5456123fb4)" + ); + assert_eq!( + format!("{pk}"), + "02827cdbbed87e45683d448be2ea15fb72ba3732247bda18474868cf5456123fb4" + ); + + let message_hash: [u8; 32] = rng.gen(); + let sig = sk.sign_prehashed(&message_hash)?; + assert_eq!( + hex::encode(sig.to_bytes()), + "6f07897d1d28b8698af5dec5ca06907b1304b227dc9f740b8c4065cf04d5e8653ae66aa17063e7120ee7f22fae54373b35230e259244b90400b65cf00d86c591" + ); + assert_eq!( + format!("{sig:?}"), + "K1Signature(6f07897d1d28b8698af5dec5ca06907b1304b227dc9f740b8c4065cf04d5e8653ae66aa17063e7120ee7f22fae54373b35230e259244b90400b65cf00d86c591)" + ); + assert_eq!( + format!("{sig}"), + "6f07897d1d28b8698af5dec5ca06907b1304b227dc9f740b8c4065cf04d5e8653ae66aa17063e7120ee7f22fae54373b35230e259244b90400b65cf00d86c591" + ); + + assert!(pk.verify_prehashed(&message_hash, &sig)); + + Ok(()) + } + + #[test] + fn test_secp256r1_key() -> anyhow::Result<()> { + let mut rng = ChaCha8Rng::seed_from_u64(1337); + + let sk = R1SecretKey::from_bytes(&rng.gen())?; + assert_eq!( + hex::encode(sk.to_bytes()), + "ae491886341a539a1ccfaffcc9c78650ad1adc6270620c882b8d29bf6b9bc4cd" + ); + assert_eq!(format!("{sk:?}"), "R1SecretKey(...)"); + + let pk = sk.public_key(); + assert_eq!( + hex::encode(pk.to_bytes()), + "037dc85102f5eb7867b9580fea8b242c774173e1a47db320c798242d3a7a7579e4" + ); + assert_eq!( + format!("{pk:?}"), + "R1PublicKey(037dc85102f5eb7867b9580fea8b242c774173e1a47db320c798242d3a7a7579e4)" + ); + assert_eq!( + format!("{pk}"), + "037dc85102f5eb7867b9580fea8b242c774173e1a47db320c798242d3a7a7579e4" + ); + + let message_hash: [u8; 32] = rng.gen(); + let sig = sk.sign_prehashed(&message_hash)?; + assert_eq!( + hex::encode(sig.to_bytes()), + "550e83da8cf9b2d407ed093ae213869ebd7ceaea603920f87d535690e52b40537915d8fe3d5a96c87e700c56dc638c32f7a2954f2ba409367d1a132000cc2228" + ); + assert_eq!( + format!("{sig:?}"), + "R1Signature(550e83da8cf9b2d407ed093ae213869ebd7ceaea603920f87d535690e52b40537915d8fe3d5a96c87e700c56dc638c32f7a2954f2ba409367d1a132000cc2228)" + ); + assert_eq!( + format!("{sig}"), + "550e83da8cf9b2d407ed093ae213869ebd7ceaea603920f87d535690e52b40537915d8fe3d5a96c87e700c56dc638c32f7a2954f2ba409367d1a132000cc2228" + ); + + assert!(pk.verify_prehashed(&message_hash, &sig)); + + Ok(()) + } +} diff --git a/crates/chia-secp/src/secp256k1.rs b/crates/chia-secp/src/secp256k1.rs new file mode 100644 index 000000000..bff7b3cd2 --- /dev/null +++ b/crates/chia-secp/src/secp256k1.rs @@ -0,0 +1,7 @@ +mod public_key; +mod secret_key; +mod signature; + +pub use public_key::*; +pub use secret_key::*; +pub use signature::*; diff --git a/crates/chia-secp/src/secp256k1/public_key.rs b/crates/chia-secp/src/secp256k1/public_key.rs new file mode 100644 index 000000000..8d9e9f7dd --- /dev/null +++ b/crates/chia-secp/src/secp256k1/public_key.rs @@ -0,0 +1,59 @@ +use std::fmt; +use std::hash::{Hash, Hasher}; + +use chia_sha2::Sha256; +use k256::ecdsa::signature::hazmat::PrehashVerifier; +use k256::ecdsa::{Error, VerifyingKey}; + +use super::K1Signature; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct K1PublicKey(pub(crate) VerifyingKey); + +impl Hash for K1PublicKey { + fn hash(&self, state: &mut H) { + self.to_bytes().hash(state); + } +} + +impl fmt::Debug for K1PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "K1PublicKey({self})") + } +} + +impl fmt::Display for K1PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(self.to_bytes())) + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for K1PublicKey { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + Self::from_bytes(&u.arbitrary()?).map_err(|_| arbitrary::Error::IncorrectFormat) + } +} + +impl K1PublicKey { + pub const SIZE: usize = 33; + + pub fn to_bytes(&self) -> [u8; Self::SIZE] { + self.0.to_encoded_point(true).as_ref().try_into().unwrap() + } + + pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> Result { + Ok(Self(VerifyingKey::from_sec1_bytes(bytes)?)) + } + + pub fn verify_prehashed(&self, message_hash: &[u8; 32], signature: &K1Signature) -> bool { + self.0.verify_prehash(message_hash, &signature.0).is_ok() + } + + pub fn fingerprint(&self) -> u32 { + let mut hasher = Sha256::new(); + hasher.update(self.to_bytes()); + let hash = hasher.finalize(); + u32::from_be_bytes(hash[0..4].try_into().unwrap()) + } +} diff --git a/crates/chia-secp/src/secp256k1/secret_key.rs b/crates/chia-secp/src/secp256k1/secret_key.rs new file mode 100644 index 000000000..515cdf269 --- /dev/null +++ b/crates/chia-secp/src/secp256k1/secret_key.rs @@ -0,0 +1,50 @@ +use std::{ + fmt, + hash::{Hash, Hasher}, +}; + +use k256::ecdsa::{Error, SigningKey}; + +use super::{K1PublicKey, K1Signature}; + +#[derive(Clone, PartialEq, Eq)] +pub struct K1SecretKey(SigningKey); + +impl Hash for K1SecretKey { + fn hash(&self, state: &mut H) { + self.to_bytes().hash(state); + } +} + +impl fmt::Debug for K1SecretKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "K1SecretKey(...)") + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for K1SecretKey { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + Self::from_bytes(&u.arbitrary()?).map_err(|_| arbitrary::Error::IncorrectFormat) + } +} + +impl K1SecretKey { + pub fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes().into() + } + + pub fn from_bytes(bytes: &[u8; 32]) -> Result { + Ok(Self(SigningKey::from_bytes(bytes.into())?)) + } + + pub fn public_key(&self) -> K1PublicKey { + K1PublicKey(*self.0.verifying_key()) + } + + pub fn sign_prehashed(&self, message_hash: &[u8; 32]) -> Result { + Ok(K1Signature( + self.0.sign_prehash_recoverable(message_hash)?.0, + )) + } +} diff --git a/crates/chia-secp/src/secp256k1/signature.rs b/crates/chia-secp/src/secp256k1/signature.rs new file mode 100644 index 000000000..18635bb43 --- /dev/null +++ b/crates/chia-secp/src/secp256k1/signature.rs @@ -0,0 +1,46 @@ +use std::{ + fmt, + hash::{Hash, Hasher}, +}; + +use k256::ecdsa::{Error, Signature}; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct K1Signature(pub(crate) Signature); + +impl Hash for K1Signature { + fn hash(&self, state: &mut H) { + self.to_bytes().hash(state); + } +} + +impl fmt::Debug for K1Signature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "K1Signature({self})") + } +} + +impl fmt::Display for K1Signature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(self.to_bytes())) + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for K1Signature { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + Self::from_bytes(&u.arbitrary()?).map_err(|_| arbitrary::Error::IncorrectFormat) + } +} + +impl K1Signature { + pub const SIZE: usize = 64; + + pub fn to_bytes(&self) -> [u8; Self::SIZE] { + self.0.to_bytes().into() + } + + pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> Result { + Ok(Self(Signature::from_slice(bytes)?)) + } +} diff --git a/crates/chia-secp/src/secp256r1.rs b/crates/chia-secp/src/secp256r1.rs new file mode 100644 index 000000000..bff7b3cd2 --- /dev/null +++ b/crates/chia-secp/src/secp256r1.rs @@ -0,0 +1,7 @@ +mod public_key; +mod secret_key; +mod signature; + +pub use public_key::*; +pub use secret_key::*; +pub use signature::*; diff --git a/crates/chia-secp/src/secp256r1/public_key.rs b/crates/chia-secp/src/secp256r1/public_key.rs new file mode 100644 index 000000000..183774ac9 --- /dev/null +++ b/crates/chia-secp/src/secp256r1/public_key.rs @@ -0,0 +1,59 @@ +use std::fmt; +use std::hash::{Hash, Hasher}; + +use chia_sha2::Sha256; +use p256::ecdsa::signature::hazmat::PrehashVerifier; +use p256::ecdsa::{Error, VerifyingKey}; + +use super::R1Signature; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct R1PublicKey(pub(crate) VerifyingKey); + +impl Hash for R1PublicKey { + fn hash(&self, state: &mut H) { + self.to_bytes().hash(state); + } +} + +impl fmt::Debug for R1PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "R1PublicKey({self})") + } +} + +impl fmt::Display for R1PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(self.to_bytes())) + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for R1PublicKey { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + Self::from_bytes(&u.arbitrary()?).map_err(|_| arbitrary::Error::IncorrectFormat) + } +} + +impl R1PublicKey { + pub const SIZE: usize = 33; + + pub fn to_bytes(&self) -> [u8; Self::SIZE] { + self.0.to_encoded_point(true).as_ref().try_into().unwrap() + } + + pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> Result { + Ok(Self(VerifyingKey::from_sec1_bytes(bytes)?)) + } + + pub fn verify_prehashed(&self, message_hash: &[u8; 32], signature: &R1Signature) -> bool { + self.0.verify_prehash(message_hash, &signature.0).is_ok() + } + + pub fn fingerprint(&self) -> u32 { + let mut hasher = Sha256::new(); + hasher.update(self.to_bytes()); + let hash = hasher.finalize(); + u32::from_be_bytes(hash[0..4].try_into().unwrap()) + } +} diff --git a/crates/chia-secp/src/secp256r1/secret_key.rs b/crates/chia-secp/src/secp256r1/secret_key.rs new file mode 100644 index 000000000..eb6d10558 --- /dev/null +++ b/crates/chia-secp/src/secp256r1/secret_key.rs @@ -0,0 +1,50 @@ +use std::{ + fmt, + hash::{Hash, Hasher}, +}; + +use p256::ecdsa::{Error, SigningKey}; + +use super::{R1PublicKey, R1Signature}; + +#[derive(Clone, PartialEq, Eq)] +pub struct R1SecretKey(SigningKey); + +impl Hash for R1SecretKey { + fn hash(&self, state: &mut H) { + self.to_bytes().hash(state); + } +} + +impl fmt::Debug for R1SecretKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "R1SecretKey(...)") + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for R1SecretKey { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + Self::from_bytes(&u.arbitrary()?).map_err(|_| arbitrary::Error::IncorrectFormat) + } +} + +impl R1SecretKey { + pub fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes().into() + } + + pub fn from_bytes(bytes: &[u8; 32]) -> Result { + Ok(Self(SigningKey::from_bytes(bytes.into())?)) + } + + pub fn public_key(&self) -> R1PublicKey { + R1PublicKey(*self.0.verifying_key()) + } + + pub fn sign_prehashed(&self, message_hash: &[u8; 32]) -> Result { + Ok(R1Signature( + self.0.sign_prehash_recoverable(message_hash)?.0, + )) + } +} diff --git a/crates/chia-secp/src/secp256r1/signature.rs b/crates/chia-secp/src/secp256r1/signature.rs new file mode 100644 index 000000000..e619897ba --- /dev/null +++ b/crates/chia-secp/src/secp256r1/signature.rs @@ -0,0 +1,46 @@ +use std::{ + fmt, + hash::{Hash, Hasher}, +}; + +use p256::ecdsa::{Error, Signature}; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct R1Signature(pub(crate) Signature); + +impl Hash for R1Signature { + fn hash(&self, state: &mut H) { + self.to_bytes().hash(state); + } +} + +impl fmt::Debug for R1Signature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "R1Signature({self})") + } +} + +impl fmt::Display for R1Signature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(self.to_bytes())) + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for R1Signature { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + Self::from_bytes(&u.arbitrary()?).map_err(|_| arbitrary::Error::IncorrectFormat) + } +} + +impl R1Signature { + pub const SIZE: usize = 64; + + pub fn to_bytes(&self) -> [u8; Self::SIZE] { + self.0.to_bytes().into() + } + + pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> Result { + Ok(Self(Signature::from_slice(bytes)?)) + } +} diff --git a/crates/clvm-traits/Cargo.toml b/crates/clvm-traits/Cargo.toml index bada6d170..3b32a7981 100644 --- a/crates/clvm-traits/Cargo.toml +++ b/crates/clvm-traits/Cargo.toml @@ -17,6 +17,7 @@ workspace = true [features] derive = ["dep:clvm-derive"] chia-bls = ["dep:chia-bls"] +chia-secp = ["dep:chia-secp"] py-bindings = ["dep:pyo3"] [dependencies] @@ -24,6 +25,7 @@ pyo3 = { workspace = true, optional = true } clvmr = { workspace = true } clvm-derive = { workspace = true, optional = true } chia-bls = { workspace = true, optional = true } +chia-secp = { workspace = true, optional = true } num-bigint = { workspace = true } thiserror = { workspace = true } diff --git a/crates/clvm-traits/src/from_clvm.rs b/crates/clvm-traits/src/from_clvm.rs index 897ffe8e9..bc57ab10a 100644 --- a/crates/clvm-traits/src/from_clvm.rs +++ b/crates/clvm-traits/src/from_clvm.rs @@ -220,6 +220,78 @@ impl> FromClvm for chia_bls::Signature { } } +#[cfg(feature = "chia-secp")] +impl FromClvm for chia_secp::K1PublicKey +where + D: ClvmDecoder, +{ + fn from_clvm(decoder: &D, node: D::Node) -> Result { + let atom = decoder.decode_atom(&node)?; + let bytes: [u8; Self::SIZE] = + atom.as_ref() + .try_into() + .map_err(|_| FromClvmError::WrongAtomLength { + expected: Self::SIZE, + found: atom.len(), + })?; + Self::from_bytes(&bytes).map_err(|error| FromClvmError::Custom(error.to_string())) + } +} + +#[cfg(feature = "chia-secp")] +impl FromClvm for chia_secp::K1Signature +where + D: ClvmDecoder, +{ + fn from_clvm(decoder: &D, node: D::Node) -> Result { + let atom = decoder.decode_atom(&node)?; + let bytes: [u8; Self::SIZE] = + atom.as_ref() + .try_into() + .map_err(|_| FromClvmError::WrongAtomLength { + expected: Self::SIZE, + found: atom.len(), + })?; + Self::from_bytes(&bytes).map_err(|error| FromClvmError::Custom(error.to_string())) + } +} + +#[cfg(feature = "chia-secp")] +impl FromClvm for chia_secp::R1PublicKey +where + D: ClvmDecoder, +{ + fn from_clvm(decoder: &D, node: D::Node) -> Result { + let atom = decoder.decode_atom(&node)?; + let bytes: [u8; Self::SIZE] = + atom.as_ref() + .try_into() + .map_err(|_| FromClvmError::WrongAtomLength { + expected: Self::SIZE, + found: atom.len(), + })?; + Self::from_bytes(&bytes).map_err(|error| FromClvmError::Custom(error.to_string())) + } +} + +#[cfg(feature = "chia-secp")] +impl FromClvm for chia_secp::R1Signature +where + D: ClvmDecoder, +{ + fn from_clvm(decoder: &D, node: D::Node) -> Result { + let atom = decoder.decode_atom(&node)?; + let bytes: [u8; Self::SIZE] = + atom.as_ref() + .try_into() + .map_err(|_| FromClvmError::WrongAtomLength { + expected: Self::SIZE, + found: atom.len(), + })?; + Self::from_bytes(&bytes).map_err(|error| FromClvmError::Custom(error.to_string())) + } +} + #[cfg(test)] mod tests { use clvmr::{serde::node_from_bytes, Allocator}; @@ -373,4 +445,94 @@ mod tests { }) ); } + + #[cfg(feature = "chia-secp")] + #[test] + fn test_secp_public_key() { + use chia_secp::{K1PublicKey, R1PublicKey}; + use hex_literal::hex; + + let a = &mut Allocator::new(); + + let bytes = hex!("02827cdbbed87e45683d448be2ea15fb72ba3732247bda18474868cf5456123fb4"); + + assert_eq!( + decode( + a, + "a102827cdbbed87e45683d448be2ea15fb72ba3732247bda18474868cf5456123fb4" + ), + Ok(K1PublicKey::from_bytes(&bytes).unwrap()) + ); + assert_eq!( + decode::(a, "8568656c6c6f"), + Err(FromClvmError::WrongAtomLength { + expected: 33, + found: 5 + }) + ); + + let bytes = hex!("037dc85102f5eb7867b9580fea8b242c774173e1a47db320c798242d3a7a7579e4"); + + assert_eq!( + decode( + a, + "a1037dc85102f5eb7867b9580fea8b242c774173e1a47db320c798242d3a7a7579e4" + ), + Ok(R1PublicKey::from_bytes(&bytes).unwrap()) + ); + assert_eq!( + decode::(a, "8568656c6c6f"), + Err(FromClvmError::WrongAtomLength { + expected: 33, + found: 5 + }) + ); + } + + #[cfg(feature = "chia-secp")] + #[test] + fn test_secp_signature() { + use chia_secp::K1Signature; + use hex_literal::hex; + + let a = &mut Allocator::new(); + + let bytes = hex!( + " + 6f07897d1d28b8698af5dec5ca06907b1304b227dc9f740b8c4065cf04d5e865 + 3ae66aa17063e7120ee7f22fae54373b35230e259244b90400b65cf00d86c591 + " + ); + + assert_eq!( + decode(a, "c0406f07897d1d28b8698af5dec5ca06907b1304b227dc9f740b8c4065cf04d5e8653ae66aa17063e7120ee7f22fae54373b35230e259244b90400b65cf00d86c591"), + Ok(K1Signature::from_bytes(&bytes).unwrap()) + ); + assert_eq!( + decode::(a, "8568656c6c6f"), + Err(FromClvmError::WrongAtomLength { + expected: 64, + found: 5 + }) + ); + + let bytes = hex!( + " + 550e83da8cf9b2d407ed093ae213869ebd7ceaea603920f87d535690e52b4053 + 7915d8fe3d5a96c87e700c56dc638c32f7a2954f2ba409367d1a132000cc2228 + " + ); + + assert_eq!( + decode(a, "c040550e83da8cf9b2d407ed093ae213869ebd7ceaea603920f87d535690e52b40537915d8fe3d5a96c87e700c56dc638c32f7a2954f2ba409367d1a132000cc2228"), + Ok(K1Signature::from_bytes(&bytes).unwrap()) + ); + assert_eq!( + decode::(a, "8568656c6c6f"), + Err(FromClvmError::WrongAtomLength { + expected: 64, + found: 5 + }) + ); + } } diff --git a/crates/clvm-traits/src/to_clvm.rs b/crates/clvm-traits/src/to_clvm.rs index c2672b0f0..1ef642d5c 100644 --- a/crates/clvm-traits/src/to_clvm.rs +++ b/crates/clvm-traits/src/to_clvm.rs @@ -173,6 +173,46 @@ impl> ToClvm for chia_bls::Signature { } } +#[cfg(feature = "chia-secp")] +impl ToClvm for chia_secp::K1PublicKey +where + E: ClvmEncoder, +{ + fn to_clvm(&self, encoder: &mut E) -> Result { + encoder.encode_atom(Atom::Borrowed(&self.to_bytes())) + } +} + +#[cfg(feature = "chia-secp")] +impl ToClvm for chia_secp::K1Signature +where + E: ClvmEncoder, +{ + fn to_clvm(&self, encoder: &mut E) -> Result { + encoder.encode_atom(Atom::Borrowed(&self.to_bytes())) + } +} + +#[cfg(feature = "chia-secp")] +impl ToClvm for chia_secp::R1PublicKey +where + E: ClvmEncoder, +{ + fn to_clvm(&self, encoder: &mut E) -> Result { + encoder.encode_atom(Atom::Borrowed(&self.to_bytes())) + } +} + +#[cfg(feature = "chia-secp")] +impl ToClvm for chia_secp::R1Signature +where + E: ClvmEncoder, +{ + fn to_clvm(&self, encoder: &mut E) -> Result { + encoder.encode_atom(Atom::Borrowed(&self.to_bytes())) + } +} + #[cfg(test)] mod tests { use clvmr::{serde::node_to_bytes, Allocator}; @@ -345,4 +385,58 @@ mod tests { Ok("c060a3994dc9c0ef41a903d3335f0afe42ba16c88e7881706798492da4a1653cd10c69c841eeb56f44ae005e2bad27fb7ebb16ce8bbfbd708ea91dd4ff24f030497b50e694a8270eccd07dbc206b8ffe0c34a9ea81291785299fae8206a1e1bbc1d1".to_string()) ); } + + #[cfg(feature = "chia-secp")] + #[test] + fn test_secp_public_key() { + use chia_secp::{K1PublicKey, R1PublicKey}; + use hex_literal::hex; + + let a = &mut Allocator::new(); + + let k1_pk = K1PublicKey::from_bytes(&hex!( + "02827cdbbed87e45683d448be2ea15fb72ba3732247bda18474868cf5456123fb4" + )) + .unwrap(); + assert_eq!( + encode(a, k1_pk), + Ok("a102827cdbbed87e45683d448be2ea15fb72ba3732247bda18474868cf5456123fb4".to_string()) + ); + + let r1_pk = R1PublicKey::from_bytes(&hex!( + "037dc85102f5eb7867b9580fea8b242c774173e1a47db320c798242d3a7a7579e4" + )) + .unwrap(); + assert_eq!( + encode(a, r1_pk), + Ok("a1037dc85102f5eb7867b9580fea8b242c774173e1a47db320c798242d3a7a7579e4".to_string()) + ); + } + + #[cfg(feature = "chia-secp")] + #[test] + fn test_secp_signature() { + use chia_secp::{K1Signature, R1Signature}; + use hex_literal::hex; + + let a = &mut Allocator::new(); + + let k1_sig = K1Signature::from_bytes(&hex!( + "6f07897d1d28b8698af5dec5ca06907b1304b227dc9f740b8c4065cf04d5e8653ae66aa17063e7120ee7f22fae54373b35230e259244b90400b65cf00d86c591" + )) + .unwrap(); + assert_eq!( + encode(a, k1_sig), + Ok("c0406f07897d1d28b8698af5dec5ca06907b1304b227dc9f740b8c4065cf04d5e8653ae66aa17063e7120ee7f22fae54373b35230e259244b90400b65cf00d86c591".to_string()) + ); + + let r1_sig = R1Signature::from_bytes(&hex!( + "550e83da8cf9b2d407ed093ae213869ebd7ceaea603920f87d535690e52b40537915d8fe3d5a96c87e700c56dc638c32f7a2954f2ba409367d1a132000cc2228" + )) + .unwrap(); + assert_eq!( + encode(a, r1_sig), + Ok("c040550e83da8cf9b2d407ed093ae213869ebd7ceaea603920f87d535690e52b40537915d8fe3d5a96c87e700c56dc638c32f7a2954f2ba409367d1a132000cc2228".to_string()) + ); + } } diff --git a/src/lib.rs b/src/lib.rs index 1df6d0b21..26eb6956c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub use chia_client as client; pub use chia_consensus as consensus; pub use chia_protocol as protocol; pub use chia_puzzles as puzzles; +pub use chia_secp as secp; pub use chia_sha2 as sha2; pub use chia_ssl as ssl; pub use chia_traits as traits;