use crate::encoding::hex;
use crate::error::PublicKeyError;
use crate::private_key::PrivateKey;
use secp256k1::{PublicKey as Secp256k1PublicKey, Secp256k1};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PublicKeyFormat {
Compressed,
Uncompressed,
}
#[derive(Clone, PartialEq, Eq)]
pub struct PublicKey {
inner: Secp256k1PublicKey,
}
impl PublicKey {
pub fn from_private_key(private_key: &PrivateKey) -> Self {
let secp = Secp256k1::new();
let secret_key = secp256k1::SecretKey::from_slice(&private_key.to_bytes())
.expect("PrivateKey should always contain valid bytes");
let public_key = Secp256k1PublicKey::from_secret_key(&secp, &secret_key);
Self { inner: public_key }
}
pub fn from_compressed(bytes: &[u8; 33]) -> Result<Self, PublicKeyError> {
let public_key =
Secp256k1PublicKey::from_slice(bytes).map_err(|_| PublicKeyError::InvalidPoint)?;
Ok(Self { inner: public_key })
}
pub fn from_uncompressed(bytes: &[u8; 65]) -> Result<Self, PublicKeyError> {
let public_key =
Secp256k1PublicKey::from_slice(bytes).map_err(|_| PublicKeyError::InvalidPoint)?;
Ok(Self { inner: public_key })
}
pub fn from_hex(hex_str: &str) -> Result<Self, PublicKeyError> {
let expected_len = match hex_str.len() {
66 => 33, 130 => 65, _ => {
return Err(PublicKeyError::InvalidLength {
expected: 33,
actual: hex_str.len() / 2,
})
}
};
let bytes = hex::decode(hex_str).map_err(|e| PublicKeyError::InvalidHex(e.to_string()))?;
if bytes.len() != expected_len {
return Err(PublicKeyError::InvalidLength {
expected: expected_len,
actual: bytes.len(),
});
}
let public_key =
Secp256k1PublicKey::from_slice(&bytes).map_err(|_| PublicKeyError::InvalidPoint)?;
Ok(Self { inner: public_key })
}
pub fn to_compressed(&self) -> [u8; 33] {
self.inner.serialize()
}
pub fn to_uncompressed(&self) -> [u8; 65] {
self.inner.serialize_uncompressed()
}
pub fn to_hex(&self, format: PublicKeyFormat) -> String {
match format {
PublicKeyFormat::Compressed => hex::encode(&self.to_compressed()),
PublicKeyFormat::Uncompressed => hex::encode(&self.to_uncompressed()),
}
}
pub fn to_bytes(&self, format: PublicKeyFormat) -> Vec<u8> {
match format {
PublicKeyFormat::Compressed => self.to_compressed().to_vec(),
PublicKeyFormat::Uncompressed => self.to_uncompressed().to_vec(),
}
}
}
impl std::fmt::Debug for PublicKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "PublicKey({})", self.to_hex(PublicKeyFormat::Compressed))
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
fn valid_private_key_bytes() -> impl Strategy<Value = [u8; 32]> {
prop::array::uniform32(any::<u8>()).prop_filter("must be valid secp256k1 key", |bytes| {
PrivateKey::is_valid(bytes)
})
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn property_public_key_derivation_determinism(bytes in valid_private_key_bytes()) {
let private_key = PrivateKey::from_bytes(bytes).unwrap();
let public_key1 = private_key.public_key();
let public_key2 = private_key.public_key();
let public_key3 = PublicKey::from_private_key(&private_key);
prop_assert_eq!(&public_key1, &public_key2);
prop_assert_eq!(&public_key2, &public_key3);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn property_public_key_format_invariants(bytes in valid_private_key_bytes()) {
let private_key = PrivateKey::from_bytes(bytes).unwrap();
let public_key = private_key.public_key();
let compressed = public_key.to_compressed();
prop_assert_eq!(compressed.len(), 33);
prop_assert!(compressed[0] == 0x02 || compressed[0] == 0x03,
"Compressed prefix must be 02 or 03");
let uncompressed = public_key.to_uncompressed();
prop_assert_eq!(uncompressed.len(), 65);
prop_assert_eq!(uncompressed[0], 0x04,
"Uncompressed prefix must be 04");
let compressed_hex = public_key.to_hex(PublicKeyFormat::Compressed);
prop_assert_eq!(compressed_hex.len(), 66);
let uncompressed_hex = public_key.to_hex(PublicKeyFormat::Uncompressed);
prop_assert_eq!(uncompressed_hex.len(), 130);
let compressed_bytes = public_key.to_bytes(PublicKeyFormat::Compressed);
prop_assert_eq!(compressed_bytes.len(), 33);
let uncompressed_bytes = public_key.to_bytes(PublicKeyFormat::Uncompressed);
prop_assert_eq!(uncompressed_bytes.len(), 65);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn property_public_key_format_roundtrip(bytes in valid_private_key_bytes()) {
let private_key = PrivateKey::from_bytes(bytes).unwrap();
let original = private_key.public_key();
let compressed = original.to_compressed();
let from_compressed = PublicKey::from_compressed(&compressed).unwrap();
prop_assert_eq!(&original, &from_compressed);
let uncompressed = original.to_uncompressed();
let from_uncompressed = PublicKey::from_uncompressed(&uncompressed).unwrap();
prop_assert_eq!(&original, &from_uncompressed);
let from_compressed_uncompressed = from_compressed.to_uncompressed();
let back_to_compressed = PublicKey::from_uncompressed(&from_compressed_uncompressed)
.unwrap()
.to_compressed();
prop_assert_eq!(compressed, back_to_compressed);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn property_public_key_serialization_roundtrip(bytes in valid_private_key_bytes()) {
let private_key = PrivateKey::from_bytes(bytes).unwrap();
let original = private_key.public_key();
let hex_compressed = original.to_hex(PublicKeyFormat::Compressed);
let from_hex_compressed = PublicKey::from_hex(&hex_compressed).unwrap();
prop_assert_eq!(&original, &from_hex_compressed);
let hex_uncompressed = original.to_hex(PublicKeyFormat::Uncompressed);
let from_hex_uncompressed = PublicKey::from_hex(&hex_uncompressed).unwrap();
prop_assert_eq!(&original, &from_hex_uncompressed);
}
}
}