Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog for Tezex

## vNext
Comment thread
vhf marked this conversation as resolved.

- [BREAKING][crypto/bls]: drop `is_` prefix from predicates (`is_zero?` → `zero?`, `is_one?` → `one?`, `is_infinity?` → `infinity?`) across `Fq`, `Fr`, `Fq2`, `Fq12`, `FqP`, `G1`, `G2`
- [BREAKING][crypto/bls]: `Fq12.inv/1` and `FqP.inv/1` now return `{:ok, t} | {:error, :not_invertible}`
- [BREAKING][crypto/bls]: `Fr.from_bytes/1` rejects out-of-range scalars with `:out_of_range` instead of silently reducing
- [zarith]: raise `ArgumentError` on malformed/truncated hex input
- [forge_operation]: fix `endorsement/1`, `endorsement_with_slot/1`, `failing_noop/1` (were passing the kind string to `forge_tag` and raising `ArgumentError`)
- [crypto/private_key]: `from_encoded_key!/2` preserves the underlying error instead of masking everything as `invalid_base58`
- [crypto/math]: `mod_inverse/2` handles negative input
- [crypto/bls]: validate scalars in `Fr.from_bytes/1`, fix `Fq.sqrt/1` zero case, handle negative input in `Fq.from_integer/1`, use `from_integer` reduction in `BLS.from_seed/1`
- [crypto/ecdsa]: fix `k` upper-bound check in signature generation (was comparing integer to binary, always false)
- [crypto/nacl]: fix `crypto_secretbox_open/3` typespec to include `:invalid_nonce_length` and `:invalid_key_length`
- [crypto/bls]: pre-compute Miller loop bits in pairing for faster verification

## v4.0.0

- [BREAKING]: `Tezex.Rpc.get_counter_for_account/2` now returns a tuple
Expand Down
25 changes: 13 additions & 12 deletions lib/crypto/bls.ex
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,13 @@ defmodule Tezex.Crypto.BLS do
32
"""
@spec from_seed(binary()) :: {:ok, t()} | {:error, :invalid_seed}
def from_seed(seed) when byte_size(seed) == @secret_key_size do
with {:ok, fr_element} <- Fr.from_bytes(seed),
false <- Fr.is_zero?(fr_element) do
{:ok, %__MODULE__{secret_key: fr_element}}
def from_seed(<<seed_int::big-unsigned-integer-size(256)>>) do
fr_element = Fr.from_integer(seed_int)

if Fr.zero?(fr_element) do
{:error, :invalid_seed}
else
_ -> {:error, :invalid_seed}
{:ok, %__MODULE__{secret_key: fr_element}}
end
end

Expand Down Expand Up @@ -121,7 +122,7 @@ defmodule Tezex.Crypto.BLS do
def from_secret_exponent(secret) when is_integer(secret) and secret > 0 do
fr_element = Fr.from_integer(secret)

if Fr.is_zero?(fr_element) do
if Fr.zero?(fr_element) do
{:error, :invalid_secret}
else
{:ok, %__MODULE__{secret_key: fr_element}}
Expand Down Expand Up @@ -262,7 +263,7 @@ defmodule Tezex.Crypto.BLS do
else
# Non-infinity point: try to decompress and validate
case G2.from_compressed_bytes(signature) do
{:ok, point} -> G2.is_on_curve?(point) and not G2.is_infinity?(point)
{:ok, point} -> G2.is_on_curve?(point) and not G2.infinity?(point)
{:error, _} -> false
end
end
Expand Down Expand Up @@ -294,7 +295,7 @@ defmodule Tezex.Crypto.BLS do
else
# Try to decompress and validate the point
case G1.from_compressed_bytes(pubkey) do
{:ok, point} -> G1.is_on_curve?(point) and not G1.is_infinity?(point)
{:ok, point} -> G1.is_on_curve?(point) and not G1.infinity?(point)
{:error, _} -> false
end
end
Expand All @@ -311,8 +312,8 @@ defmodule Tezex.Crypto.BLS do

with {:ok, pubkey_point} <- G1.from_compressed_bytes(public_key),
{:ok, signature_point} <- G2.from_compressed_bytes(signature),
false <- G1.is_infinity?(pubkey_point),
false <- G2.is_infinity?(signature_point) do
false <- G1.infinity?(pubkey_point),
false <- G2.infinity?(signature_point) do
# Hash the message to G2
message_point = G2.hash_to_curve(message, ciphersuite)

Expand Down Expand Up @@ -457,7 +458,7 @@ defmodule Tezex.Crypto.BLS do
secret_key_int = derive_key_with_hkdf(ikm, salt, key_info, 0)
fr_element = Fr.from_integer(secret_key_int)

if Fr.is_zero?(fr_element) do
if Fr.zero?(fr_element) do
derive_key_retry(ikm, salt, key_info, 1)
else
{:ok, fr_element}
Expand All @@ -470,7 +471,7 @@ defmodule Tezex.Crypto.BLS do
secret_key_int = derive_key_with_hkdf(ikm, salt, key_info, iteration)
fr_element = Fr.from_integer(secret_key_int)

if Fr.is_zero?(fr_element) do
if Fr.zero?(fr_element) do
derive_key_retry(ikm, base_salt, key_info, iteration + 1)
else
{:ok, fr_element}
Expand Down
21 changes: 11 additions & 10 deletions lib/crypto/bls/constants.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ defmodule Tezex.Crypto.BLS.Constants do
# Curve order (number of points on the curve)
@curve_order 52_435_875_175_126_190_479_447_740_508_185_965_837_690_552_500_527_637_822_603_658_699_938_581_184_513

# Pseudo-binary encoding of the ATE loop count for efficient Miller loop
# This is the binary representation (LSB first) for the Miller algorithm
# Binary: 1101001000000001000000000000000000000000000000010000000000000000
# Reversed for Miller loop (LSB first):
@pseudo_binary_encoding for char <-
String.graphemes(
"0000000000000000100000000000000000000000000000001000000001001011"
),
do: String.to_integer(char)
# Pseudo-binary encoding of the ATE loop count for the BLS12-381 Miller loop.
# Source string is LSB-first (64 bits); the Miller loop iterates bits 62..0,
# so we precompute that iteration order once at compile time.
@miller_loop_bits "0000000000000000100000000000000000000000000000001000000001001011"
|> String.graphemes()
|> Enum.map(&String.to_integer/1)
|> Enum.slice(0..62)
|> Enum.reverse()

63 = length(@miller_loop_bits)

def field_modulus, do: @field_modulus
def curve_order, do: @curve_order
def pseudo_binary_encoding, do: @pseudo_binary_encoding
def miller_loop_bits, do: @miller_loop_bits
end
41 changes: 16 additions & 25 deletions lib/crypto/bls/fq.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,7 @@ defmodule Tezex.Crypto.BLS.Fq do
"""
@spec from_integer(integer()) :: t()
def from_integer(n) when is_integer(n) do
# Handle negative integers by converting to positive modular equivalent
reduced =
if n >= 0 do
rem(n, @modulus)
else
# For negative n, compute n mod p as (p - ((-n) mod p)) mod p
@modulus - rem(-n, @modulus)
end

reduced = Integer.mod(n, @modulus)
<<reduced::big-unsigned-integer-size(384)>>
end

Expand Down Expand Up @@ -86,16 +78,16 @@ defmodule Tezex.Crypto.BLS.Fq do
@doc """
Checks if a field element is zero.
"""
@spec is_zero?(t()) :: boolean()
def is_zero?(fq) do
@spec zero?(t()) :: boolean()
def zero?(fq) do
fq == @zero
end

@doc """
Checks if a field element is one.
"""
@spec is_one?(t()) :: boolean()
def is_one?(fq) do
@spec one?(t()) :: boolean()
def one?(fq) do
fq == @one
end

Expand Down Expand Up @@ -137,7 +129,7 @@ defmodule Tezex.Crypto.BLS.Fq do
"""
@spec neg(t()) :: t()
def neg(a) when byte_size(a) == 48 do
if is_zero?(a) do
if zero?(a) do
@zero
else
a_int = to_integer(a)
Expand All @@ -160,7 +152,7 @@ defmodule Tezex.Crypto.BLS.Fq do
"""
@spec inv(t()) :: {:ok, t()} | {:error, :not_invertible}
def inv(a) when byte_size(a) == 48 do
with false <- is_zero?(a),
with false <- zero?(a),
a_int = to_integer(a),
{:ok, inv_int} <- Math.mod_inverse(a_int, @modulus) do
{:ok, from_integer(inv_int)}
Expand Down Expand Up @@ -190,20 +182,19 @@ defmodule Tezex.Crypto.BLS.Fq do
@doc """
Computes the square root of a field element if it exists.
"""
@sqrt_exp div(@modulus + 1, 4)

@spec sqrt(t()) :: {:ok, t()} | {:error, :no_sqrt}
def sqrt(@zero), do: {:ok, @zero}

def sqrt(a) when byte_size(a) == 48 do
# Use Tonelli-Shanks algorithm for square root
# Since q ≡ 3 (mod 4), we can use a^((q+1)/4)
with false <- is_zero?(a) and :is_zero,
3 <- rem(@modulus, 4),
exp = div(@modulus + 1, 4),
candidate = pow(a, exp),
# Verify it's actually a square root
true <- eq?(square(candidate), a) do
# q ≡ 3 (mod 4), so √a = a^((q+1)/4) when a is a quadratic residue.
candidate = pow(a, @sqrt_exp)

if eq?(square(candidate), a) do
{:ok, candidate}
else
:is_zero -> {:ok, @zero}
_ -> {:error, :no_sqrt}
{:error, :no_sqrt}
end
end

Expand Down
19 changes: 10 additions & 9 deletions lib/crypto/bls/fq12.ex
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,20 @@ defmodule Tezex.Crypto.BLS.Fq12 do

@doc """
Computes the modular inverse of an Fq12 element.
Uses the FqP inverse implementation.
Returns `{:ok, inverse}` or `{:error, :not_invertible}` if the element is zero.
"""
@spec inv(t()) :: t()
@spec inv(t()) :: {:ok, t()} | {:error, :not_invertible}
def inv(a) do
FqP.inv(a)
end

@doc """
Divides two Fq12 elements (a / b = a * b^(-1)).
Divides two Fq12 elements (a / b = a * b^(-1)). Raises if `b` is zero.
"""
@spec field_div(t(), t()) :: t()
def field_div(a, b) do
mul(a, inv(b))
{:ok, b_inv} = inv(b)
mul(a, b_inv)
end

@doc """
Expand All @@ -112,16 +113,16 @@ defmodule Tezex.Crypto.BLS.Fq12 do
@doc """
Checks if an Fq12 element is zero.
"""
@spec is_zero?(t()) :: boolean()
def is_zero?(a) do
FqP.is_zero?(a)
@spec zero?(t()) :: boolean()
def zero?(a) do
FqP.zero?(a)
end

@doc """
Checks if an Fq12 element is one.
"""
@spec is_one?(t()) :: boolean()
def is_one?(a) do
@spec one?(t()) :: boolean()
def one?(a) do
FqP.eq?(a, one())
end

Expand Down
16 changes: 8 additions & 8 deletions lib/crypto/bls/fq2.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,17 @@ defmodule Tezex.Crypto.BLS.Fq2 do
@doc """
Checks if an Fq2 element is zero.
"""
@spec is_zero?(t()) :: boolean()
def is_zero?({a, b}) do
Fq.is_zero?(a) and Fq.is_zero?(b)
@spec zero?(t()) :: boolean()
def zero?({a, b}) do
Fq.zero?(a) and Fq.zero?(b)
end

@doc """
Checks if an Fq2 element is one.
"""
@spec is_one?(t()) :: boolean()
def is_one?({a, b}) do
Fq.is_one?(a) and Fq.is_zero?(b)
@spec one?(t()) :: boolean()
def one?({a, b}) do
Fq.one?(a) and Fq.zero?(b)
end

@doc """
Expand Down Expand Up @@ -141,7 +141,7 @@ defmodule Tezex.Crypto.BLS.Fq2 do
"""
@spec inv(t()) :: {:ok, t()} | {:error, :not_invertible}
def inv(element) do
if is_zero?(element) do
if zero?(element) do
{:error, :not_invertible}
else
case Fq.inv(norm(element)) do
Expand Down Expand Up @@ -215,7 +215,7 @@ defmodule Tezex.Crypto.BLS.Fq2 do
@spec sqrt(t()) :: {:ok, t()} | {:error, :no_sqrt}
def sqrt(element) do
cond do
is_zero?(element) ->
zero?(element) ->
{:ok, zero()}

eq?(element, one()) ->
Expand Down
22 changes: 11 additions & 11 deletions lib/crypto/bls/fqp.ex
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ defmodule Tezex.Crypto.BLS.FqP do
top_pos = degree + exp
top = Map.get(acc_map, top_pos, @zero)

if Fq.is_zero?(top) do
if Fq.zero?(top) do
acc_map
else
# Remove the high-degree term
Expand Down Expand Up @@ -157,9 +157,9 @@ defmodule Tezex.Crypto.BLS.FqP do
@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)
@spec zero?(t()) :: boolean()
def zero?(%{coeffs: coeffs}) do
Enum.all?(coeffs, &Fq.zero?/1)
end

@doc """
Expand All @@ -176,15 +176,15 @@ defmodule Tezex.Crypto.BLS.FqP do

@doc """
Computes the modular inverse using the extended Euclidean algorithm.
Polynomial inversion using extended Euclidean algorithm.
Returns `{:ok, inverse}` or `{:error, :not_invertible}` if the element is zero.
"""
@spec inv(t()) :: t()
@spec inv(t()) :: {:ok, t()} | {:error, :not_invertible}
def inv(%{coeffs: coeffs, modulus_coeffs: modulus_coeffs} = elem) do
if is_zero?(elem) do
raise ArithmeticError, "Cannot invert zero element"
if zero?(elem) do
{:error, :not_invertible}
else
{:ok, polynomial_inv(coeffs, modulus_coeffs)}
end

polynomial_inv(coeffs, modulus_coeffs)
end

# Helper functions
Expand Down Expand Up @@ -276,7 +276,7 @@ defmodule Tezex.Crypto.BLS.FqP do
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
if Fq.zero?(Enum.at(p, d)) and d > 0 do
deg_helper(p, d - 1)
else
d
Expand Down
19 changes: 7 additions & 12 deletions lib/crypto/bls/fr.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,14 @@ defmodule Tezex.Crypto.BLS.Fr do

@doc """
Creates a field element from a binary (32 bytes, big-endian).
Rejects values outside `[0, @order)`.
"""
@spec from_bytes(binary()) :: {:ok, t()} | {:error, :invalid_size}
def from_bytes(bytes) when byte_size(bytes) == 32 do
<<value::big-unsigned-integer-size(256)>> = bytes

if value < @order do
{:ok, bytes}
else
# Reduce if needed
{:ok, from_integer(value)}
end
@spec from_bytes(binary()) :: {:ok, t()} | {:error, :invalid_size | :out_of_range}
def from_bytes(<<value::big-unsigned-integer-size(256)>> = bytes) when value < @order do
{:ok, bytes}
end

def from_bytes(bytes) when byte_size(bytes) == 32, do: {:error, :out_of_range}
def from_bytes(_), do: {:error, :invalid_size}

@doc """
Expand Down Expand Up @@ -75,8 +70,8 @@ defmodule Tezex.Crypto.BLS.Fr do
@doc """
Checks if a field element is zero.
"""
@spec is_zero?(t()) :: boolean()
def is_zero?(fr) do
@spec zero?(t()) :: boolean()
def zero?(fr) do
fr == zero()
end
end
Loading
Loading