//! An implementation of digitial signatures using secp256k1 ECDSA. use k256::ecdsa::{ Signature, SigningKey, VerifyingKey, signature::{Signer, Verifier}, }; use crate::error::CryptoError; pub struct SigningKeypair { sk: SigningKey, vk: VerifyingKey, } #[derive(Debug, PartialEq, Eq)] pub struct PublicKey(VerifyingKey); impl SigningKeypair { /// Generates a new random keypair pub fn new() -> Result { let mut raw_private = [0u8; 32]; rand::fill(&mut raw_private); let sk = SigningKey::from_slice(&raw_private) .expect("Key is provided generated with fixed valid size"); let vk = sk.verifying_key().to_owned(); Ok(Self { sk, vk }) } /// Create a new key from existing bytes. pub(crate) fn from_bytes(bytes: &[u8]) -> Result { if bytes.len() == 32 { let sk = SigningKey::from_slice(&bytes).expect("Key was checked to be a valid size"); let vk = sk.verifying_key().to_owned(); Ok(Self { sk, vk }) } else { Err(CryptoError::InvalidKeySize) } } /// View the raw bytes of the private key of this keypair. pub(crate) fn as_raw_private_key(&self) -> Vec { self.sk.as_nonzero_scalar().to_bytes().to_vec() } /// Get the public part of this keypair. pub fn public_key(&self) -> PublicKey { PublicKey(self.vk) } /// Sign data with the private key of this `SigningKeypair`. Other parties can use the public /// key to verify the signature. The generated signature is a detached signature. pub fn sign(&self, message: &[u8]) -> Result, CryptoError> { let sig: Signature = self.sk.sign(message); Ok(sig.to_vec()) } } impl PublicKey { /// Import a public key from raw bytes pub fn from_bytes(bytes: &[u8]) -> Result { Ok(Self(VerifyingKey::from_sec1_bytes(bytes)?)) } /// Get the raw bytes of this `PublicKey`, which can be transferred to another party. /// /// The public key is SEC-1 encoded and compressed. pub fn as_bytes(&self) -> Box<[u8]> { self.0.to_encoded_point(true).to_bytes() } pub fn verify_signature(&self, message: &[u8], sig: &[u8]) -> Result<(), CryptoError> { let sig = Signature::from_slice(sig).map_err(|_| CryptoError::InvalidKeySize)?; self.0 .verify(message, &sig) .map_err(|_| CryptoError::SignatureFailed) } } #[cfg(test)] mod tests { /// Generate a key, get the public key, export the bytes of said public key, import them again /// as a public key, and verify the keys match. This make sure public keys can be exchanged. #[test] fn recover_public_key() { let sk = super::SigningKeypair::new().expect("Can generate new key"); let pk = sk.public_key(); let pk_bytes = pk.as_bytes(); let pk2 = super::PublicKey::from_bytes(&pk_bytes).expect("Can import public key"); assert_eq!(pk, pk2); } /// Sign a message and validate the signature with the public key. Together with the above test /// this makes sure a remote system can receive our public key and validate messages we sign. #[test] fn validate_signature() { let sk = super::SigningKeypair::new().expect("Can generate new key"); let pk = sk.public_key(); let message = b"this is an arbitrary message we want to sign"; let sig = sk.sign(message).expect("Message can be signed"); assert!(pk.verify_signature(message, &sig).is_ok()); } /// Make sure a signature which is tampered with does not pass signature validation #[test] fn corrupt_signature_does_not_validate() { let sk = super::SigningKeypair::new().expect("Can generate new key"); let pk = sk.public_key(); let message = b"this is an arbitrary message we want to sign"; let mut sig = sk.sign(message).expect("Message can be signed"); // Tamper with the sig sig[0] = sig[0].wrapping_add(1); assert!(pk.verify_signature(message, &sig).is_err()); } /// Make sure a valid signature does not work for a message which has been modified #[test] fn tampered_message_does_not_validate() { let sk = super::SigningKeypair::new().expect("Can generate new key"); let pk = sk.public_key(); let message = b"this is an arbitrary message we want to sign"; let mut message_clone = message.to_vec(); let sig = sk.sign(message).expect("Message can be signed"); // Modify the message message_clone[0] = message[0].wrapping_add(1); assert!(pk.verify_signature(&message_clone, &sig).is_err()); } }