1use std::array;
11
12use anyhow::Result;
13use binius_core::{oracle::OracleId, transparent::constant::Constant};
14use binius_field::{
15 linear_transformation::Transformation, make_aes_to_binary_packed_transformer,
16 packed::get_packed_slice, BinaryField1b, BinaryField32b, ExtensionField, Field,
17 PackedAESBinaryField8x32b, PackedBinaryField8x32b, PackedExtension, PackedField, TowerField,
18};
19use binius_hash::{Vision32MDSTransform, INV_PACKED_TRANS_AES};
20use binius_macros::arith_expr;
21use binius_math::ArithExpr;
22use bytemuck::must_cast_slice;
23
24use crate::builder::{types::F, ConstraintSystemBuilder};
25
26pub fn vision_permutation(
27 builder: &mut ConstraintSystemBuilder,
28 log_size: usize,
29 p_in: [OracleId; STATE_SIZE],
30) -> Result<[OracleId; STATE_SIZE]> {
31 type B32 = BinaryField32b;
33
34 let round_0_input = builder.add_committed_multiple::<STATE_SIZE>(
35 "round_0_input",
36 log_size,
37 BinaryField32b::TOWER_LEVEL,
38 );
39
40 if let Some(witness) = builder.witness() {
41 let perm_in_data_owned: [_; STATE_SIZE] =
42 array_util::try_from_fn(|i| witness.get::<B32>(p_in[i]))?;
43 let perm_in_data: [_; STATE_SIZE] = perm_in_data_owned.map(|elem| elem.as_slice::<B32>());
44 let mut round_0_input_data: [_; STATE_SIZE] =
45 round_0_input.map(|id| witness.new_column::<B32>(id));
46 let round_0_input_slice = round_0_input_data
47 .each_mut()
48 .map(|elem| elem.as_mut_slice::<B32>());
49
50 for s in 0..STATE_SIZE {
51 for z in 0..1 << log_size {
52 round_0_input_slice[s][z] = perm_in_data[s][z] + B32::new(VISION_ROUND_0[s]);
53 }
54 }
55 }
56
57 for s in 0..STATE_SIZE {
58 builder.assert_zero(
59 format!("vision_round_begin_{s}"),
60 [p_in[s], round_0_input[s]],
61 vision_round_begin_expr(s).convert_field(),
62 );
63 }
64
65 let perm_out = (0..N_ROUNDS).try_fold(round_0_input, |state, round_i| {
66 vision_round(builder, log_size, round_i, state)
67 })?;
68
69 #[cfg(debug_assertions)]
70 if let Some(witness) = builder.witness() {
71 use binius_hash::{permutation::Permutation, Vision32bPermutation};
72
73 let vision_perm = Vision32bPermutation::default();
74 let p_in_data: [_; STATE_SIZE] =
75 array_util::try_from_fn(|i| witness.get::<B32>(p_in[i])).unwrap();
76 let p_in_slice: [_; STATE_SIZE] = p_in_data.map(|elem| elem.as_slice::<B32>());
77 let p_out_data: [_; STATE_SIZE] =
78 array_util::try_from_fn(|i| witness.get::<B32>(perm_out[i])).unwrap();
79 let p_out_slice: [_; STATE_SIZE] = p_out_data.map(|elem| elem.as_slice::<B32>());
80 for z in 0..1 << log_size {
81 let mut in_out: [_; 3] = array::from_fn(|i| {
82 PackedAESBinaryField8x32b::from_fn(|j| p_in_slice[i * 8 + j][z].into())
83 });
84 let expected_out: [B32; STATE_SIZE] = array::from_fn(|s| p_out_slice[s][z]);
85
86 vision_perm.permute_mut(&mut in_out);
87
88 for (out, expected) in
89 PackedAESBinaryField8x32b::iter_slice(&in_out).zip(expected_out.iter())
90 {
91 assert_eq!(out, binius_field::AESTowerField32b::from(*expected));
92 }
93 }
94 }
95
96 Ok(perm_out)
97}
98
99const N_ROUNDS: usize = 8;
100const STATE_SIZE: usize = 24;
101
102#[rustfmt::skip]
103const VISION_RC_EVEN: [[u32; 8]; STATE_SIZE] = [
104 [0x73fa03e1, 0x8bd2f341, 0x89841f23, 0xd6561783, 0x4e28a23c, 0x52538d7d, 0xd1504060, 0x00d80bd4],
105 [0x2551a651, 0x59dc2758, 0x8bd0c3e1, 0x88153c99, 0xdbe6f0db, 0xdd441420, 0x005d8a96, 0x3d8b3d56],
106 [0x0541031f, 0x5146c720, 0xde2dd62b, 0x1a04e141, 0x9cf4faeb, 0x38a2e2d5, 0x058e317a, 0xcc18a7a9],
107 [0xec1d59dc, 0x9df43021, 0x37799416, 0x62631076, 0x2fde2616, 0xccd05f31, 0x30d9d3c6, 0x0105e9bb],
108 [0x780f0b43, 0x0d1c49ea, 0x558834c7, 0xb20b52a2, 0x22dedea1, 0x2a49f3a6, 0xa585af56, 0x71f0e736],
109 [0x04843f97, 0x81d4b0a5, 0x939df560, 0x1df18264, 0x08ef118e, 0xe533cc9b, 0x084c5111, 0x4cc71fa4],
110 [0xd379e20b, 0xdbfae4d1, 0xb1a9f457, 0x05176f17, 0xd7f16ae2, 0xa18de92e, 0x498da85e, 0x1a2ec96b],
111 [0xbe4d1f58, 0xc3153118, 0xcb24dadb, 0x505b2752, 0xa13b30a8, 0x495f684a, 0x0149987d, 0xe1b8b093],
112 [0xe4c2f8bb, 0x8a3aec81, 0x4f702a2a, 0x914a71aa, 0x2ceb58c1, 0x0028e3ae, 0xe130153b, 0x329232ab],
113 [0xf29aee17, 0xeacd8854, 0x65ad5822, 0x1b6cf96d, 0xca587d86, 0xd4072861, 0x817cc725, 0xb4285526],
114 [0x228e51f2, 0xdd4b2576, 0x7ecf577d, 0x5a8b3b59, 0xf6d54fcd, 0x370fd7a3, 0x75f726b1, 0x02326fe9],
115 [0x840ee72b, 0x7dd5cee9, 0x728b4092, 0x3ab885cc, 0x9cd9f3f5, 0x728224bc, 0x23941339, 0xe79accab],
116 [0x0cb3b70e, 0x5e9e77b7, 0x89e4fa7d, 0xed662f24, 0x9b0f94a2, 0xa8b6b3d7, 0x1f26e9dd, 0xd893b618],
117 [0xbacc914a, 0x6b6efd8d, 0x10cd7556, 0xa859f626, 0xdede0863, 0xdada7046, 0xdb013723, 0x9bd74bd5],
118 [0x490bfa7e, 0xf11db400, 0x1de77ab7, 0xd91136bb, 0xa608eb2d, 0xea9e71df, 0x81f36069, 0x2062577c],
119 [0xc2c3018e, 0x0e6258b7, 0x2374c530, 0x6da2d95b, 0x4d3c4469, 0x914f7d53, 0xe4167ba1, 0x94f82da9],
120 [0xf6d13bd2, 0x37b3b6e3, 0x95b289d4, 0x043fd679, 0x53784235, 0x9b796ac9, 0x50d59f82, 0xb551d97a],
121 [0x6a4d1fe1, 0xed884c61, 0xa6ad3862, 0xb9e685e8, 0x4cf6aa1e, 0xe7f61a69, 0xbf011350, 0x862483f0],
122 [0x4c2bc742, 0xb948717c, 0xc6b1a233, 0xdf796fa5, 0xcb6ec0d5, 0x67a68f71, 0x3ae71f42, 0x5f8e4e3e],
123 [0x4508cb46, 0x3d7554cf, 0xac501639, 0x53fc28a3, 0xf334b49e, 0x7eb15ce6, 0x9966d041, 0x098d5e44],
124 [0xed63a2f1, 0x42419311, 0x3f6072a3, 0x0c15dc77, 0xe5f7a67a, 0xeb9af9e1, 0xdbe09577, 0xbe326102],
125 [0x1802f859, 0x422d11d3, 0xf8ae7cc4, 0x079255d2, 0x989658a2, 0xa75f54b1, 0xa830b8f0, 0x4f5f050e],
126 [0xa00483b5, 0x5392b2e7, 0x622f4cf3, 0x3373a2a0, 0xa1a672ca, 0x59210427, 0x0c018c2d, 0x1bd571d5],
127 [0x56e12e78, 0x79c1591d, 0xf7ccf75b, 0xfc6b012e, 0x6fb7eced, 0x75093378, 0x08beab4f, 0xcdd8e583],
128];
129
130#[rustfmt::skip]
131const VISION_RC_ODD: [[u32; 8]; STATE_SIZE] = [
132 [0xbace7a4a, 0x27df48ae, 0xaedf6aac, 0xb3359ff0, 0x2bbdf7b8, 0x27866fea, 0x20898252, 0x1b525e1b],
133 [0xc3a71400, 0x948bc10e, 0xd64356b2, 0xa471acdc, 0xa8626256, 0x3bd84dca, 0xac8aa337, 0x1cccb851],
134 [0x5a29b316, 0xcb079dc1, 0x1cbba169, 0x6ad3e18a, 0xd95bf688, 0x681d1d3a, 0x5c5bbcad, 0x45b3c777],
135 [0xeedc8d26, 0xed183a37, 0x688602ae, 0x4f012f65, 0x43245a87, 0xe7fb7496, 0x2fa58f41, 0x63cc9153],
136 [0x51c14d7e, 0x81dcc076, 0x6231b358, 0xebd4392f, 0xc14af030, 0x86fd9bf8, 0xf2446068, 0xdfa0fd4a],
137 [0x2add9be8, 0x24cb0490, 0x1fba8b86, 0x25d3af23, 0x28e5933a, 0xc1f28786, 0xfff46a79, 0x0cf20c06],
138 [0xfec386f3, 0x52d69fb8, 0xf7b83f1c, 0x7a68469c, 0x3aeb3e0d, 0xb3f17a06, 0x0b1980d8, 0x72fdd2f3],
139 [0x630765dc, 0x8b576666, 0x465c4050, 0xd479ea57, 0x169f7dea, 0x60c43dbe, 0x01b14c53, 0xf9b6f564],
140 [0xaef6c21b, 0x7499fe4d, 0x4403e74c, 0xb55b6450, 0x4cd4d1e4, 0x16fee1be, 0x4e432072, 0x9552a62b],
141 [0x8c98fc1a, 0x8f879e34, 0x5f51c2f3, 0x86ef0a15, 0x8db556b5, 0xa8407554, 0xfc610a31, 0x1e848099],
142 [0x3f9c4f9d, 0xcb11780a, 0x1b114a4d, 0xeefd412f, 0xdd1a49ea, 0xca909e3b, 0x80ba5531, 0x3ba1a5a6],
143 [0x399e7231, 0x5e876b29, 0x8f32bf48, 0xc8e98f30, 0xe64eff5d, 0xb1fc461c, 0xc14507a5, 0x17ff06e0],
144 [0xba238b04, 0xb72d96ab, 0x87990cfc, 0x61e0c12d, 0x8bd56648, 0xd84d663e, 0x2433c5d2, 0x8cae82ed],
145 [0x787d67ec, 0xac28e621, 0x71b55cb1, 0x36c4680c, 0x2c3422be, 0x2e7d669b, 0x8a461cf3, 0xb5b29fbc],
146 [0x313ad8af, 0x18aeca7e, 0x73083164, 0xe818ab96, 0x5cffb53f, 0x5b5b5a56, 0x187849cd, 0x9322d5a6],
147 [0xdd622ac3, 0xf3d30baf, 0x2fbd58ae, 0xfcb765f2, 0x6b7aaa6e, 0x6c53d090, 0x3d4f51e8, 0x77f40c4c],
148 [0xe0a8d9b8, 0xc7fca53f, 0x59bbcbbf, 0xcbb47fea, 0xc2a8d1af, 0x236707a6, 0x3d9cd125, 0x0843ce60],
149 [0xaa0e6306, 0xf7b3281a, 0xb0dc1eba, 0xc9e202a8, 0x7e79bed4, 0x7f1f4e97, 0xe15e09ca, 0x86ddb97f],
150 [0x29864574, 0xdaf5559f, 0xf2f169ff, 0xc762caec, 0xd0b08e51, 0xe95b23f3, 0x8c6287c6, 0xe5a12a04],
151 [0x67ee41da, 0x27aca0b3, 0x54cc93e8, 0x366f08fd, 0x1861ba54, 0x8cd1e3dd, 0xfa0ec2f4, 0x9bd65cd6],
152 [0x5502278d, 0x9515d3ee, 0x975cfc83, 0x5e2f3a19, 0xb7d3c6b4, 0x928f3212, 0x65435f29, 0x1b16bea6],
153 [0xa92e20b1, 0xa39fd2e1, 0xbefc67cf, 0x242c8397, 0x6a9bd7ca, 0x9c7c1c20, 0xd33a4f3d, 0xf4066cee],
154 [0x0fdc5328, 0xf61b52c2, 0xb841429b, 0x638a0042, 0x129d3aa5, 0x00eeebe3, 0xd61bb963, 0xdcb3c788],
155 [0x74dbee7a, 0x83ec5a0f, 0xff127d64, 0x63f1c9c5, 0x809e9413, 0xc0572f52, 0x991005f9, 0x499b6483],
156];
157
158#[rustfmt::skip]
159const VISION_ROUND_0: [u32; STATE_SIZE] = [0x545e66a7, 0x073fdd58, 0x84362677, 0x95fe8565, 0x06269cd8, 0x9c17909e, 0xf1f0adee, 0x2694c698, 0x94b2788f, 0x5eac14ad, 0x21677a78, 0x5755730b, 0x37cef9cf, 0x2fb31ffe, 0xfc0082ec, 0x609c12f0, 0x102769ee, 0x4732860d, 0xf97935e0, 0x36e77c02, 0xba9e70df, 0x67b701d7, 0x829d77a4, 0xf6ec454d];
160
161const SBOX_FWD_TRANS: [BinaryField32b; 3] = [
162 BinaryField32b::new(0xdb43e603),
163 BinaryField32b::new(0x391c8e32),
164 BinaryField32b::new(0x9fd55d88),
165];
166
167const SBOX_FWD_CONST: BinaryField32b = BinaryField32b::new(0x7cf0bc6c);
168const SBOX_INV_CONST: BinaryField32b = BinaryField32b::new(0x9fa712f2);
169
170#[rustfmt::skip]
171const MDS_TRANS: [[u8; STATE_SIZE]; STATE_SIZE] = [
172 [0xad, 0x3b, 0xd4, 0x25, 0xab, 0x37, 0xd7, 0x2d, 0x9a, 0x4d, 0x6a, 0xd8, 0x90, 0x44, 0x6b, 0xdb, 0x06, 0x0f, 0x0e, 0x04, 0x0d, 0x0c, 0x0a, 0x09],
173 [0x3b, 0xad, 0x25, 0xd4, 0x37, 0xab, 0x2d, 0xd7, 0x4d, 0x9a, 0xd8, 0x6a, 0x44, 0x90, 0xdb, 0x6b, 0x0f, 0x06, 0x04, 0x0e, 0x0c, 0x0d, 0x09, 0x0a],
174 [0xd4, 0x25, 0xad, 0x3b, 0xd7, 0x2d, 0xab, 0x37, 0x6a, 0xd8, 0x9a, 0x4d, 0x6b, 0xdb, 0x90, 0x44, 0x0e, 0x04, 0x06, 0x0f, 0x0a, 0x09, 0x0d, 0x0c],
175 [0x25, 0xd4, 0x3b, 0xad, 0x2d, 0xd7, 0x37, 0xab, 0xd8, 0x6a, 0x4d, 0x9a, 0xdb, 0x6b, 0x44, 0x90, 0x04, 0x0e, 0x0f, 0x06, 0x09, 0x0a, 0x0c, 0x0d],
176 [0xab, 0x37, 0xd7, 0x2d, 0xad, 0x3b, 0xd4, 0x25, 0x90, 0x44, 0x6b, 0xdb, 0x9a, 0x4d, 0x6a, 0xd8, 0x0d, 0x0c, 0x0a, 0x09, 0x06, 0x0f, 0x0e, 0x04],
177 [0x37, 0xab, 0x2d, 0xd7, 0x3b, 0xad, 0x25, 0xd4, 0x44, 0x90, 0xdb, 0x6b, 0x4d, 0x9a, 0xd8, 0x6a, 0x0c, 0x0d, 0x09, 0x0a, 0x0f, 0x06, 0x04, 0x0e],
178 [0xd7, 0x2d, 0xab, 0x37, 0xd4, 0x25, 0xad, 0x3b, 0x6b, 0xdb, 0x90, 0x44, 0x6a, 0xd8, 0x9a, 0x4d, 0x0a, 0x09, 0x0d, 0x0c, 0x0e, 0x04, 0x06, 0x0f],
179 [0x2d, 0xd7, 0x37, 0xab, 0x25, 0xd4, 0x3b, 0xad, 0xdb, 0x6b, 0x44, 0x90, 0xd8, 0x6a, 0x4d, 0x9a, 0x09, 0x0a, 0x0c, 0x0d, 0x04, 0x0e, 0x0f, 0x06],
180 [0xa9, 0x0f, 0x7d, 0x24, 0x23, 0x14, 0x45, 0xed, 0x54, 0xdf, 0x62, 0xc0, 0x67, 0xf8, 0x22, 0xf7, 0xd5, 0x47, 0x06, 0xf2, 0x93, 0x83, 0x8b, 0xff],
181 [0x0f, 0xa9, 0x24, 0x7d, 0x14, 0x23, 0xed, 0x45, 0xdf, 0x54, 0xc0, 0x62, 0xf8, 0x67, 0xf7, 0x22, 0x47, 0xd5, 0xf2, 0x06, 0x83, 0x93, 0xff, 0x8b],
182 [0x7d, 0x24, 0xa9, 0x0f, 0x45, 0xed, 0x23, 0x14, 0x62, 0xc0, 0x54, 0xdf, 0x22, 0xf7, 0x67, 0xf8, 0x06, 0xf2, 0xd5, 0x47, 0x8b, 0xff, 0x93, 0x83],
183 [0x24, 0x7d, 0x0f, 0xa9, 0xed, 0x45, 0x14, 0x23, 0xc0, 0x62, 0xdf, 0x54, 0xf7, 0x22, 0xf8, 0x67, 0xf2, 0x06, 0x47, 0xd5, 0xff, 0x8b, 0x83, 0x93],
184 [0x23, 0x14, 0x45, 0xed, 0xa9, 0x0f, 0x7d, 0x24, 0x67, 0xf8, 0x22, 0xf7, 0x54, 0xdf, 0x62, 0xc0, 0x93, 0x83, 0x8b, 0xff, 0xd5, 0x47, 0x06, 0xf2],
185 [0x14, 0x23, 0xed, 0x45, 0x0f, 0xa9, 0x24, 0x7d, 0xf8, 0x67, 0xf7, 0x22, 0xdf, 0x54, 0xc0, 0x62, 0x83, 0x93, 0xff, 0x8b, 0x47, 0xd5, 0xf2, 0x06],
186 [0x45, 0xed, 0x23, 0x14, 0x7d, 0x24, 0xa9, 0x0f, 0x22, 0xf7, 0x67, 0xf8, 0x62, 0xc0, 0x54, 0xdf, 0x8b, 0xff, 0x93, 0x83, 0x06, 0xf2, 0xd5, 0x47],
187 [0xed, 0x45, 0x14, 0x23, 0x24, 0x7d, 0x0f, 0xa9, 0xf7, 0x22, 0xf8, 0x67, 0xc0, 0x62, 0xdf, 0x54, 0xff, 0x8b, 0x83, 0x93, 0xf2, 0x06, 0x47, 0xd5],
188 [0xaf, 0x0f, 0x78, 0x2c, 0x2b, 0x10, 0x4c, 0xe2, 0x59, 0xdc, 0x63, 0xc7, 0x66, 0xf3, 0x2a, 0xfc, 0x99, 0x8d, 0x85, 0xf4, 0xd6, 0x4e, 0x06, 0xf9],
189 [0x0f, 0xaf, 0x2c, 0x78, 0x10, 0x2b, 0xe2, 0x4c, 0xdc, 0x59, 0xc7, 0x63, 0xf3, 0x66, 0xfc, 0x2a, 0x8d, 0x99, 0xf4, 0x85, 0x4e, 0xd6, 0xf9, 0x06],
190 [0x78, 0x2c, 0xaf, 0x0f, 0x4c, 0xe2, 0x2b, 0x10, 0x63, 0xc7, 0x59, 0xdc, 0x2a, 0xfc, 0x66, 0xf3, 0x85, 0xf4, 0x99, 0x8d, 0x06, 0xf9, 0xd6, 0x4e],
191 [0x2c, 0x78, 0x0f, 0xaf, 0xe2, 0x4c, 0x10, 0x2b, 0xc7, 0x63, 0xdc, 0x59, 0xfc, 0x2a, 0xf3, 0x66, 0xf4, 0x85, 0x8d, 0x99, 0xf9, 0x06, 0x4e, 0xd6],
192 [0x2b, 0x10, 0x4c, 0xe2, 0xaf, 0x0f, 0x78, 0x2c, 0x66, 0xf3, 0x2a, 0xfc, 0x59, 0xdc, 0x63, 0xc7, 0xd6, 0x4e, 0x06, 0xf9, 0x99, 0x8d, 0x85, 0xf4],
193 [0x10, 0x2b, 0xe2, 0x4c, 0x0f, 0xaf, 0x2c, 0x78, 0xf3, 0x66, 0xfc, 0x2a, 0xdc, 0x59, 0xc7, 0x63, 0x4e, 0xd6, 0xf9, 0x06, 0x8d, 0x99, 0xf4, 0x85],
194 [0x4c, 0xe2, 0x2b, 0x10, 0x78, 0x2c, 0xaf, 0x0f, 0x2a, 0xfc, 0x66, 0xf3, 0x63, 0xc7, 0x59, 0xdc, 0x06, 0xf9, 0xd6, 0x4e, 0x85, 0xf4, 0x99, 0x8d],
195 [0xe2, 0x4c, 0x10, 0x2b, 0x2c, 0x78, 0x0f, 0xaf, 0xfc, 0x2a, 0xf3, 0x66, 0xc7, 0x63, 0xdc, 0x59, 0xf9, 0x06, 0x4e, 0xd6, 0xf4, 0x85, 0x8d, 0x99],
196];
197
198fn vision_round_begin_expr(state_idx: usize) -> ArithExpr<BinaryField32b> {
199 assert!(state_idx < STATE_SIZE);
200 arith_expr!(BinaryField32b[x, y] = x + y + ArithExpr::Const(BinaryField32b::new(VISION_ROUND_0[state_idx])))
201}
202
203fn s_box_linearized_eval_expr() -> ArithExpr<BinaryField32b> {
204 let input = ArithExpr::Var(0);
205 let output = ArithExpr::Var(1);
206 let input_pow2 = input.clone().pow(2);
208 let input_pow4 = input_pow2.clone().pow(2);
209
210 let result = ArithExpr::Const(SBOX_FWD_CONST)
211 + input * ArithExpr::Const(SBOX_FWD_TRANS[0])
212 + input_pow2 * ArithExpr::Const(SBOX_FWD_TRANS[1])
213 + input_pow4 * ArithExpr::Const(SBOX_FWD_TRANS[2]);
214
215 result - output
216}
217
218fn inv_constraint_expr<F: TowerField>() -> Result<ArithExpr<F>> {
219 let x = ArithExpr::Var(0);
220 let inv = ArithExpr::Var(1);
221
222 let non_zero_case = x.clone() * inv.clone() - ArithExpr::one();
224
225 let beta = <F as ExtensionField<BinaryField1b>>::basis_checked(1 << 5)?;
228 let zero_case = x + inv * ArithExpr::Const(beta);
229
230 Ok(non_zero_case * zero_case)
232}
233
234fn vision_round(
235 builder: &mut ConstraintSystemBuilder,
236 log_size: usize,
237 round_i: usize,
238 perm_in: [OracleId; STATE_SIZE],
239) -> Result<[OracleId; STATE_SIZE]>
240where {
241 builder.push_namespace(format!("round[{round_i}]"));
242 let inv_0 = builder.add_committed_multiple::<STATE_SIZE>(
243 "inv_evens",
244 log_size,
245 BinaryField32b::TOWER_LEVEL,
246 );
247 let s_box_out_0 = builder.add_committed_multiple::<STATE_SIZE>(
248 "sbox_out_evens",
249 log_size,
250 BinaryField32b::TOWER_LEVEL,
251 );
252 let inv_1 = builder.add_committed_multiple::<STATE_SIZE>(
253 "inv_odds",
254 log_size,
255 BinaryField32b::TOWER_LEVEL,
256 );
257 let s_box_out_1: [_; STATE_SIZE] =
258 builder.add_committed_multiple("sbox_out_odds", log_size, BinaryField32b::TOWER_LEVEL);
259
260 let even_round_consts: [OracleId; STATE_SIZE] = array::from_fn(|i| {
261 builder
262 .add_transparent(
263 format!("even_round_consts_{}", i),
264 Constant::new(log_size, BinaryField32b::new(VISION_RC_EVEN[i][round_i])),
265 )
266 .unwrap()
267 });
268 let odd_round_consts: [OracleId; STATE_SIZE] = array::from_fn(|i| {
269 builder
270 .add_transparent(
271 format!("odd_round_consts_{}", i),
272 Constant::new(log_size, BinaryField32b::new(VISION_RC_ODD[i][round_i])),
273 )
274 .unwrap()
275 });
276
277 let mds_out_0: [_; STATE_SIZE] = array::from_fn(|row| {
278 builder
279 .add_linear_combination(
280 format!("mds_out_evens_{}", row),
281 log_size,
282 MDS_TRANS[row]
283 .iter()
284 .enumerate()
285 .map(|(i, &elem)| (s_box_out_0[i], F::from(BinaryField32b::new(elem as u32)))),
286 )
287 .unwrap()
288 });
289 let mds_out_1: [_; STATE_SIZE] = array::from_fn(|row| {
290 builder
291 .add_linear_combination(
292 format!("mds_out_odds_{}", row),
293 log_size,
294 MDS_TRANS[row]
295 .iter()
296 .enumerate()
297 .map(|(i, &elem)| (s_box_out_1[i], F::from(BinaryField32b::new(elem as u32)))),
298 )
299 .unwrap()
300 });
301 let round_out_0: [_; STATE_SIZE] = array::from_fn(|row| {
302 builder
303 .add_linear_combination(
304 format!("round_out_evens_{}", row),
305 log_size,
306 [
307 (mds_out_0[row], Field::ONE),
308 (even_round_consts[row], Field::ONE),
309 ],
310 )
311 .unwrap()
312 });
313
314 let perm_out = array::from_fn(|row| {
315 builder
316 .add_linear_combination(
317 format!("round_out_odd_{}", row),
318 log_size,
319 [
320 (mds_out_1[row], Field::ONE),
321 (odd_round_consts[row], Field::ONE),
322 ],
323 )
324 .unwrap()
325 });
326
327 builder.pop_namespace();
328
329 let mds_trans = Vision32MDSTransform::default();
330 let aes_to_bin_packed =
331 make_aes_to_binary_packed_transformer::<PackedAESBinaryField8x32b, PackedBinaryField8x32b>(
332 );
333 let inv_const_packed = PackedBinaryField8x32b::broadcast(SBOX_INV_CONST);
334
335 type B32 = BinaryField32b;
336
337 if let Some(witness) = builder.witness() {
339 let perm_in_data_owned: [_; STATE_SIZE] =
340 array_util::try_from_fn(|i| witness.get::<B32>(perm_in[i]))?;
341 let perm_in_data: [_; STATE_SIZE] = perm_in_data_owned.map(|elem| elem.as_slice::<B32>());
342
343 let mut even_round_consts = even_round_consts.map(|id| witness.new_column::<B32>(id));
344 let mut inv_0 = inv_0.map(|id| witness.new_column::<B32>(id));
345 let mut s_box_out_0 = s_box_out_0.map(|id| witness.new_column::<B32>(id));
346 let mut mds_out_0 = mds_out_0.map(|id| witness.new_column::<B32>(id));
347 let mut round_out_0 = round_out_0.map(|id| witness.new_column::<B32>(id));
348 let mut odd_round_consts = odd_round_consts.map(|id| witness.new_column::<B32>(id));
349 let mut inv_1 = inv_1.map(|id| witness.new_column::<B32>(id));
350 let mut s_box_out_1 = s_box_out_1.map(|id| witness.new_column::<B32>(id));
351 let mut mds_out_1 = mds_out_1.map(|id| witness.new_column::<B32>(id));
352 let mut perm_out = perm_out.map(|id| witness.new_column::<B32>(id));
353
354 let inv_0_slice = inv_0.each_mut().map(|elem| elem.as_mut_slice());
355 let s_box_out_0_slice = s_box_out_0.each_mut().map(|elem| elem.as_mut_slice());
356 let mds_out_0_slice = mds_out_0.each_mut().map(|elem| elem.as_mut_slice());
357 let round_out_0_slice = round_out_0
358 .each_mut()
359 .map(|elem| elem.as_mut_slice::<B32>());
360 let inv_1_slice = inv_1.each_mut().map(|elem| elem.as_mut_slice());
361 let s_box_out_1_slice = s_box_out_1.each_mut().map(|elem| elem.as_mut_slice());
362 let mds_out_1_slice = mds_out_1.each_mut().map(|elem| elem.as_mut_slice());
363 let perm_out_slice = perm_out.each_mut().map(|elem| elem.as_mut_slice());
364 let even_round_consts_slice = even_round_consts
365 .each_mut()
366 .map(|elem| elem.as_mut_slice::<B32>());
367 let odd_round_consts_slice = odd_round_consts
368 .each_mut()
369 .map(|elem| elem.as_mut_slice::<B32>());
370
371 for i in 0..STATE_SIZE {
373 even_round_consts_slice[i]
374 .iter_mut()
375 .for_each(|rc| *rc = BinaryField32b::new(VISION_RC_EVEN[i][round_i]));
376 odd_round_consts_slice[i]
377 .iter_mut()
378 .for_each(|rc| *rc = BinaryField32b::new(VISION_RC_ODD[i][round_i]));
379 }
380
381 for z in 0..1 << log_size {
382 let input: [_; STATE_SIZE] =
384 array::from_fn(|row| must_cast_slice::<_, B32>(perm_in_data[row])[z]);
385 let inverse_0 = input.map(B32::invert_or_zero);
386
387 let sbox_out_packed: [PackedBinaryField8x32b; 3] = array::from_fn(|arr_idx| {
388 let inp = PackedAESBinaryField8x32b::from_fn(|pack_idx| {
389 inverse_0[pack_idx + arr_idx * 8].into()
390 });
391 Transformation::<PackedAESBinaryField8x32b, PackedBinaryField8x32b>::transform(
392 &aes_to_bin_packed,
393 &INV_PACKED_TRANS_AES.transform(&inp),
394 ) + inv_const_packed
395 });
396
397 for i in 0..STATE_SIZE {
398 let sbox_out = get_packed_slice(&sbox_out_packed, i);
399 inv_0_slice[i][z] = inverse_0[i];
400 s_box_out_0_slice[i][z] = sbox_out;
401 }
402 let mut inp_as_packed_aes: [PackedAESBinaryField8x32b; 3] = array::from_fn(|arr_idx| {
403 PackedAESBinaryField8x32b::from_fn(|pack_idx| {
404 s_box_out_0_slice[pack_idx + arr_idx * 8][z].into()
405 })
406 });
407 mds_trans
408 .transform(PackedAESBinaryField8x32b::cast_base_arr_mut(&mut inp_as_packed_aes));
409 let inp_as_packed_bin: [PackedBinaryField8x32b; 3] =
410 inp_as_packed_aes.map(|x| aes_to_bin_packed.transform(&x));
411
412 for i in 0..STATE_SIZE {
413 let mds_even_out: B32 = get_packed_slice(&inp_as_packed_bin, i);
414 let round_even_out = mds_even_out + even_round_consts_slice[i][z];
415 mds_out_0_slice[i][z] = mds_even_out;
416 round_out_0_slice[i][z] = round_even_out;
417
418 let inv_odd = round_even_out.invert_or_zero();
420 let inv_pow2_odd = inv_odd.square();
421 let inv_pow4_odd = inv_pow2_odd.square();
422 let sbox_out_odd = SBOX_FWD_CONST
423 + inv_odd * SBOX_FWD_TRANS[0]
424 + inv_pow2_odd * SBOX_FWD_TRANS[1]
425 + inv_pow4_odd * SBOX_FWD_TRANS[2];
426
427 inv_1_slice[i][z] = inv_odd;
428 s_box_out_1_slice[i][z] = sbox_out_odd;
429 }
430
431 let mut inp_as_packed_aes: [PackedAESBinaryField8x32b; 3] = array::from_fn(|arr_idx| {
432 PackedAESBinaryField8x32b::from_fn(|pack_idx| {
433 s_box_out_1_slice[pack_idx + arr_idx * 8][z].into()
434 })
435 });
436 mds_trans
437 .transform(PackedAESBinaryField8x32b::cast_base_arr_mut(&mut inp_as_packed_aes));
438 let inp_as_packed: [PackedBinaryField8x32b; 3] =
439 inp_as_packed_aes.map(|x| aes_to_bin_packed.transform(&x));
440 for i in 0..24 {
441 let mds_out_odd: B32 = get_packed_slice(&inp_as_packed, i);
442 mds_out_1_slice[i][z] = mds_out_odd;
443 let output = mds_out_odd + odd_round_consts_slice[i][z];
444 perm_out_slice[i][z] = output;
445 }
446 }
447 }
448
449 for s in 0..STATE_SIZE {
451 builder.assert_zero(format!("inv0_{s}"), [perm_in[s], inv_0[s]], inv_constraint_expr()?);
453 builder.assert_zero(
455 format!("inv1_{s}"),
456 [round_out_0[s], inv_1[s]],
457 inv_constraint_expr()?,
458 );
459
460 builder.assert_zero(
462 format!("sbox_linearized0_{s}"),
463 [s_box_out_0[s], inv_0[s]],
464 s_box_linearized_eval_expr().convert_field(),
465 );
466 builder.assert_zero(
467 format!("sbox_linearized1_{s}"),
468 [inv_1[s], s_box_out_1[s]],
469 s_box_linearized_eval_expr().convert_field(),
470 );
471 }
472
473 Ok(perm_out)
474}
475
476#[cfg(test)]
477mod tests {
478 use binius_core::oracle::OracleId;
479 use binius_field::BinaryField32b;
480
481 use super::vision_permutation;
482 use crate::{builder::test_utils::test_circuit, unconstrained::unconstrained};
483
484 #[test]
485 fn test_vision32b() {
486 test_circuit(|builder| {
487 let log_size = 8;
488 let state_in: [OracleId; 24] = std::array::from_fn(|i| {
489 unconstrained::<BinaryField32b>(builder, format!("p_in[{i}]"), log_size).unwrap()
490 });
491 let _state_out = vision_permutation(builder, log_size, state_in).unwrap();
492 Ok(vec![])
493 })
494 .unwrap();
495 }
496}