| /* Copyright 2024 The BoringSSL Authors |
| * |
| * Permission to use, copy, modify, and/or distribute this software for any |
| * purpose with or without fee is hereby granted, provided that the above |
| * copyright notice and this permission notice appear in all copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
| * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
| * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ |
| |
| //! ML-DSA |
| //! |
| //! ML-DSA is a post-quantum key signature scheme, specified in |
| //! [FIPS 204](https://csrc.nist.gov/pubs/fips/204/final). |
| //! |
| //! ``` |
| //! use bssl_crypto::mldsa; |
| //! |
| //! // Generate a key pair. |
| //! let (serialized_public_key, private_key, _private_seed) = mldsa::PrivateKey65::generate(); |
| //! |
| //! // Send `serialized_public_key` to the verifier. The verifier parses it: |
| //! let public_key = mldsa::PublicKey65::parse(&serialized_public_key).unwrap(); |
| //! |
| //! // The signer signs a message. |
| //! let message = &[0u8, 1, 2, 3]; |
| //! let signature = private_key.sign(message); |
| //! |
| //! // Send `message` and `signature` to the verifier. The verifier checks the signature. |
| //! assert!(public_key.verify(message, &signature).is_ok()); |
| //! ``` |
| |
| use crate::{ |
| as_cbs, cbb_to_vec, initialized_boxed_struct, initialized_boxed_struct_fallible, |
| with_output_vec, with_output_vec_fallible, FfiSlice, InvalidSignatureError, |
| }; |
| use alloc::{boxed::Box, vec::Vec}; |
| use core::mem::MaybeUninit; |
| |
| /// An ML-DSA-65 private key. |
| pub struct PrivateKey65(Box<bssl_sys::MLDSA65_private_key>); |
| |
| /// An ML-DSA-65 public key. |
| pub struct PublicKey65(Box<bssl_sys::MLDSA65_public_key>); |
| |
| /// The number of bytes in an encoded ML-DSA-65 public key. |
| pub const PUBLIC_KEY_BYTES_65: usize = bssl_sys::MLDSA65_PUBLIC_KEY_BYTES as usize; |
| |
| /// The number of bytes in an encoded ML-DSA-65 signature. |
| pub const SIGNATURE_BYTES_65: usize = bssl_sys::MLDSA65_SIGNATURE_BYTES as usize; |
| |
| /// The number of bytes in an ML-DSA seed value. |
| pub const SEED_BYTES: usize = bssl_sys::MLDSA_SEED_BYTES as usize; |
| |
| impl PrivateKey65 { |
| /// Generates a random public/private key pair returning a serialized public |
| /// key, a private key, and a private seed value that can be used to |
| /// regenerate the same private key in the future. |
| pub fn generate() -> (Vec<u8>, Self, [u8; SEED_BYTES]) { |
| let mut public_key_bytes = Box::new_uninit_slice(PUBLIC_KEY_BYTES_65); |
| let mut seed = MaybeUninit::<[u8; SEED_BYTES]>::uninit(); |
| |
| let private_key = unsafe { |
| // Safety: the buffers are the sizes that the FFI code requires. |
| initialized_boxed_struct(|priv_key| { |
| let ok = bssl_sys::MLDSA65_generate_key( |
| public_key_bytes.as_mut_ptr() as *mut u8, |
| seed.as_mut_ptr() as *mut u8, |
| priv_key, |
| ); |
| // This function can only fail if out of memory, which is not a |
| // case that this crate handles. |
| assert_eq!(ok, 1); |
| }) |
| }; |
| |
| unsafe { |
| ( |
| // Safety: the buffers are always fully initialized by |
| // `MLDSA65_generate_key`. |
| public_key_bytes.assume_init().into(), |
| Self(private_key), |
| seed.assume_init(), |
| ) |
| } |
| } |
| |
| /// Regenerates a private key from a seed value. |
| pub fn from_seed(seed: &[u8; SEED_BYTES]) -> Self { |
| Self(unsafe { |
| // Safety: `priv_key` is the correct size via the type system and |
| // is always fully written. |
| initialized_boxed_struct(|priv_key| { |
| let ok = bssl_sys::MLDSA65_private_key_from_seed( |
| priv_key, |
| seed.as_ffi_ptr(), |
| seed.len(), |
| ); |
| // Since the seed value has the correct length, this function can |
| // never fail. |
| assert_eq!(ok, 1); |
| }) |
| }) |
| } |
| |
| /// Derives the public key corresponding to this private key. |
| pub fn to_public_key(&self) -> PublicKey65 { |
| PublicKey65(unsafe { |
| // Safety: `pub_key` is the correct size via the type system and |
| // is always fully written. |
| initialized_boxed_struct(|pub_key| { |
| bssl_sys::MLDSA65_public_from_private(pub_key, &*self.0); |
| }) |
| }) |
| } |
| |
| /// Signs a message using this private key. |
| pub fn sign(&self, msg: &[u8]) -> Vec<u8> { |
| unsafe { |
| // Safety: `signature` is the correct size via the type system and |
| // is always fully written. |
| with_output_vec(SIGNATURE_BYTES_65, |signature| { |
| let ok = bssl_sys::MLDSA65_sign( |
| signature, |
| &*self.0, |
| msg.as_ffi_ptr(), |
| msg.len(), |
| core::ptr::null(), |
| 0, |
| ); |
| // This function can only fail if out of memory, which is not a |
| // case that this crate handles. |
| assert_eq!(ok, 1); |
| SIGNATURE_BYTES_65 |
| }) |
| } |
| } |
| |
| /// 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>> { |
| unsafe { |
| // Safety: `signature` is the correct size via the type system and |
| // is always fully written. |
| with_output_vec_fallible(SIGNATURE_BYTES_65, |signature| { |
| if bssl_sys::MLDSA65_sign( |
| signature, |
| &*self.0, |
| msg.as_ffi_ptr(), |
| msg.len(), |
| context.as_ffi_ptr(), |
| context.len(), |
| ) == 1 |
| { |
| Some(SIGNATURE_BYTES_65) |
| } else { |
| None |
| } |
| }) |
| } |
| } |
| } |
| |
| impl PublicKey65 { |
| /// Parses a public key from a byte slice. |
| pub fn parse(encoded: &[u8]) -> Option<Self> { |
| let mut cbs = as_cbs(encoded); |
| unsafe { |
| // Safety: `pub_key` is the correct size via the type system and |
| // is fully written if this function returns 1. |
| initialized_boxed_struct_fallible(|pub_key| { |
| bssl_sys::MLDSA65_parse_public_key(pub_key, &mut cbs) == 1 && cbs.len == 0 |
| }) |
| } |
| .map(Self) |
| } |
| |
| /// Return the serialization of this public key. |
| pub fn to_bytes(&self) -> Vec<u8> { |
| unsafe { |
| cbb_to_vec(PUBLIC_KEY_BYTES_65, |buf| { |
| let ok = bssl_sys::MLDSA65_marshal_public_key(buf, &*self.0); |
| // `MLKEM768_marshal_public_key` only fails if it cannot |
| // allocate memory, but `cbb_to_vec` allocates a fixed |
| // amount of memory. |
| assert_eq!(ok, 1); |
| }) |
| } |
| } |
| |
| /// Verifies a signature for a given message using this public key. |
| pub fn verify(&self, msg: &[u8], signature: &[u8]) -> Result<(), InvalidSignatureError> { |
| unsafe { |
| let ok = bssl_sys::MLDSA65_verify( |
| &*self.0, |
| signature.as_ffi_ptr(), |
| signature.len(), |
| msg.as_ffi_ptr(), |
| msg.len(), |
| core::ptr::null(), |
| 0, |
| ); |
| if ok == 1 { |
| Ok(()) |
| } else { |
| Err(InvalidSignatureError) |
| } |
| } |
| } |
| |
| /// 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> { |
| unsafe { |
| let ok = bssl_sys::MLDSA65_verify( |
| &*self.0, |
| signature.as_ffi_ptr(), |
| signature.len(), |
| msg.as_ffi_ptr(), |
| msg.len(), |
| context.as_ffi_ptr(), |
| context.len(), |
| ); |
| if ok == 1 { |
| Ok(()) |
| } else { |
| Err(InvalidSignatureError) |
| } |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| |
| #[test] |
| fn basic() { |
| let (serialized_public_key, private_key, private_seed) = PrivateKey65::generate(); |
| let public_key = PublicKey65::parse(&serialized_public_key).unwrap(); |
| let message = &[0u8, 1, 2, 3]; |
| let signature = private_key.sign(message); |
| let private_key2 = PrivateKey65::from_seed(&private_seed); |
| let mut signature2 = private_key2.sign(message); |
| assert!(public_key.verify(message, &signature).is_ok()); |
| assert!(public_key.verify(message, &signature2).is_ok()); |
| |
| signature2[5] ^= 1; |
| assert!(public_key.verify(message, &signature2).is_err()); |
| |
| let context = b"context"; |
| let signature3 = private_key.sign_with_context(message, context).unwrap(); |
| assert!(public_key.verify(message, &signature3).is_err()); |
| assert!(public_key |
| .verify_with_context(message, &signature3, context) |
| .is_ok()); |
| } |
| |
| #[test] |
| fn marshal_public_key() { |
| let (serialized_public_key, private_key, _) = PrivateKey65::generate(); |
| let public_key = PublicKey65::parse(&serialized_public_key).unwrap(); |
| assert_eq!(serialized_public_key, public_key.to_bytes()); |
| assert_eq!( |
| serialized_public_key, |
| private_key.to_public_key().to_bytes() |
| ); |
| } |
| } |