mirror of
https://boringssl.googlesource.com/boringssl
synced 2026-07-21 14:43:51 +00:00
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>
This commit is contained in:
committed by
Boringssl LUCI CQ
parent
567272392b
commit
0ece7ad693
@@ -54,6 +54,18 @@ pub const PUBLIC_KEY_BYTES_65: usize = bssl_sys::MLDSA65_PUBLIC_KEY_BYTES as usi
|
||||
/// 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 std::io::Write for Prehash65 {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ mod test {
|
||||
}
|
||||
|
||||
#[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 @@ mod test {
|
||||
|
||||
#[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 @@ mod test {
|
||||
}
|
||||
|
||||
#[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 @@ mod test {
|
||||
|
||||
#[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 @@ mod test {
|
||||
}
|
||||
|
||||
#[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 @@ mod test {
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user