tectonic-bedrock 0.3.0

Tectonic's common cryptography library
//! Falcon key and signature methods
//!
//! Because FIPS-206 (FN-DSA) has not been published, Falcon is not a stable DSA recommended for
//! production. The signature and key formats here may change once the final standard lands.

use crate::{
    deserialize_hex_or_bin,
    error::{Error, Result},
    serialize_hex_or_bin,
};
use serde::{Deserialize, Serialize};

#[cfg(feature = "eth_falcon")]
mod eth_falcon;

/// Map a [`FalconScheme`] to the fn-dsa `logn` degree parameter.
fn logn(scheme: FalconScheme) -> u32 {
    match scheme {
        FalconScheme::Dsa512 => 9,
        FalconScheme::Dsa1024 => 10,
        #[cfg(feature = "eth_falcon")]
        FalconScheme::Ethereum => 9,
    }
}

macro_rules! impl_falcon_struct {
    ($name:ident, $validate:ident, $expect:expr) => {

        #[derive(Clone, Serialize, Deserialize)]
        #[cfg_attr(test, derive(PartialEq, Eq))]
        #[doc = concat!("A [`", stringify!($name), "`] for fn-dsa")]
        #[repr(transparent)]
        pub struct $name(pub(crate) InnerFalcon);

        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<InnerFalcon> for $name {
            fn from(inner: InnerFalcon) -> Self {
                Self(inner)
            }
        }

        impl $name {
            /// The [`FalconScheme`] represented by this struct
            pub fn scheme(&self) -> FalconScheme {
                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: FalconScheme, bytes: &[u8]) -> Result<Self> {
                scheme.$validate(bytes)?;
                Ok(InnerFalcon {
                    scheme,
                    value: bytes.to_vec(),
                }.into())
            }
        }
    };
}

scheme_impl_pure!(
    /// Falcon schemes
    FalconScheme,
    #[default]
    /// DSA-512
    Dsa512 => "FN-DSA-512" ; 1 ; 32,
    /// DSA-1024
    Dsa1024 => "FN-DSA-1024" ; 2 ; 48,
    @cfg(feature = "eth_falcon")
    /// ETHFALCON
    Ethereum => "ETHFALCON" ; 3 ; 32,
);

serde_impl!(FalconScheme);

impl FalconScheme {
    /// Validate a Falcon signing (secret) key encoding for this scheme (by length).
    fn validate_signing_key(&self, bytes: &[u8]) -> Result<()> {
        if bytes.len() == fn_dsa_comm::sign_key_size(logn(*self)) {
            Ok(())
        } else {
            Err(Error::FnDsaError("an invalid signing key".to_string()))
        }
    }

    /// Validate a Falcon verification (public) key encoding for this scheme (by length).
    fn validate_public_key(&self, bytes: &[u8]) -> Result<()> {
        if bytes.len() == fn_dsa_comm::vrfy_key_size(logn(*self)) {
            Ok(())
        } else {
            Err(Error::FnDsaError("an invalid public key".to_string()))
        }
    }

    /// Validate a Falcon signature length for this scheme.
    fn validate_signature(&self, bytes: &[u8]) -> Result<()> {
        if bytes.len() == fn_dsa_comm::signature_size(logn(*self)) {
            Ok(())
        } else {
            Err(Error::FnDsaError("an invalid signature".to_string()))
        }
    }

    #[cfg(feature = "kgen")]
    /// Generate a new Falcon signing and verification key pair
    pub fn keypair(&self) -> Result<(FalconVerificationKey, FalconSigningKey)> {
        use fn_dsa_kgen::{sign_key_size, vrfy_key_size, KeyPairGenerator};
        let logn = logn(*self);
        let mut sk = vec![0u8; sign_key_size(logn)];
        let mut vk = vec![0u8; vrfy_key_size(logn)];
        let mut kg = fn_dsa_kgen::KeyPairGeneratorStandard::default();
        kg.keygen(logn, &mut rand_core::OsRng, &mut sk, &mut vk);
        Ok(self.pack_keypair(vk, sk))
    }

    #[cfg(feature = "kgen")]
    /// Generate a new Falcon signing and verification key pair from a seed
    pub fn keypair_from_seed(
        &self,
        seed: &[u8],
    ) -> Result<(FalconVerificationKey, FalconSigningKey)> {
        if seed.len() < 32 || seed.len() > 64 {
            return Err(Error::InvalidSeedLength(seed.len()));
        }
        use fn_dsa_kgen::{sign_key_size, vrfy_key_size, KeyPairGenerator};
        let logn = logn(*self);
        let mut sk = vec![0u8; sign_key_size(logn)];
        let mut vk = vec![0u8; vrfy_key_size(logn)];
        let mut kg = fn_dsa_kgen::KeyPairGeneratorStandard::default();
        kg.keygen_from_seed(logn, seed, &mut sk, &mut vk);
        Ok(self.pack_keypair(vk, sk))
    }

    #[cfg(feature = "kgen")]
    fn pack_keypair(&self, vk: Vec<u8>, sk: Vec<u8>) -> (FalconVerificationKey, FalconSigningKey) {
        (
            InnerFalcon {
                scheme: *self,
                value: vk,
            }
            .into(),
            InnerFalcon {
                scheme: *self,
                value: sk,
            }
            .into(),
        )
    }

    #[cfg(feature = "sign")]
    fn sign_inner(
        &self,
        message: &[u8],
        signing_key: &FalconSigningKey,
    ) -> Result<FalconSignature> {
        // Use the original Falcon hash-to-point (SHAKE256(nonce ‖ message), the NIST round-3
        // convention), which fn-dsa exposes as `HASH_ID_ORIGINAL_FALCON` — NOT `HASH_ID_RAW`.
        // This is the convention the on-chain CATX precompiles verify against. When FIPS-206
        // (FN-DSA) is published this hash-to-point may change and will need to be revisited.
        use fn_dsa_sign::{signature_size, SigningKey, DOMAIN_NONE, HASH_ID_ORIGINAL_FALCON};
        let mut sk = fn_dsa_sign::SigningKeyStandard::decode(signing_key.0.value.as_slice())
            .ok_or_else(|| Error::FnDsaError("an invalid signing key".to_string()))?;
        let mut sig = vec![0u8; signature_size(sk.get_logn())];
        sk.sign(
            &mut rand_core::OsRng,
            &DOMAIN_NONE,
            &HASH_ID_ORIGINAL_FALCON,
            message,
            &mut sig,
        );
        Ok(InnerFalcon {
            scheme: *self,
            value: sig,
        }
        .into())
    }

    #[cfg(all(feature = "sign", not(feature = "eth_falcon")))]
    /// Sign a message with the specified signing key
    pub fn sign(&self, message: &[u8], signing_key: &FalconSigningKey) -> Result<FalconSignature> {
        self.sign_inner(message, signing_key)
    }

    #[cfg(feature = "vrfy")]
    fn verify_inner(
        &self,
        message: &[u8],
        signature: &FalconSignature,
        verification_key: &FalconVerificationKey,
    ) -> Result<()> {
        // Must match the signing convention: original-Falcon hash-to-point, for compatibility
        // with the signatures verified on-chain by the CATX precompiles (see `sign_inner`).
        use fn_dsa_vrfy::{VerifyingKey, DOMAIN_NONE, HASH_ID_ORIGINAL_FALCON};
        let vk = fn_dsa_vrfy::VerifyingKeyStandard::decode(verification_key.0.value.as_slice())
            .ok_or_else(|| Error::FnDsaError("an invalid public key".to_string()))?;
        if vk.verify(
            signature.0.value.as_slice(),
            &DOMAIN_NONE,
            &HASH_ID_ORIGINAL_FALCON,
            message,
        ) {
            Ok(())
        } else {
            Err(Error::FnDsaError(
                "signature verification failed".to_string(),
            ))
        }
    }

    #[cfg(all(feature = "vrfy", not(feature = "eth_falcon")))]
    /// Verify a signature
    pub fn verify(
        &self,
        message: &[u8],
        signature: &FalconSignature,
        verification_key: &FalconVerificationKey,
    ) -> Result<()> {
        self.verify_inner(message, signature, verification_key)
    }
}

impl_falcon_struct!(
    FalconSigningKey,
    validate_signing_key,
    "a valid signing key"
);
impl_falcon_struct!(
    FalconVerificationKey,
    validate_public_key,
    "a valid public key"
);
impl_falcon_struct!(FalconSignature, validate_signature, "a valid signature");

#[derive(Clone, Serialize, Deserialize)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub(crate) struct InnerFalcon {
    pub(crate) scheme: FalconScheme,
    #[serde(
        serialize_with = "serialize_hex_or_bin",
        deserialize_with = "deserialize_hex_or_bin"
    )]
    pub(crate) value: Vec<u8>,
}

impl std::fmt::Debug for InnerFalcon {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("InnerFalcon")
            .field("scheme", &self.scheme)
            .field("value", &"<redacted>")
            .finish()
    }
}

#[cfg(feature = "zeroize")]
impl zeroize::Zeroize for InnerFalcon {
    fn zeroize(&mut self) {
        self.value.zeroize();
    }
}

#[cfg(feature = "zeroize")]
impl zeroize::Zeroize for FalconSigningKey {
    fn zeroize(&mut self) {
        self.0.zeroize();
    }
}

#[cfg(feature = "zeroize")]
impl zeroize::ZeroizeOnDrop for FalconSigningKey {}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use super::*;
    use rstest::*;

    #[cfg(all(feature = "kgen", feature = "sign", feature = "eth_falcon"))]
    #[rstest]
    #[case::fn512(FalconScheme::Dsa512)]
    #[case::fn1024(FalconScheme::Dsa1024)]
    #[case::ethereum(FalconScheme::Ethereum)]
    fn serdes(#[case] scheme: FalconScheme) {
        let (pk, sk) = scheme.keypair().unwrap();

        let bytes = postcard::to_stdvec(&sk).unwrap();
        let sk2 = postcard::from_bytes::<FalconSigningKey>(&bytes).unwrap();
        assert_eq!(sk, sk2);

        let string = serde_json::to_string(&sk).unwrap();
        println!("{}", string);
        let sk2 = serde_json::from_str::<FalconSigningKey>(&string).unwrap();
        assert_eq!(sk, sk2);

        let bytes = postcard::to_stdvec(&pk).unwrap();
        let pk2 = postcard::from_bytes::<FalconVerificationKey>(&bytes).unwrap();
        assert_eq!(pk, pk2);

        let string = serde_json::to_string(&pk).unwrap();
        let pk2 = serde_json::from_str::<FalconVerificationKey>(&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::<FalconSignature>(&bytes).unwrap();
        assert_eq!(sig, sig2);

        let string = serde_json::to_string(&sig).unwrap();
        let sig2 = serde_json::from_str::<FalconSignature>(&string).unwrap();
        assert_eq!(sig, sig2);
    }

    #[cfg(all(
        feature = "kgen",
        feature = "sign",
        feature = "vrfy",
        feature = "eth_falcon"
    ))]
    #[rstest]
    #[case::fn512(FalconScheme::Dsa512)]
    #[case::fn1024(FalconScheme::Dsa1024)]
    #[case::ethereum(FalconScheme::Ethereum)]
    fn flow(#[case] scheme: FalconScheme) {
        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());
    }

    // Seeded Falcon keygen is deterministic, but the seed→key derivation is implementation
    // specific and not fixed by a published standard. These vectors are the current outputs.
    // On-chain verification does not depend on seeded-keygen reproducibility — only on the
    // standard NIST signature/public-key wire format. Once FIPS-206 (FN-DSA) is published the
    // seed derivation may change and these vectors will need to be regenerated.
    #[cfg(feature = "kgen")]
    #[test]
    fn expected_seed() {
        const SEED: &[u8] = &[1u8; 32];
        let (pk, sk) = FalconScheme::Dsa512.keypair_from_seed(SEED).unwrap();

        assert_eq!(
            &hex::encode(pk.to_raw_bytes()),
            "099e0e1530bdc063a39d69d6f2c43a88908d723703318825bec72bd79824aae2b56557595174c173199eb6491165290ac7726448e04a69abef4184ad5ae8ce9c2ab9fa06b3ed564078ae36dd4df81b9ad906fd5ed04bedba4143472a37ccdd6574a3588be5c49111ee2daafd8488ce991e1c3cbbc6204ce51eeeed400cc72a1b1cdea94e2f9a8f32d631f6542b47bca938cccb10b982f89069132f2e0288894c24ef7796f5f352adb504c95811b59bdc24a49c4199da36b778184470e9e52d0cfaf8288a558944e164f4aa173dfeacbbdb54291d7660a249c473b385514b5080acb0a222c4a152f66d8cf1a978a05404928fa8a9ad4c8965ab31cb439118de353ac604037e4657e93e06d77e0a5e7b9994d6b5a5b9836e58548ad55d690cd2071f71f484a51c3552b87c891c938d0db563884beee23192bd9806ef7245e8bfd886e3fb53d5e778ec69b62c4688a3668806b26a50f9f9e64f7be02da202c291824036327715789742043a54551e5a5df4f639edcfb8927ef970d0067e4887bbb71d0c1d1abd9092d45d6c260f7b2dce0cabd01051a286dbbf4df83488ae9d606cb7eb0adb8a33dc0a96148f65b538c6b6588fb5060867269c1ebd662cad92636b75a3545f090823d0416a1885c0ef48797dc7b3dfc82470d80ca28d172a4a81218b925cd669b6b0d272e83718c0b1b1f39bee3c4c57f8eb2bcd296998152d6ab06c78c522c70127a4b4e811bf99579f7ae1fa9c429287769c6414e62a2b965e96cfa509afb288995e56d670f89ca88248864c167a24becc341d08f9b2e4ab5a5e31c924a4564973badf8a5f485a2961f6294f451b5ebdfac0c36053831a72708d242b5fe6088782e18d593e05fb20c8680ae91f7b741d0a9242e0178a897171a8b0d0700995af34d8b74d4995174c90e524a2301201dbc7e7526e5c5add93ca8d3b95a4afd7fe9b362f91cf39fb8c0dbe8211a534ec523b9e63961b9242bcb17b0aa5a4cd2706d0ebd31a09030ab8a966685471305700e4a2ea15461f25b659e8ae6807faf5a6f0878aca7120e74c3ee2ef26495a338e016c678729dc78ec6c2080128a4bec76b88288046a1098d820d0bf13417b638ec0afac8900169951fe01297177d2eaf733aeae625cdfe1a685d3474ae902251e6f93480c4d80d0d24b100c6a54eda4a5852fbd63e6d0d1991f9b9f4240019b1f7ab9b19b76090138a17c8d315d9ec4a54ecaf55a48923c628343f5005c83e8ab71b70f389b0c1750a35b2b"
        );
        assert_eq!(
            &hex::encode(sk.to_raw_bytes()),
            "59f84e80f7e0040c2efef3d0c213bf3c082083f4108008807e20307eebef0207e0fd242fbf084ebc08a000f0603f187f8213f0b81bcf7e10207ef44ffe13ef84e7ef8603f17e0c1fbe07de87f49e7c006fc307f0bc0c20c3f40ffaebf1fdfc314513b081f3d07fdfd23f24407d07e082003f3efbaf7ef3a0040be03d17e1bc0b4f8003a046003d7ff040ff0fd08007a0400c3fc60bc084fc000303bfc71be03be3e1bb17f0b8fbbd7bfc00c1e79f82f080bafbb002f3d040ffc042e3bfbed8003eec804207e004104081f471841b9f7ef01fbf0c2f83f7f18313a0410ff0071fe049088f84084106fc61bbf04fbd03df3efbef7b03eefb17c27e0c2eff07f17cf7ff80f4003d084e7e0c20faef90050f9e43042f7dfbef8817d0c9f4413f00507f00017ddc30fdf811810c1fc3e00202143f3be00f04102142fc3ffff41f41dc5fbf005002ebf042e7cdbbec61c310607bfc11410bb18110404813bf041f917cf3cec70c4ffd13df3e17dfc0f0417cfc11b517eebc006f460bf13b07fe830830ff04307e0c01fbfc6fc00c1f890baec5efdefe148f4100217ff39002046fbfebef7f1c7e8213effc0810b90fcfc1ff9ec20bd040037f7b0022c90c0103ec0d80fbfff9042082ffcec1fc60c2fc203df3cffef3df3c17bf83083fffe87f7e106041efb03c003efce0518307b0b90befbf1c1fc5185fbd0fdebd08107fe430c5f00fc3183dc5100179e38f800ba07d0000c103c00607b1bffbdfbf0860fce08f02ec3f89000202ffb082f0507ef02185000103084082e850c700207c13f03c145efe0821f7ec8100000e420bdf431400be039ff6e81dc814004207af02e42044082fb9ec4e04e7e08010af3dfbff7efc303c0bef0003907b043fff0fef851830791bc04327bfbcfc5080141f3f0880bf1021000c70bafbe000180fff0fe002040081f4313bffb17c102ec1ec6f4908004203607f039e48f00082f810071bf006f83fbff80e46ffb04c1001c40c40fff4317debe041f3f0411800800c013c007040f440c2e82f47ebf0400baec10c0f40e810c123e1c200013f07c17df7afc1042df906fef319d1e836f60ff111fb0ff21fe219fffb0d1eea1ae508f5ef071f0cd3e601edfdf50d13dad9f60add212225f8fcf8131e0e202ac4f32d0810f7f9ecbb020705ef061c1d1ee3023245ffdb22f9271e0f20d9f7e80b0e0b05e61acee21221110dff07c922e501f6eaedf5efd3e70e33272923fa00e10401eaf000000dc8ecd7f713e30c25dbf0e2f2bcf31907bede0bed2e282513f00c270ef4fd36e2f6eaf3082938190e250405f21df6d212f107f8072203fb0dfbf9f215fcf2e5dc0c0708e6f9f6ede8c20c0e1a0604f6d6000e15f0f81b06d90ef130f8dfcd00f81cfb0adfdcdc3b3f230328dbe2e9fcfb0217f2f9e6f5f5f10e1004b9f6370ef5fe0cec0e2d20e21b25ddf4df252d1430201d231a200eddf3d311f1ecf717ecede4eb2ae0150fe6f2d40c0b2c2227fdfe2606dc2b2b2205c0fe100de7f3001535e1cffb1c12f92a06d7ff01f9c5f121fd013506e20fe7f41fe5e2e3e900f3e3100816d1e9d912edf613e2e3ed08fb1d53312f1af9f9f11a1eee1ae3fff32a18ff160cfcee11e731eaf1edfef7262c1410cd14f12ae30d09f2edfb0de709fc0a15d801290900fc07f7bd2dd2cbd3eddfff02030adf23cbfddc1a25f0ecddca01011ce8e02df6f309db04fc200de314f1eb231015da20f8f3081414f0fec91ff01f12d302f4051f330e0be122040ae412fffcccf61ccc3c162be1f2f9fb19f21df8f4f7"
        );
    }

    #[test]
    fn seed_boundaries() {
        assert!(FalconScheme::Dsa512.keypair_from_seed(&[]).is_err());
        assert!(FalconScheme::Dsa512.keypair_from_seed(&[1u8; 31]).is_err());
        assert!(FalconScheme::Dsa512.keypair_from_seed(&[1u8; 32]).is_ok());
        assert!(FalconScheme::Dsa512.keypair_from_seed(&[1u8; 33]).is_ok());
        assert!(FalconScheme::Dsa512.keypair_from_seed(&[1u8; 48]).is_ok());
        assert!(FalconScheme::Dsa512.keypair_from_seed(&[1u8; 64]).is_ok());
        assert!(FalconScheme::Dsa512.keypair_from_seed(&[1u8; 65]).is_err());
        assert!(FalconScheme::Dsa1024.keypair_from_seed(&[1u8; 31]).is_err());
        assert!(FalconScheme::Dsa1024.keypair_from_seed(&[1u8; 64]).is_ok());
        assert!(FalconScheme::Dsa1024.keypair_from_seed(&[1u8; 65]).is_err());
    }
}