pq-mayo 0.5.0

MAYO post-quantum signature scheme
Documentation

pq-mayo

Crate Docs Apache2/MIT licensed Downloads build MSRV

A Rust implementation of the MAYO post-quantum signature scheme, submitted to the NIST PQC standardization process.

Includes the latest updates as suggested for Round 3 and MAYO team NIST update with the MAYO2 tweak.

NOTE: MAYO-2 signature size increased from 186 B to 216 B as a result of the Round 2 findings.

Supported Parameter Sets

Parameter Set Security Level Signature Size Public Key Size Private Key Size
Mayo1 1 454 B 1420 B 24 B
Mayo2 1 216 B 4368 B 24 B
Mayo3 3 681 B 2986 B 32 B
Mayo5 5 964 B 5554 B 40 B

Performance

Benchmarked with Criterion. Times are point estimates from local runs and will vary by CPU, compiler, and feature flags.

Apple Silicon (aarch64)

Benchmarked with -C target-cpu=native. Use the compact key types for minimal memory and serialized storage. Use the expanded/context types when repeatedly signing or verifying with the same key.

Operation Compact Expanded Context
Mayo1/keygen 148 µs - -
Mayo2/keygen 207 µs - -
Mayo3/keygen 384 µs - -
Mayo5/keygen 848 µs - -
Mayo1/sign 526 µs 344 µs -
Mayo2/sign 489 µs 241 µs -
Mayo3/sign 1.23 ms 734 µs -
Mayo5/sign 2.69 ms 1.56 ms -
Mayo1/verify 128 µs 98.3 µs 91.5 µs
Mayo2/verify 64.6 µs 31.3 µs 28.1 µs
Mayo3/verify 288 µs 203 µs 192 µs
Mayo5/verify 608 µs 418 µs 402 µs

Linux (x86_64-unknown-linux-gnu)

Benchmarked with rustc 1.95.0.

Operation Compact Expanded Context
Mayo1/keygen 78.8 µs - -
Mayo2/keygen 103 µs - -
Mayo3/keygen 220 µs - -
Mayo5/keygen 469 µs - -
Mayo1/sign 283 µs 192 µs -
Mayo2/sign 246 µs 127 µs -
Mayo3/sign 711 µs 455 µs -
Mayo5/sign 1.54 ms 949 µs -
Mayo1/verify 79.7 µs 48.4 µs 44.9 µs
Mayo2/verify 37.4 µs 15.1 µs 13.7 µs
Mayo3/verify 203 µs 131 µs 124 µs
Mayo5/verify 405 µs 279 µs 265 µs

Run your own benchmarks:

cargo bench

The expanded signing key stores secret-derived expanded material for faster repeated signing. The verification context stores public expanded material and mutable scratch buffers for faster repeated verification.

Usage

Basic Sign and Verify

use pq_mayo::{KeyPair, Mayo1};
use signature::{Signer, Verifier};

let mut rng = rand::rng();
let keypair = KeyPair::<Mayo1>::generate(&mut rng).expect("keygen");
let msg = b"hello world";

let sig = keypair.signing_key().try_sign(msg).expect("sign");
keypair.verifying_key().verify(msg, &sig).expect("verify");

Using Different Parameter Sets

use pq_mayo::{KeyPair, Mayo2, Mayo3, Mayo5};
use signature::{Signer, Verifier};

let mut rng = rand::rng();

// NIST security level 1, tweaked after the wedge attack analysis
let kp2 = KeyPair::<Mayo2>::generate(&mut rng).expect("keygen");
let sig2 = kp2.signing_key().try_sign(b"message").expect("sign");
kp2.verifying_key().verify(b"message", &sig2).expect("verify");

// NIST security level 3
let kp3 = KeyPair::<Mayo3>::generate(&mut rng).expect("keygen");

// NIST security level 5
let kp5 = KeyPair::<Mayo5>::generate(&mut rng).expect("keygen");

Key Serialization

Keys and signatures implement AsRef<[u8]> and TryFrom<&[u8]> for raw byte serialization:

use pq_mayo::{KeyPair, Mayo1, SigningKey, VerifyingKey, Signature};
use signature::{Signer, Verifier};

let mut rng = rand::rng();
let keypair = KeyPair::<Mayo1>::generate(&mut rng).expect("keygen");

// Export keys as raw bytes
let sk_bytes: &[u8] = keypair.signing_key().as_ref();
let vk_bytes: &[u8] = keypair.verifying_key().as_ref();

// Reconstruct keys from bytes
let sk = SigningKey::<Mayo1>::try_from(sk_bytes).expect("valid signing key");
let vk = VerifyingKey::<Mayo1>::try_from(vk_bytes).expect("valid verifying key");

// Sign with reconstructed key, verify with reconstructed key
let sig = sk.try_sign(b"hello").expect("sign");
vk.verify(b"hello", &sig).expect("verify");

// Signatures can also be serialized/deserialized
let sig_bytes: &[u8] = sig.as_ref();
let sig2 = Signature::<Mayo1>::try_from(sig_bytes).expect("valid signature");

Deriving a Verifying Key from a Signing Key

use pq_mayo::{KeyPair, Mayo1, VerifyingKey};
use signature::{Signer, Verifier};

let mut rng = rand::rng();
let keypair = KeyPair::<Mayo1>::generate(&mut rng).expect("keygen");

// Derive the verifying (public) key from the signing (secret) key
let vk = VerifyingKey::<Mayo1>::from(keypair.signing_key());
let sig = keypair.signing_key().try_sign(b"test").expect("sign");
vk.verify(b"test", &sig).expect("verify");

Faster Repeated Signing

Use ExpandedSigningKey when signing many messages with the same key. The compact SigningKey remains the default storage format; the expanded form keeps additional secret-derived material in memory and zeroizes it on drop.

use pq_mayo::{ExpandedSigningKey, KeyPair, Mayo1};
use signature::{Signer, Verifier};

let mut rng = rand::rng();
let keypair = KeyPair::<Mayo1>::generate(&mut rng).expect("keygen");
let expanded = ExpandedSigningKey::<Mayo1>::from(keypair.signing_key());

let sig = expanded.try_sign(b"message").expect("sign");
keypair.verifying_key().verify(b"message", &sig).expect("verify");

Faster Repeated Verification

Use ExpandedVerifyingKey to cache expanded public key material. Use VerificationContext when verifying many signatures with the same public key; it also reuses mutable scratch buffers, so verification takes &mut self.

use pq_mayo::{ExpandedVerifyingKey, KeyPair, Mayo1, VerificationContext};
use signature::{Signer, Verifier};

let mut rng = rand::rng();
let keypair = KeyPair::<Mayo1>::generate(&mut rng).expect("keygen");
let msg = b"message";
let sig = keypair.signing_key().try_sign(msg).expect("sign");

let expanded = ExpandedVerifyingKey::<Mayo1>::from(keypair.verifying_key());
expanded.verify(msg, &sig).expect("verify");

let mut context = VerificationContext::<Mayo1>::from(&expanded);
context.verify(msg, &sig).expect("verify with cached scratch");

Serde Support

Enable the serde feature for JSON/binary serialization:

[dependencies]
pq-mayo = { version = "0.5", features = ["serde"] }
use pq_mayo::{KeyPair, Mayo1};

let mut rng = rand::rng();
let keypair = KeyPair::<Mayo1>::generate(&mut rng).expect("keygen");

// Serialize to JSON
let json = serde_json::to_string(&keypair).expect("serialize");
let restored: KeyPair<Mayo1> = serde_json::from_str(&json).expect("deserialize");

PKCS#8 and SPKI Support

Enable the pkcs8 feature for DER-encoded key serialization compatible with X.509 and PKCS#8 standards:

[dependencies]
pq-mayo = { version = "0.5", features = ["pkcs8"] }

This implements the standard RustCrypto key encoding traits:

  • EncodePrivateKey / DecodePrivateKey for KeyPair (PKCS#8 DER)
  • DecodePrivateKey for SigningKey (PKCS#8 DER)
  • EncodePublicKey / DecodePublicKey for VerifyingKey (SPKI DER)
use pq_mayo::{KeyPair, Mayo1, SigningKey, VerifyingKey};
use pkcs8::DecodePrivateKey;
use pkcs8::EncodePrivateKey;
use pkcs8::spki::{DecodePublicKey, EncodePublicKey};
use signature::{Signer, Verifier};

let mut rng = rand::rng();
let keypair = KeyPair::<Mayo1>::generate(&mut rng).expect("keygen");

// Encode private key to PKCS#8 DER
let sk_der = keypair.to_pkcs8_der().expect("encode");

// Decode private key from PKCS#8 DER
let restored = KeyPair::<Mayo1>::from_pkcs8_der(sk_der.as_bytes()).expect("decode");

// Encode public key to SPKI DER
let vk_der = keypair.verifying_key().to_public_key_der().expect("encode");

// Decode public key from SPKI DER
let restored_vk = VerifyingKey::<Mayo1>::from_public_key_der(vk_der.as_bytes()).expect("decode");

// Sign and verify with round-tripped keys
let sig = restored.signing_key().try_sign(b"hello").expect("sign");
restored_vk.verify(b"hello", &sig).expect("verify");

Since MAYO has not yet been standardized by NIST, experimental OIDs from the Open Quantum Safe project are used (1.3.9999.8.{1,2,3,5}.3). These will be replaced with official NIST OIDs upon standardization.

WebAssembly Support

This crate compiles to wasm32-unknown-unknown using pure Rust implementations for all cryptographic primitives. Enable the js feature to use the browser's crypto.getRandomValues for randomness:

[dependencies]
pq-mayo = { version = "0.5", features = ["js"] }

Serialization

This crate has been tested against the following serde compatible formats:

  • postcard
  • ciborium
  • serde_json
  • noyalib
  • toml

Mitigations

There is a known fault injection attack against MAYO described in MAYO Key Recovery by Fixing Vinegar Seeds. This code contains mitigations to these attacks.

License

Licensed under either of Apache License, Version 2.0 or MIT License at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.