Rust wrappers for external mu variant of ML-DSA
Change-Id: I7ae00968199c8297fa43bf14c7642d7d28f68836
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/81847
Commit-Queue: Adam Langley <agl@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/rust/bssl-crypto/src/mldsa.rs b/rust/bssl-crypto/src/mldsa.rs
index 9ee28c5..655272f 100644
--- a/rust/bssl-crypto/src/mldsa.rs
+++ b/rust/bssl-crypto/src/mldsa.rs
@@ -36,7 +36,8 @@
use crate::{
as_cbs, cbb_to_vec, initialized_boxed_struct, initialized_boxed_struct_fallible,
- with_output_vec, with_output_vec_fallible, FfiSlice, InvalidSignatureError,
+ initialized_struct, with_output_vec, with_output_vec_fallible, FfiMutSlice, FfiSlice,
+ InvalidSignatureError,
};
use alloc::{boxed::Box, vec::Vec};
use core::mem::MaybeUninit;
@@ -56,6 +57,9 @@
/// The number of bytes in an ML-DSA seed value.
pub const SEED_BYTES: usize = bssl_sys::MLDSA_SEED_BYTES as usize;
+/// The number of bytes in an ML-DSA external mu.
+pub const MU_BYTES: usize = bssl_sys::MLDSA_MU_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
@@ -164,6 +168,27 @@
})
}
}
+
+ /// Sign pre-hashed data.
+ pub fn sign_prehashed(&self, prehash: Prehash65) -> Vec<u8> {
+ let representative = prehash.finalize();
+ unsafe {
+ // Safety: `signature` is the correct size via the type system and
+ // is always fully written; `representative` is an array of the
+ // correct size.
+ with_output_vec(SIGNATURE_BYTES_65, |signature| {
+ let ok = bssl_sys::MLDSA65_sign_message_representative(
+ signature,
+ &*self.0,
+ representative.as_ffi_ptr(),
+ );
+ // 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
+ })
+ }
+ }
}
impl PublicKey65 {
@@ -237,6 +262,61 @@
}
}
}
+
+ /// Start a pre-hashing operation using this public key.
+ pub fn prehash(&self) -> Prehash65 {
+ unsafe {
+ // Safety: `self.0` is the correct size via the type system and
+ // is fully written if this function returns 1.
+ initialized_struct(|prehash: *mut Prehash65| {
+ let ok = bssl_sys::MLDSA65_prehash_init(
+ &mut (*prehash).0,
+ &*self.0,
+ core::ptr::null(),
+ 0,
+ );
+ // This function can only fail if too much context is provided, but no context is
+ // used here.
+ assert_eq!(ok, 1);
+ })
+ }
+ }
+}
+
+/// An in-progress ML-DSA-65 pre-hashing operation.
+pub struct Prehash65(bssl_sys::MLDSA65_prehash);
+
+impl Prehash65 {
+ /// Add data to the pre-hashing operation.
+ pub fn update(&mut self, data: &[u8]) {
+ unsafe {
+ // Safety: `self.0` is the correct size via the type system and `data`
+ // is a valid Rust slice.
+ bssl_sys::MLDSA65_prehash_update(&mut self.0, data.as_ffi_ptr(), data.len());
+ }
+ }
+
+ /// Complete the pre-hashing operation.
+ fn finalize(mut self) -> [u8; MU_BYTES] {
+ let mut mu = [0u8; MU_BYTES];
+ unsafe {
+ // Safety: `self.0` is the correct size via the type system, as is `mu`.
+ bssl_sys::MLDSA65_prehash_finalize(mu.as_mut_ffi_ptr(), &mut self.0);
+ }
+ mu
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::io::Write for Prehash65 {
+ fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+ self.update(buf);
+ Ok(buf.len())
+ }
+
+ fn flush(&mut self) -> std::io::Result<()> {
+ Ok(())
+ }
}
#[cfg(test)]
@@ -266,6 +346,43 @@
}
#[test]
+ fn prehashed() {
+ 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, 4, 5, 6];
+
+ let mut prehash = public_key.prehash();
+ prehash.update(&message[0..2]);
+ prehash.update(&message[2..4]);
+ prehash.update(&message[4..]);
+ let mut signature = private_key.sign_prehashed(prehash);
+
+ assert!(public_key.verify(message, &signature).is_ok());
+ signature[5] ^= 1;
+ assert!(public_key.verify(message, &signature).is_err());
+ }
+
+ #[cfg(feature = "std")]
+ #[test]
+ fn prehashed_write() {
+ use std::io::Write;
+ 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, 4, 5, 6];
+
+ let mut prehash = public_key.prehash();
+ prehash.write(&message[0..2]).unwrap();
+ prehash.write(&message[2..4]).unwrap();
+ prehash.write(&message[4..]).unwrap();
+ prehash.flush().unwrap();
+ let mut signature = private_key.sign_prehashed(prehash);
+
+ assert!(public_key.verify(message, &signature).is_ok());
+ signature[5] ^= 1;
+ assert!(public_key.verify(message, &signature).is_err());
+ }
+
+ #[test]
fn marshal_public_key() {
let (serialized_public_key, private_key, _) = PrivateKey65::generate();
let public_key = PublicKey65::parse(&serialized_public_key).unwrap();