Current section
Files
Jump to
Current section
Files
lib/crypto/bls/fqp.ex
defmodule Tezex.Crypto.BLS.FqP do
@moduledoc """
Polynomial extension field base class for BLS12-381.
This is the base for building higher-degree extension fields like Fq12.
"""
alias Tezex.Crypto.BLS.Fq
@type t :: %{coeffs: list(Fq.t()), modulus_coeffs: list(integer())}
@zero Fq.zero()
@one Fq.one()
@modulus Fq.modulus()
@doc """
Creates a polynomial extension field element from coefficients.
"""
@spec new(list(Fq.t()), list(integer())) :: t()
def new(coeffs, modulus_coeffs) do
degree = length(modulus_coeffs)
# Pad or truncate coefficients to match the field degree
normalized_coeffs =
coeffs
|> Enum.take(degree)
|> pad_to_length(degree)
%{coeffs: normalized_coeffs, modulus_coeffs: modulus_coeffs}
end
@doc """
Adds two polynomial field elements.
"""
@spec add(t(), t()) :: t()
def add(%{coeffs: coeffs1, modulus_coeffs: modulus_coeffs}, %{
coeffs: coeffs2,
modulus_coeffs: modulus_coeffs
}) do
coeffs = Enum.zip_with(coeffs1, coeffs2, &Fq.add/2)
new(coeffs, modulus_coeffs)
end
@doc """
Subtracts two polynomial field elements.
"""
@spec sub(t(), t()) :: t()
def sub(%{coeffs: coeffs1, modulus_coeffs: modulus_coeffs}, %{
coeffs: coeffs2,
modulus_coeffs: modulus_coeffs
}) do
coeffs = Enum.zip_with(coeffs1, coeffs2, &Fq.sub/2)
new(coeffs, modulus_coeffs)
end
@doc """
Multiplies two polynomial field elements with modular reduction.
This implementation uses an optimized multiplication algorithm.
"""
@spec mul(t(), t()) :: t()
def mul(%{coeffs: coeffs1, modulus_coeffs: modulus_coeffs}, %{
coeffs: coeffs2,
modulus_coeffs: modulus_coeffs
}) do
degree = length(modulus_coeffs)
# Create b array of size degree * 2 - 1
b_size = degree * 2 - 1
# Step 1: Polynomial multiplication - fill b array
# Use a Map to accumulate products efficiently
b_map =
for {c1, i} <- Enum.with_index(coeffs1),
{c2, j} <- Enum.with_index(coeffs2),
i + j < b_size,
reduce: %{} do
acc ->
product = Fq.mul(c1, c2)
Map.update(acc, i + j, product, &Fq.add(&1, product))
end
# Step 2: Polynomial reduction
# Pre-compute modulus coefficients as Fq elements for efficiency
mc_tuples =
modulus_coeffs
|> Enum.with_index()
|> Enum.reduce([], fn
{0, _i}, acc -> acc
{coeff, i}, acc -> [{i, Fq.from_integer(coeff)} | acc]
end)
|> Enum.reverse()
# Step 3: Reduce from degree-2 down to 0
final_b_map =
Enum.reduce((degree - 2)..0//-1, b_map, fn exp, acc_map ->
# Get coefficient at position degree + exp (the "top" term to reduce)
top_pos = degree + exp
top = Map.get(acc_map, top_pos, @zero)
if Fq.is_zero?(top) do
acc_map
else
# Remove the high-degree term
acc_map = Map.delete(acc_map, top_pos)
# Apply reduction: for each (i, c) in mc_tuples, b[exp + i] -= top * c
Enum.reduce(mc_tuples, acc_map, fn
{i, c}, inner_acc_map when exp + i < b_size ->
Map.update(inner_acc_map, exp + i, @zero, fn existing ->
Fq.sub(existing, Fq.mul(top, c))
end)
_, inner_acc_map ->
inner_acc_map
end)
end
end)
# Step 4: Take first degree coefficients (with field modulus)
result_coeffs =
for i <- 0..(degree - 1) do
coeff = Map.get(final_b_map, i, @zero)
int_val = Fq.to_integer(coeff)
Fq.from_integer(rem(int_val, @modulus))
end
new(result_coeffs, modulus_coeffs)
end
@doc """
Negates a polynomial field element.
"""
@spec neg(t()) :: t()
def neg(%{coeffs: coeffs, modulus_coeffs: modulus_coeffs}) do
new(Enum.map(coeffs, &Fq.neg/1), modulus_coeffs)
end
@doc """
Returns the zero element.
"""
@spec zero(list(integer())) :: t()
def zero(modulus_coeffs) do
degree = length(modulus_coeffs)
zero_coeffs = List.duplicate(@zero, degree)
new(zero_coeffs, modulus_coeffs)
end
@doc """
Returns the one element.
"""
@spec one(list(integer())) :: t()
def one(modulus_coeffs) do
degree = length(modulus_coeffs)
one_coeffs = [@one | List.duplicate(@zero, degree - 1)]
new(one_coeffs, modulus_coeffs)
end
@doc """
Checks if the element is zero.
"""
@spec is_zero?(t()) :: boolean()
def is_zero?(%{coeffs: coeffs}) do
Enum.all?(coeffs, &Fq.is_zero?/1)
end
@doc """
Checks if two elements are equal.
"""
@spec eq?(t(), t()) :: boolean()
def eq?(%{coeffs: coeffs1, modulus_coeffs: modulus_coeffs}, %{
coeffs: coeffs2,
modulus_coeffs: modulus_coeffs
}) do
Enum.zip(coeffs1, coeffs2)
|> Enum.all?(fn {a, b} -> Fq.eq?(a, b) end)
end
@doc """
Computes the modular inverse using the extended Euclidean algorithm.
Polynomial inversion using extended Euclidean algorithm.
"""
@spec inv(t()) :: t()
def inv(%{coeffs: coeffs, modulus_coeffs: modulus_coeffs} = elem) do
if is_zero?(elem) do
raise ArithmeticError, "Cannot invert zero element"
end
polynomial_inv(coeffs, modulus_coeffs)
end
# Helper functions
defp pad_to_length(list, target_length) do
current_length = length(list)
if current_length < target_length do
list ++ List.duplicate(@zero, target_length - current_length)
else
list
end
end
defp polynomial_inv(coeffs, modulus_coeffs) do
degree = length(modulus_coeffs)
lm = [@one | List.duplicate(@zero, degree)]
hm = List.duplicate(@zero, degree + 1)
low = coeffs ++ [@zero]
high = Enum.map(modulus_coeffs, &Fq.from_integer/1) ++ [@one]
{final_lm, final_low} = extended_gcd_loop(lm, hm, low, high, degree)
low0_int = Fq.to_integer(hd(final_low))
inv_low0_int = prime_field_inv(low0_int, @modulus)
coeffs =
final_lm
|> Enum.take(degree)
|> Enum.map(fn coeff ->
coeff_int = Fq.to_integer(coeff)
result_int = rem(coeff_int * inv_low0_int, @modulus)
Fq.from_integer(result_int)
end)
new(coeffs, modulus_coeffs)
end
# Extended GCD loop
defp extended_gcd_loop(lm, hm, low, high, degree) do
case deg(low) do
0 ->
{lm, low}
_ ->
r = optimized_poly_rounded_div(high, low)
r_padded = r ++ List.duplicate(@zero, degree + 1 - length(r))
{nm, new} =
Enum.reduce(0..degree, {hm, high}, fn i, {nm_acc, acc} ->
Enum.reduce(0..(degree - i), {nm_acc, acc}, fn
j, {nm_inner, inner} when i + j <= degree ->
nm_val = Enum.at(nm_inner, i + j)
lm_val = Enum.at(lm, i)
r_val = Enum.at(r_padded, j)
nm_val = Fq.sub(nm_val, Fq.mul(lm_val, r_val))
val = Enum.at(inner, i + j)
low_val = Enum.at(low, i)
val = Fq.sub(val, Fq.mul(low_val, r_val))
{List.replace_at(nm_inner, i + j, nm_val), List.replace_at(inner, i + j, val)}
_j, {nm_inner, inner} ->
{nm_inner, inner}
end)
end)
nm_mod =
Enum.map(nm, fn x ->
Fq.from_integer(rem(Fq.to_integer(x), @modulus))
end)
mod =
Enum.map(new, fn x ->
Fq.from_integer(rem(Fq.to_integer(x), @modulus))
end)
extended_gcd_loop(nm_mod, lm, mod, low, degree)
end
end
defp deg(p) do
deg_helper(p, length(p) - 1)
end
defp deg_helper(_p, 0), do: 0
defp deg_helper(p, d) do
if Fq.is_zero?(Enum.at(p, d)) and d > 0 do
deg_helper(p, d - 1)
else
d
end
end
defp optimized_poly_rounded_div(a_list, b_list) do
deg_a = deg(a_list)
deg_b = deg(b_list)
tmp = Enum.map(a_list, & &1)
o = List.duplicate(@zero, length(a_list))
{o, _} =
Enum.reduce((deg_a - deg_b)..0//-1, {o, tmp}, fn i, {o_acc, tmp_acc} ->
if i >= 0 and deg_b + i < length(tmp_acc) and deg_b < length(b_list) do
tmp_val = Enum.at(tmp_acc, deg_b + i)
b_val = Enum.at(b_list, deg_b)
b_int =
Fq.to_integer(b_val)
|> prime_field_inv(@modulus)
o_val =
(Fq.to_integer(Enum.at(o_acc, i)) + Fq.to_integer(tmp_val) * b_int)
|> Fq.from_integer()
o = List.replace_at(o_acc, i, o_val)
tmp =
Enum.reduce(0..deg_b, tmp_acc, fn c, tmp_inner ->
if c + i < length(tmp_inner) and c < length(o) do
tmp_c_i = Enum.at(tmp_inner, c + i)
o_c = Enum.at(o, c)
List.replace_at(tmp_inner, c + i, Fq.sub(tmp_c_i, o_c))
else
tmp_inner
end
end)
{o, tmp}
else
{o_acc, tmp_acc}
end
end)
o
|> Enum.take(deg(o) + 1)
|> Enum.map(fn x ->
int_val = Fq.to_integer(x)
Fq.from_integer(rem(int_val, @modulus))
end)
end
# prime_field_inv function
defp prime_field_inv(a, n) do
# To address a == n edge case.
a = rem(a, n)
if a == 0 do
0
else
{lm, _low} = prime_field_inv_loop(1, 0, rem(a, n), n)
# Ensure the result is positive
result = rem(lm, n)
if result < 0 do
result + n
else
result
end
end
end
defp prime_field_inv_loop(lm, hm, low, high) do
if low > 1 do
r = div(high, low)
nm = hm - lm * r
new = high - low * r
# lm, low, hm, high = nm, new, lm, low
prime_field_inv_loop(nm, lm, new, low)
else
{lm, low}
end
end
end