dcrypt_algorithms/ec/b283k/
field.rs

1//! sect283k1 binary field arithmetic GF(2^283)
2//! Irreducible polynomial: x^283 + x^12 + x^7 + x^5 + 1
3
4use crate::ec::b283k::constants::B283K_FIELD_ELEMENT_SIZE;
5use crate::error::{Error, Result};
6
7/// A field element in GF(2^283) represented by 5 u64 limbs (320 bits).
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub struct FieldElement(pub(crate) [u64; 5]);
10
11impl FieldElement {
12    // The irreducible polynomial for sect283k1: f(x) = x^283 + x^12 + x^7 + x^5 + 1
13    const REDUCER: [u64; 5] = [1 << 12 | 1 << 7 | 1 << 5 | 1, 0, 0, 0, 0];
14
15    /// The additive identity element (zero).
16    pub fn zero() -> Self {
17        FieldElement([0; 5])
18    }
19
20    /// The multiplicative identity element (one).
21    pub fn one() -> Self {
22        FieldElement([1, 0, 0, 0, 0])
23    }
24
25    /// Create a field element from its canonical byte representation.
26    ///
27    /// The bytes are interpreted as a big-endian representation of the field element.
28    pub fn from_bytes(bytes: &[u8; B283K_FIELD_ELEMENT_SIZE]) -> Result<Self> {
29        let mut limbs = [0u64; 5];
30        // Read 36 bytes big-endian into 5 u64 limbs
31        // bytes[0..4] contains the top 27 bits (283-256)
32        limbs[4] = u64::from_be_bytes([0, 0, 0, 0, bytes[0], bytes[1], bytes[2], bytes[3]]);
33        limbs[3] = u64::from_be_bytes(bytes[4..12].try_into().unwrap());
34        limbs[2] = u64::from_be_bytes(bytes[12..20].try_into().unwrap());
35        limbs[1] = u64::from_be_bytes(bytes[20..28].try_into().unwrap());
36        limbs[0] = u64::from_be_bytes(bytes[28..36].try_into().unwrap());
37
38        // Verify that the highest bits are zero (only 283 bits should be used)
39        if limbs[4] & !((1u64 << 27) - 1) != 0 {
40            return Err(Error::param(
41                "FieldElement B283k",
42                "Value exceeds field size",
43            ));
44        }
45
46        Ok(FieldElement(limbs))
47    }
48
49    /// Convert this field element to its canonical byte representation.
50    ///
51    /// The bytes are a big-endian representation of the field element.
52    pub fn to_bytes(&self) -> [u8; B283K_FIELD_ELEMENT_SIZE] {
53        let mut bytes = [0u8; B283K_FIELD_ELEMENT_SIZE];
54        // Write 5 u64 limbs to 36 bytes big-endian
55        // Only use the lower 4 bytes of limb[4] since we only need 283 bits
56        let top_limb_bytes = self.0[4].to_be_bytes();
57        bytes[0..4].copy_from_slice(&top_limb_bytes[4..]);
58        bytes[4..12].copy_from_slice(&self.0[3].to_be_bytes());
59        bytes[12..20].copy_from_slice(&self.0[2].to_be_bytes());
60        bytes[20..28].copy_from_slice(&self.0[1].to_be_bytes());
61        bytes[28..36].copy_from_slice(&self.0[0].to_be_bytes());
62        bytes
63    }
64
65    /// Check if this field element is zero.
66    pub fn is_zero(&self) -> bool {
67        self.0.iter().all(|&l| l == 0)
68    }
69
70    /// Add two field elements in GF(2^283).
71    ///
72    /// In binary fields, addition is performed using XOR.
73    pub fn add(&self, other: &Self) -> Self {
74        let mut res = [0u64; 5];
75        for (i, (a, b)) in self.0.iter().zip(other.0.iter()).enumerate() {
76            res[i] = a ^ b;
77        }
78        FieldElement(res)
79    }
80
81    /// Multiply two field elements in GF(2^283).
82    ///
83    /// Uses the irreducible polynomial for reduction.
84    pub fn mul(&self, other: &Self) -> Self {
85        let mut res = FieldElement::zero();
86        let mut a = *self;
87        let mut b = *other;
88
89        for _ in 0..283 {
90            if (b.0[0] & 1) == 1 {
91                res = res.add(&a);
92            }
93            b = b.shr1();
94            a = a.shl1();
95
96            // After shifting left, check if bit 283 is set
97            // Bit 283 is at position 27 in limb[4] (since 283 = 256 + 27)
98            if (a.0[4] >> 27) & 1 == 1 {
99                // Reduce by XORing with the reduction polynomial
100                a = a.add(&FieldElement(Self::REDUCER));
101                // Clear the overflow bit
102                a.0[4] &= (1u64 << 27) - 1;
103            }
104        }
105        res
106    }
107
108    /// Square a field element in GF(2^283).
109    pub fn square(&self) -> Self {
110        self.mul(self) // Non-optimized square
111    }
112
113    /// Compute the multiplicative inverse of a field element.
114    ///
115    /// Uses Fermat's Little Theorem: a^(2^m - 2) = a^(-1) in GF(2^m).
116    /// Returns an error if the element is zero.
117    pub fn invert(&self) -> Result<Self> {
118        if self.is_zero() {
119            return Err(Error::param("FieldElement B283k", "Inversion of zero"));
120        }
121        // Fermat's Little Theorem: a^(2^m - 2)
122        let mut res = *self;
123        for _ in 1..282 {
124            res = res.square();
125            res = res.mul(self);
126        }
127        res = res.square();
128        Ok(res)
129    }
130
131    /// Compute the square root of a field element.
132    ///
133    /// In binary fields of characteristic 2, sqrt(x) = x^(2^(m-1)).
134    pub fn sqrt(&self) -> Self {
135        // sqrt(x) = x^(2^(m-1))
136        let mut res = *self;
137        for _ in 0..282 {
138            res = res.square();
139        }
140        res
141    }
142
143    // Shift left by 1
144    fn shl1(&self) -> Self {
145        let mut r = [0u64; 5];
146        r[0] = self.0[0] << 1;
147
148        // Use zip to iterate over current and previous elements
149        for (i, (&curr, &prev)) in self.0[1..].iter().zip(self.0[..4].iter()).enumerate() {
150            r[i + 1] = (curr << 1) | (prev >> 63);
151        }
152
153        // Ensure we don't have bits beyond position 282
154        r[4] &= (1u64 << 28) - 1; // Allow up to bit 283 for overflow detection
155        FieldElement(r)
156    }
157
158    // Shift right by 1
159    fn shr1(&self) -> Self {
160        let mut r = [0u64; 5];
161
162        // Use zip to iterate over current and next elements
163        for (i, (&curr, &next)) in self.0[..4].iter().zip(self.0[1..].iter()).enumerate() {
164            r[i] = (curr >> 1) | (next << 63);
165        }
166
167        r[4] = self.0[4] >> 1;
168        FieldElement(r)
169    }
170
171    /// Get the trace of the element.
172    ///
173    /// The trace is Tr(z) = z + z^2 + z^4 + ... + z^(2^(m-1)).
174    /// For compressed points, it's used to disambiguate the y-coordinate.
175    pub fn trace(&self) -> u64 {
176        let mut res = *self;
177        let mut temp = *self;
178        for _ in 0..282 {
179            temp = temp.square();
180            res = res.add(&temp);
181        }
182        res.0[0] & 1
183    }
184}