hekate_math/algebra.rs
1// SPDX-License-Identifier: Apache-2.0
2// This file is part of the hekate-math project.
3// Copyright (C) 2026 Andrei Kochergin <andrei@oumuamua.dev>
4// Copyright (C) 2026 Oumuamua Labs <info@oumuamua.dev>.
5//
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! `BinaryFieldExtras` (square, Frobenius, trace, Artin–Schreier
19//! solver) and the macro that implements it for Block16 to Block128.
20
21use crate::{Bit, TowerField};
22
23/// Square, Frobenius, absolute trace and the Artin–Schreier solver of
24/// a binary field; the solver builds the Cantor basis of the additive FFT.
25pub trait BinaryFieldExtras: TowerField {
26 /// Returns `self^2`; squaring is additive in characteristic 2.
27 fn square(&self) -> Self {
28 *self * *self
29 }
30
31 /// Returns `self^(2^k)`, `k` reduced mod `BITS` since
32 /// `x^(2^BITS) = x`; costs `k % BITS` squarings.
33 fn frobenius(&self, k: u32) -> Self {
34 let reps = (k % Self::BITS as u32) as usize;
35
36 let mut acc = *self;
37 for _ in 0..reps {
38 acc = acc.square();
39 }
40
41 acc
42 }
43
44 /// Returns the absolute trace Σ_{i<BITS} self^(2^i), always
45 /// 0 or 1; the tower fields compute it as a masked parity.
46 fn trace(&self) -> Bit {
47 let mut acc = Self::ZERO;
48 let mut p = *self;
49
50 for _ in 0..Self::BITS {
51 acc += p;
52 p = p.square();
53 }
54
55 Bit::new((acc == Self::ONE) as u8)
56 }
57
58 /// Returns a root of x^2 + x = c, or `None` iff Tr(c) = 1; the other
59 /// root is `result + ONE`. The fixed choice defines the Cantor basis.
60 /// The value path is constant time; only `Some`/`None` reveals Tr(c).
61 fn solve_quadratic(c: Self) -> Option<Self>;
62}
63
64macro_rules! impl_binary_field_extras {
65 ($block:ty, $map_ct:ident, $trace_mask:ident, $solve_basis:ident) => {
66 impl BinaryFieldExtras for $block {
67 #[inline(always)]
68 fn square(&self) -> Self {
69 // char 2:
70 // (lo + hi·X)^2 = lo^2 + hi^2·X^2,
71 // no cross term.
72 let (lo, hi) = self.split();
73 let hi2 = hi.square();
74
75 Self::new(lo.square() + hi2.mul_tau(), hi2)
76 }
77
78 #[inline(always)]
79 fn trace(&self) -> Bit {
80 Bit::new(((self.0 & constants::$trace_mask).count_ones() & 1) as u8)
81 }
82
83 #[inline(always)]
84 fn solve_quadratic(c: Self) -> Option<Self> {
85 if c.trace() != Bit::ZERO {
86 return None;
87 }
88
89 Some(Self($map_ct(c.0, &constants::$solve_basis)))
90 }
91 }
92 };
93}
94
95pub(crate) use impl_binary_field_extras;