1use crate::predicate::{AffineRow, Unknown, WireRole};
11use alloc::vec;
12use alloc::vec::Vec;
13use core::marker::PhantomData;
14use hekate_math::{Block128, Flat, HardwareField, TowerField};
15
16pub const BITS: usize = 128;
17
18pub const CHAIN_ROWS: usize = 2 * (BITS - 1);
19
20const DUAL_KERNEL: [u8; 8] = [0x29, 0xb0, 0x58, 0x05, 0xa6, 0x53, 0xa4, 0x52];
21
22pub(crate) struct LinearMap<I, O> {
26 tables: Vec<[Flat<O>; 256]>,
27 input: PhantomData<I>,
28}
29
30impl<I, O> LinearMap<I, O>
31where
32 I: HardwareField + Into<Block128>,
33 O: HardwareField,
34{
35 pub(crate) fn new(image: impl Fn(Flat<I>) -> Flat<O>) -> Self {
36 let mut tables = vec![[Flat::from_raw(O::ZERO); 256]; BITS / 8];
37 for (w, table) in tables.iter_mut().enumerate() {
38 let images: [Flat<O>; 8] =
39 core::array::from_fn(|k| image(Flat::from_raw(I::from(1u128 << (8 * w + k)))));
40
41 for v in 1usize..256 {
42 table[v] = table[v & (v - 1)] + images[v.trailing_zeros() as usize];
43 }
44 }
45
46 Self {
47 tables,
48 input: PhantomData,
49 }
50 }
51
52 pub(crate) fn apply(&self, x: Flat<I>) -> Flat<O> {
53 let raw: Block128 = x.into_raw().into();
54
55 let mut acc = Flat::from_raw(O::ZERO);
56 for (table, byte) in self.tables.iter().zip(raw.0.to_le_bytes()) {
57 acc += table[byte as usize];
58 }
59
60 acc
61 }
62}
63
64pub struct RingGadget<F> {
69 pub ring: Vec<(u32, Flat<F>)>,
70 pub mu: Vec<Flat<F>>,
71 pub first_wire: u32,
72}
73
74impl<F: HardwareField> RingGadget<F> {
75 pub fn new(ring: Vec<(u32, Flat<F>)>, mu: Vec<Flat<F>>, first_wire: u32) -> Self {
76 Self {
77 ring,
78 mu,
79 first_wire,
80 }
81 }
82
83 fn wire(&self, j: usize) -> u32 {
84 self.first_wire + j as u32 - 1
85 }
86
87 pub fn row_of(&self, j: usize) -> usize {
90 2 * (j - 1)
91 }
92
93 pub fn for_each_tie_row(&self, mut f: impl FnMut(usize, &[Flat<F>])) {
97 let mut conj: Vec<Flat<F>> = self.ring.iter().map(|&(_, a)| a).collect();
98 let mut tie = vec![Flat::from_raw(F::ZERO); conj.len()];
99
100 for j in (1..BITS).rev() {
101 for a in conj.iter_mut() {
102 *a = *a * *a;
103 }
104
105 let mu_conj = frobenius(self.mu[j], BITS - j);
106 for (t, &a) in tie.iter_mut().zip(&conj) {
107 *t = a * mu_conj;
108 }
109
110 f(j, &tie);
111 }
112 }
113
114 pub fn for_each_tie_weight(&self, scales: &[Flat<F>], mut f: impl FnMut(u32, Flat<F>))
118 where
119 F: Into<Block128>,
120 {
121 let mut lambda = [Flat::from_raw(F::ZERO); BITS];
122 for (i, l) in lambda.iter_mut().enumerate().skip(1) {
123 let j = BITS - i;
124 *l = scales[self.row_of(j)] * frobenius(self.mu[j], i);
125 }
126
127 let map = LinearMap::new(|x: Flat<F>| {
128 let mut power = x;
129 let mut acc = Flat::from_raw(F::ZERO);
130
131 for &l in &lambda[1..] {
132 power = power * power;
133 acc += l * power;
134 }
135
136 acc
137 });
138
139 for &(pad, a) in &self.ring {
140 f(pad, map.apply(a));
141 }
142 }
143
144 pub fn wire_rows(&self) -> Vec<AffineRow<F>> {
147 let one = Flat::from_raw(F::ONE);
148 let zero = Flat::from_raw(F::ZERO);
149
150 let mut rows = Vec::with_capacity(CHAIN_ROWS);
151 for j in 1..BITS {
152 let mut unknowns = vec![(
153 Unknown::Wire {
154 mul: self.wire(j),
155 role: WireRole::Lhs,
156 },
157 one,
158 )];
159
160 if j + 1 < BITS {
161 unknowns.push((
162 Unknown::Wire {
163 mul: self.wire(j + 1),
164 role: WireRole::Product,
165 },
166 one,
167 ));
168 }
169
170 rows.push(AffineRow {
171 unknowns,
172 claims: Vec::new(),
173 constant: zero,
174 });
175
176 rows.push(AffineRow {
177 unknowns: vec![
178 (
179 Unknown::Wire {
180 mul: self.wire(j),
181 role: WireRole::Rhs,
182 },
183 one,
184 ),
185 (
186 Unknown::Wire {
187 mul: self.wire(j),
188 role: WireRole::Lhs,
189 },
190 one,
191 ),
192 ],
193 claims: Vec::new(),
194 constant: zero,
195 });
196 }
197
198 rows
199 }
200
201 pub fn delta_form(&self) -> Vec<(Unknown, Flat<F>)> {
203 let mut form: Vec<(Unknown, Flat<F>)> = self
204 .ring
205 .iter()
206 .map(|&(pad, a)| (Unknown::Pad(pad), a * self.mu[0]))
207 .collect();
208
209 form.push((
210 Unknown::Wire {
211 mul: self.wire(1),
212 role: WireRole::Product,
213 },
214 Flat::from_raw(F::ONE),
215 ));
216
217 form
218 }
219
220 pub fn wires(&self, pad_values: &[Flat<F>]) -> Vec<[Flat<F>; 3]> {
223 let mut wires = vec![[Flat::from_raw(F::ZERO); 3]; BITS - 1];
224 let mut x = Flat::from_raw(F::ZERO);
225
226 self.for_each_tie_row(|j, tie| {
227 x = dot(pad_values, tie) + x * x;
228 wires[j - 1] = [x, x, x * x];
229 });
230
231 wires
232 }
233
234 pub fn delta(&self, pad_values: &[Flat<F>]) -> Flat<F> {
236 let mut x = Flat::from_raw(F::ZERO);
237 self.for_each_tie_row(|_, tie| x = dot(pad_values, tie) + x * x);
238
239 let mut t0 = Flat::from_raw(F::ZERO);
240 for (&h, &(_, a)) in pad_values.iter().zip(&self.ring) {
241 t0 += a * self.mu[0] * h;
242 }
243
244 t0 + x * x
245 }
246}
247
248pub fn dual_basis(u: usize) -> Block128 {
251 let byte = DUAL_KERNEL[u & 7] as u128;
252 let select = u >> 3;
253
254 let mut acc = 0u128;
255 for p in 0..16 {
256 if p & select == 0 {
257 acc |= byte << (8 * p);
258 }
259 }
260
261 Block128(acc)
262}
263
264pub fn linearized_coeffs(eq_mix: &[Block128]) -> Vec<Flat<Block128>> {
267 let eq: Vec<Flat<Block128>> = eq_mix.iter().map(|m| m.to_hardware()).collect();
268
269 let mut gamma: Vec<Flat<Block128>> = (0..BITS).map(|u| dual_basis(u).to_hardware()).collect();
270 let mut mu = Vec::with_capacity(BITS);
271
272 for _ in 0..BITS {
273 let mut acc = Flat::from_raw(Block128::ZERO);
274 for (g, m) in gamma.iter().zip(&eq) {
275 acc += *g * *m;
276 }
277
278 mu.push(acc);
279
280 for g in gamma.iter_mut() {
281 *g = *g * *g;
282 }
283 }
284
285 mu
286}
287
288fn dot<F: HardwareField>(a: &[Flat<F>], b: &[Flat<F>]) -> Flat<F> {
289 let mut acc = Flat::from_raw(F::ZERO);
290 for (&x, &y) in a.iter().zip(b) {
291 acc += x * y;
292 }
293
294 acc
295}
296
297fn frobenius<F: HardwareField>(x: Flat<F>, k: usize) -> Flat<F> {
298 let mut acc = x;
299 for _ in 0..k {
300 acc = acc * acc;
301 }
302
303 acc
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309 use crate::expander::{eq_tensor_b, ring_batch_b};
310 use hekate_math::BinaryFieldExtras;
311
312 fn next(state: &mut u128) -> Block128 {
313 *state ^= *state << 13;
314 *state ^= *state >> 7;
315 *state ^= *state << 17;
316
317 Block128(*state)
318 }
319
320 fn chain_rows(
323 gadget: &RingGadget<Block128>,
324 pad: &[Flat<Block128>],
325 wires: &[[Flat<Block128>; 3]],
326 ) -> Vec<Flat<Block128>> {
327 let value = |u: Unknown| -> Flat<Block128> {
328 match u {
329 Unknown::Pad(i) => pad[i as usize],
330 Unknown::Wire { mul, role } => wires[mul as usize][role as usize],
331 }
332 };
333
334 let mut acc: Vec<Flat<Block128>> = gadget
335 .wire_rows()
336 .iter()
337 .map(|row| {
338 let mut acc = row.constant;
339 for &(u, coeff) in &row.unknowns {
340 acc += coeff * value(u);
341 }
342
343 acc
344 })
345 .collect();
346
347 gadget.for_each_tie_row(|j, tie| {
348 for (&(pad_idx, _), &t) in gadget.ring.iter().zip(tie) {
349 acc[gadget.row_of(j)] += t * value(Unknown::Pad(pad_idx));
350 }
351 });
352
353 acc
354 }
355
356 #[test]
357 fn dual_basis_is_trace_dual() {
358 for u in 0..BITS {
359 for w in 0..BITS {
360 let tr = (dual_basis(u) * Block128(1u128 << w)).trace().get();
361 assert_eq!(tr == 1, u == w, "u={u} w={w}");
362 }
363 }
364 }
365
366 #[test]
367 fn coefficients_reproduce_bit_transposition() {
368 let mut state = 0x0f0f_1234_5678_9abc_def0_1122_3344_5566u128;
369
370 let r_mix: Vec<Block128> = (0..7).map(|_| next(&mut state)).collect();
371 let eq_mix = eq_tensor_b(&r_mix);
372 let mu = linearized_coeffs(&eq_mix);
373
374 for _ in 0..8 {
375 let x = next(&mut state);
376
377 let mut lin = Flat::from_raw(Block128::ZERO);
378 let mut power = x.to_hardware();
379
380 for m in &mu {
381 lin += *m * power;
382 power = power * power;
383 }
384
385 assert_eq!(lin.to_tower(), ring_batch_b(&[x], &eq_mix));
386 }
387 }
388
389 #[test]
390 fn wires_and_rows_evaluate_to_delta() {
391 let mut state = 0x7777_8888_9999_aaaa_bbbb_cccc_dddd_eeeeu128;
392
393 let r_mix: Vec<Block128> = (0..7).map(|_| next(&mut state)).collect();
394 let eq_mix = eq_tensor_b(&r_mix);
395 let mu = linearized_coeffs(&eq_mix);
396
397 let ring: Vec<(u32, Flat<Block128>)> = (0..5u32)
398 .map(|c| (c, next(&mut state).to_hardware()))
399 .collect();
400 let pad: Vec<Flat<Block128>> = (0..5).map(|_| next(&mut state).to_hardware()).collect();
401
402 let mut direct = Flat::from_raw(Block128::ZERO);
403 for (&(_, a), &h) in ring.iter().zip(&pad) {
404 direct += a * ring_batch_b(&[h.to_tower()], &eq_mix).to_hardware();
405 }
406
407 let gadget = RingGadget::new(ring, mu, 0);
408 assert_eq!(gadget.delta(&pad), direct);
409
410 let wires = gadget.wires(&pad);
411
412 for (i, v) in chain_rows(&gadget, &pad, &wires).iter().enumerate() {
413 assert_eq!(*v, Flat::from_raw(Block128::ZERO), "row {i}");
414 }
415
416 for wire in &wires {
417 assert_eq!(wire[0], wire[1]);
418 assert_eq!(wire[0] * wire[1], wire[2]);
419 }
420
421 let mut delta = Flat::from_raw(Block128::ZERO);
422 for &(u, coeff) in &gadget.delta_form() {
423 delta += coeff
424 * match u {
425 Unknown::Pad(i) => pad[i as usize],
426 Unknown::Wire { mul, role } => wires[mul as usize][role as usize],
427 };
428 }
429
430 assert_eq!(delta, direct);
431 }
432
433 #[test]
436 fn chain_is_rigid_under_single_wire_perturbation() {
437 let mut state = 0xdead_beef_0bad_f00d_1357_9bdf_2468_ace0u128;
438
439 let r_mix: Vec<Block128> = (0..7).map(|_| next(&mut state)).collect();
440 let mu = linearized_coeffs(&eq_tensor_b(&r_mix));
441
442 let ring: Vec<(u32, Flat<Block128>)> = (0..7u32)
443 .map(|c| (c, next(&mut state).to_hardware()))
444 .collect();
445 let pad: Vec<Flat<Block128>> = (0..7).map(|_| next(&mut state).to_hardware()).collect();
446
447 let gadget = RingGadget::new(ring, mu, 0);
448 let honest = gadget.wires(&pad);
449 let delta = next(&mut state).to_hardware();
450
451 let zero = Flat::from_raw(Block128::ZERO);
452
453 for target in 0..honest.len() {
454 for role in 0..3 {
455 let mut wires = honest.clone();
456 wires[target][role] += delta;
457
458 let row_broken = chain_rows(&gadget, &pad, &wires)
459 .iter()
460 .any(|acc| *acc != zero);
461 let hadamard_broken = wires.iter().any(|w| w[0] * w[1] != w[2]);
462
463 assert!(
464 row_broken || hadamard_broken,
465 "wire {target} role {role} perturbation went undetected"
466 );
467 }
468 }
469 }
470
471 #[test]
472 fn tie_weights_equal_scaled_tie_rows() {
473 let mut state = 0x0123_4567_89ab_cdef_fedc_ba98_7654_3210u128;
474 for len in [1u32, 9, 40] {
475 let r_mix: Vec<Block128> = (0..7).map(|_| next(&mut state)).collect();
476 let mu = linearized_coeffs(&eq_tensor_b(&r_mix));
477
478 let ring: Vec<(u32, Flat<Block128>)> = (0..len)
479 .map(|c| (3 * c + 1, next(&mut state).to_hardware()))
480 .collect();
481
482 let gadget = RingGadget::new(ring, mu, 0);
483
484 let scales: Vec<Flat<Block128>> = (0..CHAIN_ROWS)
485 .map(|_| next(&mut state).to_hardware())
486 .collect();
487
488 let mut expected: Vec<(u32, Flat<Block128>)> = gadget
489 .ring
490 .iter()
491 .map(|&(pad, _)| (pad, Flat::from_raw(Block128::ZERO)))
492 .collect();
493
494 gadget.for_each_tie_row(|j, tie| {
495 for ((_, w), &t) in expected.iter_mut().zip(tie) {
496 *w += scales[gadget.row_of(j)] * t;
497 }
498 });
499
500 let mut folded = Vec::new();
501 gadget.for_each_tie_weight(&scales, |pad, w| folded.push((pad, w)));
502
503 assert_eq!(folded, expected);
504 }
505 }
506}