// Copyright 2024 The BoringSSL Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! SLH-DSA-SHA2-128s. //! //! SLH-DSA-SHA2-128s is a post-quantum signature scheme. Its has a security //! reduction to the underlying hash function (SHA-256), giving an extremely high //! level of cryptoanalytic security. However, it has very large signatures (7856 //! bytes) and signing is very slow (only a few operations per core second on //! high-powered cores). A given private key may only be used to sign //! 264 different messages. //! //! ``` //! use bssl_crypto::slhdsa; //! //! // Generate a key pair. //! let (public_key, private_key) = slhdsa::PrivateKey::generate(); //! //! // Sign a message. //! let message = b"test message"; //! let signature = private_key.sign(message); //! //! // Verify the signature. //! assert!(public_key.verify(message, &signature).is_ok()); //! //! // Signing with a context value. //! let context = b"context"; //! let signature2 = private_key.sign_with_context(message, context) //! .expect("signing will always work if the context is less than 256 bytes"); //! //! // The signature isn't value without a matching context. //! assert!(public_key.verify(message, &signature2).is_err()); //! //! // ... but works with the correct context. //! assert!(public_key.verify_with_context(message, &signature2, context).is_ok()); //! ``` use crate::{with_output_vec_fallible, FfiSlice, InvalidSignatureError}; use alloc::vec::Vec; /// The number of bytes in an SLH-DSA-SHA2-128s public key. pub const PUBLIC_KEY_BYTES: usize = bssl_sys::SLHDSA_SHA2_128S_PUBLIC_KEY_BYTES as usize; /// The number of bytes in an SLH-DSA-SHA2-128s private key. pub const PRIVATE_KEY_BYTES: usize = bssl_sys::SLHDSA_SHA2_128S_PRIVATE_KEY_BYTES as usize; /// The number of bytes in an SLH-DSA-SHA2-128s signature. pub const SIGNATURE_BYTES: usize = bssl_sys::SLHDSA_SHA2_128S_SIGNATURE_BYTES as usize; /// An SLH-DSA-SHA2-128s private key. #[derive(Clone, PartialEq, Eq)] pub struct PrivateKey([u8; PRIVATE_KEY_BYTES]); /// An SLH-DSA-SHA2-128s public key. #[derive(Clone, PartialEq, Eq)] pub struct PublicKey([u8; PUBLIC_KEY_BYTES]); impl PrivateKey { /// Generates a random public/private key pair. pub fn generate() -> (PublicKey, Self) { let mut public_key = [0u8; PUBLIC_KEY_BYTES]; let mut private_key = [0u8; PRIVATE_KEY_BYTES]; // Safety: the sizes of the arrays are correct. unsafe { bssl_sys::SLHDSA_SHA2_128S_generate_key( public_key.as_mut_ptr(), private_key.as_mut_ptr(), ); } (PublicKey(public_key), Self(private_key)) } /// Derives the public key corresponding to this private key. pub fn to_public_key(&self) -> PublicKey { let mut public_key = [0u8; PUBLIC_KEY_BYTES]; // Safety: the sizes of the arrays are correct. unsafe { bssl_sys::SLHDSA_SHA2_128S_public_from_private( public_key.as_mut_ptr(), self.0.as_ptr(), ); } PublicKey(public_key) } /// Signs a message using this private key. pub fn sign(&self, msg: &[u8]) -> Vec { #[allow(clippy::expect_used)] self.sign_with_context(msg, &[]) .expect("Empty context should always succeed") } /// Signs a message using this private key and the given context. /// /// This function returns None if `context` is longer than 255 bytes. pub fn sign_with_context(&self, msg: &[u8], context: &[u8]) -> Option> { // Safety: the FFI function always writes exactly `SIGNATURE_BYTES` to // the first argument if it succeeds. The size of the array passed as // the second argument is correct. unsafe { with_output_vec_fallible(SIGNATURE_BYTES, |signature| { if bssl_sys::SLHDSA_SHA2_128S_sign( signature, self.0.as_ptr(), msg.as_ffi_ptr(), msg.len(), context.as_ffi_ptr(), context.len(), ) == 1 { Some(SIGNATURE_BYTES) } else { None } }) } } } impl AsRef<[u8]> for PrivateKey { /// Returns the bytes of the public key. fn as_ref(&self) -> &[u8] { &self.0 } } impl From<&[u8; PRIVATE_KEY_BYTES]> for PrivateKey { fn from(bytes: &[u8; PRIVATE_KEY_BYTES]) -> Self { Self(*bytes) } } impl PublicKey { /// Verifies a signature for a given message using this public key. pub fn verify(&self, msg: &[u8], signature: &[u8]) -> Result<(), InvalidSignatureError> { self.verify_with_context(msg, signature, &[]) } /// Verifies a signature for a given message using this public key and the given context. pub fn verify_with_context( &self, msg: &[u8], signature: &[u8], context: &[u8], ) -> Result<(), InvalidSignatureError> { // Safety: the size of the array passed as the third argument is correct. let ok = unsafe { bssl_sys::SLHDSA_SHA2_128S_verify( signature.as_ffi_ptr(), signature.len(), self.0.as_ptr(), msg.as_ffi_ptr(), msg.len(), context.as_ffi_ptr(), context.len(), ) }; if ok == 1 { Ok(()) } else { Err(InvalidSignatureError) } } } impl AsRef<[u8]> for PublicKey { /// Returns the bytes of the public key. fn as_ref(&self) -> &[u8] { &self.0 } } impl From<&[u8; PUBLIC_KEY_BYTES]> for PublicKey { fn from(bytes: &[u8; PUBLIC_KEY_BYTES]) -> Self { Self(*bytes) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_key_generation_and_signature() { let (public_key, private_key) = PrivateKey::generate(); let msg = b"test message"; let sig = private_key.sign(msg); assert!(public_key.verify(msg, &sig).is_ok()); let mut invalid_sig = sig.clone(); invalid_sig[0] ^= 1; assert!(public_key.verify(msg, &invalid_sig).is_err()); } #[test] fn test_sign_and_verify_with_context() { let (public_key, private_key) = PrivateKey::generate(); let msg = b"test message"; let context = b"test context"; let sig = private_key.sign_with_context(msg, context).unwrap(); assert!(public_key.verify_with_context(msg, &sig, context).is_ok()); assert!(public_key .verify_with_context(msg, &sig, b"wrong context") .is_err()); } #[test] fn test_public_key_from_private() { let (public_key, private_key) = PrivateKey::generate(); let derived_public_key = private_key.to_public_key(); assert_eq!(public_key.0, derived_public_key.0); } #[test] fn test_empty_message_and_context() { let (public_key, private_key) = PrivateKey::generate(); let msg = b""; let context = b""; let sig = private_key.sign_with_context(msg, context).unwrap(); assert!(public_key.verify_with_context(msg, &sig, context).is_ok()); } #[test] fn test_max_context_length() { let (public_key, private_key) = PrivateKey::generate(); let msg = b"test message"; let context = vec![0u8; 255]; // Maximum allowed context length let sig = private_key.sign_with_context(msg, &context).unwrap(); assert!(public_key.verify_with_context(msg, &sig, &context).is_ok()); let too_long_context = vec![0u8; 256]; assert!(private_key .sign_with_context(msg, &too_long_context) .is_none()); } }