risc0_zkp_core/fp4.rs
1// Copyright 2022 Risc0, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Support for the rank 4 extension field of the base field.
16
17use core::ops;
18
19use bytemuck::{Pod, Zeroable};
20use rand::Rng;
21
22use crate::{
23 fp::{Fp, FpMul, P},
24 Random,
25};
26
27const BETA: Fp = Fp::new(11);
28const NBETA: Fp = Fp::new(P - 11);
29
30/// The size of the extension field in elements, 4 in this case.
31pub const EXT_SIZE: usize = 4;
32
33/// Instances of `Fp4` are elements of a finite field `F_p^4`. They are
34/// represented as elements of `F_p[X] / (X^4 - 11)`. Basically, this is a *big*
35/// finite field (about `2^128` elements), which is used when the security of
36/// various operations depends on the size of the field. It has the field
37/// `Fp` as a subfield, which means operations by the two are compatable, which
38/// is important. The irreducible polynomial was choosen to be the most simple
39/// possible one, `x^4 - B`, where `11` is the smallest `B` which makes the
40/// polynomial irreducable.
41#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Zeroable, Pod)]
42#[repr(transparent)]
43pub struct Fp4([Fp; EXT_SIZE]);
44
45impl Fp4 {
46 /// Explicitly construct an Fp4 from parts.
47 pub fn new(x0: Fp, x1: Fp, x2: Fp, x3: Fp) -> Self {
48 Self([x0, x1, x2, x3])
49 }
50
51 /// Create a [Fp4] from a [Fp].
52 pub fn from_fp(x: Fp) -> Self {
53 Self([x, Fp::new(0), Fp::new(0), Fp::new(0)])
54 }
55
56 /// Create a [Fp4] from a raw integer.
57 pub fn from_u32(x0: u32) -> Self {
58 Self([Fp::new(x0), Fp::new(0), Fp::new(0), Fp::new(0)])
59 }
60
61 /// Returns the value zero.
62 pub fn zero() -> Self {
63 Self::from_u32(0)
64 }
65
66 /// Returns the value one.
67 pub fn one() -> Self {
68 Self::from_u32(1)
69 }
70
71 /// Returns the constant portion of a [Fp].
72 pub fn const_part(self) -> Fp {
73 self.0[0]
74 }
75
76 /// Returns the elements of a [Fp].
77 pub fn elems(&self) -> &[Fp] {
78 &self.0
79 }
80
81 /// Raise a [Fp4] to a power of `n`.
82 pub fn pow(self, n: usize) -> Self {
83 let mut n = n;
84 let mut tot = Fp4::from(1);
85 let mut x = self;
86 while n != 0 {
87 if n % 2 == 1 {
88 tot *= x;
89 }
90 n = n / 2;
91 x *= x;
92 }
93 tot
94 }
95
96 /// Compute the multiplicative inverse of an `Fp4`.
97 pub fn inv(self) -> Self {
98 let a = &self.0;
99 // Compute the multiplicative inverse by looking at `Fp4` as a composite field
100 // and using the same basic methods used to invert complex numbers. We imagine
101 // that initially we have a numerator of `1`, and a denominator of `a`.
102 // `out = 1 / a`; We set `a'` to be a with the first and third components
103 // negated. We then multiply the numerator and the denominator by `a'`,
104 // producing `out = a' / (a * a')`. By construction `(a * a')` has `0`s
105 // in its first and third elements. We call this number, `b` and compute
106 // it as follows.
107 let mut b0 = a[0] * a[0] + BETA * (a[1] * (a[3] + a[3]) - a[2] * a[2]);
108 let mut b2 = a[0] * (a[2] + a[2]) - a[1] * a[1] + BETA * (a[3] * a[3]);
109 // Now, we make `b'` by inverting `b2`. When we muliply both sizes by `b'`, we
110 // get `out = (a' * b') / (b * b')`. But by construction `b * b'` is in
111 // fact an element of `Fp`, call it `c`.
112 let c = b0 * b0 + BETA * b2 * b2;
113 // But we can now invert `C` direcly, and multiply by `a' * b'`:
114 // `out = a' * b' * inv(c)`
115 let ic = c.inv();
116 // Note: if c == 0 (really should only happen if in == 0), our 'safe' version of
117 // inverse results in ic == 0, and thus out = 0, so we have the same 'safe'
118 // behavior for Fp4. Oh, and since we want to multiply everything by ic, it's
119 // slightly faster to pre-multiply the two parts of b by ic (2 multiplies
120 // instead of 4).
121 b0 *= ic;
122 b2 *= ic;
123 Fp4([
124 a[0] * b0 + BETA * a[2] * b2,
125 -a[1] * b0 + NBETA * a[3] * b2,
126 -a[0] * b2 + a[2] * b0,
127 a[1] * b2 - a[3] * b0,
128 ])
129 }
130}
131
132impl FpMul for Fp4 {
133 fn fp_mul(self, x: Fp) -> Self {
134 self * x
135 }
136}
137
138impl Random for Fp4 {
139 /// Generate a random field element uniformly.
140 fn random<R: Rng>(rng: &mut R) -> Self {
141 Self([
142 Fp::random(rng),
143 Fp::random(rng),
144 Fp::random(rng),
145 Fp::random(rng),
146 ])
147 }
148}
149
150impl ops::Add for Fp4 {
151 type Output = Self;
152 fn add(self, rhs: Self) -> Self {
153 let mut lhs = self;
154 lhs += rhs;
155 lhs
156 }
157}
158
159impl ops::AddAssign for Fp4 {
160 fn add_assign(&mut self, rhs: Self) {
161 for i in 0..self.0.len() {
162 self.0[i] += rhs.0[i];
163 }
164 }
165}
166
167impl ops::Sub for Fp4 {
168 type Output = Self;
169 fn sub(self, rhs: Self) -> Self {
170 let mut lhs = self;
171 lhs -= rhs;
172 lhs
173 }
174}
175
176impl ops::SubAssign for Fp4 {
177 fn sub_assign(&mut self, rhs: Self) {
178 for i in 0..self.0.len() {
179 self.0[i] -= rhs.0[i];
180 }
181 }
182}
183
184/// Implement the simple multiplication case by the subfield Fp.
185impl ops::MulAssign<Fp> for Fp4 {
186 fn mul_assign(&mut self, rhs: Fp) {
187 for i in 0..self.0.len() {
188 self.0[i] *= rhs;
189 }
190 }
191}
192
193impl ops::Mul<Fp> for Fp4 {
194 type Output = Self;
195 fn mul(self, rhs: Fp) -> Self {
196 let mut lhs = self;
197 lhs *= rhs;
198 lhs
199 }
200}
201
202impl ops::Mul<Fp4> for Fp {
203 type Output = Fp4;
204 fn mul(self, rhs: Fp4) -> Fp4 {
205 rhs * self
206 }
207}
208
209// Now we get to the interesting case of multiplication. Basically, multiply
210// out the polynomial representations, and then reduce module `x^4 - B`, which
211// means powers >= 4 get shifted back 4 and multiplied by `-beta`. We could
212// write this as a double loops with some `if`s and hope it gets unrolled
213// properly, but it's small enough to just hand write.
214impl ops::MulAssign for Fp4 {
215 fn mul_assign(&mut self, rhs: Self) {
216 // Rename the element arrays to something small for readability.
217 let a = &self.0;
218 let b = &rhs.0;
219 self.0 = [
220 a[0] * b[0] + NBETA * (a[1] * b[3] + a[2] * b[2] + a[3] * b[1]),
221 a[0] * b[1] + a[1] * b[0] + NBETA * (a[2] * b[3] + a[3] * b[2]),
222 a[0] * b[2] + a[1] * b[1] + a[2] * b[0] + NBETA * (a[3] * b[3]),
223 a[0] * b[3] + a[1] * b[2] + a[2] * b[1] + a[3] * b[0],
224 ];
225 }
226}
227
228impl ops::Mul for Fp4 {
229 type Output = Fp4;
230 fn mul(self, rhs: Fp4) -> Fp4 {
231 let mut lhs = self;
232 lhs *= rhs;
233 lhs
234 }
235}
236
237impl ops::Neg for Fp4 {
238 type Output = Self;
239 fn neg(self) -> Self {
240 Fp4::default() - self
241 }
242}
243
244impl From<u32> for Fp4 {
245 fn from(x: u32) -> Self {
246 Self([Fp::from(x), Fp::default(), Fp::default(), Fp::default()])
247 }
248}
249
250impl From<Fp> for Fp4 {
251 fn from(x: Fp) -> Self {
252 Self([x, Fp::default(), Fp::default(), Fp::default()])
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::Fp4;
259 use crate::Random;
260 use rand::SeedableRng;
261
262 #[test]
263 fn isa_field() {
264 let mut rng = rand::rngs::SmallRng::seed_from_u64(2);
265 // Pick random sets of 3 elements of Fp4, and verify they meet the requirements
266 // of a field.
267 for _ in 0..1_000 {
268 let a = Fp4::random(&mut rng);
269 let b = Fp4::random(&mut rng);
270 let c = Fp4::random(&mut rng);
271 // Addition + multiplication commute
272 assert_eq!(a + b, b + a);
273 assert_eq!(a * b, b * a);
274 // Addition + multiplication are associative
275 assert_eq!(a + (b + c), (a + b) + c);
276 assert_eq!(a * (b * c), (a * b) * c);
277 // Distributive property
278 assert_eq!(a * (b + c), a * b + a * c);
279 // Inverses
280 if a != Fp4::default() {
281 assert_eq!(a.inv() * a, Fp4::from(1));
282 }
283 assert_eq!(Fp4::default() - a, -a);
284 assert_eq!(a + (-a), Fp4::default());
285 }
286 }
287}