binius_circuits/lasso/lookups/
u8_arithmetic.rs1use anyhow::Result;
4use binius_core::oracle::OracleId;
5use binius_field::{BinaryField32b, TowerField};
6
7use crate::builder::ConstraintSystemBuilder;
8
9type B32 = BinaryField32b;
10const T_LOG_SIZE_MUL: usize = 16;
11const T_LOG_SIZE_ADD: usize = 17;
12const T_LOG_SIZE_DCI: usize = 10;
13
14pub fn mul_lookup(
15 builder: &mut ConstraintSystemBuilder,
16 name: impl ToString + Clone,
17) -> Result<OracleId, anyhow::Error> {
18 builder.push_namespace(name);
19
20 let lookup_t = builder.add_committed("lookup_t", T_LOG_SIZE_MUL, B32::TOWER_LEVEL);
21
22 if let Some(witness) = builder.witness() {
23 let mut lookup_t = witness.new_column::<B32>(lookup_t);
24
25 let lookup_t_u32 = lookup_t.as_mut_slice::<u32>();
26
27 for (i, lookup_t) in lookup_t_u32.iter_mut().enumerate() {
28 let a_int = (i >> 8) & 0xff;
29 let b_int = i & 0xff;
30 let ab_product = a_int * b_int;
31 let lookup_index = a_int << 8 | b_int;
32 assert_eq!(lookup_index, i);
33 *lookup_t = (lookup_index << 16 | ab_product) as u32;
34 }
35 }
36
37 builder.pop_namespace();
38 Ok(lookup_t)
39}
40
41pub fn add_lookup(
42 builder: &mut ConstraintSystemBuilder,
43 name: impl ToString + Clone,
44) -> Result<OracleId, anyhow::Error> {
45 builder.push_namespace(name);
46
47 let lookup_t = builder.add_committed("lookup_t", T_LOG_SIZE_ADD, B32::TOWER_LEVEL);
48
49 if let Some(witness) = builder.witness() {
50 let mut lookup_t = witness.new_column::<B32>(lookup_t);
51
52 let lookup_t_u32 = lookup_t.as_mut_slice::<u32>();
53
54 for carry_in_usize in 0..(1 << 1) {
55 for x_in_usize in 0..(1 << 8) {
56 for y_in_usize in 0..(1 << 8) {
57 let lookup_index = (carry_in_usize << 16) | (x_in_usize << 8) | y_in_usize;
58 let xy_sum_with_carry_out = x_in_usize + y_in_usize + carry_in_usize;
59 let xy_sum_usize = xy_sum_with_carry_out & 0xff;
60 let carry_out_usize = xy_sum_with_carry_out >> 8;
61 let lookup_value = (carry_in_usize << 25)
62 | (carry_out_usize << 24)
63 | (x_in_usize << 16)
64 | (y_in_usize << 8)
65 | xy_sum_usize;
66 lookup_t_u32[lookup_index] = lookup_value as u32;
67 }
68 }
69 }
70 }
71
72 builder.pop_namespace();
73 Ok(lookup_t)
74}
75
76pub fn add_carryfree_lookup(
77 builder: &mut ConstraintSystemBuilder,
78 name: impl ToString + Clone,
79) -> Result<OracleId, anyhow::Error> {
80 builder.push_namespace(name);
81
82 let lookup_t = builder.add_committed("lookup_t", T_LOG_SIZE_ADD, B32::TOWER_LEVEL);
83
84 if let Some(witness) = builder.witness() {
85 let mut lookup_t = witness.new_column::<B32>(lookup_t);
86
87 let lookup_t_u32 = lookup_t.as_mut_slice::<u32>();
88
89 for carry_in_usize in 0..(1 << 1) {
90 for x_in_usize in 0..(1 << 8) {
91 for y_in_usize in 0..(1 << 8) {
92 let lookup_index = (carry_in_usize << 16) | (x_in_usize << 8) | y_in_usize;
93 let xy_sum_usize = x_in_usize + y_in_usize + carry_in_usize;
94
95 let lookup_value = if xy_sum_usize <= 0xff {
97 (carry_in_usize << 24)
98 | (x_in_usize << 16) | (y_in_usize << 8)
99 | xy_sum_usize
100 } else {
101 0
102 };
103 lookup_t_u32[lookup_index] = lookup_value as u32;
104 }
105 }
106 }
107 }
108
109 builder.pop_namespace();
110 Ok(lookup_t)
111}
112
113pub fn dci_lookup(
114 builder: &mut ConstraintSystemBuilder,
115 name: impl ToString + Clone,
116) -> Result<OracleId, anyhow::Error> {
117 builder.push_namespace(name);
118
119 let lookup_t = builder.add_committed("lookup_t", T_LOG_SIZE_DCI, B32::TOWER_LEVEL);
120
121 if let Some(witness) = builder.witness() {
122 let mut lookup_t = witness.new_column::<B32>(lookup_t);
123
124 let lookup_t_u32 = lookup_t.as_mut_slice::<u32>();
125
126 for first_carry_in_usize in 0..(1 << 1) {
127 for second_carry_in_usize in 0..(1 << 1) {
128 for x_in_usize in 0..(1 << 8) {
129 let lookup_index =
130 (first_carry_in_usize << 9) | (second_carry_in_usize << 8) | x_in_usize;
131 let sum_with_carry_out =
132 x_in_usize + first_carry_in_usize + second_carry_in_usize;
133 let sum_usize = sum_with_carry_out & 0xff;
134 let carry_out_usize = sum_with_carry_out >> 8;
135 let lookup_value = (first_carry_in_usize << 18)
136 | (second_carry_in_usize << 17)
137 | (carry_out_usize << 16)
138 | (x_in_usize << 8)
139 | sum_usize;
140 lookup_t_u32[lookup_index] = lookup_value as u32;
141 }
142 }
143 }
144 }
145
146 builder.pop_namespace();
147 Ok(lookup_t)
148}
149
150#[cfg(test)]
151mod tests {
152 use binius_field::{BinaryField1b, BinaryField32b, BinaryField8b};
153
154 use crate::{
155 builder::test_utils::test_circuit,
156 lasso::{self, batch::LookupBatch},
157 unconstrained::unconstrained,
158 };
159
160 #[test]
161 fn test_lasso_u8add_carryfree_rejects_carry() {
162 test_circuit(|builder| {
164 let log_size = 14;
165 let x_in = unconstrained::<BinaryField8b>(builder, "x", log_size)?;
166 let y_in = unconstrained::<BinaryField8b>(builder, "y", log_size)?;
167 let c_in = unconstrained::<BinaryField1b>(builder, "c", log_size)?;
168
169 let lookup_t = super::add_carryfree_lookup(builder, "add cf table")?;
170 let mut lookup_batch = LookupBatch::new([lookup_t]);
171 let _sum_and_cout = lasso::u8add_carryfree(
172 builder,
173 &mut lookup_batch,
174 "lasso_u8add",
175 x_in,
176 y_in,
177 c_in,
178 log_size,
179 )?;
180 lookup_batch.execute::<BinaryField32b>(builder)?;
181 Ok(vec![])
182 })
183 .expect_err("Rejected overflowing add");
184 }
185
186 #[test]
187 fn test_lasso_u8mul() {
188 test_circuit(|builder| {
189 let log_size = 10;
190
191 let mult_a = unconstrained::<BinaryField8b>(builder, "mult_a", log_size)?;
192 let mult_b = unconstrained::<BinaryField8b>(builder, "mult_b", log_size)?;
193
194 let mul_lookup_table = super::mul_lookup(builder, "mul table")?;
195
196 let mut lookup_batch = LookupBatch::new([mul_lookup_table]);
197
198 let _product = lasso::u8mul(
199 builder,
200 &mut lookup_batch,
201 "lasso_u8mul",
202 mult_a,
203 mult_b,
204 1 << log_size,
205 )?;
206
207 lookup_batch.execute::<BinaryField32b>(builder)?;
208 Ok(vec![])
209 })
210 .unwrap();
211 }
212
213 #[test]
214 fn test_lasso_batched_u8mul() {
215 test_circuit(|builder| {
216 let log_size = 10;
217 let mul_lookup_table = super::mul_lookup(builder, "mul table")?;
218
219 let mut lookup_batch = LookupBatch::new([mul_lookup_table]);
220
221 for _ in 0..10 {
222 let mult_a = unconstrained::<BinaryField8b>(builder, "mult_a", log_size)?;
223 let mult_b = unconstrained::<BinaryField8b>(builder, "mult_b", log_size)?;
224
225 let _product = lasso::u8mul(
226 builder,
227 &mut lookup_batch,
228 "lasso_u8mul",
229 mult_a,
230 mult_b,
231 1 << log_size,
232 )?;
233 }
234
235 lookup_batch.execute::<BinaryField32b>(builder)?;
236 Ok(vec![])
237 })
238 .unwrap();
239 }
240
241 #[test]
242 fn test_lasso_batched_u8mul_rejects() {
243 test_circuit(|builder| {
244 let log_size = 10;
245
246 let mul_lookup_table = super::add_lookup(builder, "mul table")?;
248
249 let mut lookup_batch = LookupBatch::new([mul_lookup_table]);
250
251 for _ in 0..10 {
253 let mult_a = unconstrained::<BinaryField8b>(builder, "mult_a", log_size)?;
254 let mult_b = unconstrained::<BinaryField8b>(builder, "mult_b", log_size)?;
255 let _product = lasso::u8mul(
256 builder,
257 &mut lookup_batch,
258 "lasso_u8mul",
259 mult_a,
260 mult_b,
261 1 << log_size,
262 )?;
263 }
264
265 lookup_batch.execute::<BinaryField32b>(builder)?;
266 Ok(vec![])
267 })
268 .expect_err("Channels should be unbalanced");
269 }
270}