Skip to main content

mldsa_native_rs/wrapper/
signing_key.rs

1pub use signature::Signer;
2
3use generic_array::GenericArray;
4
5use super::{ParameterSet, SignatureLen, SigningKeyLen, VerifyingKey, VerifyingKeyLen};
6
7use super::utils;
8use utils::transcoding;
9use utils::typenum::Unsigned;
10
11use crate::ffi::{SUCCESS, c_int};
12use crate::{FFIError, prepare_domain_separation_prefix};
13
14/// Secret key for signature generation.
15#[derive(Debug, PartialEq)]
16#[repr(transparent)]
17pub struct SigningKey<P: ParameterSet> {
18    sk: GenericArray<u8, <P as SigningKeyLen>::LEN>,
19}
20
21/// A seed for a ML-DSA signing key.
22///
23/// A `&MlDsaSeed` can be created from a `&[u8]` via the
24/// `TryInto::<&MlDsaSeed>::try_into` method. The length of the seed is the same
25/// for all parameter sets.
26pub type MlDsaSeed = GenericArray<u8, generic_array::typenum::U32>;
27
28pub type SecretKey<P> = SigningKey<P>;
29
30#[cfg(feature = "rand")]
31/// Generate a keypair.
32pub fn keygen<P: ParameterSet>() -> Result<(SigningKey<P>, VerifyingKey<P>), FFIError> {
33    let mut pk: GenericArray<u8, <P as VerifyingKeyLen>::LEN> = GenericArray::default();
34    let mut sk: GenericArray<u8, <P as SigningKeyLen>::LEN> = GenericArray::default();
35    let seed: MlDsaSeed = utils::rand::random_generic_byte_array();
36    match unsafe { P::KEYGEN_FROM_SEED_FN(pk.as_mut_ptr(), sk.as_mut_ptr(), seed.as_ptr()) } {
37        SUCCESS => Ok((SigningKey { sk }, VerifyingKey { pk })),
38        error_code => Err(FFIError { code: error_code }),
39    }
40}
41
42/// Generate a keypair from the given seed.
43pub fn keygen_from_seed<P: ParameterSet>(
44    seed: &MlDsaSeed,
45) -> Result<(SigningKey<P>, VerifyingKey<P>), FFIError> {
46    let mut pk: GenericArray<u8, <P as VerifyingKeyLen>::LEN> = GenericArray::default();
47    let mut sk: GenericArray<u8, <P as SigningKeyLen>::LEN> = GenericArray::default();
48    match unsafe { P::KEYGEN_FROM_SEED_FN(pk.as_mut_ptr(), sk.as_mut_ptr(), seed.as_ptr()) } {
49        SUCCESS => Ok((SigningKey { sk }, VerifyingKey { pk })),
50        error_code => Err(FFIError { code: error_code }),
51    }
52}
53
54pub(super) fn pk_from_sk<P: ParameterSet>(sk: &SigningKey<P>) -> Result<VerifyingKey<P>, FFIError> {
55    let mut pk: GenericArray<u8, <P as VerifyingKeyLen>::LEN> = GenericArray::default();
56    match unsafe { P::PK_FROM_SK_FN(pk.as_mut_ptr(), sk.sk.as_ptr()) } {
57        SUCCESS => Ok(VerifyingKey { pk }),
58        error_code => Err(FFIError { code: error_code }),
59    }
60}
61
62impl<P: ParameterSet> SigningKey<P> {
63    #[cfg(feature = "rand")]
64    /// Generate a new signing key.
65    pub fn new() -> Result<Self, FFIError> {
66        let (sk, _) = keygen::<P>()?;
67        Ok(sk)
68    }
69
70    /// Generate a new signing key from the given seed.
71    pub fn new_from_seed(seed: &MlDsaSeed) -> Result<Self, FFIError> {
72        let (sk, _) = keygen_from_seed::<P>(seed)?;
73        Ok(sk)
74    }
75
76    /// Attempt to use [`Self`] to sign the given `message` bytestring under the
77    /// associated `context` bytestring, using the seed `seed`, returning
78    /// a digital signature on success or a [`signature::Error`] if something
79    /// went wrong.
80    ///
81    /// # Errors
82    ///
83    /// The main intended use case for signing errors is when communicating
84    /// with external signers, e.g. cloud KMS, HSMs, or other hardware tokens.
85    ///
86    /// This method returns a [`signature::Error`] if the underlying FFI
87    /// signature generation fails.
88    pub fn try_sign_with_ctx_seeded(
89        &self,
90        message: &[u8],
91        context: &[u8],
92        seed: &MlDsaSeed,
93    ) -> Result<super::Signature<P>, signature::Error> {
94        type Siglen<P> = <P as SignatureLen>::LEN;
95        let mut sig: GenericArray<u8, Siglen<P>> = GenericArray::default();
96
97        let mut siglen: usize = 0;
98        let ret: c_int = {
99            let sk = self.sk.as_ptr();
100            let prefix = prepare_domain_separation_prefix::<P>(context)?;
101
102            unsafe {
103                P::SIGN_WITH_SEED_FN(
104                    sig.as_mut_ptr(),
105                    &mut siglen as *mut usize,
106                    message.as_ptr(),
107                    message.len(),
108                    prefix.as_ptr(),
109                    prefix.len(),
110                    seed.as_ptr(),
111                    sk,
112                    0, // do not use "external mu" mode
113                )
114            }
115        };
116        if ret != SUCCESS || siglen != <Siglen<P>>::USIZE {
117            return Err(signature::Error::new());
118        }
119
120        // SAFETY: We assume the backend fully initialized all bytes of the
121        // array, if it set the expected siglen and didn't return an error code.
122        let s = super::Signature::<P> { sig };
123
124        Ok(s)
125    }
126}
127
128pub(super) const EMPTY_CTX: &[u8; 0] = &[];
129
130#[cfg(feature = "rand")]
131impl<P: ParameterSet> signature::Signer<super::Signature<P>> for SigningKey<P> {
132    fn try_sign(&self, msg: &[u8]) -> Result<super::Signature<P>, signature::Error> {
133        let seed = utils::rand::random_generic_byte_array();
134        self.try_sign_with_ctx_seeded(msg, EMPTY_CTX, &seed)
135    }
136}
137
138#[cfg(feature = "rand")]
139impl<P: ParameterSet> ContextSigner<super::Signature<P>> for SigningKey<P> {
140    fn try_sign_with_ctx(
141        &self,
142        msg: &[u8],
143        ctx: &[u8],
144    ) -> Result<super::Signature<P>, signature::Error> {
145        let seed = utils::rand::random_generic_byte_array();
146        self.try_sign_with_ctx_seeded(msg, ctx, &seed)
147    }
148}
149
150impl<P: ParameterSet> SeededContextSigner<super::Signature<P>> for SigningKey<P> {
151    fn try_sign_with_ctx_and_seed(
152        &self,
153        seed: &[u8],
154        msg: &[u8],
155        ctx: &[u8],
156    ) -> Result<super::Signature<P>, signature::Error> {
157        let seed = GenericArray::try_from_slice(seed).map_err(|_| signature::Error::new())?;
158        self.try_sign_with_ctx_seeded(msg, ctx, seed)
159    }
160}
161
162impl<P: ParameterSet> SeededSigner<super::Signature<P>> for SigningKey<P> {
163    fn try_sign_with_seed(
164        &self,
165        seed: &[u8],
166        msg: &[u8],
167    ) -> Result<super::Signature<P>, signature::Error> {
168        let seed = GenericArray::try_from_slice(seed).map_err(|_| signature::Error::new())?;
169        self.try_sign_with_ctx_seeded(msg, EMPTY_CTX, seed)
170    }
171}
172
173impl<P: ParameterSet> From<SigningKey<P>> for GenericArray<u8, <P as crate::SigningKeyLen>::LEN> {
174    fn from(sk: SigningKey<P>) -> Self {
175        sk.sk
176    }
177}
178
179impl<P: ParameterSet> TryFrom<&[u8]> for SigningKey<P> {
180    type Error = transcoding::TranscodingError;
181
182    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
183        // Type inference for arr due to its later use in constructing a Self (i.e. a SigningKey<P>)
184        // ensures the byte slice is the correct length to be a signing key in this parameter set.
185        let arr = GenericArray::try_from_slice(bytes)?;
186
187        // In mldsa-native, the validity of a (purported) signing key is only checked when
188        // attempting to derive the corresponding public key. Here we provisionally assume the bytes
189        // represent a valid secret key in order to perform that derivation, but we discard the
190        // result if successful.
191        let provisional_sk = Self { sk: arr.clone() };
192        let _ = pk_from_sk(&provisional_sk)?;
193
194        Ok(provisional_sk)
195    }
196}
197
198impl<P: ParameterSet> AsRef<[u8]> for SigningKey<P> {
199    fn as_ref(&self) -> &[u8] {
200        self.sk.as_ref()
201    }
202}
203
204/// Sign the given message, using the provided seed for the signing process.
205pub trait SeededSigner<S> {
206    /// Sign the given message and return a digital signature
207    fn sign_with_seed(&self, seed: &[u8], msg: &[u8]) -> S {
208        self.try_sign_with_seed(seed, msg)
209            .expect("signature operation failed")
210    }
211
212    /// Attempt to sign the given message, returning a digital signature on
213    /// success, or an error if something went wrong.
214    ///
215    /// The main intended use case for signing errors is when communicating
216    /// with external signers, e.g. cloud KMS, HSMs, or other hardware tokens.
217    fn try_sign_with_seed(&self, seed: &[u8], msg: &[u8]) -> Result<S, signature::Error>;
218}
219
220/// Sign the given message, using the provided context string.
221pub trait ContextSigner<S> {
222    /// Sign the given message and return a digital signature
223    fn sign_with_context(&self, msg: &[u8], ctx: &[u8]) -> S {
224        self.try_sign_with_ctx(msg, ctx)
225            .expect("signature operation failed")
226    }
227
228    /// Attempt to sign the given message, returning a digital signature on
229    /// success, or an error if something went wrong.
230    ///
231    /// The main intended use case for signing errors is when communicating
232    /// with external signers, e.g. cloud KMS, HSMs, or other hardware tokens.
233    fn try_sign_with_ctx(&self, msg: &[u8], ctx: &[u8]) -> Result<S, signature::Error>;
234}
235
236/// Sign the given message, using the provided context string and the provided
237/// seed for the signing process.
238pub trait SeededContextSigner<S> {
239    /// Sign the given message and return a digital signature
240    fn sign_with_context_and_seed(&self, seed: &[u8], msg: &[u8], ctx: &[u8]) -> S {
241        self.try_sign_with_ctx_and_seed(seed, msg, ctx)
242            .expect("signature operation failed")
243    }
244
245    /// Attempt to sign the given message, returning a digital signature on
246    /// success, or an error if something went wrong.
247    ///
248    /// The main intended use case for signing errors is when communicating
249    /// with external signers, e.g. cloud KMS, HSMs, or other hardware tokens.
250    fn try_sign_with_ctx_and_seed(
251        &self,
252        seed: &[u8],
253        msg: &[u8],
254        ctx: &[u8],
255    ) -> Result<S, signature::Error>;
256}