1use crate::{inner_perm, septic_extension::SepticExtension};
4use deepsize2::DeepSizeOf;
5use serde::{Deserialize, Serialize};
6use slop_algebra::{AbstractExtensionField, AbstractField, Field, PrimeField32};
7use slop_symmetric::Permutation;
8use sp1_primitives::SP1Field;
9use std::ops::Add;
10
11#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq, Hash, DeepSizeOf)]
14#[repr(C)]
15pub struct SepticCurve<F> {
16 pub x: SepticExtension<F>,
18 pub y: SepticExtension<F>,
20}
21
22pub const CURVE_WITNESS_DUMMY_POINT_X: [u32; 7] =
24 [0x2718281 + (1 << 24), 0x8284590, 0x4523536, 0x0287471, 0x3526624, 0x9775724, 0x7093699];
25
26pub const CURVE_WITNESS_DUMMY_POINT_Y: [u32; 7] =
28 [1250555984, 1592495468, 656721246, 420301347, 2125819749, 819876460, 17687681];
29
30impl<F: Field> SepticCurve<F> {
31 #[must_use]
33 pub fn dummy() -> Self {
34 Self {
35 x: SepticExtension::from_base_fn(|i| {
36 F::from_canonical_u32(CURVE_WITNESS_DUMMY_POINT_X[i])
37 }),
38 y: SepticExtension::from_base_fn(|i| {
39 F::from_canonical_u32(CURVE_WITNESS_DUMMY_POINT_Y[i])
40 }),
41 }
42 }
43
44 pub fn check_on_point(&self) -> bool {
46 self.y.square() == Self::curve_formula(self.x)
47 }
48
49 #[must_use]
51 pub fn neg(&self) -> Self {
52 SepticCurve { x: self.x, y: -self.y }
53 }
54
55 #[must_use]
56 pub fn add_incomplete(&self, other: SepticCurve<F>) -> Self {
59 let slope = (other.y - self.y) / (other.x - self.x);
60 let result_x = slope.square() - self.x - other.x;
61 let result_y = slope * (self.x - result_x) - self.y;
62 Self { x: result_x, y: result_y }
63 }
64
65 pub fn add_assign(&mut self, other: SepticCurve<F>) {
68 let result = self.add_incomplete(other);
69 self.x = result.x;
70 self.y = result.y;
71 }
72
73 #[must_use]
74 pub fn double(&self) -> Self {
76 let slope = (self.x * self.x * F::from_canonical_u8(3u8) + F::from_canonical_u16(45))
77 / (self.y * F::two());
78 let result_x = slope.square() - self.x * F::two();
79 let result_y = slope * (self.x - result_x) - self.y;
80 Self { x: result_x, y: result_y }
81 }
82
83 #[must_use]
86 pub fn sub_incomplete(&self, other: SepticCurve<F>) -> Self {
87 self.add_incomplete(other.neg())
88 }
89
90 pub fn sub_assign(&mut self, other: SepticCurve<F>) {
93 let result = self.add_incomplete(other.neg());
94 self.x = result.x;
95 self.y = result.y;
96 }
97}
98
99impl<F: AbstractField> SepticCurve<F> {
100 pub fn curve_formula(x: SepticExtension<F>) -> SepticExtension<F> {
102 x.cube()
103 + x * F::from_canonical_u16(45)
104 + SepticExtension::from_base_slice(&[
105 F::zero(),
106 F::zero(),
107 F::zero(),
108 F::from_canonical_u32(41),
109 F::zero(),
110 F::zero(),
111 F::zero(),
112 ])
113 }
114}
115
116impl<F: PrimeField32> SepticCurve<F> {
117 pub fn lift_x(m: [F; 8]) -> (Self, u8, [F; 16], [F; 16]) {
125 let perm = inner_perm();
126 for offset in 0..=255 {
127 let m_trial = [
128 m[0],
129 m[1],
130 m[2],
131 m[3],
132 m[4],
133 m[5],
134 m[6],
135 m[7] + F::from_canonical_u32(1 << 16) * F::from_canonical_u8(offset),
136 F::zero(),
137 F::zero(),
138 F::zero(),
139 F::zero(),
140 F::zero(),
141 F::zero(),
142 F::zero(),
143 F::zero(),
144 ];
145
146 let m_hash = perm
147 .permute(m_trial.map(|x| SP1Field::from_canonical_u32(x.as_canonical_u32())))
148 .map(|x| F::from_canonical_u32(x.as_canonical_u32()));
149 let result = m_hash[..7].try_into().unwrap();
150 let x_trial = SepticExtension(result);
151
152 let y_sq = Self::curve_formula(x_trial);
153 if let Some(y) = y_sq.sqrt() {
154 if y.is_exception() {
155 continue;
156 }
157 if y.is_send() {
158 return (Self { x: x_trial, y: -y }, offset, m_trial, m_hash);
159 }
160 return (Self { x: x_trial, y }, offset, m_trial, m_hash);
161 }
162 }
163 panic!("curve point couldn't be found after 256 attempts");
164 }
165}
166
167impl<F: AbstractField> SepticCurve<F> {
168 pub fn sum_checker_x(
171 p1: SepticCurve<F>,
172 p2: SepticCurve<F>,
173 p3: SepticCurve<F>,
174 ) -> SepticExtension<F> {
175 (p1.x.clone() + p2.x.clone() + p3.x) * (p2.x.clone() - p1.x.clone()).square()
176 - (p2.y - p1.y).square()
177 }
178
179 pub fn sum_checker_y(
182 p1: SepticCurve<F>,
183 p2: SepticCurve<F>,
184 p3: SepticCurve<F>,
185 ) -> SepticExtension<F> {
186 (p1.y.clone() + p3.y.clone()) * (p2.x.clone() - p1.x.clone())
187 - (p2.y - p1.y.clone()) * (p1.x - p3.x)
188 }
189}
190
191impl<T> SepticCurve<T> {
192 pub fn convert<S: Copy, G: FnMut(S) -> T>(point: SepticCurve<S>, mut f: G) -> Self {
195 SepticCurve {
196 x: SepticExtension(point.x.0.map(&mut f)),
197 y: SepticExtension(point.y.0.map(&mut f)),
198 }
199 }
200}
201
202#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
205pub enum SepticCurveComplete<T> {
206 Infinity,
208 Affine(SepticCurve<T>),
210}
211
212impl<F: Field> Add for SepticCurveComplete<F> {
213 type Output = Self;
214 fn add(self, rhs: Self) -> Self::Output {
215 if self.is_infinity() {
216 return rhs;
217 }
218 if rhs.is_infinity() {
219 return self;
220 }
221 let point1 = self.point();
222 let point2 = rhs.point();
223 if point1.x != point2.x {
224 return Self::Affine(point1.add_incomplete(point2));
225 }
226 if point1.y == point2.y {
227 return Self::Affine(point1.double());
228 }
229 Self::Infinity
230 }
231}
232
233impl<F: Field> SepticCurveComplete<F> {
234 pub fn is_infinity(&self) -> bool {
236 match self {
237 Self::Infinity => true,
238 Self::Affine(_) => false,
239 }
240 }
241
242 pub fn point(&self) -> SepticCurve<F> {
244 match self {
245 Self::Infinity => panic!("point() called for point at infinity"),
246 Self::Affine(point) => *point,
247 }
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use rayon::prelude::*;
254 use rayon_scan::ScanParallelIterator;
255 use sp1_primitives::SP1Field;
256 use std::time::Instant;
257
258 use super::*;
259
260 #[test]
261 fn test_lift_x() {
262 let x = [
263 SP1Field::from_canonical_u32(0x2013),
264 SP1Field::from_canonical_u32(0x2015),
265 SP1Field::from_canonical_u32(0x2016),
266 SP1Field::from_canonical_u32(0x2023),
267 SP1Field::from_canonical_u32(0x2024),
268 SP1Field::from_canonical_u32(0x2016),
269 SP1Field::from_canonical_u32(0x2017),
270 SP1Field::from_canonical_u32(0),
271 ];
272 let (curve_point, _, _, _) = SepticCurve::<SP1Field>::lift_x(x);
273 assert!(curve_point.check_on_point());
274 assert!(curve_point.y.is_receive());
275 }
276
277 #[test]
278 fn test_double() {
279 let x = [
280 SP1Field::from_canonical_u32(0x2013),
281 SP1Field::from_canonical_u32(0x2015),
282 SP1Field::from_canonical_u32(0x2016),
283 SP1Field::from_canonical_u32(0x2023),
284 SP1Field::from_canonical_u32(0x2024),
285 SP1Field::from_canonical_u32(0x2016),
286 SP1Field::from_canonical_u32(0x2017),
287 SP1Field::from_canonical_u32(0x2018),
288 ];
289 let (curve_point, _, _, _) = SepticCurve::<SP1Field>::lift_x(x);
290 let double_point = curve_point.double();
291 assert!(double_point.check_on_point());
292 }
293
294 #[test]
295 #[ignore = "benchmarking purposes only"]
296 #[allow(clippy::print_stdout)]
297 fn test_simple_bench() {
298 const D: u32 = 1 << 16;
299 let mut vec = Vec::with_capacity(D as usize);
300 let mut sum = Vec::with_capacity(D as usize);
301 let start = Instant::now();
302 for i in 0..D {
303 let x = [
304 SP1Field::from_canonical_u32(i + 25),
305 SP1Field::from_canonical_u32(2 * i + 376),
306 SP1Field::from_canonical_u32(4 * i + 23),
307 SP1Field::from_canonical_u32(8 * i + 531),
308 SP1Field::from_canonical_u32(16 * i + 542),
309 SP1Field::from_canonical_u32(32 * i + 196),
310 SP1Field::from_canonical_u32(64 * i + 667),
311 SP1Field::from_canonical_u32(128 * i + 123),
312 ];
313 let (curve_point, _, _, _) = SepticCurve::<SP1Field>::lift_x(x);
314 vec.push(curve_point);
315 }
316 println!("Time elapsed: {:?}", start.elapsed());
317 let start = Instant::now();
318 for i in 0..D {
319 sum.push(vec[i as usize].add_incomplete(vec[((i + 1) % D) as usize]));
320 }
321 println!("Time elapsed: {:?}", start.elapsed());
322 let start = Instant::now();
323 for i in 0..(D as usize) {
324 assert!(
325 SepticCurve::<SP1Field>::sum_checker_x(vec[i], vec[(i + 1) % D as usize], sum[i])
326 == SepticExtension::<SP1Field>::zero()
327 );
328 assert!(
329 SepticCurve::<SP1Field>::sum_checker_y(vec[i], vec[(i + 1) % D as usize], sum[i])
330 == SepticExtension::<SP1Field>::zero()
331 );
332 }
333 println!("Time elapsed: {:?}", start.elapsed());
334 }
335
336 #[test]
337 #[ignore = "benchmarking purposes only"]
338 #[allow(clippy::print_stdout)]
339 fn test_parallel_bench() {
340 const D: u32 = 1 << 20;
341 let mut vec = Vec::with_capacity(D as usize);
342 let start = Instant::now();
343 for i in 0..D {
344 let x = [
345 SP1Field::from_canonical_u32(i + 25),
346 SP1Field::from_canonical_u32(2 * i + 376),
347 SP1Field::from_canonical_u32(4 * i + 23),
348 SP1Field::from_canonical_u32(8 * i + 531),
349 SP1Field::from_canonical_u32(16 * i + 542),
350 SP1Field::from_canonical_u32(32 * i + 196),
351 SP1Field::from_canonical_u32(64 * i + 667),
352 SP1Field::from_canonical_u32(128 * i + 123),
353 ];
354 let (curve_point, _, _, _) = SepticCurve::<SP1Field>::lift_x(x);
355 vec.push(SepticCurveComplete::Affine(curve_point));
356 }
357 println!("Time elapsed: {:?}", start.elapsed());
358
359 let mut cum_sum = SepticCurveComplete::Infinity;
360 let start = Instant::now();
361 for point in &vec {
362 cum_sum = cum_sum + *point;
363 }
364 println!("Time elapsed: {:?}", start.elapsed());
365 let start = Instant::now();
366 let par_sum = vec
367 .into_par_iter()
368 .with_min_len(1 << 16)
369 .scan(|a, b| *a + *b, SepticCurveComplete::Infinity)
370 .collect::<Vec<SepticCurveComplete<SP1Field>>>();
371 println!("Time elapsed: {:?}", start.elapsed());
372 assert_eq!(cum_sum, *par_sum.last().unwrap());
373 }
374}