Skip to content

Commit

Permalink
Merge pull request #821 from Chia-Network/secp
Browse files Browse the repository at this point in the history
Add secp256k1 and secp256r1 types
  • Loading branch information
Rigidity authored Dec 12, 2024
2 parents d812c4f + 04a5edf commit 5741047
Show file tree
Hide file tree
Showing 16 changed files with 737 additions and 1 deletion.
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -71,6 +72,7 @@ ignored = ["clvmr"]
[features]
default = [
"bls",
"secp",
"client",
"consensus",
"protocol",
Expand All @@ -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"]
Expand All @@ -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" }
Expand Down Expand Up @@ -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"
27 changes: 27 additions & 0 deletions crates/chia-secp/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>"]
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 }
103 changes: 103 additions & 0 deletions crates/chia-secp/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
}
7 changes: 7 additions & 0 deletions crates/chia-secp/src/secp256k1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod public_key;
mod secret_key;
mod signature;

pub use public_key::*;
pub use secret_key::*;
pub use signature::*;
59 changes: 59 additions & 0 deletions crates/chia-secp/src/secp256k1/public_key.rs
Original file line number Diff line number Diff line change
@@ -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<H: Hasher>(&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> {
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<Self, Error> {
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())
}
}
50 changes: 50 additions & 0 deletions crates/chia-secp/src/secp256k1/secret_key.rs
Original file line number Diff line number Diff line change
@@ -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<H: Hasher>(&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> {
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<Self, Error> {
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<K1Signature, Error> {
Ok(K1Signature(
self.0.sign_prehash_recoverable(message_hash)?.0,
))
}
}
46 changes: 46 additions & 0 deletions crates/chia-secp/src/secp256k1/signature.rs
Original file line number Diff line number Diff line change
@@ -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<H: Hasher>(&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> {
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<Self, Error> {
Ok(Self(Signature::from_slice(bytes)?))
}
}
7 changes: 7 additions & 0 deletions crates/chia-secp/src/secp256r1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod public_key;
mod secret_key;
mod signature;

pub use public_key::*;
pub use secret_key::*;
pub use signature::*;
Loading

0 comments on commit 5741047

Please sign in to comment.