1use std::ops::Index;
4
5use rand_core::{CryptoRng, RngCore};
6use zeroize::Zeroize;
7
8use crate::error::RuneError;
9use crate::params::Params;
10
11pub(crate) const MAX_N: usize = 512;
13
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[derive(Clone, PartialEq, Eq, Zeroize)]
18pub struct Poly(pub(crate) Vec<i64>);
19
20impl Poly {
21 pub fn from_coefficients(
28 coefficients: impl Into<Vec<i64>>,
29 params: &Params,
30 ) -> Result<Self, RuneError> {
31 let poly = Self(coefficients.into());
32 if poly_is_well_formed(&poly, params) {
33 Ok(poly)
34 } else {
35 Err(RuneError::MalformedPublicKey)
36 }
37 }
38
39 #[must_use]
41 pub fn len(&self) -> usize {
42 self.0.len()
43 }
44
45 #[must_use]
47 pub fn is_empty(&self) -> bool {
48 self.0.is_empty()
49 }
50
51 #[must_use]
53 pub fn coefficients(&self) -> &[i64] {
54 &self.0
55 }
56
57 pub(crate) fn coefficients_mut(&mut self) -> &mut [i64] {
58 &mut self.0
59 }
60}
61
62impl std::fmt::Debug for Poly {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 write!(
65 f,
66 "Poly(n={}, ||.||_inf={})",
67 self.0.len(),
68 self.0
69 .iter()
70 .map(|coeff| i64::saturating_abs(*coeff))
71 .max()
72 .unwrap_or(0)
73 )
74 }
75}
76
77impl Index<usize> for Poly {
78 type Output = i64;
79
80 fn index(&self, index: usize) -> &Self::Output {
81 &self.0[index]
82 }
83}
84
85pub(crate) struct ZeroizingPoly(pub(crate) Poly);
87
88impl Drop for ZeroizingPoly {
89 fn drop(&mut self) {
90 self.0 .0.zeroize();
92 }
93}
94
95pub(crate) fn zero_poly(params: &Params) -> Poly {
96 Poly(vec![0; params.n()])
97}
98
99pub(crate) fn poly_reduce_mod_q(x: i128, q: i64) -> i64 {
100 let modulus = i128::from(q);
101 let mut reduced = x % modulus;
102 if reduced < 0 {
103 reduced += modulus;
104 }
105
106 let half = modulus / 2;
107 if reduced > half {
108 reduced -= modulus;
109 }
110
111 i64::try_from(reduced).expect("centered residue fits in i64")
112}
113
114pub(crate) fn poly_is_well_formed(poly: &Poly, params: &Params) -> bool {
115 if poly.0.len() != params.n() {
116 return false;
117 }
118
119 let half_q = (params.q() - 1) / 2;
120 poly.0
121 .iter()
122 .all(|coeff| (-half_q..=half_q).contains(coeff))
123}
124
125pub(crate) fn poly_add(a: &Poly, b: &Poly, params: &Params) -> Poly {
126 let coeffs =
127 a.0.iter()
128 .zip(&b.0)
129 .map(|(lhs, rhs)| poly_reduce_mod_q(i128::from(*lhs) + i128::from(*rhs), params.q()))
130 .collect();
131 Poly(coeffs)
132}
133
134pub(crate) fn poly_sub(a: &Poly, b: &Poly, params: &Params) -> Poly {
135 let coeffs =
136 a.0.iter()
137 .zip(&b.0)
138 .map(|(lhs, rhs)| poly_reduce_mod_q(i128::from(*lhs) - i128::from(*rhs), params.q()))
139 .collect();
140 Poly(coeffs)
141}
142
143pub(crate) fn poly_mul_schoolbook(a: &Poly, b: &Poly, params: &Params) -> Poly {
151 let n = params.n();
152 let mut acc = vec![0_i128; n];
153
154 for (i, a_coeff) in a.0.iter().enumerate() {
155 for (j, b_coeff) in b.0.iter().enumerate() {
156 let product = i128::from(*a_coeff) * i128::from(*b_coeff);
157 let degree = i + j;
158 if degree < n {
159 acc[degree] += product;
160 } else {
161 acc[degree - n] -= product;
162 }
163 }
164 }
165
166 Poly(
167 acc.into_iter()
168 .map(|coeff| poly_reduce_mod_q(coeff, params.q()))
169 .collect(),
170 )
171}
172
173pub(crate) fn poly_infinity_norm(a: &Poly) -> i64 {
181 a.0.iter()
182 .map(|coeff| i64::saturating_abs(*coeff))
183 .max()
184 .unwrap_or(0)
185}
186
187pub(crate) fn sample_uniform_poly(params: &Params, rng: &mut (impl CryptoRng + RngCore)) -> Poly {
188 let q_u = u64::try_from(params.q()).expect("modulus is positive");
189 let max_acceptable = u64::MAX - (u64::MAX % q_u);
190 let mut coeffs = Vec::with_capacity(params.n());
191
192 for _ in 0..params.n() {
193 loop {
194 let sample = rng.next_u64();
195 if sample < max_acceptable {
196 coeffs.push(poly_reduce_mod_q(i128::from(sample % q_u), params.q()));
197 break;
198 }
199 }
200 }
201
202 Poly(coeffs)
203}
204
205pub(crate) fn sample_cbd(params: &Params, rng: &mut (impl CryptoRng + RngCore)) -> Poly {
206 let mut coeffs = Vec::with_capacity(params.n());
207
208 for _ in 0..params.n() {
209 let mut lhs = 0_i64;
210 let mut rhs = 0_i64;
211 for _ in 0..params.eta() {
212 lhs += i64::from(rng.next_u32() & 1);
213 rhs += i64::from(rng.next_u32() & 1);
214 }
215 coeffs.push(lhs - rhs);
216 }
217
218 Poly(coeffs)
219}
220
221pub(crate) fn sample_bounded_poly(
222 bound: i64,
223 params: &Params,
224 rng: &mut (impl CryptoRng + RngCore),
225) -> Poly {
226 let range = u64::try_from(2 * bound + 1).expect("bound is positive");
227 let mut coeffs = Vec::with_capacity(params.n());
228
229 for _ in 0..params.n() {
230 let sample = sample_below(range, rng);
231 let signed = i64::try_from(sample).expect("range fits in i64") - bound;
232 coeffs.push(poly_reduce_mod_q(i128::from(signed), params.q()));
233 }
234
235 Poly(coeffs)
236}
237
238fn sample_below(bound: u64, rng: &mut (impl CryptoRng + RngCore)) -> u64 {
239 let max_acceptable = u64::MAX - (u64::MAX % bound);
240 loop {
241 let sample = rng.next_u64();
242 if sample < max_acceptable {
243 return sample % bound;
244 }
245 }
246}