Skip to main content

eyvara/
params.rs

1//! System parameters for the Eyvara VRF.
2//!
3//! This module defines the sealed cryptographic parameters for Eyvara-I
4//! (NIST Category 1) and Eyvara-III (NIST Category 3). The public API supports
5//! only the predefined parameter sets because invalid custom parameters can
6//! silently break security.
7
8#[cfg(feature = "serde")]
9use serde::Serialize;
10
11/// Ring dimension. The polynomial ring is `Z[X]/(X^N + 1)`.
12pub const N: usize = 256;
13
14/// Prime modulus for the polynomial ring `R_q = Z_q[X]/(X^N + 1)`.
15pub const Q: i64 = 8_380_417;
16
17/// Half of Q, used for centered reduction.
18pub const Q_HALF: i64 = (Q - 1) / 2;
19
20/// Montgomery parameter: R = 2^32 mod Q, used for Montgomery multiplication.
21pub const MONT_R: i64 = 4_193_792;
22
23/// Inverse of R in Z_q: R^{-1} * R == 1 mod Q.
24pub const MONT_R_INV: i64 = 8_265_825;
25
26/// Q^{-1} mod 2^32, used in Montgomery reduction.
27pub const Q_INV: i64 = 58_728_449;
28
29/// Maximum number of Fiat-Shamir with Aborts iterations before returning an error.
30pub const MAX_ATTEMPTS: usize = 256;
31
32/// Size of the VRF output in bytes. Produced by SHAKE-256.
33pub const OUTPUT_SIZE: usize = 64;
34
35/// Size of the challenge seed in bytes. Produced by SHAKE-256.
36pub const CHALLENGE_SEED_SIZE: usize = 32;
37
38/// Size of the public seed rho in bytes.
39pub const SEED_SIZE: usize = 32;
40
41/// Domain separation tag for the challenge hash H_1.
42pub const DOMAIN_CHALLENGE: &[u8] = b"eyvara-challenge";
43
44/// Domain separation tag for the output hash H_2.
45pub const DOMAIN_OUTPUT: &[u8] = b"eyvara-output";
46
47/// Domain separation tag for matrix expansion.
48pub const DOMAIN_MATRIX: &[u8] = b"vrf-matrix\x00\x00\x00\x00\x00\x00";
49
50/// System parameters for the Eyvara VRF scheme.
51///
52/// The fields are private; use [`EYVARA_128`] or [`EYVARA_192`]. Custom
53/// parameters are not part of the public API because bad choices break security.
54/// Serialization is supported via the `serde` feature for inspection and
55/// logging purposes. Deserialization is intentionally not supported: parameter
56/// sets must be constructed via the provided constants [`EYVARA_128`] and
57/// [`EYVARA_192`].
58///
59/// ```compile_fail
60/// fn assert_deserializable<T: serde::de::DeserializeOwned>() {}
61/// assert_deserializable::<eyvara::params::Params>();
62/// ```
63#[cfg_attr(feature = "serde", derive(Serialize))]
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub struct Params {
66    n: usize,
67    k: usize,
68    q: i64,
69    eta: i64,
70    gamma_1: i64,
71    gamma_2: i64,
72    tau: usize,
73    beta: i64,
74    omega: usize,
75}
76
77impl Params {
78    #[allow(clippy::too_many_arguments)]
79    const fn new(
80        n: usize,
81        k: usize,
82        q: i64,
83        eta: i64,
84        gamma_1: i64,
85        gamma_2: i64,
86        tau: usize,
87        beta: i64,
88        omega: usize,
89    ) -> Self {
90        assert!(tau <= n, "tau must not exceed n");
91        assert!(tau > 0, "tau must be positive");
92        assert!(gamma_1 > beta, "gamma_1 must exceed beta");
93        assert!(gamma_2 > 0, "gamma_2 must be positive");
94        assert!(beta > 0, "beta must be positive");
95        assert!(eta > 0, "eta must be positive");
96        assert!(omega > 0, "omega must be positive");
97        assert!(k > 0, "k must be positive");
98        assert!(q > 0, "q must be positive");
99        Self {
100            n,
101            k,
102            q,
103            eta,
104            gamma_1,
105            gamma_2,
106            tau,
107            beta,
108            omega,
109        }
110    }
111
112    /// Returns the ring dimension.
113    pub const fn n(&self) -> usize {
114        self.n
115    }
116
117    /// Returns the module rank.
118    pub const fn k(&self) -> usize {
119        self.k
120    }
121
122    /// Returns the modulus.
123    pub const fn q(&self) -> i64 {
124        self.q
125    }
126
127    /// Returns the centered binomial distribution parameter.
128    pub const fn eta(&self) -> i64 {
129        self.eta
130    }
131
132    /// Returns the masking range bound.
133    pub const fn gamma_1(&self) -> i64 {
134        self.gamma_1
135    }
136
137    /// Returns the rounding divisor parameter.
138    pub const fn gamma_2(&self) -> i64 {
139        self.gamma_2
140    }
141
142    /// Returns the number of nonzero challenge coefficients.
143    pub const fn tau(&self) -> usize {
144        self.tau
145    }
146
147    /// Returns beta = tau * eta.
148    pub const fn beta(&self) -> i64 {
149        self.beta
150    }
151
152    /// Returns the maximum allowed hint weight.
153    pub const fn omega(&self) -> usize {
154        self.omega
155    }
156
157    /// Rejection sampling bound: gamma_1 - beta.
158    pub const fn rejection_bound(&self) -> i64 {
159        self.gamma_1 - self.beta
160    }
161}
162
163/// NIST Category 1 parameter set. Suitable for most applications.
164pub const EYVARA_128: Params = Params::new(256, 2, 8_380_417, 2, 131_072, 95_232, 39, 78, 80);
165
166/// NIST Category 3 parameter set. Use when a higher security margin is
167/// required, at the cost of larger proofs and slower operations.
168pub const EYVARA_192: Params = Params::new(256, 3, 8_380_417, 2, 524_288, 261_888, 49, 98, 120);
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn test_eyvara_i_rejection_bound() {
176        assert_eq!(EYVARA_128.rejection_bound(), 131_072 - 78);
177    }
178
179    #[test]
180    fn test_eyvara_iii_rejection_bound() {
181        assert_eq!(EYVARA_192.rejection_bound(), 524_288 - 98);
182    }
183
184    #[test]
185    fn test_gamma2_divides_q_minus_1() {
186        assert_eq!((Q - 1) % (2 * EYVARA_128.gamma_2()), 0);
187        assert_eq!((Q - 1) % (2 * EYVARA_192.gamma_2()), 0);
188    }
189
190    #[test]
191    fn test_domain_tags_are_nonempty() {
192        assert_eq!(DOMAIN_CHALLENGE.len(), 16);
193        assert!(!DOMAIN_OUTPUT.is_empty());
194        assert_eq!(DOMAIN_MATRIX.len(), 16);
195    }
196}