use core::fmt;
use crate::error::{Error, Result};
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct PublicKey([u8; 65]);
impl PublicKey {
pub fn new(bytes: [u8; 65]) -> Result<Self> {
if bytes[0] != 0x04 {
return Err(Error::BadPublicKeyPrefix);
}
Ok(Self(bytes))
}
pub fn from_slice(slice: &[u8]) -> Result<Self> {
let bytes: [u8; 65] = slice
.try_into()
.map_err(|_| Error::WrongPublicKeyLength(slice.len()))?;
Self::new(bytes)
}
#[must_use]
pub fn as_bytes(&self) -> &[u8; 65] {
&self.0
}
pub fn verify(&self, message: &[u8], signature: &crate::signature::Signature) -> Result<()> {
use ring::signature::{UnparsedPublicKey, ECDSA_P256_SHA256_FIXED};
let key = UnparsedPublicKey::new(&ECDSA_P256_SHA256_FIXED, &self.0[..]);
key.verify(message, signature.as_bytes())
.map_err(|_| Error::SignatureVerificationFailed)
}
}
impl fmt::Debug for PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"PublicKey({:02x}{:02x}{:02x}{:02x}…)",
self.0[1], self.0[2], self.0[3], self.0[4]
)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)] mod tests {
use ring::rand::SystemRandom;
use ring::signature::{EcdsaKeyPair, KeyPair, ECDSA_P256_SHA256_FIXED_SIGNING};
use super::*;
use crate::signature::Signature;
fn make_keypair() -> (PublicKey, EcdsaKeyPair) {
let rng = SystemRandom::new();
let pkcs8 = EcdsaKeyPair::generate_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, &rng).unwrap();
let key_pair =
EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, pkcs8.as_ref(), &rng)
.unwrap();
let our_pub = PublicKey::from_slice(key_pair.public_key().as_ref()).unwrap();
(our_pub, key_pair)
}
fn sign(key_pair: &EcdsaKeyPair, message: &[u8]) -> Signature {
let rng = SystemRandom::new();
let sig = key_pair.sign(&rng, message).unwrap();
Signature::from_slice(sig.as_ref()).unwrap()
}
#[test]
fn verify_accepts_correct_signature() {
let (pub_key, key_pair) = make_keypair();
let message = b"matter-cert phase 2 test message";
let sig = sign(&key_pair, message);
assert!(pub_key.verify(message, &sig).is_ok());
}
#[test]
fn verify_rejects_signature_from_different_key() {
let (_, key_a) = make_keypair();
let (pub_b, _) = make_keypair();
let message = b"signed by A, verified against B";
let sig = sign(&key_a, message);
let err = pub_b.verify(message, &sig).unwrap_err();
assert!(matches!(err, Error::SignatureVerificationFailed));
}
#[test]
fn verify_rejects_signature_for_different_message() {
let (pub_key, key_pair) = make_keypair();
let sig = sign(&key_pair, b"signed message");
let err = pub_key.verify(b"different message", &sig).unwrap_err();
assert!(matches!(err, Error::SignatureVerificationFailed));
}
#[test]
fn verify_rejects_tampered_signature() {
let (pub_key, key_pair) = make_keypair();
let message = b"this is the original message";
let mut sig = sign(&key_pair, message);
let mut raw = *sig.as_bytes();
raw[0] ^= 0x01;
sig = Signature::from_slice(&raw).unwrap();
let err = pub_key.verify(message, &sig).unwrap_err();
assert!(matches!(err, Error::SignatureVerificationFailed));
}
#[test]
fn new_rejects_non_0x04_prefix() {
let mut bytes = [0u8; 65];
bytes[0] = 0x02;
assert!(matches!(
PublicKey::new(bytes),
Err(Error::BadPublicKeyPrefix)
));
}
#[test]
fn new_accepts_0x04_prefix() {
let mut bytes = [0u8; 65];
bytes[0] = 0x04;
bytes[1] = 0xAB;
let key = PublicKey::new(bytes).unwrap();
assert_eq!(key.as_bytes(), &bytes);
}
#[test]
fn from_slice_rejects_wrong_length() {
let short = [0x04u8; 10];
assert!(matches!(
PublicKey::from_slice(&short),
Err(Error::WrongPublicKeyLength(10))
));
}
#[test]
fn debug_format_does_not_leak_full_key() {
let mut bytes = [0u8; 65];
bytes[0] = 0x04;
bytes[1] = 0xAB;
bytes[2] = 0xCD;
bytes[3] = 0xEF;
bytes[4] = 0x12;
let key = PublicKey::new(bytes).unwrap();
let s = format!("{key:?}");
assert!(s.contains("abcdef12"));
assert!(!s.contains("00000000"));
}
}