use rand::{CryptoRng, Rng};
use zeroize::ZeroizeOnDrop;
use crate::pgp::{
crypto::{hash::HashAlgorithm, Signer},
errors::{bail, ensure, ensure_eq, format_err, Result},
ser::Serialize,
types::{Ed448PublicParams, SignatureBytes},
};
const MIN_HASH_LEN_BITS: usize = 512;
pub const KEY_LEN: usize = 57;
#[derive(Clone, PartialEq, Eq, ZeroizeOnDrop, derive_more::Debug)]
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
pub struct SecretKey {
#[debug("..")]
#[cfg_attr(test, proptest(strategy = "tests::key_gen()"))]
secret: cx448::SigningKey,
}
impl From<&SecretKey> for Ed448PublicParams {
fn from(value: &SecretKey) -> Self {
Self {
key: value.secret.verifying_key(),
}
}
}
impl SecretKey {
pub fn generate<R: Rng + CryptoRng>(rng: R) -> Self {
let secret = cx448::SigningKey::generate(rng);
SecretKey { secret }
}
pub fn try_from_bytes(raw: [u8; KEY_LEN]) -> Result<Self> {
let secret = cx448::SigningKey::from(cx448::SecretKey::from_slice(&raw));
Ok(Self { secret })
}
pub fn as_bytes(&self) -> &[u8; KEY_LEN] {
let r: &[u8] = self.secret.as_bytes().as_ref();
r.try_into().expect("known length")
}
}
impl Signer for SecretKey {
fn sign(&self, hash: HashAlgorithm, digest: &[u8]) -> Result<SignatureBytes> {
let Some(digest_size) = hash.digest_size() else {
bail!("EdDSA signature: invalid hash algorithm: {:?}", hash);
};
ensure_eq!(
digest.len(),
digest_size,
"Unexpected digest length {} for hash algorithm {:?}",
digest.len(),
hash,
);
ensure!(
digest_size * 8 >= MIN_HASH_LEN_BITS,
"EdDSA signature: hash algorithm {:?} is too weak for Ed448",
hash,
);
let signature = self.secret.sign_raw(digest);
let bytes = signature.to_bytes();
Ok(SignatureBytes::Native(bytes.to_vec().into()))
}
}
impl Serialize for SecretKey {
fn to_writer<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
let x = self.as_bytes();
writer.write_all(x)?;
Ok(())
}
fn write_len(&self) -> usize {
KEY_LEN
}
}
pub fn verify(
key: &cx448::VerifyingKey,
hash: HashAlgorithm,
hashed: &[u8],
sig_bytes: &[u8],
) -> Result<()> {
let Some(digest_size) = hash.digest_size() else {
bail!("EdDSA signature: invalid hash algorithm: {:?}", hash);
};
ensure!(
digest_size * 8 >= MIN_HASH_LEN_BITS,
"EdDSA signature: hash algorithm {:?} is too weak for Ed448",
hash,
);
let sig_bytes = sig_bytes
.try_into()
.map_err(|_| format_err!("invalid signature length"))?;
let sig = cx448::Signature::from_bytes(&sig_bytes)?;
Ok(key.verify_raw(&sig, hashed)?)
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
prop_compose! {
pub fn key_gen()(bytes: [u8; 57]) -> cx448::SigningKey {
cx448::SigningKey::from(cx448::SecretKey::from_slice(&bytes))
}
}
}