Skip to content

Commit

Permalink
Merge remote-tracking branch 'refs/remotes/origin/final_exp_witness' …
Browse files Browse the repository at this point in the history
…into final_exp_witness
  • Loading branch information
raugfer committed Aug 7, 2024
2 parents 119dc91 + d945540 commit 29d0c33
Show file tree
Hide file tree
Showing 62 changed files with 72,617 additions and 69,859 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/cairo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- uses: actions/checkout@v3
- uses: software-mansion/setup-scarb@v1
with:
scarb-version: "2.7.0-rc.3"
scarb-version: "2.7.0"
- run: scarb fmt --check
working-directory: src/cairo
- run: cd src/cairo && scarb test
51 changes: 28 additions & 23 deletions hydra/algebra.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,21 +405,25 @@ def __init__(
self,
coefficients: list[T],
):
if all(isinstance(c, (ModuloCircuitElement, PyFelt)) for c in coefficients):
self.coefficients: list[PyFelt] = [c.felt for c in coefficients]
self.type = PyFelt
self.p = coefficients[0].p
self.field = BaseField(self.p)
elif all(isinstance(c, Fp2) for c in coefficients):
self.coefficients: list[Fp2] = coefficients
self.type = Fp2
self.p = coefficients[0].p
self.field = BaseFp2Field(self.p)
coeffs_types = {type(c) for c in coefficients}
if coeffs_types == {PyFelt}:
self._initialize(coefficients, PyFelt, BaseField)
elif coeffs_types == {ModuloCircuitElement}:
self._initialize([c.felt for c in coefficients], PyFelt, BaseField)
elif coeffs_types == {Fp2}:
self._initialize(coefficients, Fp2, BaseFp2Field)
else:
raise TypeError(
f"All elements in the list must be of the same type, either ModuloCircuitElement, PyFelt or Fp2., got {set([type(c) for c in coefficients])}"
f"All elements in the list must be of the same type, either ModuloCircuitElement, PyFelt or Fp2., got {coeffs_types}"
)
return

def _initialize(
self, coefficients: list[T], coeff_type: type[T], field_class: type
):
self.coefficients: list[T] = coefficients
self.type = coeff_type
self.p = coefficients[0].p
self.field = field_class(self.p)

def __repr__(self) -> str:
if self.type == PyFelt:
Expand Down Expand Up @@ -579,22 +583,23 @@ def __divmod__(self, denominator: "Polynomial") -> tuple[Polynomial, Polynomial]
if self.degree() < denominator.degree():
return (Polynomial.zero(self.p, self.type), self)
field = self.field
remainder = Polynomial([n for n in self.coefficients])
quotient_coefficients = [
field.zero() for i in range(self.degree() - denominator.degree() + 1)
]
for i in range(self.degree() - denominator.degree() + 1):
if remainder.degree() < denominator.degree():
break
coefficient = (
remainder.leading_coefficient() / denominator.leading_coefficient()
)
remainder = Polynomial(self.coefficients[:])
quotient_coefficients = [field.zero()] * (
self.degree() - denominator.degree() + 1
)

denom_lead_inv = denominator.leading_coefficient().__inv__()

while remainder.degree() >= denominator.degree():
shift = remainder.degree() - denominator.degree()
coefficient = remainder.leading_coefficient() * denom_lead_inv
quotient_coefficients[shift] = coefficient

subtractee = (
Polynomial([field.zero()] * shift + [coefficient]) * denominator
)
quotient_coefficients[shift] = coefficient
remainder = remainder - subtractee

quotient = Polynomial(quotient_coefficients)
return quotient, remainder

Expand Down
96 changes: 66 additions & 30 deletions hydra/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import random
from dataclasses import dataclass
from enum import Enum
from fastecdsa import curvemath


from starkware.python.math_utils import EcInfinity, ec_safe_add, ec_safe_mult

Expand All @@ -25,23 +27,57 @@
ED25519_ID = 4


class ProofSystem(Enum):
Groth16 = "groth16"

@property
def supported_curves(self) -> set[int]:
if self == ProofSystem.Groth16:
return {BN254_ID, BLS12_381_ID}
return set()


class CurveID(Enum):
BN254 = 0
BLS12_381 = 1
SECP256K1 = 2
SECP256R1 = 3
ED25519 = 4

@staticmethod
def from_str(s: str) -> "CurveID":
return CurveID(CurveID.find_value_in_string(s))

@staticmethod
def find_value_in_string(s: str) -> int | None:
"""
Find the value of the curve ID in the string.
"""
for member in CurveID:
if member.name in s:
if member.name.lower() in s.lower():
return member.value
return None

@staticmethod
def get_proving_system_curve(
curve_id: int, proving_system: ProofSystem
) -> "CurveID":
"""
Get the curve ID for proving systems. Only curves supported by the given proving system are valid.
"""
if curve_id not in CurveID._value2member_map_:
raise ValueError(
f"Invalid curve ID: {curve_id}. Supported curves are: {', '.join([f'{c.name} (ID: {c.value})' for c in CurveID])}"
)
if curve_id not in proving_system.supported_curves:
supported_curves = ", ".join(
f"{CurveID(c).name} (ID: {c})" for c in proving_system.supported_curves
)
raise ValueError(
f"Invalid curve ID for {proving_system.name}. Supported curves are: {supported_curves}"
)
return CurveID(curve_id)


@dataclass(slots=True, frozen=True)
class WeierstrassCurve:
Expand Down Expand Up @@ -449,6 +485,9 @@ class G1Point:
y: int
curve_id: CurveID

def __hash__(self):
return hash((self.x, self.y, self.curve_id))

def __eq__(self, other: "G1Point") -> bool:
"""
Checks if two G1Point instances are equal.
Expand Down Expand Up @@ -577,14 +616,8 @@ def gen_random_point(curve_id: CurveID) -> "G1Point":
G1Point: A random point on the curve.
"""
scalar = random.randint(1, CURVES[curve_id.value].n - 1)
if curve_id.value in GNARK_CLI_SUPPORTED_CURVES:
from tools.gnark_cli import GnarkCLI

cli = GnarkCLI(curve_id)
ng1ng2 = cli.nG1nG2_operation(scalar, 1, raw=True)
return G1Point(ng1ng2[0], ng1ng2[1], curve_id)
else:
return G1Point.get_nG(curve_id, scalar)
return G1Point.get_nG(curve_id, scalar)

@staticmethod
def get_nG(curve_id: CurveID, n: int) -> "G1Point":
Expand All @@ -605,17 +638,8 @@ def get_nG(curve_id: CurveID, n: int) -> "G1Point":
n < CURVES[curve_id.value].n
), f"n must be less than the order of the curve"

if curve_id.value in GNARK_CLI_SUPPORTED_CURVES:
from tools.gnark_cli import GnarkCLI

cli = GnarkCLI(curve_id)
ng1ng2 = cli.nG1nG2_operation(n, 1, raw=True)
return G1Point(ng1ng2[0], ng1ng2[1], curve_id)
else:
gen = G1Point(
CURVES[curve_id.value].Gx, CURVES[curve_id.value].Gy, curve_id
)
return gen.scalar_mul(n)
gen = G1Point(CURVES[curve_id.value].Gx, CURVES[curve_id.value].Gy, curve_id)
return gen.scalar_mul(n)

@staticmethod
def msm(points: list["G1Point"], scalars: list[int]) -> "G1Point":
Expand All @@ -638,24 +662,33 @@ def scalar_mul(self, scalar: int) -> "G1Point":
Performs scalar multiplication on the point.
Args:
scalar (int): The scalar to multiply by.
scalar (int): The scalar to multiply by. abs(scalar) should be less than the order of the curve.
Returns:
G1Point: The resulting point after scalar multiplication.
"""
if self.is_infinity():
return self
if scalar < 0:
return -self.scalar_mul(-scalar)
res = ec_safe_mult(
scalar,
(self.x, self.y),
CURVES[self.curve_id.value].a,
CURVES[self.curve_id.value].p,
)
if isinstance(res, EcInfinity):
if scalar == 0:
return G1Point(0, 0, self.curve_id)
return G1Point(res[0], res[1], self.curve_id)

# Fastecdsa C binding.
x, y = curvemath.mul(
str(self.x),
str(self.y),
str(abs(scalar)),
str(CURVES[self.curve_id.value].p),
str(CURVES[self.curve_id.value].a),
str(CURVES[self.curve_id.value].b),
str(CURVES[self.curve_id.value].n),
str(CURVES[self.curve_id.value].Gx),
str(CURVES[self.curve_id.value].Gy),
)
# Fastecdsa already returns (0, 0) for the identity element.
if scalar < 0:
return -G1Point(int(x), int(y), self.curve_id)
else:
return G1Point(int(x), int(y), self.curve_id)

def add(self, other: "G1Point") -> "G1Point":
"""
Expand Down Expand Up @@ -832,6 +865,9 @@ class G1G2Pair:
q: G2Point
curve_id: CurveID = None

def __hash__(self):
return hash((self.p, self.q, self.curve_id))

def __post_init__(self):
if self.p.curve_id != self.q.curve_id:
raise ValueError("Points are not on the same curve")
Expand Down
39 changes: 28 additions & 11 deletions hydra/hints/ecip.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@
)
from hydra.poseidon_transcript import hades_permutation

# Check if a Fp2 element is a quadratic residue in Fp2, assuming the irreducible polynomial is x^2 + 1
# def is_quad_residue_fp2(x: Fp2) -> bool:


def get_field_type_from_ec_point(P) -> type[T]:
if isinstance(P, G1Point):
Expand Down Expand Up @@ -114,9 +111,19 @@ def zk_ecip_hint(


def verify_ecip(
Bs: list[G1Point] | list[G2Point], scalars: list[int], A0: G1Point | G2Point = None
Bs: list[G1Point] | list[G2Point],
scalars: list[int],
Q: G1Point | G2Point = None,
sum_dlog: FunctionFelt[T] = None,
A0: G1Point | G2Point = None,
) -> bool:
Q, sum_dlog = zk_ecip_hint(Bs, scalars)
# Prover :
if Q is None or sum_dlog is None:
Q, sum_dlog = zk_ecip_hint(Bs, scalars)
else:
Q = Q
sum_dlog = sum_dlog
# Verifier :
assert sum_dlog.validate_degrees(len(Bs))
epns = [
positive_negative_multiplicities(neg_3_base_le(scalar)) for scalar in scalars
Expand All @@ -128,6 +135,8 @@ def verify_ecip(

field = get_base_field(Q.curve_id.value, field_type)

# Verifier must derive a random point A0.
# In non-interactive context, hash public inputs (Bs, scalars) and prover info (Q, sum_dlog) then map the resulting field element to a point.
if A0 is None:
A0 = ec_group_class.gen_random_point(Q.curve_id)
else:
Expand Down Expand Up @@ -161,8 +170,12 @@ def verify_ecip(

LHS = coeff0 * sum_dlog.evaluate(xA0, yA0) - coeff2 * sum_dlog.evaluate(xA2, yA2)

# Verifier must check that LHS = RHS.
assert LHS == RHS, f"LHS: {LHS}, RHS: {RHS}"
assert Q == ec_group_class.msm(Bs, scalars)

#########
assert Q == ec_group_class.msm(Bs, scalars) # Sanity check.
##########
return True


Expand Down Expand Up @@ -256,15 +269,16 @@ def line(P: G1Point | G2Point, Q: G1Point | G2Point) -> FF[T]:
@dataclass
class FF:
"""
Represents a polynomial over F_p[x]
Represents a polynomial over F_p[x] or F_p^2[x]
Example : F(x, y) = c0(x) + c1(x) * y + c2(x) * y^2 + ...
where c0, c1, c2, ... are polynomials over F_p[x] or F_p^2[x]
"""

coeffs: list[Polynomial[T]]
y2: Polynomial[T]
y2: Polynomial[T] # = x^3 + ax + b, where a, b are the curve parameters
p: int
curve_id: CurveID
type: type[T]
type: type[T] # PyFelt or Fp2

def __init__(self, coeffs: list[Polynomial[T]], curve_id: CurveID):
self.coeffs = coeffs
Expand Down Expand Up @@ -360,11 +374,14 @@ def reduce(self) -> "FF":
y2 = y2 * y2
return FF([deg_0_coeff, deg_1_coeff], self.curve_id)

def to_poly(self) -> Polynomial:
def to_poly(self) -> Polynomial[T]:
"""
"Downcasts" the FF to a Polynomial iff the FF is of degree 1.
"""
assert len(self.coeffs) == 1
return self.coeffs[0]

def div_by_poly(self, poly: Polynomial) -> "FF":
def div_by_poly(self, poly: Polynomial[T]) -> "FF":
return FF([c // poly for c in self.coeffs], self.curve_id)

def normalize(self) -> "FF":
Expand Down
22 changes: 22 additions & 0 deletions hydra/hints/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,28 @@ def bigint_split(
return coeffs[::-1]


def to_int(value: str | int) -> int:
"""
Convert a string or integer to an integer. Supports hexadecimal and decimal strings.
"""
if isinstance(value, str):
value = value.strip() # Trim whitespaces
if value.lower().startswith("0x"):
try:
return int(value, 16)
except ValueError:
raise ValueError(f"Invalid hexadecimal value: {value}")
else:
try:
return int(value)
except ValueError:
raise ValueError(f"Invalid decimal value: {value}")
elif isinstance(value, int):
return value
else:
raise TypeError(f"Expected str or int, got {type(value).__name__}")


def int_to_u384(x: int | PyFelt, as_hex=True) -> str:
limbs = bigint_split(x, 4, 2**96)
if as_hex:
Expand Down
Loading

0 comments on commit 29d0c33

Please sign in to comment.