Add ML-DSA-87 variants to Rust wrappers
Pure copy-paste then replace s/65/87/g
Change-Id: I7f5d7e9849612f8c0acc2033ccf1448f61593f98
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/81848
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 309c3da..8c4baa6 100644
--- a/rust/bssl-crypto/src/mldsa.rs
+++ b/rust/bssl-crypto/src/mldsa.rs
@@ -54,6 +54,18 @@
/// The number of bytes in an encoded ML-DSA-65 signature.
pub const SIGNATURE_BYTES_65: usize = bssl_sys::MLDSA65_SIGNATURE_BYTES as usize;
+/// An ML-DSA-87 private key.
+pub struct PrivateKey87(Box<bssl_sys::MLDSA87_private_key>);
+
+/// An ML-DSA-87 public key.
+pub struct PublicKey87(Box<bssl_sys::MLDSA87_public_key>);
+
+/// The number of bytes in an encoded ML-DSA-87 public key.
+pub const PUBLIC_KEY_BYTES_87: usize = bssl_sys::MLDSA87_PUBLIC_KEY_BYTES as usize;
+
+/// The number of bytes in an encoded ML-DSA-87 signature.
+pub const SIGNATURE_BYTES_87: usize = bssl_sys::MLDSA87_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;
@@ -341,12 +353,293 @@
}
}
+impl PrivateKey87 {
+ /// 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_87);
+ 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::MLDSA87_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
+ // `MLDSA87_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::MLDSA87_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) -> PublicKey87 {
+ PublicKey87(unsafe {
+ // Safety: `pub_key` is the correct size via the type system and
+ // is always fully written.
+ initialized_boxed_struct(|pub_key| {
+ bssl_sys::MLDSA87_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_87, |signature| {
+ let ok = bssl_sys::MLDSA87_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_87
+ })
+ }
+ }
+
+ /// 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_87, |signature| {
+ if bssl_sys::MLDSA87_sign(
+ signature,
+ &*self.0,
+ msg.as_ffi_ptr(),
+ msg.len(),
+ context.as_ffi_ptr(),
+ context.len(),
+ ) == 1
+ {
+ Some(SIGNATURE_BYTES_87)
+ } else {
+ None
+ }
+ })
+ }
+ }
+
+ /// Sign pre-hashed data.
+ pub fn sign_prehashed(&self, prehash: Prehash87) -> 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_87, |signature| {
+ let ok = bssl_sys::MLDSA87_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_87
+ })
+ }
+ }
+}
+
+impl PublicKey87 {
+ /// 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::MLDSA87_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_87, |buf| {
+ let ok = bssl_sys::MLDSA87_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::MLDSA87_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::MLDSA87_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)
+ }
+ }
+ }
+
+ /// Verify pre-hashed data.
+ pub fn verify_prehashed(
+ &self,
+ prehash: Prehash87,
+ signature: &[u8],
+ ) -> Result<(), InvalidSignatureError> {
+ let representative = prehash.finalize();
+ unsafe {
+ let ok = bssl_sys::MLDSA87_verify_message_representative(
+ &*self.0,
+ signature.as_ffi_ptr(),
+ signature.len(),
+ representative.as_ffi_ptr(),
+ );
+ if ok == 1 {
+ Ok(())
+ } else {
+ Err(InvalidSignatureError)
+ }
+ }
+ }
+
+ /// Start a pre-hashing operation using this public key.
+ pub fn prehash(&self) -> Prehash87 {
+ 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 Prehash87| {
+ let ok = bssl_sys::MLDSA87_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-87 pre-hashing operation.
+pub struct Prehash87(bssl_sys::MLDSA87_prehash);
+
+impl Prehash87 {
+ /// 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::MLDSA87_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::MLDSA87_prehash_finalize(mu.as_mut_ffi_ptr(), &mut self.0);
+ }
+ mu
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::io::Write for Prehash87 {
+ 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)]
mod test {
use super::*;
#[test]
- fn basic() {
+ fn basic65() {
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];
@@ -368,7 +661,7 @@
}
#[test]
- fn sign_prehashed() {
+ fn sign_prehashed65() {
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];
@@ -386,7 +679,7 @@
#[cfg(feature = "std")]
#[test]
- fn sign_prehashed_write() {
+ fn sign_prehashed65_write() {
use std::io::Write;
let (serialized_public_key, private_key, _private_seed) = PrivateKey65::generate();
let public_key = PublicKey65::parse(&serialized_public_key).unwrap();
@@ -405,7 +698,7 @@
}
#[test]
- fn verify_prehashed() {
+ fn verify_prehashed65() {
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];
@@ -426,7 +719,7 @@
#[cfg(feature = "std")]
#[test]
- fn verify_prehashed_write() {
+ fn verify_prehashed65_write() {
use std::io::Write;
let (serialized_public_key, private_key, _private_seed) = PrivateKey65::generate();
let public_key = PublicKey65::parse(&serialized_public_key).unwrap();
@@ -449,7 +742,7 @@
}
#[test]
- fn marshal_public_key() {
+ fn marshal_public_key65() {
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());
@@ -458,4 +751,118 @@
private_key.to_public_key().to_bytes()
);
}
+
+ #[test]
+ fn basic87() {
+ let (serialized_public_key, private_key, private_seed) = PrivateKey87::generate();
+ let public_key = PublicKey87::parse(&serialized_public_key).unwrap();
+ let message = &[0u8, 1, 2, 3];
+ let signature = private_key.sign(message);
+ let private_key2 = PrivateKey87::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 sign_prehashed87() {
+ let (serialized_public_key, private_key, _private_seed) = PrivateKey87::generate();
+ let public_key = PublicKey87::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 sign_prehashed87_write() {
+ use std::io::Write;
+ let (serialized_public_key, private_key, _private_seed) = PrivateKey87::generate();
+ let public_key = PublicKey87::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 verify_prehashed87() {
+ let (serialized_public_key, private_key, _private_seed) = PrivateKey87::generate();
+ let public_key = PublicKey87::parse(&serialized_public_key).unwrap();
+ let message = &[0u8, 1, 2, 3, 4, 5, 6];
+
+ let signature = private_key.sign(message);
+
+ let mut prehash = public_key.prehash();
+ prehash.update(&message[0..2]);
+ prehash.update(&message[2..4]);
+ assert!(public_key.verify_prehashed(prehash, &signature).is_err());
+
+ let mut prehash = public_key.prehash();
+ prehash.update(&message[0..2]);
+ prehash.update(&message[2..4]);
+ prehash.update(&message[4..]);
+ assert!(public_key.verify_prehashed(prehash, &signature).is_ok());
+ }
+
+ #[cfg(feature = "std")]
+ #[test]
+ fn verify_prehashed87_write() {
+ use std::io::Write;
+ let (serialized_public_key, private_key, _private_seed) = PrivateKey87::generate();
+ let public_key = PublicKey87::parse(&serialized_public_key).unwrap();
+ let message = &[0u8, 1, 2, 3, 4, 5, 6];
+
+ let signature = private_key.sign(message);
+
+ let mut prehash = public_key.prehash();
+ prehash.write(&message[0..2]).unwrap();
+ prehash.write(&message[2..4]).unwrap();
+ prehash.flush().unwrap();
+ assert!(public_key.verify_prehashed(prehash, &signature).is_err());
+
+ 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();
+ assert!(public_key.verify_prehashed(prehash, &signature).is_ok());
+ }
+
+ #[test]
+ fn marshal_public_key87() {
+ let (serialized_public_key, private_key, _) = PrivateKey87::generate();
+ let public_key = PublicKey87::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()
+ );
+ }
}