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| {
165 let log_size = 14;
166 let x_in = unconstrained::<BinaryField8b>(builder, "x", log_size)?;
167 let y_in = unconstrained::<BinaryField8b>(builder, "y", log_size)?;
168 let c_in = unconstrained::<BinaryField1b>(builder, "c", log_size)?;
169
170 let lookup_t = super::add_carryfree_lookup(builder, "add cf table")?;
171 let mut lookup_batch = LookupBatch::new([lookup_t]);
172 let _sum_and_cout = lasso::u8add_carryfree(
173 builder,
174 &mut lookup_batch,
175 "lasso_u8add",
176 x_in,
177 y_in,
178 c_in,
179 log_size,
180 )?;
181 lookup_batch.execute::<BinaryField32b>(builder)?;
182 Ok(vec![])
183 })
184 .expect_err("Rejected overflowing add");
185 }
186
187 #[test]
188 fn test_lasso_u8mul() {
189 test_circuit(|builder| {
190 let log_size = 10;
191
192 let mult_a = unconstrained::<BinaryField8b>(builder, "mult_a", log_size)?;
193 let mult_b = unconstrained::<BinaryField8b>(builder, "mult_b", log_size)?;
194
195 let mul_lookup_table = super::mul_lookup(builder, "mul table")?;
196
197 let mut lookup_batch = LookupBatch::new([mul_lookup_table]);
198
199 let _product = lasso::u8mul(
200 builder,
201 &mut lookup_batch,
202 "lasso_u8mul",
203 mult_a,
204 mult_b,
205 1 << log_size,
206 )?;
207
208 lookup_batch.execute::<BinaryField32b>(builder)?;
209 Ok(vec![])
210 })
211 .unwrap();
212 }
213
214 #[test]
215 fn test_lasso_batched_u8mul() {
216 test_circuit(|builder| {
217 let log_size = 10;
218 let mul_lookup_table = super::mul_lookup(builder, "mul table")?;
219
220 let mut lookup_batch = LookupBatch::new([mul_lookup_table]);
221
222 for _ in 0..10 {
223 let mult_a = unconstrained::<BinaryField8b>(builder, "mult_a", log_size)?;
224 let mult_b = unconstrained::<BinaryField8b>(builder, "mult_b", log_size)?;
225
226 let _product = lasso::u8mul(
227 builder,
228 &mut lookup_batch,
229 "lasso_u8mul",
230 mult_a,
231 mult_b,
232 1 << log_size,
233 )?;
234 }
235
236 lookup_batch.execute::<BinaryField32b>(builder)?;
237 Ok(vec![])
238 })
239 .unwrap();
240 }
241
242 #[test]
243 fn test_lasso_batched_u8mul_rejects() {
244 test_circuit(|builder| {
245 let log_size = 10;
246
247 let mul_lookup_table = super::add_lookup(builder, "mul table")?;
249
250 let mut lookup_batch = LookupBatch::new([mul_lookup_table]);
251
252 for _ in 0..10 {
255 let mult_a = unconstrained::<BinaryField8b>(builder, "mult_a", log_size)?;
256 let mult_b = unconstrained::<BinaryField8b>(builder, "mult_b", log_size)?;
257 let _product = lasso::u8mul(
258 builder,
259 &mut lookup_batch,
260 "lasso_u8mul",
261 mult_a,
262 mult_b,
263 1 << log_size,
264 )?;
265 }
266
267 lookup_batch.execute::<BinaryField32b>(builder)?;
268 Ok(vec![])
269 })
270 .expect_err("Channels should be unbalanced");
271 }
272}