blob: 6e94f0e1a79eed1b3c165f0932af0a6f541dde54 [file] [log] [blame]
David Benjamin6f415952024-12-10 21:28:37 -05001/* Copyright 2024 The BoringSSL Authors
Adam Langley905c3902024-09-19 13:34:28 -07002 *
3 * Permission to use, copy, modify, and/or distribute this software for any
4 * purpose with or without fee is hereby granted, provided that the above
5 * copyright notice and this permission notice appear in all copies.
6 *
7 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
14
15//! SLH-DSA-SHA2-128s.
16//!
17//! SLH-DSA-SHA2-128s is a post-quantum signature scheme. Its has a security
18//! reduction to the underlying hash function (SHA-256), giving an extremely high
19//! level of cryptoanalytic security. However, it has very large signatures (7856
20//! bytes) and signing is very slow (only a few operations per core second on
21//! high-powered cores). A given private key may only be used to sign
22//! 2<sup>64</sup> different messages.
23//!
24//! ```
25//! use bssl_crypto::slhdsa;
26//!
27//! // Generate a key pair.
28//! let (public_key, private_key) = slhdsa::PrivateKey::generate();
29//!
30//! // Sign a message.
31//! let message = b"test message";
32//! let signature = private_key.sign(message);
33//!
34//! // Verify the signature.
35//! assert!(public_key.verify(message, &signature).is_ok());
36//!
37//! // Signing with a context value.
38//! let context = b"context";
39//! let signature2 = private_key.sign_with_context(message, context)
40//! .expect("signing will always work if the context is less than 256 bytes");
41//!
42//! // The signature isn't value without a matching context.
43//! assert!(public_key.verify(message, &signature2).is_err());
44//!
45//! // ... but works with the correct context.
46//! assert!(public_key.verify_with_context(message, &signature2, context).is_ok());
47//! ```
48
Adam Langley571c76e2024-11-06 15:33:44 -080049use crate::{with_output_vec_fallible, FfiSlice, InvalidSignatureError};
Adam Langley905c3902024-09-19 13:34:28 -070050use alloc::vec::Vec;
51
52/// The number of bytes in an SLH-DSA-SHA2-128s public key.
53pub const PUBLIC_KEY_BYTES: usize = bssl_sys::SLHDSA_SHA2_128S_PUBLIC_KEY_BYTES as usize;
54
55/// The number of bytes in an SLH-DSA-SHA2-128s private key.
56pub const PRIVATE_KEY_BYTES: usize = bssl_sys::SLHDSA_SHA2_128S_PRIVATE_KEY_BYTES as usize;
57
58/// The number of bytes in an SLH-DSA-SHA2-128s signature.
59pub const SIGNATURE_BYTES: usize = bssl_sys::SLHDSA_SHA2_128S_SIGNATURE_BYTES as usize;
60
61/// An SLH-DSA-SHA2-128s private key.
62#[derive(Clone, PartialEq, Eq)]
63pub struct PrivateKey([u8; PRIVATE_KEY_BYTES]);
64
65/// An SLH-DSA-SHA2-128s public key.
66#[derive(Clone, PartialEq, Eq)]
67pub struct PublicKey([u8; PUBLIC_KEY_BYTES]);
68
69impl PrivateKey {
70 /// Generates a random public/private key pair.
71 pub fn generate() -> (PublicKey, Self) {
72 let mut public_key = [0u8; PUBLIC_KEY_BYTES];
73 let mut private_key = [0u8; PRIVATE_KEY_BYTES];
74
75 // Safety: the sizes of the arrays are correct.
76 unsafe {
77 bssl_sys::SLHDSA_SHA2_128S_generate_key(
78 public_key.as_mut_ptr(),
79 private_key.as_mut_ptr(),
80 );
81 }
82
83 (PublicKey(public_key), Self(private_key))
84 }
85
86 /// Derives the public key corresponding to this private key.
87 pub fn to_public_key(&self) -> PublicKey {
88 let mut public_key = [0u8; PUBLIC_KEY_BYTES];
89
90 // Safety: the sizes of the arrays are correct.
91 unsafe {
92 bssl_sys::SLHDSA_SHA2_128S_public_from_private(
93 public_key.as_mut_ptr(),
94 self.0.as_ptr(),
95 );
96 }
97
98 PublicKey(public_key)
99 }
100
101 /// Signs a message using this private key.
102 pub fn sign(&self, msg: &[u8]) -> Vec<u8> {
103 #[allow(clippy::expect_used)]
104 self.sign_with_context(msg, &[])
105 .expect("Empty context should always succeed")
106 }
107
108 /// Signs a message using this private key and the given context.
109 ///
110 /// This function returns None if `context` is longer than 255 bytes.
111 pub fn sign_with_context(&self, msg: &[u8], context: &[u8]) -> Option<Vec<u8>> {
Adam Langley06d50782024-10-23 15:03:08 -0700112 // Safety: the FFI function always writes exactly `SIGNATURE_BYTES` to
113 // the first argument if it succeeds. The size of the array passed as
114 // the second argument is correct.
Adam Langley905c3902024-09-19 13:34:28 -0700115 unsafe {
116 with_output_vec_fallible(SIGNATURE_BYTES, |signature| {
117 if bssl_sys::SLHDSA_SHA2_128S_sign(
118 signature,
119 self.0.as_ptr(),
120 msg.as_ffi_ptr(),
121 msg.len(),
122 context.as_ffi_ptr(),
123 context.len(),
124 ) == 1
125 {
126 Some(SIGNATURE_BYTES)
127 } else {
128 None
129 }
130 })
131 }
132 }
133}
134
135impl AsRef<[u8]> for PrivateKey {
136 /// Returns the bytes of the public key.
137 fn as_ref(&self) -> &[u8] {
138 &self.0
139 }
140}
141
142impl From<&[u8; PRIVATE_KEY_BYTES]> for PrivateKey {
143 fn from(bytes: &[u8; PRIVATE_KEY_BYTES]) -> Self {
144 Self(*bytes)
145 }
146}
147
148impl PublicKey {
149 /// Verifies a signature for a given message using this public key.
150 pub fn verify(&self, msg: &[u8], signature: &[u8]) -> Result<(), InvalidSignatureError> {
151 self.verify_with_context(msg, signature, &[])
152 }
153
154 /// Verifies a signature for a given message using this public key and the given context.
155 pub fn verify_with_context(
156 &self,
157 msg: &[u8],
158 signature: &[u8],
159 context: &[u8],
160 ) -> Result<(), InvalidSignatureError> {
161 // Safety: the size of the array passed as the third argument is correct.
162 let ok = unsafe {
163 bssl_sys::SLHDSA_SHA2_128S_verify(
164 signature.as_ffi_ptr(),
165 signature.len(),
166 self.0.as_ptr(),
167 msg.as_ffi_ptr(),
168 msg.len(),
169 context.as_ffi_ptr(),
170 context.len(),
171 )
172 };
173
174 if ok == 1 {
175 Ok(())
176 } else {
177 Err(InvalidSignatureError)
178 }
179 }
180}
181
182impl AsRef<[u8]> for PublicKey {
183 /// Returns the bytes of the public key.
184 fn as_ref(&self) -> &[u8] {
185 &self.0
186 }
187}
188
189impl From<&[u8; PUBLIC_KEY_BYTES]> for PublicKey {
190 fn from(bytes: &[u8; PUBLIC_KEY_BYTES]) -> Self {
191 Self(*bytes)
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn test_key_generation_and_signature() {
201 let (public_key, private_key) = PrivateKey::generate();
202 let msg = b"test message";
203 let sig = private_key.sign(msg);
204
205 assert!(public_key.verify(msg, &sig).is_ok());
206
207 let mut invalid_sig = sig.clone();
208 invalid_sig[0] ^= 1;
209 assert!(public_key.verify(msg, &invalid_sig).is_err());
210 }
211
212 #[test]
213 fn test_sign_and_verify_with_context() {
214 let (public_key, private_key) = PrivateKey::generate();
215 let msg = b"test message";
216 let context = b"test context";
217
218 let sig = private_key.sign_with_context(msg, context).unwrap();
219 assert!(public_key.verify_with_context(msg, &sig, context).is_ok());
220 assert!(public_key
221 .verify_with_context(msg, &sig, b"wrong context")
222 .is_err());
223 }
224
225 #[test]
Adam Langley905c3902024-09-19 13:34:28 -0700226 fn test_public_key_from_private() {
227 let (public_key, private_key) = PrivateKey::generate();
228 let derived_public_key = private_key.to_public_key();
229
230 assert_eq!(public_key.0, derived_public_key.0);
231 }
232
233 #[test]
234 fn test_empty_message_and_context() {
235 let (public_key, private_key) = PrivateKey::generate();
236 let msg = b"";
237 let context = b"";
238
239 let sig = private_key.sign_with_context(msg, context).unwrap();
240 assert!(public_key.verify_with_context(msg, &sig, context).is_ok());
241 }
242
243 #[test]
244 fn test_max_context_length() {
245 let (public_key, private_key) = PrivateKey::generate();
246 let msg = b"test message";
247 let context = vec![0u8; 255]; // Maximum allowed context length
248
249 let sig = private_key.sign_with_context(msg, &context).unwrap();
250 assert!(public_key.verify_with_context(msg, &sig, &context).is_ok());
251
252 let too_long_context = vec![0u8; 256];
253 assert!(private_key
254 .sign_with_context(msg, &too_long_context)
255 .is_none());
256 }
257}