Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add secp256k1 and secp256r1 types #821

Merged
merged 5 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <b.haggstrom@chia.net>"]
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
Loading