#[cfg(feature = "kgen")]
use crate::os_rng;
use crate::{deserialize_hex_or_bin, error::*, serialize_hex_or_bin};
use serde::{Deserialize, Serialize};
macro_rules! impl_mayo_struct {
($name:ident, $validate:ident, $expect:expr) => {
#[derive(Clone, Serialize, Deserialize)]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[doc = concat!("A [`", stringify!($name), "`] for mayo")]
#[repr(transparent)]
pub struct $name(pub(crate) InnerMayo);
impl std::fmt::Debug for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(stringify!($name))
.field("scheme", &self.0.scheme)
.field("value", &"<redacted>")
.finish()
}
}
impl AsRef<[u8]> for $name {
fn as_ref(&self) -> &[u8] {
self.0.value.as_ref()
}
}
impl From<InnerMayo> for $name {
fn from(inner: InnerMayo) -> Self {
$name(inner)
}
}
impl $name {
pub fn scheme(&self) -> MayoScheme {
self.0.scheme
}
#[doc = concat!("Convert [`", stringify!($name), "`] to its raw byte representation")]
pub fn to_raw_bytes(&self) -> Vec<u8> {
self.0.value.clone()
}
#[doc = concat!("Convert [`", stringify!($name), "`] from its raw byte representation and scheme")]
pub fn from_raw_bytes(scheme: MayoScheme, bytes: &[u8]) -> Result<Self> {
scheme.$validate(bytes)?;
Ok(InnerMayo {
scheme,
value: bytes.to_vec(),
}.into())
}
}
};
}
scheme_impl_pure!(
MayoScheme,
#[default]
Mayo1 => "MAYO-1" ; 1 ; 24,
Mayo2 => "MAYO-2" ; 2 ; 24,
Mayo3 => "MAYO-3" ; 3 ; 32,
Mayo5 => "MAYO-5" ; 4 ; 40,
);
serde_impl!(MayoScheme);
macro_rules! with_mayo_params {
($scheme:expr, |$P:ident| $body:block) => {
match $scheme {
MayoScheme::Mayo1 => {
type $P = pq_mayo::Mayo1;
$body
}
MayoScheme::Mayo2 => {
type $P = pq_mayo::Mayo2;
$body
}
MayoScheme::Mayo3 => {
type $P = pq_mayo::Mayo3;
$body
}
MayoScheme::Mayo5 => {
type $P = pq_mayo::Mayo5;
$body
}
}
};
}
impl MayoScheme {
fn validate_public_key(&self, bytes: &[u8]) -> Result<()> {
with_mayo_params!(self, |P| {
pq_mayo::VerifyingKey::<P>::try_from(bytes)
.map_err(|_| Error::MayoError("an invalid public key".to_string()))?;
Ok(())
})
}
fn validate_signing_key(&self, bytes: &[u8]) -> Result<()> {
with_mayo_params!(self, |P| {
pq_mayo::SigningKey::<P>::try_from(bytes)
.map_err(|_| Error::MayoError("an invalid signing key".to_string()))?;
Ok(())
})
}
fn validate_signature(&self, bytes: &[u8]) -> Result<()> {
with_mayo_params!(self, |P| {
pq_mayo::Signature::<P>::try_from(bytes)
.map_err(|_| Error::MayoError("an invalid signature".to_string()))?;
Ok(())
})
}
#[cfg(feature = "kgen")]
pub fn keypair(&self) -> Result<(MayoVerificationKey, MayoSigningKey)> {
with_mayo_params!(self, |P| {
let mut rng = os_rng();
let keypair = pq_mayo::KeyPair::<P>::generate(&mut rng)
.map_err(|e| Error::MayoError(e.to_string()))?;
Ok(self.pack_keypair(
keypair.verifying_key().as_ref().to_vec(),
keypair.signing_key().as_ref().to_vec(),
))
})
}
#[cfg(feature = "kgen")]
pub fn keypair_from_seed(&self, seed: &[u8]) -> Result<(MayoVerificationKey, MayoSigningKey)> {
if seed.len() != self.seed_size() {
return Err(Error::InvalidSeedLength(seed.len()));
}
with_mayo_params!(self, |P| {
let keypair = pq_mayo::KeyPair::<P>::from_seed(seed)
.map_err(|e| Error::MayoError(e.to_string()))?;
Ok(self.pack_keypair(
keypair.verifying_key().as_ref().to_vec(),
keypair.signing_key().as_ref().to_vec(),
))
})
}
#[cfg(feature = "kgen")]
fn pack_keypair(&self, vk: Vec<u8>, sk: Vec<u8>) -> (MayoVerificationKey, MayoSigningKey) {
(
InnerMayo {
scheme: *self,
value: vk,
}
.into(),
InnerMayo {
scheme: *self,
value: sk,
}
.into(),
)
}
#[cfg(feature = "sign")]
pub fn sign(&self, message: &[u8], signing_key: &MayoSigningKey) -> Result<MayoSignature> {
use signature::Signer;
if signing_key.0.scheme != *self {
return Err(Error::MayoError(
"signing key scheme does not match".to_string(),
));
}
with_mayo_params!(self, |P| {
let sk = pq_mayo::SigningKey::<P>::try_from(signing_key.0.value.as_slice())
.map_err(|_| Error::MayoError("an invalid signing key".to_string()))?;
let signature = sk
.try_sign(message)
.map_err(|e| Error::MayoError(e.to_string()))?;
Ok(InnerMayo {
scheme: *self,
value: signature.as_ref().to_vec(),
}
.into())
})
}
#[cfg(feature = "vrfy")]
pub fn verify(
&self,
message: &[u8],
signature: &MayoSignature,
verification_key: &MayoVerificationKey,
) -> Result<()> {
use signature::Verifier;
if verification_key.0.scheme != *self || signature.0.scheme != *self {
return Err(Error::MayoError("scheme does not match".to_string()));
}
with_mayo_params!(self, |P| {
let vk = pq_mayo::VerifyingKey::<P>::try_from(verification_key.0.value.as_slice())
.map_err(|_| Error::MayoError("an invalid public key".to_string()))?;
let sig = pq_mayo::Signature::<P>::try_from(signature.0.value.as_slice())
.map_err(|_| Error::MayoError("an invalid signature".to_string()))?;
vk.verify(message, &sig)
.map_err(|_| Error::MayoError("signature verification failed".to_string()))
})
}
}
impl_mayo_struct!(MayoSigningKey, validate_signing_key, "a valid signing key");
impl_mayo_struct!(
MayoVerificationKey,
validate_public_key,
"a valid public key"
);
impl_mayo_struct!(MayoSignature, validate_signature, "a valid signature");
#[derive(Clone, Serialize, Deserialize)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub(crate) struct InnerMayo {
scheme: MayoScheme,
#[serde(
serialize_with = "serialize_hex_or_bin",
deserialize_with = "deserialize_hex_or_bin"
)]
value: Vec<u8>,
}
impl std::fmt::Debug for InnerMayo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InnerMayo")
.field("scheme", &self.scheme)
.field("value", &"<redacted>")
.finish()
}
}
#[cfg(feature = "zeroize")]
impl zeroize::Zeroize for InnerMayo {
fn zeroize(&mut self) {
self.value.zeroize();
}
}
#[cfg(feature = "zeroize")]
impl zeroize::Zeroize for MayoSigningKey {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
#[cfg(feature = "zeroize")]
impl zeroize::ZeroizeOnDrop for MayoSigningKey {}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use rstest::*;
#[cfg(all(feature = "kgen", feature = "sign"))]
#[rstest]
#[case::mayo1(MayoScheme::Mayo1)]
#[case::mayo2(MayoScheme::Mayo2)]
#[case::mayo3(MayoScheme::Mayo3)]
#[case::mayo5(MayoScheme::Mayo5)]
fn serdes(#[case] scheme: MayoScheme) {
let (pk, sk) = scheme.keypair().unwrap();
let bytes = postcard::to_stdvec(&sk).unwrap();
let sk2 = postcard::from_bytes::<MayoSigningKey>(&bytes).unwrap();
assert_eq!(sk, sk2);
let string = serde_json::to_string(&sk).unwrap();
let sk2 = serde_json::from_str::<MayoSigningKey>(&string).unwrap();
assert_eq!(sk, sk2);
let bytes = postcard::to_stdvec(&pk).unwrap();
let pk2 = postcard::from_bytes::<MayoVerificationKey>(&bytes).unwrap();
assert_eq!(pk, pk2);
let string = serde_json::to_string(&pk).unwrap();
let pk2 = serde_json::from_str::<MayoVerificationKey>(&string).unwrap();
assert_eq!(pk, pk2);
let msg = [0u8; 8];
let sig = sk.0.scheme.sign(&msg, &sk).unwrap();
let bytes = postcard::to_stdvec(&sig).unwrap();
let sig2 = postcard::from_bytes::<MayoSignature>(&bytes).unwrap();
assert_eq!(sig, sig2);
let string = serde_json::to_string(&sig).unwrap();
let sig2 = serde_json::from_str::<MayoSignature>(&string).unwrap();
assert_eq!(sig, sig2);
}
#[cfg(all(feature = "kgen", feature = "sign", feature = "vrfy"))]
#[rstest]
#[case::mayo1(MayoScheme::Mayo1)]
#[case::mayo2(MayoScheme::Mayo2)]
#[case::mayo3(MayoScheme::Mayo3)]
#[case::mayo5(MayoScheme::Mayo5)]
fn flow(#[case] scheme: MayoScheme) {
const MSG: &[u8] = &[0u8; 8];
let (pk, sk) = scheme.keypair().unwrap();
let signature = sk.0.scheme.sign(MSG, &sk).unwrap();
let res = pk.0.scheme.verify(MSG, &signature, &pk);
assert!(res.is_ok());
let res = pk.0.scheme.verify(&[1u8; 8], &signature, &pk);
assert!(res.is_err());
}
#[cfg(all(feature = "kgen", feature = "sign", feature = "vrfy"))]
#[rstest]
#[case::mayo1(MayoScheme::Mayo1, 1420, 24, 454)]
#[case::mayo2(MayoScheme::Mayo2, 4368, 24, 216)]
#[case::mayo3(MayoScheme::Mayo3, 2986, 32, 681)]
#[case::mayo5(MayoScheme::Mayo5, 5554, 40, 964)]
fn fixed_sizes(
#[case] scheme: MayoScheme,
#[case] pk_len: usize,
#[case] sk_len: usize,
#[case] sig_len: usize,
) {
let (pk, sk) = scheme.keypair().unwrap();
assert_eq!(pk.to_raw_bytes().len(), pk_len);
assert_eq!(sk.to_raw_bytes().len(), sk_len);
let sig = scheme.sign(&[0u8; 8], &sk).unwrap();
assert_eq!(sig.to_raw_bytes().len(), sig_len);
}
#[cfg(all(feature = "kgen", feature = "sign", feature = "vrfy"))]
#[test]
fn cross_scheme_mayo1_mayo2_rejected() {
const MSG: &[u8] = &[0u8; 8];
let (_pk1, sk1) = MayoScheme::Mayo1.keypair().unwrap();
let (pk2, sk2) = MayoScheme::Mayo2.keypair().unwrap();
assert!(MayoScheme::Mayo2.sign(MSG, &sk1).is_err());
let sig2 = MayoScheme::Mayo2.sign(MSG, &sk2).unwrap();
assert!(MayoScheme::Mayo1.verify(MSG, &sig2, &pk2).is_err());
assert!(MayoScheme::Mayo2.verify(MSG, &sig2, &pk2).is_ok());
}
#[cfg(feature = "kgen")]
#[rstest]
#[case::mayo1(MayoScheme::Mayo1, 24)]
#[case::mayo2(MayoScheme::Mayo2, 24)]
#[case::mayo3(MayoScheme::Mayo3, 32)]
#[case::mayo5(MayoScheme::Mayo5, 40)]
fn seed_determinism(#[case] scheme: MayoScheme, #[case] seed_len: usize) {
let seed = vec![7u8; seed_len];
let (pk1, sk1) = scheme.keypair_from_seed(&seed).unwrap();
let (pk2, sk2) = scheme.keypair_from_seed(&seed).unwrap();
assert_eq!(pk1.as_ref(), pk2.as_ref());
assert_eq!(sk1.as_ref(), sk2.as_ref());
assert!(scheme.keypair_from_seed(&vec![0u8; seed_len - 1]).is_err());
}
}