Skip to main content

pq_mayo/
lib.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3//! MAYO post-quantum signature scheme.
4//!
5//! This crate implements the [MAYO](https://pqmayo.org/) signature scheme, a
6//! post-quantum multivariate-based signature scheme submitted to the NIST PQC
7//! standardization process. MAYO is based on the "oil and vinegar" trapdoor
8//! and produces compact signatures with small public keys relative to other
9//! multivariate schemes.
10//!
11//! # Supported Parameter Sets
12//!
13//! | Type | Security Level | Signature Size | Public Key Size | Private Key Size |
14//! |------|----------------|----------------|-----------------|------------------|
15//! | [`Mayo1`] | 1 | 454 B | 1,420 B | 24 B |
16//! | [`Mayo2`] | 1 | 186 B | 4,912 B | 24 B |
17//! | [`Mayo3`] | 3 | 681 B | 2,986 B | 32 B |
18//! | [`Mayo5`] | 5 | 964 B | 5,554 B | 40 B |
19//!
20//! All parameter sets implement the [`MayoParameter`] trait and can be used
21//! interchangeably as the generic parameter on [`KeyPair`], [`SigningKey`],
22//! [`VerifyingKey`], and [`Signature`].
23//!
24//! # Quick Start
25//!
26//! ```
27//! use pq_mayo::{KeyPair, Mayo1};
28//! use signature::{Signer, Verifier};
29//!
30//! let mut rng = rand::rng();
31//! let keypair = KeyPair::<Mayo1>::generate(&mut rng).expect("keygen");
32//! let msg = b"hello world";
33//!
34//! let sig = keypair.signing_key().try_sign(msg).expect("sign");
35//! keypair.verifying_key().verify(msg, &sig).expect("verify");
36//! ```
37//!
38//! ```
39//! use pq_mayo::{KeyPair, Mayo1, Mayo2, Mayo3, Mayo5};
40//! use signature::{Signer, Verifier};
41//!
42//! let mut rng = rand::rng();
43//!
44//! // NIST security level 1 — smallest signatures
45//! let kp1 = KeyPair::<Mayo1>::generate(&mut rng).expect("keygen");
46//!
47//! // NIST security level 2 — smallest signatures overall (186 bytes)
48//! let kp2 = KeyPair::<Mayo2>::generate(&mut rng).expect("keygen");
49//! let sig = kp2.signing_key().try_sign(b"message").expect("sign");
50//! kp2.verifying_key().verify(b"message", &sig).expect("verify");
51//!
52//! // NIST security level 3
53//! let kp3 = KeyPair::<Mayo3>::generate(&mut rng).expect("keygen");
54//!
55//! // NIST security level 5 — strongest security
56//! let kp5 = KeyPair::<Mayo5>::generate(&mut rng).expect("keygen");
57//! ```
58//!
59//! # Key Serialization
60//!
61//! Keys and signatures implement [`AsRef<[u8]>`] for exporting raw bytes
62//! and [`TryFrom<&[u8]>`] for importing them. This allows easy
63//! integration with any transport or storage layer.
64//!
65//! ```
66//! use pq_mayo::{KeyPair, Mayo1, SigningKey, VerifyingKey, Signature};
67//! use signature::{Signer, Verifier};
68//!
69//! let mut rng = rand::rng();
70//! let keypair = KeyPair::<Mayo1>::generate(&mut rng).expect("keygen");
71//!
72//! // Export keys as raw bytes
73//! let sk_bytes: &[u8] = keypair.signing_key().as_ref();
74//! let vk_bytes: &[u8] = keypair.verifying_key().as_ref();
75//!
76//! // Reconstruct keys from bytes
77//! let sk = SigningKey::<Mayo1>::try_from(sk_bytes).expect("valid signing key");
78//! let vk = VerifyingKey::<Mayo1>::try_from(vk_bytes).expect("valid verifying key");
79//!
80//! // Sign with reconstructed key, verify with reconstructed key
81//! let sig = sk.try_sign(b"hello").expect("sign");
82//! vk.verify(b"hello", &sig).expect("verify");
83//!
84//! // Signatures can also be round-tripped through bytes
85//! let sig_bytes: &[u8] = sig.as_ref();
86//! let sig2 = Signature::<Mayo1>::try_from(sig_bytes).expect("valid signature");
87//! vk.verify(b"hello", &sig2).expect("verify restored sig");
88//! ```
89//!
90//! # Deriving a Verifying Key from a Signing Key
91//!
92//! A [`VerifyingKey`] can be derived from a [`SigningKey`] without
93//! needing the original [`KeyPair`]. This is useful when only the
94//! secret key was stored or transmitted.
95//!
96//! ```
97//! use pq_mayo::{KeyPair, Mayo1, VerifyingKey};
98//! use signature::{Signer, Verifier};
99//!
100//! let mut rng = rand::rng();
101//! let keypair = KeyPair::<Mayo1>::generate(&mut rng).expect("keygen");
102//!
103//! let vk = VerifyingKey::<Mayo1>::from(keypair.signing_key());
104//! assert_eq!(&vk, keypair.verifying_key());
105//!
106//! let sig = keypair.signing_key().try_sign(b"test").expect("sign");
107//! vk.verify(b"test", &sig).expect("verify");
108//! ```
109//!
110//! # Deterministic Key Generation from a Seed
111//!
112//! [`KeyPair::from_seed`] generates a keypair deterministically from a
113//! fixed-length seed. The seed length depends on the parameter set
114//! (24 bytes for Mayo1/Mayo2, 32 bytes for Mayo3, 40 bytes for Mayo5).
115//!
116//! ```
117//! use pq_mayo::{KeyPair, Mayo1};
118//!
119//! let seed = [42u8; 24]; // Mayo1 requires a 24-byte seed
120//! let kp1 = KeyPair::<Mayo1>::from_seed(&seed).expect("from seed");
121//! let kp2 = KeyPair::<Mayo1>::from_seed(&seed).expect("from seed");
122//!
123//! // Same seed always produces the same keypair
124//! assert_eq!(
125//!     kp1.verifying_key().as_ref(),
126//!     kp2.verifying_key().as_ref()
127//! );
128//! ```
129//!
130//! # Signing with a Caller-Provided RNG
131//!
132//! The [`SigningKey::sign_with_rng`] method allows passing a custom
133//! [`CryptoRng`](rand::CryptoRng) for salt generation. This is useful
134//! for reproducible testing or when a specific entropy source is required.
135//!
136//! ```
137//! use pq_mayo::{KeyPair, Mayo1};
138//! use signature::Verifier;
139//!
140//! let mut rng = rand::rng();
141//! let keypair = KeyPair::<Mayo1>::generate(&mut rng).expect("keygen");
142//!
143//! let sig = keypair.signing_key().sign_with_rng(&mut rng, b"data").expect("sign");
144//! keypair.verifying_key().verify(b"data", &sig).expect("verify");
145//! ```
146//!
147//! # Error Handling
148//!
149//! All fallible operations return [`error::Result<T>`](error::Result), which
150//! uses the [`Error`] enum. Key and signature deserialization validate lengths
151//! and will return descriptive errors on mismatch.
152//!
153//! ```
154//! use pq_mayo::{Error, SigningKey, VerifyingKey, Signature, Mayo1};
155//!
156//! // Wrong-length byte slices are rejected
157//! let result = SigningKey::<Mayo1>::try_from(&[0u8; 3][..]);
158//! assert!(result.is_err());
159//!
160//! let result = VerifyingKey::<Mayo1>::try_from(&[0u8; 3][..]);
161//! assert!(result.is_err());
162//!
163//! let result = Signature::<Mayo1>::try_from(&[0u8; 3][..]);
164//! assert!(result.is_err());
165//! ```
166//!
167//! # Serde Support
168//!
169//! Enable the `serde` feature for serialization with any serde-compatible
170//! format. Keys and signatures are serialized as hex strings in
171//! human-readable formats (JSON, TOML, YAML) and as raw bytes in binary
172//! formats (bincode, postcard, CBOR).
173//!
174//! ```toml
175//! [dependencies]
176//! pq-mayo = { version = "0.1", features = ["serde"] }
177//! ```
178//!
179//! ```rust,ignore
180//! use pq_mayo::{KeyPair, Mayo1};
181//!
182//! let mut rng = rand::rng();
183//! let keypair = KeyPair::<Mayo1>::generate(&mut rng).expect("keygen");
184//!
185//! // JSON (human-readable, hex-encoded)
186//! let json = serde_json::to_string(&keypair).expect("serialize");
187//! let restored: KeyPair<Mayo1> = serde_json::from_str(&json).expect("deserialize");
188//! assert_eq!(keypair, restored);
189//!
190//! // Postcard (binary, compact)
191//! let bytes = postcard::to_stdvec(&keypair).expect("serialize");
192//! let restored: KeyPair<Mayo1> = postcard::from_bytes(&bytes).expect("deserialize");
193//! assert_eq!(keypair, restored);
194//! ```
195//!
196//! # WebAssembly Support
197//!
198//! This crate compiles to `wasm32-unknown-unknown` using pure Rust
199//! implementations for all cryptographic primitives. Enable the `js`
200//! feature to use the browser's `crypto.getRandomValues` for randomness:
201//!
202//! ```toml
203//! [dependencies]
204//! pq-mayo = { version = "0.1", features = ["js"] }
205//! ```
206//!
207//! # Security Considerations
208//!
209//! - All operations are implemented in **constant time** to resist
210//!   timing side-channel attacks. There are no secret-dependent branches
211//!   or memory accesses.
212//! - Signing keys are **zeroized on drop** via the [`zeroize`](https://docs.rs/zeroize)
213//!   crate to prevent secret material from lingering in memory.
214//! - The [`Debug`](core::fmt::Debug) implementation for [`SigningKey`] redacts
215//!   the key bytes, printing `**FILTERED**` instead.
216//!
217//! # PKCS#8 and SPKI Support
218//!
219//! Enable the `pkcs8` feature for DER-encoded key serialization compatible
220//! with X.509 and PKCS#8 standards. This implements the [`EncodePrivateKey`],
221//! [`DecodePrivateKey`], [`EncodePublicKey`], and [`DecodePublicKey`] traits
222//! from the [`pkcs8`](https://docs.rs/pkcs8) ecosystem.
223//!
224//! Since MAYO has not yet been standardized by NIST, experimental OIDs
225//! from the [Open Quantum Safe](https://openquantumsafe.org/) project are
226//! used. These will be replaced with official NIST OIDs upon standardization.
227//!
228//! ```toml
229//! [dependencies]
230//! pq-mayo = { version = "0.1", features = ["pkcs8"] }
231//! ```
232//!
233//! [`EncodePrivateKey`]: https://docs.rs/pkcs8/latest/pkcs8/trait.EncodePrivateKey.html
234//! [`DecodePrivateKey`]: https://docs.rs/pkcs8/latest/pkcs8/trait.DecodePrivateKey.html
235//! [`EncodePublicKey`]: https://docs.rs/spki/latest/spki/trait.EncodePublicKey.html
236//! [`DecodePublicKey`]: https://docs.rs/spki/latest/spki/trait.DecodePublicKey.html
237
238mod error;
239mod keypair;
240mod mayo_signature;
241mod params;
242#[cfg(feature = "pkcs8")]
243mod pkcs8;
244mod signing_key;
245mod verifying_key;
246
247mod bitsliced;
248mod codec;
249mod echelon;
250mod gf16;
251mod keygen;
252mod matrix_ops;
253mod sample;
254mod sign;
255mod verify;
256
257pub use error::{Error, Result};
258pub use keypair::KeyPair;
259pub use mayo_signature::Signature;
260pub use params::{Mayo1, Mayo2, Mayo3, Mayo5, MayoParameter};
261pub use signing_key::SigningKey;
262pub use verifying_key::VerifyingKey;
263
264#[cfg(feature = "pkcs8")]
265pub use crate::pkcs8::{MAYO1_OID, MAYO2_OID, MAYO3_OID, MAYO5_OID};
266
267#[cfg(feature = "serde")]
268#[cfg(test)]
269mod tests {
270    use super::*;
271    use signature::Signer;
272
273    fn keypair_serde<P: MayoParameter>() {
274        let mut rng = rand::rng();
275        let keypair = KeyPair::<P>::generate(&mut rng).expect("keygen");
276        let serialized = serde_json::to_string(&keypair).expect("serialize");
277        let deserialized: KeyPair<P> = serde_json::from_str(&serialized).expect("deserialize");
278        assert_eq!(keypair, deserialized);
279
280        let serialized = postcard::to_stdvec(&keypair).expect("serialize");
281        let deserialized: KeyPair<P> = postcard::from_bytes(&serialized).expect("deserialize");
282        assert_eq!(keypair, deserialized);
283    }
284
285    fn signing_key_serde<P: MayoParameter>() {
286        let mut rng = rand::rng();
287        let keypair = KeyPair::<P>::generate(&mut rng).expect("keygen");
288        let serialized = serde_json::to_string(keypair.signing_key()).expect("serialize");
289        let deserialized: SigningKey<P> = serde_json::from_str(&serialized).expect("deserialize");
290        assert_eq!(keypair.signing_key(), &deserialized);
291
292        let serialized = postcard::to_stdvec(keypair.signing_key()).expect("serialize");
293        let deserialized: SigningKey<P> = postcard::from_bytes(&serialized).expect("deserialize");
294        assert_eq!(keypair.signing_key(), &deserialized);
295    }
296
297    fn verifying_key_serde<P: MayoParameter>() {
298        let mut rng = rand::rng();
299        let keypair = KeyPair::<P>::generate(&mut rng).expect("keygen");
300        let serialized = serde_json::to_string(keypair.verifying_key()).expect("serialize");
301        let deserialized: VerifyingKey<P> = serde_json::from_str(&serialized).expect("deserialize");
302        assert_eq!(keypair.verifying_key(), &deserialized);
303
304        let serialized = postcard::to_stdvec(keypair.verifying_key()).expect("serialize");
305        let deserialized: VerifyingKey<P> = postcard::from_bytes(&serialized).expect("deserialize");
306        assert_eq!(keypair.verifying_key(), &deserialized);
307    }
308
309    fn signature_serde<P: MayoParameter>() {
310        let mut rng = rand::rng();
311        let keypair = KeyPair::<P>::generate(&mut rng).expect("keygen");
312        let msg = b"hello world";
313        let sig = keypair.signing_key().try_sign(msg).expect("sign");
314        let serialized = serde_json::to_string(&sig).expect("serialize");
315        let deserialized: Signature<P> = serde_json::from_str(&serialized).expect("deserialize");
316        assert_eq!(sig, deserialized);
317
318        let serialized = postcard::to_stdvec(&sig).expect("serialize");
319        let deserialized: Signature<P> = postcard::from_bytes(&serialized).expect("deserialize");
320        assert_eq!(sig, deserialized);
321    }
322
323    #[test]
324    fn keypair_serde_mayo1() {
325        keypair_serde::<Mayo1>();
326    }
327    #[test]
328    fn keypair_serde_mayo2() {
329        keypair_serde::<Mayo2>();
330    }
331    #[test]
332    fn keypair_serde_mayo3() {
333        keypair_serde::<Mayo3>();
334    }
335    #[test]
336    fn keypair_serde_mayo5() {
337        keypair_serde::<Mayo5>();
338    }
339
340    #[test]
341    fn signing_key_serde_mayo1() {
342        signing_key_serde::<Mayo1>();
343    }
344    #[test]
345    fn signing_key_serde_mayo2() {
346        signing_key_serde::<Mayo2>();
347    }
348    #[test]
349    fn signing_key_serde_mayo3() {
350        signing_key_serde::<Mayo3>();
351    }
352    #[test]
353    fn signing_key_serde_mayo5() {
354        signing_key_serde::<Mayo5>();
355    }
356
357    #[test]
358    fn verifying_key_serde_mayo1() {
359        verifying_key_serde::<Mayo1>();
360    }
361    #[test]
362    fn verifying_key_serde_mayo2() {
363        verifying_key_serde::<Mayo2>();
364    }
365    #[test]
366    fn verifying_key_serde_mayo3() {
367        verifying_key_serde::<Mayo3>();
368    }
369    #[test]
370    fn verifying_key_serde_mayo5() {
371        verifying_key_serde::<Mayo5>();
372    }
373
374    #[test]
375    fn signature_serde_mayo1() {
376        signature_serde::<Mayo1>();
377    }
378    #[test]
379    fn signature_serde_mayo2() {
380        signature_serde::<Mayo2>();
381    }
382    #[test]
383    fn signature_serde_mayo3() {
384        signature_serde::<Mayo3>();
385    }
386    #[test]
387    fn signature_serde_mayo5() {
388        signature_serde::<Mayo5>();
389    }
390}