sp1_core_machine/operations/
global_interaction.rs1use crate::air::WordAirBuilder;
2use slop_air::AirBuilder;
3use slop_algebra::{AbstractExtensionField, AbstractField, Field, PrimeField32};
4use sp1_core_executor::{
5 events::{ByteLookupEvent, ByteRecord},
6 ByteOpcode,
7};
8use sp1_derive::AlignedBorrow;
9use sp1_hypercube::{
10 air::SP1AirBuilder,
11 operations::poseidon2::{
12 air::{eval_external_round, eval_internal_rounds},
13 permutation::Poseidon2Cols,
14 trace::populate_perm_deg3,
15 Poseidon2Operation, NUM_EXTERNAL_ROUNDS,
16 },
17 septic_curve::{SepticCurve, CURVE_WITNESS_DUMMY_POINT_X, CURVE_WITNESS_DUMMY_POINT_Y},
18 septic_extension::{SepticBlock, SepticExtension},
19};
20
21#[derive(AlignedBorrow, Clone, Copy)]
23#[repr(C)]
24pub struct GlobalInteractionOperation<T: Copy> {
25 pub x_coordinate: SepticBlock<T>,
26 pub y_coordinate: SepticBlock<T>,
27 pub permutation: Poseidon2Operation<T>,
28 pub offset: T,
29 pub y6_byte_decomp: [T; 4],
30}
31
32impl<F: PrimeField32> GlobalInteractionOperation<F> {
33 pub fn get_digest(
34 values: [u32; 8],
35 is_receive: bool,
36 kind: u8,
37 ) -> (SepticCurve<F>, u8, [F; 16], [F; 16]) {
38 let mut new_values = values.map(|x| F::from_canonical_u32(x));
39 new_values[0] = new_values[0] + F::from_canonical_u32((kind as u32) << 24);
40 let (point, offset, m_trial, m_hash) = SepticCurve::<F>::lift_x(new_values);
41 if !is_receive {
42 return (point.neg(), offset, m_trial, m_hash);
43 }
44 (point, offset, m_trial, m_hash)
45 }
46
47 pub fn populate(
48 &mut self,
49 blu: &mut impl ByteRecord,
50 values: [u32; 8],
51 is_receive: bool,
52 is_real: bool,
53 kind: u8,
54 ) {
55 if is_real {
56 let (point, offset, m_trial, m_hash) = Self::get_digest(values, is_receive, kind);
57 blu.add_byte_lookup_event(ByteLookupEvent {
58 opcode: ByteOpcode::U8Range,
59 a: 0,
60 b: 0,
61 c: offset,
62 });
63 self.offset = F::from_canonical_u8(offset);
64 self.x_coordinate = SepticBlock::<F>::from(point.x.0);
65 self.y_coordinate = SepticBlock::<F>::from(point.y.0);
66 let range_check_value = if is_receive {
67 point.y.0[6].as_canonical_u32() - 1
68 } else {
69 F::ORDER_U32 - point.y.0[6].as_canonical_u32() - 1
70 };
71 assert!(range_check_value < 63 * (1 << 24));
72 for i in 0..3 {
73 let byte = ((range_check_value >> (8 * i)) & 0xFF) as u8;
74 self.y6_byte_decomp[i] = F::from_canonical_u8(byte);
75 blu.add_byte_lookup_event(ByteLookupEvent {
76 opcode: ByteOpcode::U8Range,
77 a: 0,
78 b: 0,
79 c: byte,
80 });
81 }
82 let last_byte = (range_check_value >> 24) as u8;
83 self.y6_byte_decomp[3] = F::from_canonical_u8(last_byte);
84 blu.add_byte_lookup_event(ByteLookupEvent {
85 opcode: ByteOpcode::LTU,
86 a: 1,
87 b: last_byte,
88 c: 63,
89 });
90 self.permutation = populate_perm_deg3(m_trial, Some(m_hash));
91
92 assert_eq!(self.x_coordinate.0[0], self.permutation.permutation.perm_output()[0]);
93 } else {
94 self.populate_dummy();
95 assert_eq!(self.x_coordinate.0[0], self.permutation.permutation.perm_output()[0]);
96 }
97 }
98
99 pub fn populate_dummy(&mut self) {
100 self.x_coordinate = SepticBlock::<F>::from_base_fn(|i| {
101 F::from_canonical_u32(CURVE_WITNESS_DUMMY_POINT_X[i])
102 });
103 self.y_coordinate = SepticBlock::<F>::from_base_fn(|i| {
104 F::from_canonical_u32(CURVE_WITNESS_DUMMY_POINT_Y[i])
105 });
106 self.offset = F::zero();
107 for i in 0..4 {
108 self.y6_byte_decomp[i] = F::zero();
109 }
110 self.permutation = populate_perm_deg3([F::zero(); 16], None);
111 }
112}
113
114impl<F: Field> GlobalInteractionOperation<F> {
115 #[allow(clippy::too_many_arguments)]
117 pub fn eval_single_digest<AB: SP1AirBuilder + slop_air::PairBuilder>(
118 builder: &mut AB,
119 values: [AB::Expr; 8],
120 cols: GlobalInteractionOperation<AB::Var>,
121 is_receive: AB::Expr,
122 is_send: AB::Expr,
123 is_real: AB::Var,
124 kind: AB::Var,
125 message_0_limbs: [AB::Var; 2],
126 ) {
127 builder.assert_bool(is_real);
129 builder.when(is_real).assert_eq(is_receive.clone() + is_send.clone(), AB::Expr::one());
130 builder.assert_bool(is_receive.clone());
131 builder.assert_bool(is_send.clone());
132
133 builder.send_byte(
135 AB::Expr::from_canonical_u32(ByteOpcode::U8Range as u32),
136 AB::Expr::zero(),
137 AB::Expr::zero(),
138 cols.offset.into(),
139 is_real.into(),
140 );
141
142 builder.when(is_real).assert_eq(
145 values[0].clone(),
146 message_0_limbs[0] + message_0_limbs[1] * AB::F::from_canonical_u32(1 << 16),
147 );
148 builder.slice_range_check_u16(&[message_0_limbs[0].into(), values[7].clone()], is_real);
149 builder.slice_range_check_u8(&[message_0_limbs[1]], is_real);
150 builder.send_byte(
152 AB::Expr::from_canonical_u32(ByteOpcode::Range as u32),
153 kind.into(),
154 AB::Expr::from_canonical_u32(6),
155 AB::Expr::zero(),
156 is_real.into(),
157 );
158
159 let m_trial = [
165 values[0].clone() + AB::Expr::from_canonical_u32(1 << 24) * kind,
166 values[1].clone(),
167 values[2].clone(),
168 values[3].clone(),
169 values[4].clone(),
170 values[5].clone(),
171 values[6].clone(),
172 values[7].clone() + AB::Expr::from_canonical_u32(1 << 16) * cols.offset,
173 AB::Expr::zero(),
174 AB::Expr::zero(),
175 AB::Expr::zero(),
176 AB::Expr::zero(),
177 AB::Expr::zero(),
178 AB::Expr::zero(),
179 AB::Expr::zero(),
180 AB::Expr::zero(),
181 ];
182
183 for i in 0..16 {
185 builder.when(is_real).assert_eq(
186 cols.permutation.permutation.external_rounds_state()[0][i].into(),
187 m_trial[i].clone(),
188 );
189 }
190
191 for r in 0..NUM_EXTERNAL_ROUNDS {
193 eval_external_round(builder, &cols.permutation.permutation, r);
194 }
195 eval_internal_rounds(builder, &cols.permutation.permutation);
196
197 let m_hash = cols.permutation.permutation.perm_output();
199 for i in 0..7 {
200 builder.when(is_real).assert_eq(cols.x_coordinate[i].into(), m_hash[i]);
201 }
202 let x = SepticExtension::<AB::Expr>::from_base_fn(|i| cols.x_coordinate[i].into());
203 let y = SepticExtension::<AB::Expr>::from_base_fn(|i| cols.y_coordinate[i].into());
204
205 let y2 = y.square();
207 let x3_2x_26z5 = SepticCurve::<AB::Expr>::curve_formula(x);
208 builder.assert_septic_ext_eq(y2, x3_2x_26z5);
209
210 let mut y6_value = AB::Expr::zero();
212 for i in 0..3 {
213 y6_value =
214 y6_value + cols.y6_byte_decomp[i] * AB::Expr::from_canonical_u32(1 << (8 * i));
215 builder.send_byte(
216 AB::Expr::from_canonical_u32(ByteOpcode::U8Range as u32),
217 AB::Expr::zero(),
218 AB::Expr::zero(),
219 cols.y6_byte_decomp[i].into(),
220 is_real.into(),
221 );
222 }
223 y6_value = y6_value + cols.y6_byte_decomp[3] * AB::Expr::from_canonical_u32(1 << 24);
224 builder.send_byte(
225 AB::Expr::from_canonical_u32(ByteOpcode::LTU as u32),
226 AB::Expr::one(),
227 cols.y6_byte_decomp[3].into(),
228 AB::Expr::from_canonical_u8(63),
229 is_real.into(),
230 );
231
232 builder.when(is_receive).assert_eq(y.0[6].clone(), AB::Expr::one() + y6_value.clone());
236 builder.when(is_send).assert_zero(y.0[6].clone() + AB::Expr::one() + y6_value.clone());
237 }
238}