Skip to content

Commit a7b22e1

Browse files
jotabulaciosNicoleNicolenicole-grausdiegokingston
authored
Add Binary Field (#984)
* first commit * define add function * add more functions and refactor * save work * save work * fix new function,tests and add benches * change mul function * test * change comment * change mul algorithm. Mul in level 0 and 1 working * refactor some functions and fix tests * fix inverse function * add docs and update README * fix conflicts * fix clippy * fix no_std * fix tests no std * small fixex for benches * fix typo * remove num_bits from the struct. remove set_num_level. move mul * improve add_elements() * impl Eq instead of equalls function * derive default * fix prefix in test * add readme * omit mul lifetimes * use vector of random elements for benches * fix doc --------- Co-authored-by: Nicole <nicole@Nicoles-MacBook-Air.local> Co-authored-by: Nicole <nicole@Nicoles-Air.fibertel.com.ar> Co-authored-by: Nicole <nicole.graus@lambdaclass.com> Co-authored-by: Diego K <43053772+diegokingston@users.noreply.github.com>
1 parent d938567 commit a7b22e1

File tree

8 files changed

+911
-1
lines changed

8 files changed

+911
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ List of symbols:
8989
| Mersenne 31 | :heavy_check_mark: | :x: | :x: | :x: | :x: |
9090
| Baby Bear | :heavy_check_mark: | :x: | :x: | :x: | :x: |
9191
| MiniGoldilocks | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :x: |
92-
| Binary fields | :x: | :x: | :x: | :x: | :x: |
92+
| Binary fields | :heavy_check_mark: | :x: | :x: | :x: | :x: |
9393
| **ZK friendly Hash function** | **Lambdaworks** | **Arkworks** | **Halo2** | **gnark** | **Constantine** |
9494
| Poseidon | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: |
9595
| Pedersen | 🏗️ | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: |

crates/math/benches/criterion_field.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use fields::{
1010
babybear_u32_extension_ops_benchmarks, babybear_u32_ops_benchmarks,
1111
babybear_u64_extension_ops_benchmarks, babybear_u64_ops_benchmarks,
1212
},
13+
binary::binary_ops_benchmarks,
1314
stark252::starkfield_ops_benchmarks,
1415
u64_goldilocks::u64_goldilocks_ops_benchmarks,
1516
u64_goldilocks_montgomery::u64_goldilocks_montgomery_ops_benchmarks,
@@ -25,6 +26,7 @@ criterion_group!(
2526
babybear_u64_extension_ops_benchmarks,
2627
babybear_p3_ops_benchmarks,
2728
babybear_extension_ops_benchmarks_p3,
29+
binary_ops_benchmarks,
2830
mersenne31_ops_benchmarks,
2931
mersenne31_extension_ops_benchmarks,
3032
mersenne31_mont_ops_benchmarks,

crates/math/benches/fields/binary.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
use std::hint::black_box;
2+
3+
use criterion::{BenchmarkId, Criterion};
4+
use lambdaworks_math::field::fields::binary::field::{BinaryFieldError, TowerFieldElement};
5+
use rand::Rng;
6+
7+
pub fn rand_element(num_level: usize) -> TowerFieldElement {
8+
let mut rng = rand::thread_rng();
9+
let value = rng.gen::<u128>();
10+
TowerFieldElement::new(value, num_level)
11+
}
12+
13+
fn binary_add_bench(c: &mut Criterion, num_levels_a: usize, num_levels_b: usize) {
14+
let mut group = c.benchmark_group("Binary TowerField add");
15+
let samples = 1000;
16+
let a_values: Vec<_> = (0..samples).map(|_| rand_element(num_levels_a)).collect();
17+
let b_values: Vec<_> = (0..samples).map(|_| rand_element(num_levels_b)).collect();
18+
19+
group.bench_with_input(
20+
BenchmarkId::from_parameter(format!("F{}×F{}", 1 << num_levels_a, 1 << num_levels_b)),
21+
&(num_levels_a, num_levels_b),
22+
|bencher, _params| {
23+
bencher.iter(|| {
24+
for (a, b) in a_values.iter().zip(b_values.iter()) {
25+
black_box(black_box(a) + black_box(b));
26+
}
27+
});
28+
},
29+
);
30+
group.finish();
31+
}
32+
33+
fn binary_mul_bench(c: &mut Criterion, num_levels_a: usize, num_levels_b: usize) {
34+
let mut group = c.benchmark_group("Binary TowerField mul");
35+
let samples = 1000;
36+
let a_values: Vec<_> = (0..samples).map(|_| rand_element(num_levels_a)).collect();
37+
let b_values: Vec<_> = (0..samples).map(|_| rand_element(num_levels_b)).collect();
38+
39+
group.bench_with_input(
40+
BenchmarkId::from_parameter(format!("F{}×F{}", 1 << num_levels_a, 1 << num_levels_b)),
41+
&(num_levels_a, num_levels_b),
42+
|bencher, _params| {
43+
bencher.iter(|| {
44+
for (a, b) in a_values.iter().zip(b_values.iter()) {
45+
black_box(black_box(a) * black_box(b));
46+
}
47+
});
48+
},
49+
);
50+
group.finish();
51+
}
52+
53+
fn binary_pow_bench(c: &mut Criterion, num_levels: usize, exponent: u32) {
54+
if num_levels == 0 {
55+
return;
56+
}
57+
let mut group = c.benchmark_group("Binary TowerField pow");
58+
let samples = 1000;
59+
let a_values: Vec<_> = (0..samples).map(|_| rand_element(num_levels)).collect();
60+
61+
group.bench_with_input(
62+
BenchmarkId::from_parameter(format!("F{} ^ {}", 1 << num_levels, exponent)),
63+
&(num_levels, exponent),
64+
|bencher, _params| {
65+
bencher.iter(|| {
66+
for a in a_values.iter() {
67+
black_box(black_box(a).pow(exponent));
68+
}
69+
});
70+
},
71+
);
72+
group.finish();
73+
}
74+
75+
fn binary_inv_bench(c: &mut Criterion, num_levels: usize) {
76+
if num_levels == 0 {
77+
return;
78+
}
79+
80+
let mut group = c.benchmark_group("Binary TowerField inv");
81+
82+
let samples = 1000;
83+
84+
// Generate non-zero element
85+
let mut rng = rand::thread_rng();
86+
let values: Vec<TowerFieldElement> = (0..samples)
87+
.map(|_| {
88+
let non_zero_val = rng.gen::<u128>() | 1;
89+
let a = TowerFieldElement::new(non_zero_val, num_levels);
90+
assert!(
91+
!a.is_zero(),
92+
"Failed to generate non-zero element for inversion benchmark"
93+
);
94+
a
95+
})
96+
.collect();
97+
98+
group.bench_with_input(
99+
BenchmarkId::from_parameter(format!("F{}", 1 << num_levels)),
100+
&num_levels,
101+
|bencher, _params| {
102+
bencher.iter(|| {
103+
for a in &values {
104+
match black_box(a).inv() {
105+
Ok(inv) => black_box(inv),
106+
Err(BinaryFieldError::InverseOfZero) => {
107+
panic!("Attempted to invert zero element")
108+
}
109+
};
110+
}
111+
});
112+
},
113+
);
114+
group.finish();
115+
}
116+
117+
// Main benchmarking function that runs all benches
118+
pub fn binary_ops_benchmarks(c: &mut Criterion) {
119+
let levels = [0, 1, 2, 3, 4, 5, 6, 7];
120+
121+
for &level in &levels {
122+
binary_add_bench(c, level, level);
123+
binary_mul_bench(c, level, level);
124+
binary_pow_bench(c, level, 5);
125+
binary_inv_bench(c, level);
126+
}
127+
128+
// Benchmarks for operations between different levels
129+
binary_add_bench(c, 0, 1); // F₁ + F₂ (1-bit field + 2-bit field)
130+
binary_add_bench(c, 0, 4); // F₁ + F₁₆ (1-bit field + 16-bit field)
131+
binary_add_bench(c, 1, 4); // F₂ + F₁₆ (2-bit field + 16-bit field)
132+
binary_add_bench(c, 2, 6); // F₄ + F₆₄ (4-bit field + 64-bit field)
133+
binary_add_bench(c, 0, 7); // F₁ + F₁₂₈ (1-bit field + 128-bit field)
134+
binary_add_bench(c, 4, 7); // F₁₆ + F₁₂₈ (16-bit field + 128-bit field)
135+
binary_add_bench(c, 6, 7); // F₆₄ + F₁₂₈ (64-bit field + 128-bit field)
136+
137+
binary_mul_bench(c, 0, 1); // F₁ × F₂ (1-bit field × 2-bit field)
138+
binary_mul_bench(c, 0, 4); // F₁ × F₁₆ (1-bit field × 16-bit field)
139+
binary_mul_bench(c, 1, 4); // F₂ × F₁₆ (2-bit field × 16-bit field)
140+
binary_mul_bench(c, 2, 6); // F₄ × F₆₄ (4-bit field × 64-bit field)
141+
binary_mul_bench(c, 0, 7); // F₁ × F₁₂₈ (1-bit field × 128-bit field)
142+
binary_mul_bench(c, 4, 7); // F₁₆ × F₁₂₈ (16-bit field × 128-bit field)
143+
binary_mul_bench(c, 6, 7); // F₆₄ × F₁₂₈ (64-bit field × 128-bit field)
144+
}

crates/math/benches/fields/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod baby_bear;
2+
pub mod binary;
23
pub mod mersenne31;
34
pub mod mersenne31_montgomery;
45
pub mod stark252;
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Binary Fields
2+
3+
This module implements binary fields of the form $GF(2^{2^n})$ (i.e. a finite field with $2^{2^n}$ elements) by constructing a tower of field extensions. It doesn't implement `IsField` or `FieldElement`, because we wanted elements from different field extensions to coexist within the same struct, allowing us to optimize each operation to work simultaneously across all levels.
4+
5+
Binary fields are particularly useful for verifiable computing applications, including SNARKs and STARKs, due to their efficient implementation and favorable properties.
6+
7+
## Overview
8+
9+
The tower of binary fields provides a powerful alternative to prime fields for cryptographic applications. This implementation represents field elements as multivariable polynomials with binary coefficients in $GF(2) = \{0, 1\}$, where these coefficients are stored as bits in a `u128` integer. The tower structure is built recursively, with each level representing an extension of the previous field.
10+
11+
Key features of this implementation:
12+
13+
- Supports field extensions from level 0 $(GF(2))$ to level 7 $(GF(2^{128}))$.
14+
- Efficient arithmetic operations optimized for binary fields.
15+
- Karatsuba optimization for multiplication.
16+
17+
## Implementation Details
18+
19+
### Tower Construction
20+
21+
Let's explain the theory behind. To expand the binary field $GF(2) = \{0, 1\}$, we construct a tower of field extensions in the following way:
22+
23+
**Level 0:** The tower starts at level 0 with the base field of two elements $GF(2^{2^0}) = \{0, 1\}$.
24+
25+
**Level 1:** Then at level 1, we define the field extension $GF(2^{2^1})$ whose elements are univariate polynomials with binary coefficients and variable $x_0$ such that ${x_0}^2 = x_0 + 1$. Note that this means that the polynomials are lineal (have degree at most 1). Therefore, this field extension has $2^{2^1}$ elements. We represent them as the binary expression of integers:
26+
27+
$$\begin{aligned}
28+
00 &= 0 \\
29+
01 &= 1 \\
30+
10 &= x_0 \\
31+
11 &= x_0 + 1
32+
\end{aligned}$$
33+
34+
**Level 2:** At level 2, we define the field extension $GF(2^{2^2})$. In this case the elements are polynomials with binary coefficients and two variables, $x_0$ and $x_1$. The first one keeps satisfying ${x_0}^2 = x_0 + 1$ and in addition the second one satisfies ${x_1}^2 = x_1 \cdot x_0 + 1$. This means that the polynomials are lineal in each variable. Therefore, this field extension has $2^{2^2}$ elements:
35+
36+
$\begin{array}{llll}
37+
0000 = 0 & 0100 = x_1 & 1000 = x_1x_0 & 1100 = x_1x_0 + x_1 \\
38+
0001 = 1 & 0101 = x_1 + 1 & 1001 = x_1x_0 + 1 & 1101 = x_1x_0 + x_1 + 1 \\
39+
0010 = x_0 & 0110 = x_1 + x_0 & 1010 = x_1x_0 + x_0 & 1110 = x_1x_0 + x_1 + x_0 \\
40+
0011 = x_0 + 1 & 0111 = x_1 + x_0 + 1 & 1011 = x_1x_0 + x_0 + 1 & 1111 = x_1x_0 + x_1 + x_0 + 1
41+
\end{array}$
42+
43+
**Level 3:** At level 3, we define $GF(2^{2^3})$ in the same way. This time the polynomials have three variables $x_0$, $x_1$ and $x_2$. The first two variables satisfy the equations mentioned before and in addition the last one satisfies ${x_2}^2 = x_2 \cdot x_1 + 1$. This field extension has $2^{2^3}$ elements.
44+
45+
**Level $n$:** Continuing this argument, in each level $n$ we define the field extension $GF(2^{2^n})$ using polynomials of $n$ variables with ${x_i}^2 = x_i \cdot x_{i-1} + 1$.
46+
47+
Our implementation admits until level $n = 7$.
48+
49+
50+
51+
### Element Representation
52+
53+
A `TowerFieldElement` is represented by:
54+
55+
- `value`: A `u128` integer where the bits represent the coefficients of the polynomial.
56+
- `num_level`: The level in the tower (0-7).
57+
58+
For example, if `value = 0b1101` and `num_level = 2`, this represents the polynomial $x_0\cdot x_1 + x_1 + 1$ in $GF(2^4)$.
59+
60+
61+
### Field Operations
62+
63+
The implementation provides efficient algorithms for:
64+
65+
- Addition and subtraction (XOR operations).
66+
- Multiplication using a recursive tower approach with Karatsuba optimization.
67+
- Inversion using Fermat's Little Theorem.
68+
- Exponentiation using square-and-multiply.
69+
70+
## API Usage
71+
72+
### Creating Field Elements
73+
74+
The method `new` handles possible overflows in this way:
75+
- If the level input is greater than 7, then the element's `num_level` is set as 7.
76+
- If the value input doesn't fit in the level given, then we take just the less significant bits that fit in that level and set it as the element's `value`.
77+
78+
```rust
79+
use lambdaworks_math::field::fields::binary::field::TowerFieldElement;
80+
81+
// Create elements at different tower levels
82+
let element_level_0 = TowerFieldElement::new(1, 0); // Element '1' in GF(2)
83+
let element_level_1 = TowerFieldElement::new(3, 1); // Element '11' in GF(2^2)
84+
let element_level_2 = TowerFieldElement::new(123, 2); // Element '1011' in GF(2^4)
85+
let element_level_3 = TowerFieldElement::new(123, 3); // Element '01111011'in GF(2^8)
86+
let element_level_7 = TowerFieldElement::new(123, 25); // Element '0..01111011'in GF(2^128)
87+
88+
// Create zero and one
89+
let zero = TowerFieldElement::zero();
90+
let one = TowerFieldElement::one();
91+
92+
// Create from integer values
93+
let from_u64 = TowerFieldElement::from(42u64);
94+
```
95+
96+
### Basic Operations
97+
98+
```rust
99+
use lambdaworks_math::field::fields::binary::field::TowerFieldElement;
100+
101+
// Create two elements
102+
let a = TowerFieldElement::new(5, 2); // '0101' in GF(2^4)
103+
let b = TowerFieldElement::new(3, 2); // '0011' in GF(2^4)
104+
105+
// Addition (XOR operation)
106+
let sum = a + b; // '0110' = 6
107+
108+
// Subtraction (same as addition in binary fields)
109+
let difference = a - b; // '0110' = 6
110+
111+
// Multiplication
112+
let product = a * b; // '1111' = 15
113+
114+
// Inversion
115+
let a_inverse = a.inv().unwrap();
116+
assert_eq!(a * a_inverse, TowerFieldElement::one());
117+
118+
// Exponentiation
119+
let a_cubed = a.pow(3);
120+
```
121+
122+
### Working with Different Levels
123+
124+
```rust
125+
use lambdaworks_math::field::fields::binary::field::TowerFieldElement;
126+
127+
// Elements at different levels
128+
let a = TowerFieldElement::new(3, 1); // Level 1: GF(2^2)
129+
let b = TowerFieldElement::new(5, 2); // Level 2: GF(2^4)
130+
131+
// Operations automatically promote to the higher level
132+
let sum = a + b; // Result is at level 2
133+
assert_eq!(sum.num_level(), 2);
134+
135+
// Splitting and joining elements
136+
let element = TowerFieldElement::new(0b1010, 2); // Level 2
137+
let (hi, lo) = element.split(); // Split into two level 1 elements
138+
assert_eq!(hi.value(), 0b10); // High part: '10'
139+
assert_eq!(lo.value(), 0b10); // Low part: '10'
140+
141+
// Join back
142+
let rejoined = hi.join(&lo);
143+
assert_eq!(rejoined, element);
144+
```
145+
146+
## Applications
147+
148+
Binary tower fields are particularly useful for:
149+
150+
1. **SNARKs and STARKs**: These fields enable efficient proof systems, especially when working with binary operations.
151+
152+
2. **Binius**: A SNARK system that leverages binary fields for improved performance and simplicity.
153+
154+
155+
## Performance Considerations
156+
157+
- Operations in binary fields are generally faster than in prime fields for many applications.
158+
- XOR-based addition/subtraction is extremely efficient.
159+
- The tower structure enables optimized implementations of multiplication and other operations.
160+
161+
## References
162+
163+
- [SNARKs on Binary Fields: Binius](https://blog.lambdaclass.com/snarks-on-binary-fields-binius/) - LambdaClass Blog
164+
- [Binius: SNARKs on Binary Fields](https://vitalik.eth.limo/general/2024/04/29/binius.html) - Vitalik Buterin's explanation
165+
- [Binary Tower Fields are the Future of Verifiable Computing](https://www.irreducible.com/posts/binary-tower-fields-are-the-future-of-verifiable-computing) - Irreducible

0 commit comments

Comments
 (0)