|
| 1 | +use binius_circuits::{builder::ConstraintSystemBuilder, unconstrained::unconstrained}; |
| 2 | +use binius_core::{constraint_system::validate::validate_witness, oracle::ProjectionVariant}; |
| 3 | +use binius_field::{arch::OptimalUnderlier, BinaryField128b, BinaryField8b}; |
| 4 | + |
| 5 | +type U = OptimalUnderlier; |
| 6 | +type F128 = BinaryField128b; |
| 7 | +type F8 = BinaryField8b; |
| 8 | + |
| 9 | +#[derive(Clone)] |
| 10 | +struct U8U128ProjectionInfo { |
| 11 | + log_size: usize, |
| 12 | + decimal: usize, |
| 13 | + binary: Vec<F128>, |
| 14 | + variant: ProjectionVariant, |
| 15 | +} |
| 16 | + |
| 17 | +// The idea behind projection is that data from a column of some given field (F8) |
| 18 | +// can be interpreted as a data of some or greater field (F128) and written to another column with equal or smaller length, |
| 19 | +// which depends on LOG_SIZE and values of projection. Also two possible variants of projections are available, which |
| 20 | +// has significant impact on input data processing. |
| 21 | +// In the following example we have input column with bytes (u8) projected to the output column with u128 values. |
| 22 | +fn projection( |
| 23 | + builder: &mut ConstraintSystemBuilder<U, F128>, |
| 24 | + projection_info: U8U128ProjectionInfo, |
| 25 | + namespace: &str, |
| 26 | +) { |
| 27 | + builder.push_namespace(format!("projection {}", namespace)); |
| 28 | + |
| 29 | + let input = |
| 30 | + unconstrained::<U, F128, F8>(builder, "in", projection_info.clone().log_size).unwrap(); |
| 31 | + |
| 32 | + let projected = builder |
| 33 | + .add_projected( |
| 34 | + "projected", |
| 35 | + input, |
| 36 | + projection_info.clone().binary, |
| 37 | + projection_info.clone().variant, |
| 38 | + ) |
| 39 | + .unwrap(); |
| 40 | + |
| 41 | + if let Some(witness) = builder.witness() { |
| 42 | + let input_values = witness.get::<F8>(input).unwrap().as_slice::<u8>(); |
| 43 | + let mut projected_witness = witness.new_column::<F128>(projected); |
| 44 | + let projected_values = projected_witness.as_mut_slice::<F128>(); |
| 45 | + |
| 46 | + assert_eq!(projected_values.len(), projection_info.expected_projection_len()); |
| 47 | + |
| 48 | + match projection_info.variant { |
| 49 | + ProjectionVariant::FirstVars => { |
| 50 | + // Quite elaborated regularity, on my opinion |
| 51 | + for idx in 0..projected_values.len() { |
| 52 | + projected_values[idx] = F128::new( |
| 53 | + input_values[(idx |
| 54 | + * 2usize.pow(projection_info.clone().binary.len() as u32)) |
| 55 | + + projection_info.clone().decimal] as u128, |
| 56 | + ); |
| 57 | + } |
| 58 | + } |
| 59 | + ProjectionVariant::LastVars => { |
| 60 | + // decimal representation of the binary values is used as a simple offset |
| 61 | + for idx in 0..projected_values.len() { |
| 62 | + projected_values[idx] = |
| 63 | + F128::new(input_values[projection_info.clone().decimal + idx] as u128); |
| 64 | + } |
| 65 | + } |
| 66 | + }; |
| 67 | + } |
| 68 | + builder.pop_namespace(); |
| 69 | +} |
| 70 | + |
| 71 | +impl U8U128ProjectionInfo { |
| 72 | + fn new( |
| 73 | + log_size: usize, |
| 74 | + decimal: usize, |
| 75 | + binary: Vec<F128>, |
| 76 | + variant: ProjectionVariant, |
| 77 | + ) -> U8U128ProjectionInfo { |
| 78 | + assert!(log_size >= binary.len()); |
| 79 | + |
| 80 | + if variant == ProjectionVariant::LastVars { |
| 81 | + // Pad with zeroes to LOG_SIZE len iterator. |
| 82 | + // In this case we interpret binary values in a reverse order, meaning that the very first |
| 83 | + // element is elder byte, so zeroes must be explicitly appended |
| 84 | + let mut binary_clone = binary.clone(); |
| 85 | + let mut zeroes = vec![F128::new(0u128); log_size - binary.len()]; |
| 86 | + binary_clone.append(&mut zeroes); |
| 87 | + |
| 88 | + let coefficients = (0..binary_clone.len()) |
| 89 | + .map(|degree| F128::new(2usize.pow(degree as u32) as u128)) |
| 90 | + .collect::<Vec<F128>>(); |
| 91 | + |
| 92 | + let value = binary_clone |
| 93 | + .iter() |
| 94 | + .zip(coefficients.iter().rev()) |
| 95 | + .fold(F128::new(0u128), |acc, (byte, coefficient)| acc + (*byte) * (*coefficient)); |
| 96 | + |
| 97 | + assert_eq!(decimal as u128, value.val()); |
| 98 | + } |
| 99 | + |
| 100 | + U8U128ProjectionInfo { |
| 101 | + log_size, |
| 102 | + decimal, |
| 103 | + binary, |
| 104 | + variant, |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + fn expected_projection_len(&self) -> usize { |
| 109 | + 2usize.pow((self.log_size - self.binary.len()) as u32) |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +fn main() { |
| 114 | + let allocator = bumpalo::Bump::new(); |
| 115 | + let mut builder = ConstraintSystemBuilder::<U, F128>::new_with_witness(&allocator); |
| 116 | + |
| 117 | + let projection_data = U8U128ProjectionInfo::new( |
| 118 | + 4usize, |
| 119 | + 9usize, |
| 120 | + vec![ |
| 121 | + F128::from(1u128), |
| 122 | + F128::from(0u128), |
| 123 | + F128::from(0u128), |
| 124 | + F128::from(1u128), |
| 125 | + ], |
| 126 | + ProjectionVariant::FirstVars, |
| 127 | + ); |
| 128 | + projection(&mut builder, projection_data, "test_1"); |
| 129 | + |
| 130 | + let projection_data = U8U128ProjectionInfo::new( |
| 131 | + 16usize, |
| 132 | + 34816usize, |
| 133 | + vec![ |
| 134 | + F128::from(1u128), |
| 135 | + F128::from(0u128), |
| 136 | + F128::from(0u128), |
| 137 | + F128::from(0u128), |
| 138 | + F128::from(1u128), |
| 139 | + ], |
| 140 | + ProjectionVariant::LastVars, |
| 141 | + ); |
| 142 | + projection(&mut builder, projection_data, "test_2"); |
| 143 | + |
| 144 | + let projection_data = U8U128ProjectionInfo::new( |
| 145 | + 4usize, |
| 146 | + 15usize, |
| 147 | + vec![ |
| 148 | + F128::from(1u128), |
| 149 | + F128::from(1u128), |
| 150 | + F128::from(1u128), |
| 151 | + F128::from(1u128), |
| 152 | + ], |
| 153 | + ProjectionVariant::LastVars, |
| 154 | + ); |
| 155 | + projection(&mut builder, projection_data, "test_3"); |
| 156 | + |
| 157 | + let projection_data = U8U128ProjectionInfo::new( |
| 158 | + 6usize, |
| 159 | + 60usize, |
| 160 | + vec![ |
| 161 | + F128::from(1u128), |
| 162 | + F128::from(1u128), |
| 163 | + F128::from(1u128), |
| 164 | + F128::from(1u128), |
| 165 | + ], |
| 166 | + ProjectionVariant::LastVars, |
| 167 | + ); |
| 168 | + /* |
| 169 | + With projection_data defined above we have 2^LOG_SIZE = 2^6 bytes in the input, |
| 170 | + the size of projection is computed as follows: 2.pow(LOG_SIZE - binary.len()) = 2.pow(6 - 4) = 4. |
| 171 | + the index of the input byte to use as projection is computed as follows (according to |
| 172 | + a LastVars projection variant regularity): |
| 173 | +
|
| 174 | + idx + decimal, e.g.: |
| 175 | +
|
| 176 | + 0 + 60 |
| 177 | + 1 + 60 |
| 178 | + 2 + 60 |
| 179 | + 3 + 60 |
| 180 | +
|
| 181 | + where idx is [0..4]. |
| 182 | +
|
| 183 | + Memory layout: |
| 184 | +
|
| 185 | + input: [a5, a2, b1, 60, 91, ed, 5e, fb, ae, 1c, b2, 14, 92, 73, 92, c8, 56, 6d, fa, de, a8, 46, 77, 48, e1, cc, 90, 75, 78, d5, 19, be, 0c, 86, 39, 28, 0c, cc, e9, 4e, 46, d9, 84, 65, 4a, a2, b4, 64, eb, 59, 7b, fd, 3f, 0e, 2d, ea, 06, 42, a9, ea, (19), (8f), (19), (52)], len: 64 |
| 186 | + output: [ |
| 187 | + BinaryField128b(0x00000000000000000000000000000019), |
| 188 | + BinaryField128b(0x0000000000000000000000000000008f), |
| 189 | + BinaryField128b(0x00000000000000000000000000000019), |
| 190 | + BinaryField128b(0x00000000000000000000000000000052) |
| 191 | + ] |
| 192 | + */ |
| 193 | + projection(&mut builder, projection_data, "test_4"); |
| 194 | + |
| 195 | + let projection_data = U8U128ProjectionInfo::new( |
| 196 | + 4usize, |
| 197 | + 15usize, |
| 198 | + vec![ |
| 199 | + F128::from(1u128), |
| 200 | + F128::from(1u128), |
| 201 | + F128::from(1u128), |
| 202 | + F128::from(1u128), |
| 203 | + ], |
| 204 | + ProjectionVariant::FirstVars, |
| 205 | + ); |
| 206 | + projection(&mut builder, projection_data, "test_5"); |
| 207 | + |
| 208 | + let projection_data = U8U128ProjectionInfo::new( |
| 209 | + 6usize, |
| 210 | + 13usize, |
| 211 | + vec![ |
| 212 | + F128::from(1u128), |
| 213 | + F128::from(0u128), |
| 214 | + F128::from(1u128), |
| 215 | + F128::from(1u128), |
| 216 | + ], |
| 217 | + ProjectionVariant::FirstVars, |
| 218 | + ); |
| 219 | + /* |
| 220 | + With projection_data defined above we have 2^LOG_SIZE = 2^6 bytes in the input, |
| 221 | + the size of projection is computed as follows: 2.pow(LOG_SIZE - binary.len()) = 2.pow(6 - 4) = 4. |
| 222 | + the index of the input byte to use as projection is computed as follows: |
| 223 | +
|
| 224 | + idx * 2usize.pow(binary.len()) + decimal, e.g.: |
| 225 | +
|
| 226 | + 0 * 2.pow(4) + 13 = 13, so input[13] |
| 227 | + 1 * 2.pow(4) + 13 = 29, so input[29] |
| 228 | + 2 * 2.pow(4) + 13 = 45, so input[45] |
| 229 | + 3 * 2.pow(4) + 13 = 61, so input[61] |
| 230 | +
|
| 231 | + where idx is [0..4] according to a FirstVars projection variant regularity. |
| 232 | +
|
| 233 | + Memory layout: |
| 234 | +
|
| 235 | + input: [18, d8, 58, d3, 24, f1, 8b, ec, 74, 1c, ab, 78, 13, (3e), 57, d7, 36, 15, 54, 50, 9a, cb, 98, 90, 58, cb, 79, 05, 83, (72), ea, 4d, f6, 3d, f3, 2f, af, e3, 32, 11, c9, 97, fb, ba, 24, (36), e9, 38, 7e, c7, a9, 68, bf, 31, 51, cf, 7b, 12, 20, 53, d8, (df), d7, cc], len: 64 |
| 236 | +
|
| 237 | + output: BinaryField128b(0x0000000000000000000000000000003e) |
| 238 | + output: BinaryField128b(0x00000000000000000000000000000072) |
| 239 | + output: BinaryField128b(0x00000000000000000000000000000036) |
| 240 | + output: BinaryField128b(0x000000000000000000000000000000df) |
| 241 | + */ |
| 242 | + projection(&mut builder, projection_data, "test_6"); |
| 243 | + |
| 244 | + let witness = builder.take_witness().unwrap(); |
| 245 | + let cs = builder.build().unwrap(); |
| 246 | + |
| 247 | + validate_witness(&cs, &[], &witness).unwrap(); |
| 248 | +} |
0 commit comments