|  | // 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 | 
|  | //! 2<sup>64</sup> 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<u8> { | 
|  | #[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<Vec<u8>> { | 
|  | // 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()); | 
|  | } | 
|  | } |