Skip to main content

eyvara/
keygen.rs

1//! Key generation for the Eyvara VRF.
2//!
3//! Implements key generation from the MLWE assumption. The public key contains
4//! the public matrix seed and public vector. The secret key keeps the short
5//! vectors private and redacts them in debug output.
6
7use crate::ntt::{ntt_forward, ntt_inverse, ntt_pointwise_mul, reduce_coeff};
8use crate::params::{Params, N, SEED_SIZE};
9use crate::poly::{expand_a, poly_zero, sample_cbd_vec, Poly, PolyVec};
10use rand::{CryptoRng, RngCore};
11use zeroize::{Zeroize, ZeroizeOnDrop};
12
13/// Public key for the Eyvara VRF.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct PublicKey {
16    /// Seed for deterministic matrix expansion via ExpandA.
17    pub rho: [u8; SEED_SIZE],
18
19    /// Public vector t = As + e mod q, where s and e are short secret vectors.
20    pub t: PolyVec,
21}
22
23/// Secret key for the Eyvara VRF.
24///
25/// Secret vectors are private, are not cloneable through the public API, and
26/// are redacted in debug output. The key zeroizes its memory when dropped.
27#[derive(Zeroize, ZeroizeOnDrop)]
28pub struct SecretKey {
29    seed: [u8; SEED_SIZE],
30    rho: [u8; SEED_SIZE],
31    s: PolyVec,
32    e: PolyVec,
33    t: PolyVec,
34    #[zeroize(skip)]
35    public_key: PublicKey,
36}
37
38#[cfg(feature = "serde")]
39impl serde::Serialize for PublicKey {
40    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
41    where
42        S: serde::Serializer,
43    {
44        use serde::ser::SerializeStruct;
45
46        let mut state = serializer.serialize_struct("PublicKey", 2)?;
47        state.serialize_field("rho", &self.rho.as_slice())?;
48        state.serialize_field("t", &crate::poly::polyvec_to_nested_vec(&self.t))?;
49        state.end()
50    }
51}
52
53#[cfg(feature = "serde")]
54impl<'de> serde::Deserialize<'de> for PublicKey {
55    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
56    where
57        D: serde::Deserializer<'de>,
58    {
59        #[derive(serde::Deserialize)]
60        struct PublicKeyRepr {
61            rho: Vec<u8>,
62            t: Vec<Vec<i64>>,
63        }
64
65        let repr = PublicKeyRepr::deserialize(deserializer)?;
66        if repr.rho.len() != SEED_SIZE {
67            return Err(serde::de::Error::invalid_length(
68                repr.rho.len(),
69                &"32-byte public matrix seed",
70            ));
71        }
72        let mut rho = [0_u8; SEED_SIZE];
73        rho.copy_from_slice(&repr.rho);
74        let t = crate::poly::nested_vec_to_polyvec(repr.t).map_err(serde::de::Error::custom)?;
75        Ok(Self { rho, t })
76    }
77}
78
79#[cfg(feature = "serde")]
80impl serde::Serialize for SecretKey {
81    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
82    where
83        S: serde::Serializer,
84    {
85        use serde::ser::SerializeStruct;
86
87        let mut state = serializer.serialize_struct("SecretKey", 5)?;
88        state.serialize_field("seed", &self.seed.as_slice())?;
89        state.serialize_field("rho", &self.rho.as_slice())?;
90        state.serialize_field("s", &crate::poly::polyvec_to_nested_vec(&self.s))?;
91        state.serialize_field("e", &crate::poly::polyvec_to_nested_vec(&self.e))?;
92        state.serialize_field("t", &crate::poly::polyvec_to_nested_vec(&self.t))?;
93        state.end()
94    }
95}
96
97impl SecretKey {
98    /// Reconstructs a `SecretKey` from its serialized components.
99    ///
100    /// All fields must have been produced by a prior call to [`eyvara_keygen`].
101    /// No validation of the cryptographic relationship between fields is
102    /// performed.
103    #[cfg(feature = "serde")]
104    pub fn from_parts(
105        rho: [u8; SEED_SIZE],
106        s: PolyVec,
107        e: PolyVec,
108        t: PolyVec,
109        seed: [u8; SEED_SIZE],
110    ) -> Self {
111        let public_key = PublicKey { rho, t: t.clone() };
112        Self {
113            seed,
114            rho,
115            s,
116            e,
117            t,
118            public_key,
119        }
120    }
121
122    /// Returns the public matrix seed. Safe to expose.
123    pub fn rho(&self) -> &[u8; SEED_SIZE] {
124        &self.rho
125    }
126
127    /// Returns the public key component corresponding to this secret key.
128    ///
129    /// This does not expose the secret vectors `s` or `e`.
130    pub fn public_key(&self) -> &PublicKey {
131        &self.public_key
132    }
133
134    pub(crate) fn s(&self) -> &PolyVec {
135        &self.s
136    }
137
138    pub(crate) fn e(&self) -> &PolyVec {
139        &self.e
140    }
141
142    pub(crate) fn t(&self) -> &PolyVec {
143        &self.t
144    }
145
146    pub(crate) fn seed(&self) -> &[u8; SEED_SIZE] {
147        &self.seed
148    }
149}
150
151impl std::fmt::Debug for SecretKey {
152    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153        // Redact secret material from debug output.
154        f.debug_struct("SecretKey")
155            .field("rho", &self.rho)
156            .field("seed", &"[REDACTED]")
157            .field("s", &"[REDACTED]")
158            .field("e", &"[REDACTED]")
159            .field("t", &"[REDACTED]")
160            .finish_non_exhaustive()
161    }
162}
163
164/// Generates a public key and non-cloneable secret key using a cryptographic
165/// RNG. The caller must supply a [`CryptoRng`] so key material is never derived
166/// from predictable randomness in normal use.
167pub fn eyvara_keygen<R>(params: &Params, rng: &mut R) -> (PublicKey, SecretKey)
168where
169    R: CryptoRng + RngCore,
170{
171    let mut seed = [0u8; SEED_SIZE];
172    rng.fill_bytes(&mut seed);
173
174    let mut rho = [0u8; SEED_SIZE];
175    rng.fill_bytes(&mut rho);
176
177    let a_ntt = expand_a(&rho, params.k());
178    let s = sample_cbd_vec(rng, params.k(), params.eta());
179    let e = sample_cbd_vec(rng, params.k(), params.eta());
180
181    let s_ntt: Vec<Poly> = s
182        .iter()
183        .map(|p| {
184            let mut pn = *p;
185            ntt_forward(&mut pn);
186            pn
187        })
188        .collect();
189
190    let mut t = vec![poly_zero(); params.k()];
191    for i in 0..params.k() {
192        let mut acc = poly_zero();
193        for j in 0..params.k() {
194            let product = ntt_pointwise_mul(&a_ntt[i][j], &s_ntt[j]);
195            for idx in 0..N {
196                acc[idx] += product[idx];
197            }
198        }
199        ntt_inverse(&mut acc);
200        for idx in 0..N {
201            acc[idx] = reduce_coeff(acc[idx] + e[i][idx]);
202        }
203        t[i] = acc;
204    }
205
206    let pk = PublicKey { rho, t: t.clone() };
207    let sk = SecretKey {
208        seed,
209        rho,
210        s,
211        e,
212        t,
213        public_key: pk.clone(),
214    };
215
216    (pk, sk)
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222    use crate::params::EYVARA_128;
223    use crate::poly::infinity_norm_vec;
224    use rand::SeedableRng;
225    use rand_chacha::ChaCha20Rng;
226
227    #[test]
228    fn test_keygen_produces_valid_keys() {
229        // Seeded for determinism; real usage requires OsRng.
230        let mut rng = ChaCha20Rng::seed_from_u64(42);
231        let (pk, sk) = eyvara_keygen(&EYVARA_128, &mut rng);
232
233        assert_eq!(pk.t.len(), EYVARA_128.k());
234        assert!(infinity_norm_vec(sk.s()) <= EYVARA_128.eta());
235        assert!(infinity_norm_vec(sk.e()) <= EYVARA_128.eta());
236        assert_eq!(pk.t, *sk.t());
237        assert_eq!(pk.rho, *sk.rho());
238        assert_eq!(&pk, sk.public_key());
239    }
240
241    #[test]
242    fn test_keygen_different_seeds_different_keys() {
243        // Seeded for determinism; real usage requires OsRng.
244        let mut rng1 = ChaCha20Rng::seed_from_u64(1);
245        // Seeded for determinism; real usage requires OsRng.
246        let mut rng2 = ChaCha20Rng::seed_from_u64(2);
247        let (pk1, _) = eyvara_keygen(&EYVARA_128, &mut rng1);
248        let (pk2, _) = eyvara_keygen(&EYVARA_128, &mut rng2);
249
250        assert_ne!(pk1.rho, pk2.rho);
251    }
252
253    #[test]
254    fn test_secret_key_debug_redacts_secret_fields() {
255        // Seeded for determinism; real usage requires OsRng.
256        let mut rng = ChaCha20Rng::seed_from_u64(42);
257        let (_, sk) = eyvara_keygen(&EYVARA_128, &mut rng);
258        let debug = format!("{sk:?}");
259
260        assert!(debug.contains("[REDACTED]"));
261        assert!(!debug.contains("s: [["));
262    }
263}