diff --git a/dot_ring/ring_proof/columns/columns.py b/dot_ring/ring_proof/columns/columns.py index ffc9020..c257a18 100644 --- a/dot_ring/ring_proof/columns/columns.py +++ b/dot_ring/ring_proof/columns/columns.py @@ -5,21 +5,15 @@ from dataclasses import dataclass from typing import cast -from dot_ring.ring_proof.constants import ( - MAX_RING_SIZE, - OMEGA, - S_PRIME, - SIZE, - Blinding_Base, - PaddingPoint, - SeedPoint, -) +from dot_ring.ring_proof.constants import DEFAULT_SIZE, MAX_RING_SIZE, OMEGAS, S_PRIME, Blinding_Base, PaddingPoint, SeedPoint from dot_ring.ring_proof.curve.bandersnatch import TwistedEdwardCurve as TE from dot_ring.ring_proof.helpers import Helpers as H +from dot_ring.ring_proof.params import RingProofParams from dot_ring.ring_proof.pcs.kzg import KZG from dot_ring.ring_proof.polynomial.interpolation import poly_interpolate_fft -h_vec = json.load(open(os.path.join(os.path.dirname(__file__), "h_vec.json"))) +_H_VEC_DEFAULT = json.load(open(os.path.join(os.path.dirname(__file__), "h_vec.json"))) +_H_VEC_DEFAULT = [tuple(pt) for pt in _H_VEC_DEFAULT] Scalar = int G1Point = tuple @@ -31,12 +25,14 @@ class Column: evals: list[int] coeffs: list[int] | None = None commitment: G1Point | None = None - size: int = SIZE + size: int = DEFAULT_SIZE - def interpolate(self, domain_omega: int = OMEGA, prime: int = S_PRIME) -> None: + def interpolate(self, domain_omega: int = OMEGAS[DEFAULT_SIZE], prime: int = S_PRIME) -> None: """Fill `self.coeffs` from `self.evals` using FFT interpolation.""" if self.coeffs is None: - self.evals += [0] * (SIZE - len(self.evals)) + if len(self.evals) > self.size: + raise ValueError(f"{self.name} evals length {len(self.evals)} exceeds column size {self.size}") + self.evals += [0] * (self.size - len(self.evals)) self.coeffs = poly_interpolate_fft(self.evals, domain_omega, prime) def commit(self) -> None: @@ -48,15 +44,27 @@ def commit(self) -> None: @dataclass(slots=True) class PublicColumnBuilder: - size: int = SIZE + size: int = DEFAULT_SIZE prime: int = S_PRIME - omega: int = OMEGA + omega: int = OMEGAS[DEFAULT_SIZE] + max_ring_size: int = MAX_RING_SIZE + padding_rows: int = 4 + + @classmethod + def from_params(cls, params: RingProofParams) -> PublicColumnBuilder: + return cls( + size=params.domain_size, + prime=params.prime, + omega=params.omega, + max_ring_size=params.max_ring_size, + padding_rows=params.padding_rows, + ) - def _pad_ring_with_padding_point(self, pk_ring: list[tuple[int, int]], size: int = MAX_RING_SIZE) -> list[tuple[int, int]]: + def _pad_ring_with_padding_point(self, pk_ring: list[tuple[int, int]]) -> list[tuple[int, int]]: """Pad ring in‑place with the special padding point until size.""" # padding_sw = sw.from_twisted_edwards(PaddingPoint) padding_sw = PaddingPoint - while len(pk_ring) < MAX_RING_SIZE: + while len(pk_ring) < self.max_ring_size: pk_ring.append(padding_sw) return pk_ring @@ -70,23 +78,31 @@ def _h_vector(self, blinding_base: tuple[int, int] = Blinding_Base) -> list[tupl def build(self, ring_pk: list[tuple[int, int]]) -> tuple[Column, Column, Column]: """Return (Px, Py, s) columns fully committed.""" - if len(ring_pk) < MAX_RING_SIZE: + if len(ring_pk) < self.max_ring_size: ring_pk = self._pad_ring_with_padding_point(ring_pk) + if len(ring_pk) > self.size - self.padding_rows: + raise ValueError(f"ring size {len(ring_pk)} exceeds max supported size {self.size - self.padding_rows}") # 1. ensure ring size - for i in range(self.size - 4 - len(ring_pk)): - ring_pk.append(h_vec[i]) - ring_pk.extend([(0, 0)] * 4) + fill_count = self.size - self.padding_rows - len(ring_pk) + if fill_count > 0: + if self.size == len(_H_VEC_DEFAULT): + h_vec = _H_VEC_DEFAULT + else: + h_vec = self._h_vector() + ring_pk.extend(h_vec[:fill_count]) + if self.padding_rows > 0: + ring_pk.extend([(0, 0)] * self.padding_rows) # 2. unzip into x/y vectors px, py = H.unzip(ring_pk) # 3. selector vector - sel = [1 if i < MAX_RING_SIZE else 0 for i in range(self.size)] + sel = [1 if i < self.max_ring_size else 0 for i in range(self.size)] # 4. Columns - col_px = Column("Px", px) - col_py = Column("Py", py) - col_s = Column("s", sel) + col_px = Column("Px", px, size=self.size) + col_py = Column("Py", py, size=self.size) + col_s = Column("s", sel, size=self.size) for col in (col_px, col_py, col_s): col.interpolate(self.omega, self.prime) col.commit() @@ -99,15 +115,45 @@ class WitnessColumnBuilder: selector_vector: list[int] producer_index: int secret_t: int - size: int = SIZE - omega: int = OMEGA + size: int = DEFAULT_SIZE + omega: int = OMEGAS[DEFAULT_SIZE] prime: int = S_PRIME + max_ring_size: int = MAX_RING_SIZE + padding_rows: int = 4 + + @classmethod + def from_params( + cls, + ring_pk: list[tuple[int, int]], + selector_vector: list[int], + producer_index: int, + secret_t: int, + params: RingProofParams, + ) -> WitnessColumnBuilder: + return cls( + ring_pk=ring_pk, + selector_vector=selector_vector, + producer_index=producer_index, + secret_t=secret_t, + size=params.domain_size, + omega=params.omega, + prime=params.prime, + max_ring_size=params.max_ring_size, + padding_rows=params.padding_rows, + ) def _bits_vector(self) -> list[int]: - bv = [1 if i == self.producer_index else 0 for i in range(MAX_RING_SIZE)] + bv = [1 if i == self.producer_index else 0 for i in range(self.max_ring_size)] t_bits = bin(self.secret_t)[2:][::-1] bv.extend(int(b) for b in t_bits) - while len(bv) < self.size - 4: + pad_to = self.size - self.padding_rows + if len(bv) > pad_to: + raise ValueError( + "b vector length exceeds available rows: " + f"{len(bv)} > {pad_to} (ring_size={self.max_ring_size}, " + f"secret_t_bits={len(t_bits)}, padding_rows={self.padding_rows})" + ) + while len(bv) < pad_to: bv.append(0) bv.append(0) # padding bit return bv @@ -116,14 +162,16 @@ def _conditional_sum_accumulator(self, b_vector: list[int]) -> tuple[list[int], seed_sw = SeedPoint acc = [seed_sw] - for i in range(1, self.size - 3): + acc_len = self.size - self.padding_rows + 1 + for i in range(1, acc_len): next_pt = acc[i - 1] if b_vector[i - 1] == 0 else cast(tuple[int, int], TE.add(acc[i - 1], self.ring_pk[i - 1])) acc.append(next_pt) return H.unzip(acc) def _inner_product_accumulator(self, b_vector: list[int]) -> list[int]: acc = [0] - for i in range(1, self.size - 3): + acc_len = self.size - self.padding_rows + 1 + for i in range(1, acc_len): acc.append(acc[i - 1] + b_vector[i - 1] * self.selector_vector[i - 1]) return acc @@ -133,10 +181,10 @@ def build(self) -> tuple[Column, Column, Column, Column]: acc_ip = self._inner_product_accumulator(b_vec) columns = [ - Column("b", b_vec), - Column("accx", acc_x), - Column("accy", acc_y), - Column("accip", acc_ip), + Column("b", b_vec, size=self.size), + Column("accx", acc_x, size=self.size), + Column("accy", acc_y, size=self.size), + Column("accip", acc_ip, size=self.size), ] for col in columns: col.interpolate(self.omega, self.prime) diff --git a/dot_ring/ring_proof/constants.py b/dot_ring/ring_proof/constants.py index b905c50..116a732 100644 --- a/dot_ring/ring_proof/constants.py +++ b/dot_ring/ring_proof/constants.py @@ -37,25 +37,28 @@ S_A: int = 10773120815616481058602537765553212789256758185246796157495669123169359657269 S_B: int = 29569587568322301171008055308580903175558631321415017492731745847794083609535 +DEFAULT_SIZE: int = 512 OMEGA_2048: int = 49307615728544765012166121802278658070711169839041683575071795236746050763237 - - -# 512‑th root -OMEGA_USED: int = 4214636447306890335450803789410475782380792963881561516561680164772024173390 - -# Compute the 512‑th root ourselves to cross‑check -SIZE: int = 512 # FFT domain size for witness polynomials -OMEGA: int = pow(OMEGA_2048, 2048 // SIZE, S_PRIME) - - -# if OMEGA != OMEGA_USED: # Guardrail to detect accidental param drift -# raise ValueError("Computed 512‑th root does not match reference value") +OMEGA_1024 = pow(OMEGA_2048, 2048 // 1024, S_PRIME) +OMEGA_512: int = pow(OMEGA_2048, 2048 // 512, S_PRIME) # Pre‑compute the entire evaluation domain for fast access. -D_512: list[int] = [pow(OMEGA, i, S_PRIME) for i in range(SIZE)] +D_512: list[int] = [pow(OMEGA_512, i, S_PRIME) for i in range(512)] +D_1024: list[int] = [pow(OMEGA_1024, i, S_PRIME) for i in range(1024)] D_2048: list[int] = [pow(OMEGA_2048, i, S_PRIME) for i in range(2048)] +OMEGAS = { + 512: OMEGA_512, + 1024: OMEGA_1024, + 2048: OMEGA_2048, +} + +EVAL_DOMAINS = { + 512: D_512, + 1024: D_1024, + 2048: D_2048, +} MAX_RING_SIZE: int = 255 # Upper bound enforced by the constraint system @@ -69,9 +72,9 @@ "S_A", "S_B", "OMEGA_2048", - "OMEGA_USED", - "OMEGA", - "SIZE", + "OMEGA_1024", + "OMEGA_512", + "DEFAULT_SIZE", "D_512", "D_2048", "MAX_RING_SIZE", diff --git a/dot_ring/ring_proof/constraints/aggregation.py b/dot_ring/ring_proof/constraints/aggregation.py index 9e73993..c740915 100644 --- a/dot_ring/ring_proof/constraints/aggregation.py +++ b/dot_ring/ring_proof/constraints/aggregation.py @@ -3,12 +3,9 @@ from collections.abc import Sequence from dot_ring.curve.native_field.vector_ops import vect_add -from dot_ring.ring_proof.constants import D_512 as D from dot_ring.ring_proof.constants import S_PRIME -from dot_ring.ring_proof.polynomial.interpolation import ( - poly_interpolate_fft, - poly_mul_fft, -) +from dot_ring.ring_proof.params import RingProofParams +from dot_ring.ring_proof.polynomial.interpolation import poly_interpolate_fft from dot_ring.ring_proof.polynomial.ops import ( poly_multiply, vect_scalar_mul, @@ -20,10 +17,10 @@ ] -def vanishing_poly(k: int, omega_root: int, prime: int = S_PRIME) -> list[int]: +def vanishing_poly(domain: list[int], k: int = 3, prime: int = S_PRIME) -> list[int]: vanishing_term = [1] for i in range(1, k + 1): - vanishing_term = poly_mul_fft(vanishing_term, [-D[-i], 1], prime) + vanishing_term = poly_multiply(vanishing_term, [-domain[-i], 1], prime) return vanishing_term @@ -33,6 +30,7 @@ def aggregate_constraints( omega_root: int, prime: int = S_PRIME, k: int = 3, + domain: list[int] | None = None, ) -> list[int]: result = [0] * len(polys[0]) for poly, alpha in zip(polys, alphas, strict=False): @@ -41,7 +39,9 @@ def aggregate_constraints( interpolated_result = poly_interpolate_fft(result, omega_root, prime) # get vanishing ply - v_t = vanishing_poly(k, omega_root, prime) + if domain is None: + domain = RingProofParams().domain + v_t = vanishing_poly(domain, k, prime) # mul with c_agg final_cs_agg = poly_multiply(interpolated_result, v_t, prime) diff --git a/dot_ring/ring_proof/constraints/constraints.py b/dot_ring/ring_proof/constraints/constraints.py index 2254fe0..daa8f8c 100644 --- a/dot_ring/ring_proof/constraints/constraints.py +++ b/dot_ring/ring_proof/constraints/constraints.py @@ -2,53 +2,44 @@ from collections.abc import Mapping, Sequence from dataclasses import dataclass, field +from functools import lru_cache from typing import Any, cast from dot_ring.curve.native_field.vector_ops import vect_add, vect_mul, vect_sub - -# from concurrent.futures import ProcessPoolExecutor from dot_ring.curve.specs.bandersnatch import BandersnatchParams -from dot_ring.ring_proof.constants import ( - D_512 as D, -) - -# from dot_ring.ring_proof.short_weierstrass.curve import ShortWeierstrassCurve as sw -from dot_ring.ring_proof.constants import ( - D_2048, - OMEGA, - S_PRIME, - SIZE, - SeedPoint, -) -from dot_ring.ring_proof.polynomial.ops import lagrange_basis_polynomial, poly_evaluate - - -def _to_radix4(vec: Sequence[int]) -> list[int]: - """Convert a radix‑2 evaluation vector to radix‑4""" - return cast(list[int], poly_evaluate(vec, D_2048, S_PRIME)) - - -_NOT_LAST = vect_sub(D_2048, pow(OMEGA, 508, S_PRIME), S_PRIME) +from dot_ring.ring_proof.constants import S_PRIME, SeedPoint +from dot_ring.ring_proof.params import RingProofParams +from dot_ring.ring_proof.polynomial.fft import evaluate_poly_fft +from dot_ring.ring_proof.polynomial.ops import lagrange_basis_polynomial -_L0_RAD4: list[int] | None = None -_LN4_RAD4: list[int] | None = None -_l0_coeffs = lagrange_basis_polynomial(D, 0, S_PRIME) -_ln4_coeffs = lagrange_basis_polynomial(D, SIZE - 4, S_PRIME) +def _to_radix(vec: Sequence[int], radix_domain_size: int, radix_omega: int) -> list[int]: + """Convert a radix‑2 evaluation vector to radix‑domain evaluations.""" + return cast(list[int], evaluate_poly_fft(list(vec), radix_domain_size, radix_omega, S_PRIME)) -def get_radix4_constants() -> tuple[list[int], list[int]]: - global _L0_RAD4, _LN4_RAD4 - if _L0_RAD4 is None: - _L0_RAD4 = cast(list[int], poly_evaluate(_l0_coeffs, D_2048, S_PRIME)) - if _LN4_RAD4 is None: - _LN4_RAD4 = cast(list[int], poly_evaluate(_ln4_coeffs, D_2048, S_PRIME)) - return _L0_RAD4, _LN4_RAD4 +def _shift(vec: Sequence[int], shift: int) -> list[int]: + """Rotate vector right by shift (≈ multiply index by ω).""" + return list(vec[shift:]) + list(vec[:shift]) -def _shift(vec: Sequence[int]) -> list[int]: - """Rotate vector right by 4 (≈ multiply index by ω).""" - return list(vec[4:]) + list(vec[:4]) +@lru_cache(maxsize=16) +def _constraint_context(params: RingProofParams) -> dict[str, Any]: + domain = params.domain + radix_domain = params.radix_domain + last_index = params.last_index + not_last = vect_sub(radix_domain, pow(params.omega, last_index, S_PRIME), S_PRIME) + l0_coeffs = lagrange_basis_polynomial(domain, 0, S_PRIME) + ln_coeffs = lagrange_basis_polynomial(domain, last_index, S_PRIME) + l0_radix = cast(list[int], evaluate_poly_fft(l0_coeffs, params.radix_domain_size, params.radix_omega, S_PRIME)) + ln_radix = cast(list[int], evaluate_poly_fft(ln_coeffs, params.radix_domain_size, params.radix_omega, S_PRIME)) + return { + "radix_domain": radix_domain, + "shift": params.radix_shift, + "not_last": not_last, + "l0_radix": l0_radix, + "ln_radix": ln_radix, + } @dataclass(slots=True) @@ -64,6 +55,7 @@ class RingConstraintBuilder: acc_x: Sequence[int] acc_y: Sequence[int] acc_ip: Sequence[int] + params: RingProofParams = field(default_factory=RingProofParams, repr=False) _px4: list[int] = field(init=False, repr=False) _py4: list[int] = field(init=False, repr=False) @@ -72,15 +64,18 @@ class RingConstraintBuilder: _accx4: list[int] = field(init=False, repr=False) _accy4: list[int] = field(init=False, repr=False) _accip4: list[int] = field(init=False, repr=False) + _ctx: dict[str, Any] = field(init=False, repr=False) def __post_init__(self) -> None: - self._px4 = _to_radix4(self.px) - self._py4 = _to_radix4(self.py) - self._s4 = _to_radix4(self.s) - self._b4 = _to_radix4(self.b) - self._accx4 = _to_radix4(self.acc_x) - self._accy4 = _to_radix4(self.acc_y) - self._accip4 = _to_radix4(self.acc_ip) + self._ctx = _constraint_context(self.params) + radix_domain = self._ctx["radix_domain"] + self._px4 = _to_radix(self.px, len(radix_domain), self.params.radix_omega) + self._py4 = _to_radix(self.py, len(radix_domain), self.params.radix_omega) + self._s4 = _to_radix(self.s, len(radix_domain), self.params.radix_omega) + self._b4 = _to_radix(self.b, len(radix_domain), self.params.radix_omega) + self._accx4 = _to_radix(self.acc_x, len(radix_domain), self.params.radix_omega) + self._accy4 = _to_radix(self.acc_y, len(radix_domain), self.params.radix_omega) + self._accip4 = _to_radix(self.acc_ip, len(radix_domain), self.params.radix_omega) # convenient classmethod for Column builders @classmethod @@ -110,20 +105,20 @@ def compute(self) -> dict[str, list[int]]: compute_all = compute # alias def _c1(self) -> list[int]: - accip_w = _shift(self._accip4) + accip_w = _shift(self._accip4, self._ctx["shift"]) constraint = vect_sub( vect_sub(accip_w, self._accip4, S_PRIME), vect_mul(self._b4, self._s4, S_PRIME), S_PRIME, ) - c1x = vect_mul(constraint, _NOT_LAST, S_PRIME) + c1x = vect_mul(constraint, self._ctx["not_last"], S_PRIME) return c1x def _c2(self) -> list[list[int]]: bx = self._b4 - accx_w = _shift(self._accx4) - accy_w = _shift(self._accy4) + accx_w = _shift(self._accx4, self._ctx["shift"]) + accy_w = _shift(self._accy4, self._ctx["shift"]) te_coeff_a = BandersnatchParams.EDWARDS_A # BandersnatchParams.EDWARDS_A % S_PRIME b = bx x1, x2, x3 = self._accx4, self._px4, accx_w @@ -141,7 +136,7 @@ def _c2(self) -> list[list[int]]: term1 = vect_mul(x3, vect_add(y1_y2, a_x1_x2, S_PRIME), S_PRIME) term2 = vect_mul(b, vect_sub(term1, vect_add(x1_y1, y2_x2, S_PRIME), S_PRIME), S_PRIME) term3 = vect_add(term2, vect_mul(one_m_b, x3_m_x1, S_PRIME), S_PRIME) - c2x = vect_mul(term3, _NOT_LAST, S_PRIME) + c2x = vect_mul(term3, self._ctx["not_last"], S_PRIME) # print("c2x here", c2x) # accx_m_px = vect_sub(self._accx4, self._px4, S_PRIME) @@ -187,7 +182,7 @@ def _c2(self) -> list[list[int]]: term1 = vect_mul(y3, vect_sub(x1_y2, x2_y1, S_PRIME), S_PRIME) term2 = vect_mul(b, vect_sub(term1, vect_sub(x1_y1, y2_x2, S_PRIME), S_PRIME), S_PRIME) term3 = vect_add(term2, vect_mul(one_m_b, y3_m_y1, S_PRIME), S_PRIME) - c3x = vect_mul(term3, _NOT_LAST, S_PRIME) + c3x = vect_mul(term3, self._ctx["not_last"], S_PRIME) return [c2x, c3x] def _c4(self) -> list[int]: @@ -205,9 +200,8 @@ def _c5(self) -> list[list[int]]: # print("result here:", rx_psx, ry_psy) - l0_rad4, ln4_rad4 = get_radix4_constants() - assert l0_rad4 is not None - assert ln4_rad4 is not None + l0_rad4 = self._ctx["l0_radix"] + ln4_rad4 = self._ctx["ln_radix"] c5x = vect_add( vect_mul(vect_sub(self._accx4, seed_x, S_PRIME), l0_rad4, S_PRIME), @@ -224,9 +218,8 @@ def _c5(self) -> list[list[int]]: return [c5x, c6x] def _c7(self) -> list[int]: - l0_rad4, ln4_rad4 = get_radix4_constants() - assert l0_rad4 is not None - assert ln4_rad4 is not None + l0_rad4 = self._ctx["l0_radix"] + ln4_rad4 = self._ctx["ln_radix"] c7x = vect_add( vect_mul(self._accip4, l0_rad4, S_PRIME), diff --git a/dot_ring/ring_proof/params.py b/dot_ring/ring_proof/params.py new file mode 100644 index 0000000..5ee8d4a --- /dev/null +++ b/dot_ring/ring_proof/params.py @@ -0,0 +1,146 @@ +from __future__ import annotations + +from dataclasses import dataclass +from functools import lru_cache + +from dot_ring.ring_proof.constants import D_2048, DEFAULT_SIZE, MAX_RING_SIZE, OMEGA_2048, S_PRIME + + +def _is_power_of_two(n: int) -> bool: + return n > 0 and (n & (n - 1)) == 0 + + +def _omega_for_domain(domain_size: int, prime: int = S_PRIME, base_root: int = OMEGA_2048, base_size: int = 2048) -> int: + if base_size % domain_size != 0: + raise ValueError(f"Domain size {domain_size} must divide {base_size}") + return pow(base_root, base_size // domain_size, prime) + + +@lru_cache(maxsize=32) +def _domain_for_size( + domain_size: int, + prime: int = S_PRIME, + base_root: int = OMEGA_2048, + base_size: int = 2048, +) -> tuple[int, ...]: + omega = _omega_for_domain(domain_size, prime, base_root, base_size) + return tuple(pow(omega, i, prime) for i in range(domain_size)) + + +def _sqrt_mod_prime(n: int, prime: int) -> int: + """Tonelli-Shanks modular square root for odd primes.""" + if n == 0: + return 0 + if prime % 4 == 3: + return pow(n, (prime + 1) // 4, prime) + # Check n is a quadratic residue + if pow(n, (prime - 1) // 2, prime) != 1: + raise ValueError("No square root exists for provided value") + + # Factor prime-1 = q * 2^s with q odd + q = prime - 1 + s = 0 + while q % 2 == 0: + s += 1 + q //= 2 + + # Find a quadratic non-residue z + z = 2 + while pow(z, (prime - 1) // 2, prime) != prime - 1: + z += 1 + + m = s + c = pow(z, q, prime) + x = pow(n, (q + 1) // 2, prime) + t = pow(n, q, prime) + + while t != 1: + i = 1 + t2i = (t * t) % prime + while i < m: + if t2i == 1: + break + t2i = (t2i * t2i) % prime + i += 1 + b = pow(c, 1 << (m - i - 1), prime) + x = (x * b) % prime + t = (t * b * b) % prime + c = (b * b) % prime + m = i + return x + + +@lru_cache(maxsize=8) +def _extend_root_to_size(base_root: int, base_size: int, target_size: int, prime: int) -> tuple[int, int]: + """Extend a root of unity by repeated square roots until reaching target size.""" + root = base_root + size = base_size + while size < target_size: + root = _sqrt_mod_prime(root, prime) + size *= 2 + return root, size + + +@dataclass(frozen=True) +class RingProofParams: + domain_size: int = DEFAULT_SIZE + max_ring_size: int = MAX_RING_SIZE + padding_rows: int = 4 + radix_domain_size: int | None = None + prime: int = S_PRIME + base_root: int = OMEGA_2048 + base_root_size: int = 2048 + + def __post_init__(self) -> None: + radix_domain_size = self.radix_domain_size + if radix_domain_size is None: + radix_domain_size = self.domain_size * 4 + object.__setattr__(self, "radix_domain_size", radix_domain_size) + if not _is_power_of_two(self.domain_size): + raise ValueError(f"domain_size must be a power of two, got {self.domain_size}") + if not _is_power_of_two(radix_domain_size): + raise ValueError(f"radix_domain_size must be a power of two, got {radix_domain_size}") + if radix_domain_size % self.domain_size != 0: + raise ValueError(f"domain_size {self.domain_size} must divide radix_domain_size {radix_domain_size}") + if radix_domain_size > self.base_root_size: + root, size = _extend_root_to_size(self.base_root, self.base_root_size, radix_domain_size, self.prime) + object.__setattr__(self, "base_root", root) + object.__setattr__(self, "base_root_size", size) + if self.base_root_size % radix_domain_size != 0: + raise ValueError(f"radix_domain_size {radix_domain_size} must divide base_root_size {self.base_root_size}") + if self.padding_rows < 1: + raise ValueError("padding_rows must be >= 1 to preserve accumulator structure") + if self.padding_rows >= self.domain_size: + raise ValueError("padding_rows must be less than domain_size") + if self.max_ring_size > self.domain_size - self.padding_rows: + raise ValueError(f"max_ring_size {self.max_ring_size} exceeds supported size {self.domain_size - self.padding_rows}") + + @property + def omega(self) -> int: + return _omega_for_domain(self.domain_size, self.prime, self.base_root, self.base_root_size) + + @property + def domain(self) -> list[int]: + return list(_domain_for_size(self.domain_size, self.prime, self.base_root, self.base_root_size)) + + @property + def radix_omega(self) -> int: + return _omega_for_domain(self.radix_domain_size, self.prime, self.base_root, self.base_root_size) + + @property + def radix_domain(self) -> list[int]: + if self.radix_domain_size == 2048 and self.base_root_size == 2048 and self.base_root == OMEGA_2048: + return list(D_2048) + return list(_domain_for_size(self.radix_domain_size, self.prime, self.base_root, self.base_root_size)) + + @property + def radix_shift(self) -> int: + return self.radix_domain_size // self.domain_size + + @property + def last_index(self) -> int: + return self.domain_size - self.padding_rows + + @property + def max_effective_ring_size(self) -> int: + return self.domain_size - self.padding_rows diff --git a/dot_ring/ring_proof/pcs/srs.py b/dot_ring/ring_proof/pcs/srs.py index 05356bd..2b68172 100644 --- a/dot_ring/ring_proof/pcs/srs.py +++ b/dot_ring/ring_proof/pcs/srs.py @@ -111,8 +111,8 @@ def from_loaded(cls, max_deg: int) -> SRS: return cls(g1_jac, g2_jac, g1_points=g1_points, g2_points=g2_points) @staticmethod - @lru_cache(maxsize=128) - def default(max_deg: int = 2048) -> SRS: + @lru_cache(maxsize=4) + def default(max_deg: int = 6144) -> SRS: return SRS.from_loaded(max_deg) diff --git a/dot_ring/ring_proof/polynomial/interpolation.py b/dot_ring/ring_proof/polynomial/interpolation.py index d3e231c..ba66420 100644 --- a/dot_ring/ring_proof/polynomial/interpolation.py +++ b/dot_ring/ring_proof/polynomial/interpolation.py @@ -1,4 +1,3 @@ -from dot_ring.ring_proof.constants import SIZE from dot_ring.ring_proof.polynomial.fft import _fft_in_place @@ -26,11 +25,6 @@ def fft(a: list[int], omega: int, p: int) -> list[int]: # coeffs to evaluation def poly_interpolate_fft(a: list[int], omega: int, p: int) -> list[int]: # funcs like inverse_fft from evals to poly coeffs n = len(a) - N = next_power_of_two(n) - omega_2048 = 49307615728544765012166121802278658070711169839041683575071795236746050763237 - if N > SIZE: - omega = pow(omega_2048, (2048 // N), p) - omega_inv = modinv(omega, p) y = fft(a, omega_inv, p) n_inv = modinv(n, p) diff --git a/dot_ring/ring_proof/polynomial/ops.py b/dot_ring/ring_proof/polynomial/ops.py index 64dad45..e1d1000 100644 --- a/dot_ring/ring_proof/polynomial/ops.py +++ b/dot_ring/ring_proof/polynomial/ops.py @@ -1,6 +1,7 @@ from collections.abc import Sequence -from dot_ring.ring_proof.constants import D_512, D_2048, OMEGA, OMEGA_2048 +from dot_ring.ring_proof.constants import D_512, D_2048, OMEGA_2048 +from dot_ring.ring_proof.constants import OMEGA_512 as OMEGA from dot_ring.ring_proof.polynomial.fft import evaluate_poly_fft, inverse_fft diff --git a/dot_ring/ring_proof/proof/linearization_poly.py b/dot_ring/ring_proof/proof/linearization_poly.py index ad1cb8e..cd5c2ce 100644 --- a/dot_ring/ring_proof/proof/linearization_poly.py +++ b/dot_ring/ring_proof/proof/linearization_poly.py @@ -3,8 +3,6 @@ from dot_ring.curve.native_field.vector_ops import vect_mul from dot_ring.curve.specs.bandersnatch import BandersnatchParams from dot_ring.ring_proof.columns.columns import Column -from dot_ring.ring_proof.constants import D_512 as D -from dot_ring.ring_proof.constants import OMEGA as omega from dot_ring.ring_proof.constants import S_PRIME from dot_ring.ring_proof.polynomial.ops import ( poly_add, @@ -23,10 +21,14 @@ def __init__( fixed_cols: list[Column], witness_res: list[Column], alphas: list[int], + domain: list[int], + omega: int, + padding_rows: int = 4, ) -> None: self.t, self.zeta = phase2_eval_point(cur_t, C_q) self.zeta_omega = (self.zeta * omega) % S_PRIME - self.scalar_term = (self.zeta - D[-4]) % S_PRIME + last_index = len(domain) - padding_rows + self.scalar_term = (self.zeta - domain[last_index]) % S_PRIME self.fs = fixed_cols self.wts = witness_res self.alphas = alphas diff --git a/dot_ring/ring_proof/proof/quotient_poly.py b/dot_ring/ring_proof/proof/quotient_poly.py index bd437be..5e81c88 100644 --- a/dot_ring/ring_proof/proof/quotient_poly.py +++ b/dot_ring/ring_proof/proof/quotient_poly.py @@ -1,9 +1,11 @@ -from dot_ring.ring_proof.constants import SIZE from dot_ring.ring_proof.pcs.kzg import KZG from dot_ring.ring_proof.polynomial.ops import poly_division_general class QuotientPoly: + def __init__(self, domain_size: int) -> None: + self.domain_size = domain_size + @staticmethod def poly_vector_xn_minus_1(n: int) -> list[int]: vec = [0] * (n + 1) @@ -21,7 +23,7 @@ def quotient_poly_commitment(self, q_x: list[int]) -> tuple: return c_q def quotient_poly(self, C_agg: list[int]) -> tuple[list[int], tuple]: - qnt_poly = poly_division_general(C_agg, SIZE) + qnt_poly = poly_division_general(C_agg, self.domain_size) # print("q_p:", qnt_poly) C_qp = self.quotient_poly_commitment(qnt_poly) # C_qp_nm=nm(C_qp) diff --git a/dot_ring/ring_proof/verify.py b/dot_ring/ring_proof/verify.py index 87eab5e..273d9a5 100644 --- a/dot_ring/ring_proof/verify.py +++ b/dot_ring/ring_proof/verify.py @@ -11,8 +11,8 @@ from dot_ring import blst as _blst from dot_ring.curve.native_field.scalar import Scalar from dot_ring.curve.specs.bandersnatch import BandersnatchParams -from dot_ring.ring_proof.constants import D_512 as D -from dot_ring.ring_proof.constants import OMEGA, OMEGA_2048, S_PRIME, SIZE +from dot_ring.ring_proof.constants import OMEGA_512 as OMEGA +from dot_ring.ring_proof.constants import OMEGA_2048, S_PRIME from dot_ring.ring_proof.helpers import Helpers as H from dot_ring.ring_proof.pcs.kzg import KZG from dot_ring.ring_proof.pcs.utils import g1_to_blst @@ -26,14 +26,9 @@ blst = cast(Any, _blst) # Pre-compute Scalar constants -OMEGA_S = Scalar(OMEGA) -SIZE_S = Scalar(SIZE) -D_S = [Scalar(d) for d in D] ONE_S = Scalar(1) ZERO_S = Scalar(0) EDWARDS_A_S = Scalar(BandersnatchParams.EDWARDS_A) -INV_SIZE_S = SIZE_S**-1 -OMEGA_POW_SIZE_MINUS_4 = OMEGA_S ** (SIZE - 4) def blst_msm(points: list, scalars: list) -> Any: @@ -59,13 +54,12 @@ def lagrange_at_zeta(domain_size: int, index: int, zeta: int, omega: int, prime: # Use Scalar for optimized arithmetic zeta_s = Scalar(zeta) + omega_s = Scalar(omega) # omega^i if index == 0: omega_i = ONE_S - elif index == SIZE - 4: - omega_i = OMEGA_POW_SIZE_MINUS_4 else: - omega_i = OMEGA_S**index + omega_i = omega_s**index # zeta - omega^i zeta_minus_omega_i = zeta_s - omega_i @@ -78,7 +72,8 @@ def lagrange_at_zeta(domain_size: int, index: int, zeta: int, omega: int, prime: zeta_n_minus_1 = (zeta_s**domain_size) - ONE_S # omega^i / n - omega_i_over_n = omega_i * INV_SIZE_S + inv_size = Scalar(domain_size) ** -1 + omega_i_over_n = omega_i * inv_size # Final: (omega^i / n) * (zeta^n - 1) / (zeta - omega^i) result = omega_i_over_n * zeta_n_minus_1 * (zeta_minus_omega_i**-1) @@ -98,6 +93,7 @@ def __init__( Domain: list, raw_proof_bytes: dict | None = None, transcript_challenge: bytes = b"Bandersnatch_SHA-512_ELL2", + padding_rows: int = 4, ) -> None: ( self.Cb, @@ -122,6 +118,10 @@ def __init__( self.Cpx, self.Cpy, self.Cs = fixed_cols self.relation_to_proove = rl_to_proove self.Result_plus_Seed, self.sp, self.D = rps, seed_point, Domain + self.padding_rows = padding_rows + if self.padding_rows < 1 or self.padding_rows >= len(self.D): + raise ValueError("padding_rows must be >= 1 and less than domain size") + self.last_index = len(self.D) - self.padding_rows self.Cb_blst = g1_to_blst(self.Cb) self.Caccip_blst = g1_to_blst(self.Caccip) @@ -165,7 +165,7 @@ def contributions_to_constraints_eval_at_zeta( sx, sy = Scalar(self.sp[0]), Scalar(self.sp[1]) # Precompute common values - zeta_minus_d4 = zeta - Scalar(self.D[-4]) + zeta_minus_d4 = zeta - Scalar(self.D[self.last_index]) # Inline lagrange_at_zeta for index=0 and index=SIZE-4 # L_i(zeta) = (omega^i / n) * (zeta^n - 1) / (zeta - omega^i) @@ -186,7 +186,7 @@ def contributions_to_constraints_eval_at_zeta( # L_N_4: index=SIZE-4, omega^(SIZE-4) from the domain # omega^(SIZE-4) / n - omega_i_N_4 = Scalar(self.D[-4]) + omega_i_N_4 = Scalar(self.D[self.last_index]) omega_i_over_n_N_4 = omega_i_N_4 * inv_size zeta_minus_omega_i_N_4 = zeta - omega_i_N_4 if zeta_minus_omega_i_N_4 == ZERO_S: @@ -321,7 +321,7 @@ def _prepare_linearization_poly_verification(self) -> tuple[Any, Any, int, int]: alphas_list = [Scalar(a) for a in self.alpha_list] zeta = Scalar(self.zeta_p) - zeta_minus_d4 = zeta - Scalar(self.D[-4]) + zeta_minus_d4 = zeta - Scalar(self.D[self.last_index]) # Cl1 scalar scalar_cl1 = zeta_minus_d4 diff --git a/dot_ring/scripts/export_python_proof.py b/dot_ring/scripts/export_python_proof.py new file mode 100644 index 0000000..704595b --- /dev/null +++ b/dot_ring/scripts/export_python_proof.py @@ -0,0 +1,310 @@ +""" +Export Python-generated ring proofs to arkworks-compatible JSON. + +This script generates multiple ring proof variants using the Python implementation and +exports them in the same format as Rust's arkworks-serialized proof vectors. +""" + +from __future__ import annotations + +import json +import sys +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +# Add project root to path to import dot_ring modules +sys.path.insert(0, str(Path(__file__).parent.parent.parent)) + +from py_ecc.optimized_bls12_381 import normalize as nm + +from dot_ring.curve.specs.bandersnatch import Bandersnatch, BandersnatchParams, BandersnatchPoint +from dot_ring.ring_proof.columns.columns import PublicColumnBuilder as PC +from dot_ring.ring_proof.curve.bandersnatch import TwistedEdwardCurve +from dot_ring.ring_proof.params import RingProofParams +from dot_ring.ring_proof.pcs.srs import srs +from dot_ring.vrf.ring.ring_vrf import RingVRF +from tests.utils.python_to_rust_serde import ( + serialize_bls12_381_g1, + serialize_bls12_381_g2, + serialize_fq_field_element, + serialize_ring_proof, +) + +Blinding_Base = ( + int.from_bytes(bytes.fromhex("e8c5e337ffbd7839ed5aaee576faae32eea01bff684125758d874fa909e8980d"), "little"), + int.from_bytes(bytes.fromhex("e93da06b869766b158d20b843ec648cc68e0b7ba2f7083acf0f154205d04e23e"), "little"), +) +SeedPoint = ( + int.from_bytes(bytes.fromhex("20f354ea2af5f890e0cfac3b044aca2335fc26fa900fbe429fb059b0df319553"), "little"), + int.from_bytes(bytes.fromhex("6e5574f9077fb76c885c36196a832dbadd64142d305be5487724967acf959520"), "little"), +) + +blinding_factor = int.from_bytes(bytes.fromhex("2e98974f0b99a70d4fbe7c1ea62a5ada75c899deb30e9d27f9e5da79177c0619"), "big") + + +@dataclass(frozen=True) +class VariantSpec: + name: str + domain_size: int + ring_size: int + padding_rows: int = 4 + radix_domain_size: int | None = None + prover_index: int = 42 + + +DEFAULT_VARIANTS = [ + VariantSpec( + name="ring_proof_ring64_domain512.json", + domain_size=512, + ring_size=64, + ), + VariantSpec( + name="ring_proof_ring128_domain512.json", + domain_size=512, + ring_size=128, + ), + VariantSpec( + name="ring_proof_ring256_domain1024.json", + domain_size=1024, + ring_size=256, + ), + VariantSpec( + name="ring_proof_ring1024_domain2048.json", + domain_size=2048, + ring_size=1000, + ), +] + + +def generate_test_keys( + num_keys: int, + prover_index: int, +) -> tuple[list[bytes], list[tuple[int, int]], int]: + """ + Generate deterministic, valid Bandersnatch public keys. + + Args: + num_keys: Number of keys in the ring + prover_index: Index of the prover's key + + Returns: + Tuple of (list of compressed key bytes, list of key points, prover index) + """ + if num_keys < 1: + raise ValueError("num_keys must be >= 1") + if prover_index >= num_keys: + raise ValueError("prover_index must be < num_keys") + + base = BandersnatchPoint(BandersnatchParams.GENERATOR_X, BandersnatchParams.GENERATOR_Y) + + keys_bytes: list[bytes] = [] + keys_points: list[tuple[int, int]] = [] + for i in range(1, num_keys + 1): + pt = base * i + keys_points.append((int(pt.x), int(pt.y))) + keys_bytes.append(pt.point_to_string()) + + return keys_bytes, keys_points, prover_index + + +def export_variant(variant: VariantSpec, output_dir: Path) -> dict[str, Any]: + """Generate and export a single proof variant.""" + params = RingProofParams( + domain_size=variant.domain_size, + max_ring_size=variant.ring_size, + padding_rows=variant.padding_rows, + radix_domain_size=variant.radix_domain_size, + ) + + if variant.ring_size > params.max_effective_ring_size: + raise ValueError( + f"ring_size {variant.ring_size} exceeds max supported size {params.max_effective_ring_size} " + f"for domain {variant.domain_size} with padding_rows={variant.padding_rows}" + ) + + # Deterministic test parameters + secret_bits = max(1, blinding_factor.bit_length()) + max_secret_bits = params.domain_size - params.padding_rows - params.max_ring_size + if max_secret_bits < 1: + raise ValueError( + "ring_size too large for any secret_t bits: " + f"ring_size={params.max_ring_size}, domain_size={params.domain_size}, padding_rows={params.padding_rows}" + ) + if secret_bits > max_secret_bits: + raise ValueError( + "secret_t bit length exceeds available rows: " + f"{secret_bits} > {max_secret_bits} (ring_size={params.max_ring_size}, " + f"domain_size={params.domain_size}, padding_rows={params.padding_rows})" + ) + prover_index = min(variant.prover_index, variant.ring_size - 1) + keys_bytes, keys_points, prover_index = generate_test_keys(num_keys=variant.ring_size, prover_index=prover_index) + + # Build fixed columns (this mutates the list, so use a copy) + ring_keys_for_columns = list(keys_points) + fixed_cols = PC.from_params(params).build(ring_keys_for_columns) + + # Producer key + producer_key_bytes = keys_bytes[prover_index] + producer_key_point = keys_points[prover_index] + + # Generate ring proof using a Rust-test-compatible transcript label + proof_components = RingVRF[Bandersnatch].generate_bls_signature( + blinding_factor=blinding_factor, + producer_key=producer_key_bytes, + keys=keys_bytes, + transcript_challenge=b"w3f-ring-proof-test", + params=params, + ) + + # Compute result point (blinded public key) + result_point = TwistedEdwardCurve.mul(blinding_factor, Blinding_Base) + result_point = TwistedEdwardCurve.add(result_point, producer_key_point) + + # Unpack proof components + ( + c_b, + c_acc_ip, + c_acc_x, + c_acc_y, + p_x_zeta, + p_y_zeta, + s_zeta, + b_zeta, + acc_ip_zeta, + acc_x_zeta, + acc_y_zeta, + c_q, + l_zeta_omega, + phi_z, + phi_zw, + ) = proof_components + + # Normalize commitments to affine coordinates + c_b_affine = nm(c_b.commitment) + c_acc_ip_affine = nm(c_acc_ip.commitment) + c_acc_x_affine = nm(c_acc_x.commitment) + c_acc_y_affine = nm(c_acc_y.commitment) + c_q_affine = nm(c_q.commitment) + phi_z_affine = nm(phi_z.proof) + phi_zw_affine = nm(phi_zw.proof) + + # Serialize proof bytes (for size/debug) + proof_bytes = serialize_ring_proof( + column_commitments=[c_b_affine, c_acc_ip_affine, c_acc_x_affine, c_acc_y_affine], + columns_at_zeta=[p_x_zeta, p_y_zeta, s_zeta, b_zeta, acc_ip_zeta, acc_x_zeta, acc_y_zeta], + quotient_commitment=c_q_affine, + lin_at_zeta_omega=l_zeta_omega, + agg_at_zeta_proof=phi_z_affine, + lin_at_zeta_omega_proof=phi_zw_affine, + ) + + # Serialize first 3 SRS points for verifier key + srs_g1_0 = serialize_bls12_381_g1(srs.g1_points[0]) + srs_g2_0 = serialize_bls12_381_g2(srs.g2_points[0]) + srs_g2_1 = serialize_bls12_381_g2(srs.g2_points[1]) + + # Serialize fixed column commitments + fixed_cols_cmts_affine = [ + nm(fixed_cols[0].commitment), + nm(fixed_cols[1].commitment), + nm(fixed_cols[2].commitment), + ] + + verifier_key_bytes = bytearray() + verifier_key_bytes.extend(srs_g1_0) + verifier_key_bytes.extend(srs_g2_0) + verifier_key_bytes.extend(srs_g2_1) + for commitment in fixed_cols_cmts_affine: + verifier_key_bytes.extend(serialize_bls12_381_g1(commitment)) + + # Concatenate column commitments (4 G1 points) + column_commitments_concat = ( + serialize_bls12_381_g1(c_b_affine) + + serialize_bls12_381_g1(c_acc_ip_affine) + + serialize_bls12_381_g1(c_acc_x_affine) + + serialize_bls12_381_g1(c_acc_y_affine) + ).hex() + + # Concatenate columns at zeta (7 field elements) + columns_at_zeta_concat = ( + serialize_fq_field_element(p_x_zeta) + + serialize_fq_field_element(p_y_zeta) + + serialize_fq_field_element(s_zeta) + + serialize_fq_field_element(b_zeta) + + serialize_fq_field_element(acc_ip_zeta) + + serialize_fq_field_element(acc_x_zeta) + + serialize_fq_field_element(acc_y_zeta) + ).hex() + + result = { + "proof": { + "agg_at_zeta_proof": serialize_bls12_381_g1(phi_z_affine).hex(), + "column_commitments": column_commitments_concat, + "columns_at_zeta": columns_at_zeta_concat, + "lin_at_zeta_omega": serialize_fq_field_element(l_zeta_omega).hex(), + "lin_at_zeta_omega_proof": serialize_bls12_381_g1(phi_zw_affine).hex(), + "quotient_commitment": serialize_bls12_381_g1(c_q_affine).hex(), + }, + "verifier_key": { + "verification_key": verifier_key_bytes.hex(), + }, + "metadata": { + "parameters": { + "domain_size": params.domain_size, + "h": { + "x": serialize_fq_field_element(Blinding_Base[0]).hex(), + "y": serialize_fq_field_element(Blinding_Base[1]).hex(), + }, + "seed": { + "x": serialize_fq_field_element(SeedPoint[0]).hex(), + "y": serialize_fq_field_element(SeedPoint[1]).hex(), + }, + "result": { + "x": serialize_fq_field_element(result_point[0]).hex(), + "y": serialize_fq_field_element(result_point[1]).hex(), + }, + }, + }, + } + + output_file = output_dir / variant.name + output_file.parent.mkdir(parents=True, exist_ok=True) + with open(output_file, "w") as f: + json.dump(result, f, indent=2) + + print(f"✓ Proof exported to: {output_file}") + print(f" Proof size: {len(proof_bytes)} bytes") + print(f" Ring size: {params.max_ring_size} keys") + print(f" Domain size: {params.domain_size}") + + return result + + +def export_proof_to_json(output_dir: str | None = None, variants: list[VariantSpec] | None = None) -> list[dict[str, Any]]: + """ + Generate Python ring proofs and export them to JSON in arkworks format. + + Args: + output_dir: Directory to save JSON files (default: tests/vectors/others) + variants: Optional list of VariantSpec entries to export + + Returns: + List of dictionaries containing proofs and parameters + """ + if output_dir is None: + output_dir = str(Path(__file__).parent.parent.parent / "tests" / "vectors" / "others") + + output_path = Path(output_dir) + variant_list = variants or DEFAULT_VARIANTS + + results = [] + for variant in variant_list: + results.append(export_variant(variant, output_path)) + + return results + + +if __name__ == "__main__": + output_dir = sys.argv[1] if len(sys.argv) > 1 else None + export_proof_to_json(output_dir=output_dir) diff --git a/dot_ring/vrf/ring/ring_vrf.py b/dot_ring/vrf/ring/ring_vrf.py index be4f56b..d41961f 100644 --- a/dot_ring/vrf/ring/ring_vrf.py +++ b/dot_ring/vrf/ring/ring_vrf.py @@ -6,9 +6,7 @@ from dot_ring.curve.point import CurvePoint from dot_ring.ring_proof.columns.columns import Column, WitnessColumnBuilder from dot_ring.ring_proof.columns.columns import PublicColumnBuilder as PC -from dot_ring.ring_proof.constants import D_512 as D from dot_ring.ring_proof.constants import ( - OMEGA_2048, S_PRIME, Blinding_Base, PaddingPoint, @@ -18,6 +16,7 @@ from dot_ring.ring_proof.constraints.constraints import RingConstraintBuilder from dot_ring.ring_proof.curve.bandersnatch import TwistedEdwardCurve from dot_ring.ring_proof.helpers import Helpers as H +from dot_ring.ring_proof.params import RingProofParams from dot_ring.ring_proof.pcs.kzg import Opening from dot_ring.ring_proof.pcs.srs import srs from dot_ring.ring_proof.proof.aggregation_poly import AggPoly @@ -179,6 +178,8 @@ def generate_bls_signature( blinding_factor: int, producer_key: bytes | str, keys: list[Any] | str | bytes, + transcript_challenge: bytes = b"Bandersnatch_SHA-512_ELL2", + params: RingProofParams | None = None, ) -> tuple[ Column, Column, @@ -199,6 +200,7 @@ def generate_bls_signature( """ Returns the Ring Proof as an output """ + params = params or RingProofParams() producer_key_point = cls.cv.point.string_to_point(producer_key) if isinstance(producer_key_point, str) or producer_key_point.is_identity(): @@ -221,11 +223,17 @@ def generate_bls_signature( # Handle non-string/bytes keys if necessary, or skip/raise continue - ring_root = PC() # ring_root builder + ring_root = PC.from_params(params) # ring_root builder fixed_cols = ring_root.build(keys_as_bs_points) s_v = fixed_cols[-1].evals producer_index = keys_as_bs_points.index(producer_key_pt) - witness_obj = WitnessColumnBuilder(keys_as_bs_points, s_v, producer_index, blinding_factor) + witness_obj = WitnessColumnBuilder.from_params( + keys_as_bs_points, + s_v, + producer_index, + blinding_factor, + params, + ) witness_res = witness_obj.build() witness_relation_res = witness_obj.result(Blinding_Base) Result_plus_Seed = witness_obj.result_p_seed(witness_relation_res) @@ -238,6 +246,7 @@ def generate_bls_signature( acc_x=cast(list[int], witness_res[1].coeffs), acc_y=cast(list[int], witness_res[2].coeffs), acc_ip=cast(list[int], witness_res[3].coeffs), + params=params, ) constraint_dict = constraints.compute() @@ -260,17 +269,26 @@ def generate_bls_signature( "g2": H.altered_points(srs.g2_points), "commitments": fixed_col_commits, } - t = Transcript(S_PRIME, b"Bandersnatch_SHA-512_ELL2") + t = Transcript(S_PRIME, transcript_challenge) t, alpha = phase1_alphas(t, vk, witness_relation_res, witness_commitments) cd = constraint_dict c_polys = [cd[val] for val in cd] - C_agg = aggregate_constraints(c_polys, alpha, OMEGA_2048, S_PRIME) - qp = QuotientPoly() + C_agg = aggregate_constraints(c_polys, alpha, params.radix_omega, S_PRIME, domain=params.domain) + qp = QuotientPoly(params.domain_size) Q_p, C_q = qp.quotient_poly(C_agg) C_q_commitment = Column(name="C_q", evals=[], commitment=C_q) C_q_nm = nm(C_q) - l_obj = LAggPoly(t, list(H.to_int(C_q_nm)), list(fixed_cols), list(ws), alpha) + l_obj = LAggPoly( + t, + list(H.to_int(C_q_nm)), + list(fixed_cols), + list(ws), + alpha, + domain=params.domain, + omega=params.omega, + padding_rows=params.padding_rows, + ) current_t, zeta, rel_poly_evals, l_agg, zeta_omega, l_zw = l_obj.l_agg_poly() _, _, phi_z, phi_zw = AggPoly.proof_contents_phi( zeta, @@ -313,10 +331,12 @@ def verify_ring_proof( self, message: bytes | CurvePoint, ring_root: RingRoot | bytes, + params: RingProofParams | None = None, ) -> bool: """ Verifies the Ring Proof """ + params = params or RingProofParams() # Decompress ring_root once at the start if isinstance(ring_root, bytes): ring_root = RingRoot.from_bytes(ring_root) @@ -371,17 +391,20 @@ def verify_ring_proof( rltn, res_plus_seed, SeedPoint, - D, + params.domain, + padding_rows=params.padding_rows, ).is_valid() @classmethod def construct_ring_root( cls, keys: list[bytes], + params: RingProofParams | None = None, ) -> RingRoot: """ Constructs the Ring Root """ + params = params or RingProofParams() keys_as_bs_points = [] for key in keys: if not isinstance(key, (str, bytes)): @@ -394,7 +417,7 @@ def construct_ring_root( else: keys_as_bs_points.append((cast(int, point.x), cast(int, point.y))) - ring_root = PC() # ring_root builder + ring_root = PC.from_params(params) # ring_root builder fixed_cols = ring_root.build(keys_as_bs_points) return RingRoot(*fixed_cols) @@ -407,6 +430,7 @@ def prove( secret_key: bytes, producer_key: bytes, keys: list[bytes], + params: RingProofParams | None = None, ) -> "RingVRF": """ Generate ring VRF proof (pedersen vrf proof + ring_proof) @@ -415,7 +439,7 @@ def prove( pedersen_proof = PedersenVRF[cast(Any, cls).cv].prove(alpha, secret_key, ad) # type: ignore[misc] # ring_proof - ring_proof = cls.generate_bls_signature(pedersen_proof._blinding_factor, producer_key, keys) + ring_proof = cls.generate_bls_signature(pedersen_proof._blinding_factor, producer_key, keys, params=params) return cls(pedersen_proof, *ring_proof) @@ -436,6 +460,7 @@ def verify( input: bytes, ad_data: bytes, ring_root: RingRoot | bytes, + params: RingProofParams | None = None, ) -> bool: """ Verify ring VRF proof (pedersen_proof + ring_proof) @@ -448,6 +473,6 @@ def verify( if self.pedersen_proof is None: raise ValueError("Pedersen proof is missing") p_proof_valid = self.pedersen_proof.verify(input, ad_data) - ring_proof_valid = self.verify_ring_proof(self.pedersen_proof.blinded_pk, ring_root) + ring_proof_valid = self.verify_ring_proof(self.pedersen_proof.blinded_pk, ring_root, params=params) return p_proof_valid and ring_proof_valid diff --git a/tests/test_coverage/test_aggregation.py b/tests/test_coverage/test_aggregation.py new file mode 100644 index 0000000..43f7e20 --- /dev/null +++ b/tests/test_coverage/test_aggregation.py @@ -0,0 +1,11 @@ +from dot_ring.ring_proof.constraints.aggregation import aggregate_constraints + + +def test_aggregate_constraints_uses_default_domain_and_trims_zeros(): + polys = [[0, 0, 0, 0]] + alphas = [1] + omega_root = 5 + + result = aggregate_constraints(polys, alphas, omega_root) + + assert result == [] diff --git a/tests/test_coverage/test_columns.py b/tests/test_coverage/test_columns.py new file mode 100644 index 0000000..96c16b9 --- /dev/null +++ b/tests/test_coverage/test_columns.py @@ -0,0 +1,49 @@ +import pytest + +from dot_ring.ring_proof.columns.columns import Column, PublicColumnBuilder, WitnessColumnBuilder +from dot_ring.ring_proof.constants import DEFAULT_SIZE, OMEGAS, S_PRIME, PaddingPoint +from dot_ring.ring_proof.params import RingProofParams + + +def test_column_interpolate_rejects_oversize_evals(): + col = Column("x", [1, 2, 3], size=2) + with pytest.raises(ValueError, match="exceeds column size"): + col.interpolate(domain_omega=OMEGAS[DEFAULT_SIZE], prime=S_PRIME) + + +def test_column_commit_requires_coeffs(): + col = Column("x", [1], size=1) + with pytest.raises(ValueError, match="call interpolate"): + col.commit() + + +def test_public_builder_from_params_and_padding(): + params = RingProofParams(domain_size=8, max_ring_size=3, padding_rows=1) + builder = PublicColumnBuilder.from_params(params) + assert builder.size == 8 + assert builder.max_ring_size == 3 + + ring = [(1, 1)] + padded = builder._pad_ring_with_padding_point(ring) + assert len(padded) == builder.max_ring_size + assert padded[-1] == PaddingPoint + + +def test_public_builder_rejects_oversize_ring(): + builder = PublicColumnBuilder(size=8, max_ring_size=2, padding_rows=1) + ring_pk = [(0, 0)] * 8 + with pytest.raises(ValueError, match="exceeds max supported size"): + builder.build(ring_pk) + + +def test_witness_builder_from_params_and_bits_vector_error(): + params = RingProofParams(domain_size=8, max_ring_size=6, padding_rows=1) + builder = WitnessColumnBuilder.from_params( + ring_pk=[(0, 0)] * params.max_ring_size, + selector_vector=[0] * params.max_ring_size, + producer_index=0, + secret_t=3, + params=params, + ) + with pytest.raises(ValueError, match="b vector length exceeds"): + builder._bits_vector() diff --git a/tests/test_coverage/test_fft.py b/tests/test_coverage/test_fft.py index b2e45f3..ee4e498 100644 --- a/tests/test_coverage/test_fft.py +++ b/tests/test_coverage/test_fft.py @@ -1,6 +1,7 @@ """Tests for FFT module to improve coverage.""" -from dot_ring.ring_proof.constants import OMEGA, S_PRIME +from dot_ring.ring_proof.constants import OMEGA_512 as OMEGA +from dot_ring.ring_proof.constants import S_PRIME from dot_ring.ring_proof.polynomial.fft import ( _fft_in_place, _get_bit_reverse, diff --git a/tests/test_coverage/test_interpolation.py b/tests/test_coverage/test_interpolation.py index 26116dd..d0afe33 100644 --- a/tests/test_coverage/test_interpolation.py +++ b/tests/test_coverage/test_interpolation.py @@ -1,6 +1,7 @@ """Additional tests for interpolation module to improve coverage.""" -from dot_ring.ring_proof.constants import OMEGA, S_PRIME +from dot_ring.ring_proof.constants import OMEGA_512 as OMEGA +from dot_ring.ring_proof.constants import S_PRIME from dot_ring.ring_proof.polynomial.interpolation import poly_interpolate_fft diff --git a/tests/test_coverage/test_params.py b/tests/test_coverage/test_params.py new file mode 100644 index 0000000..b26bf6b --- /dev/null +++ b/tests/test_coverage/test_params.py @@ -0,0 +1,114 @@ +import pytest + +from dot_ring.ring_proof.constants import D_2048, DEFAULT_SIZE, MAX_RING_SIZE, OMEGA_2048, S_PRIME +from dot_ring.ring_proof.params import ( + RingProofParams, + _domain_for_size, + _extend_root_to_size, + _is_power_of_two, + _omega_for_domain, + _sqrt_mod_prime, +) + + +def test_is_power_of_two(): + assert _is_power_of_two(1) + assert _is_power_of_two(8) + assert not _is_power_of_two(0) + assert not _is_power_of_two(6) + + +def test_omega_for_domain_valid(): + omega = _omega_for_domain(DEFAULT_SIZE, S_PRIME, OMEGA_2048, 2048) + assert omega == pow(OMEGA_2048, 2048 // DEFAULT_SIZE, S_PRIME) + + +def test_omega_for_domain_invalid(): + with pytest.raises(ValueError, match="must divide"): + _omega_for_domain(3, S_PRIME, OMEGA_2048, 2048) + + +def test_domain_for_size_matches_omega(): + domain = _domain_for_size(8, S_PRIME, OMEGA_2048, 2048) + omega = _omega_for_domain(8, S_PRIME, OMEGA_2048, 2048) + assert len(domain) == 8 + assert domain[0] == 1 + assert domain[1] == omega + + +def test_sqrt_mod_prime_zero(): + assert _sqrt_mod_prime(0, 7) == 0 + + +def test_sqrt_mod_prime_mod4_3(): + root = _sqrt_mod_prime(2, 7) + assert (root * root) % 7 == 2 + + +def test_sqrt_mod_prime_tonelli_shanks(): + root = _sqrt_mod_prime(10, 13) + assert (root * root) % 13 == 10 + + +def test_sqrt_mod_prime_non_residue(): + with pytest.raises(ValueError, match="No square root"): + _sqrt_mod_prime(2, 13) + + +def test_extend_root_to_size_grows(): + root, size = _extend_root_to_size(1, 4, 8, 7) + assert size == 8 + assert root == 1 + + +def test_ring_proof_params_defaults(): + params = RingProofParams() + + assert params.domain_size == DEFAULT_SIZE + assert params.max_ring_size == MAX_RING_SIZE + assert params.radix_domain_size == DEFAULT_SIZE * 4 + assert params.omega == pow(OMEGA_2048, 2048 // DEFAULT_SIZE, S_PRIME) + assert params.radix_domain == list(D_2048) + assert params.radix_shift == 4 + assert params.last_index == params.domain_size - params.padding_rows + assert params.max_effective_ring_size == params.domain_size - params.padding_rows + + +def test_ring_proof_params_extends_root_size(): + params = RingProofParams( + domain_size=2, + radix_domain_size=8, + prime=7, + base_root=1, + base_root_size=4, + padding_rows=1, + max_ring_size=1, + ) + assert params.base_root_size == 8 + assert params.radix_domain_size == 8 + + +@pytest.mark.parametrize( + ("kwargs", "match"), + [ + ({"domain_size": 3}, "domain_size must be a power of two"), + ({"domain_size": 4, "radix_domain_size": 6}, "radix_domain_size must be a power of two"), + ({"domain_size": 8, "radix_domain_size": 4}, "must divide radix_domain_size"), + ( + { + "domain_size": 4, + "radix_domain_size": 8, + "base_root_size": 12, + "padding_rows": 1, + "max_ring_size": 1, + }, + "must divide base_root_size", + ), + ({"domain_size": 8, "padding_rows": 0, "max_ring_size": 1}, "padding_rows must be >= 1"), + ({"domain_size": 8, "padding_rows": 8, "max_ring_size": 1}, "padding_rows must be less than domain_size"), + ({"domain_size": 8, "padding_rows": 2, "max_ring_size": 7}, "exceeds supported size"), + ], +) +def test_ring_proof_params_validation_errors(kwargs, match): + with pytest.raises(ValueError, match=match): + RingProofParams(**kwargs) diff --git a/tests/test_coverage/test_verify.py b/tests/test_coverage/test_verify.py index aaff3ca..eab5de5 100644 --- a/tests/test_coverage/test_verify.py +++ b/tests/test_coverage/test_verify.py @@ -4,7 +4,9 @@ from dot_ring.curve.native_field.scalar import Scalar from py_ecc.optimized_bls12_381 import curve_order -from dot_ring.ring_proof.constants import OMEGA, OMEGA_2048, S_PRIME, SIZE +from dot_ring.ring_proof.constants import DEFAULT_SIZE as SIZE +from dot_ring.ring_proof.constants import OMEGA_512 as OMEGA +from dot_ring.ring_proof.constants import OMEGA_2048, S_PRIME from dot_ring.ring_proof.pcs.srs import srs from dot_ring.ring_proof.verify import Verify, blst_msm, lagrange_at_zeta @@ -100,6 +102,21 @@ def test_legacy_methods_raise(self): with pytest.raises(NotImplementedError): verifier.evaluation_of_linearization_poly_at_zeta_omega() + def test_init_rejects_invalid_padding_rows(self): + """Verify init validates padding_rows against domain size.""" + proof = (0,) * 15 + with pytest.raises(ValueError, match="padding_rows"): + Verify( + proof=proof, + vk={}, + fixed_cols=[0, 0, 0], + rl_to_proove=(0, 0), + rps=(0, 0), + seed_point=(0, 0), + Domain=[1, 2, 3], + padding_rows=3, + ) + def test_contributions_handles_zeta_equal_domain_point(self): """Zeta equal to a domain point should hit the zero-difference branches.""" verifier = Verify.__new__(Verify) @@ -116,6 +133,8 @@ def test_contributions_handles_zeta_equal_domain_point(self): verifier.s_zeta = 19 verifier.Result_plus_Seed = (23, 29) + verifier.last_index = len(verifier.D) - 1 + result = verifier.contributions_to_constraints_eval_at_zeta() assert len(result) == 7 @@ -139,6 +158,8 @@ def test_linearization_uses_expected_omega(self): verifier.Phi_zeta_omega_blst = srs.blst_g1[3] verifier.l_zeta_omega = 13 + verifier.last_index = len(verifier.D) - 1 + _, _, zeta_omega, _ = verifier._prepare_linearization_poly_verification() expected_omega = pow(OMEGA_2048, 2048 // 1024, S_PRIME) expected_zeta_omega = (verifier.zeta_p * expected_omega) % S_PRIME @@ -163,6 +184,8 @@ def test_linearization_uses_fallback_omega(self): verifier.Phi_zeta_omega_blst = srs.blst_g1[3] verifier.l_zeta_omega = 13 + verifier.last_index = len(verifier.D) - 1 + _, _, zeta_omega, _ = verifier._prepare_linearization_poly_verification() expected_omega = pow(OMEGA_2048, 2048 // 16, S_PRIME) expected_zeta_omega = (verifier.zeta_p * expected_omega) % S_PRIME @@ -198,6 +221,8 @@ def test_prepare_quotient_poly_verification_smoke(self): verifier.Cq_blst = srs.blst_g1[7] verifier.Phi_zeta_blst = srs.blst_g1[8] + verifier.last_index = len(verifier.D) - 1 + _, phi, zeta, agg = verifier._prepare_quotient_poly_verification() assert phi is verifier.Phi_zeta_blst diff --git a/tests/test_verify_ring_sig.py b/tests/test_verify_ring_sig.py new file mode 100644 index 0000000..5841a8e --- /dev/null +++ b/tests/test_verify_ring_sig.py @@ -0,0 +1,170 @@ +import json +import sys +from pathlib import Path + +import pytest + +# Add project root to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from dot_ring import blst +from dot_ring.ring_proof.constants import EVAL_DOMAINS +from dot_ring.ring_proof.curve.bandersnatch import TwistedEdwardCurve +from dot_ring.ring_proof.pcs import srs +from dot_ring.ring_proof.pcs.utils import g2_to_blst +from dot_ring.ring_proof.verify import Verify +from tests.utils.arkworks_serde import ( + compressed_g1_to_uncompressed_bytes, + compressed_g2_to_uncompressed_bytes, + deserialize_bandersnatch_point, + deserialize_bls12_381_g1, + deserialize_bls12_381_g2, + deserialize_fq_field_element, +) + + +def parse_proof_from_json(proof_json: dict) -> tuple: + proof = proof_json["proof"] + + col_cmts_bytes = bytes.fromhex(proof["column_commitments"]) + c_b = deserialize_bls12_381_g1(col_cmts_bytes[0:48]) + c_accip = deserialize_bls12_381_g1(col_cmts_bytes[48:96]) + c_accx = deserialize_bls12_381_g1(col_cmts_bytes[96:144]) + c_accy = deserialize_bls12_381_g1(col_cmts_bytes[144:192]) + + cols_at_zeta_bytes = bytes.fromhex(proof["columns_at_zeta"]) + px_zeta = deserialize_fq_field_element(cols_at_zeta_bytes[0:32]) + py_zeta = deserialize_fq_field_element(cols_at_zeta_bytes[32:64]) + s_zeta = deserialize_fq_field_element(cols_at_zeta_bytes[64:96]) + b_zeta = deserialize_fq_field_element(cols_at_zeta_bytes[96:128]) + accip_zeta = deserialize_fq_field_element(cols_at_zeta_bytes[128:160]) + accx_zeta = deserialize_fq_field_element(cols_at_zeta_bytes[160:192]) + accy_zeta = deserialize_fq_field_element(cols_at_zeta_bytes[192:224]) + + cq_bytes = bytes.fromhex(proof["quotient_commitment"]) + c_q = deserialize_bls12_381_g1(cq_bytes) + l_zeta_omega = deserialize_fq_field_element(bytes.fromhex(proof["lin_at_zeta_omega"])) + + phi_zeta = deserialize_bls12_381_g1(bytes.fromhex(proof["agg_at_zeta_proof"])) + phi_zeta_omega = deserialize_bls12_381_g1(bytes.fromhex(proof["lin_at_zeta_omega_proof"])) + + proof_tuple = ( + c_b, + c_accip, + c_accx, + c_accy, + px_zeta, + py_zeta, + s_zeta, + b_zeta, + accip_zeta, + accx_zeta, + accy_zeta, + c_q, + l_zeta_omega, + phi_zeta, + phi_zeta_omega, + ) + + raw_bytes = { + "col_commitments": [ + col_cmts_bytes[0:48], + col_cmts_bytes[48:96], + col_cmts_bytes[96:144], + col_cmts_bytes[144:192], + ], + "quotient_commitment": cq_bytes, + } + + return proof_tuple, raw_bytes + + +def parse_verifier_key(vk_hex: str) -> tuple[bytes, list]: + vk_bytes = bytes.fromhex(vk_hex) + + c_px = deserialize_bls12_381_g1(vk_bytes[240:288]) + c_py = deserialize_bls12_381_g1(vk_bytes[288:336]) + c_s = deserialize_bls12_381_g1(vk_bytes[336:384]) + + return vk_bytes, [c_px, c_py, c_s] + + +def verify_vector(vector_path: Path) -> None: + with open(vector_path) as f: + proof_data = json.load(f) + + params = proof_data["metadata"]["parameters"] + domain = EVAL_DOMAINS[params["domain_size"]] + padding_rows = params.get("padding_rows", 4) + + seed_x_bytes = bytes.fromhex(params["seed"]["x"]) + seed_y_bytes = bytes.fromhex(params["seed"]["y"]) + seed_point = deserialize_bandersnatch_point(seed_x_bytes, seed_y_bytes) + + result_x_bytes = bytes.fromhex(params["result"]["x"]) + result_y_bytes = bytes.fromhex(params["result"]["y"]) + result_point = deserialize_bandersnatch_point(result_x_bytes, result_y_bytes) + + result_ark_bytes = result_x_bytes + result_y_bytes + result_plus_seed = TwistedEdwardCurve.add(result_point, seed_point) + + proof_tuple, raw_bytes = parse_proof_from_json(proof_data) + vk_bytes, fixed_cols = parse_verifier_key(proof_data["verifier_key"]["verification_key"]) + + # Update SRS from verifier key + g1_0_bytes = vk_bytes[0:48] + g2_0_bytes = vk_bytes[48:144] + g2_1_bytes = vk_bytes[144:240] + + g1_0_blst = blst.P1(blst.P1_Affine(g1_0_bytes)) + g2_0_affine = deserialize_bls12_381_g2(g2_0_bytes) + g2_1_affine = deserialize_bls12_381_g2(g2_1_bytes) + g2_0_blst = g2_to_blst(g2_0_affine) + g2_1_blst = g2_to_blst(g2_1_affine) + + original_g1 = srs.srs.blst_g1 + original_g2 = srs.srs.blst_g2 + srs.srs.blst_g1 = [g1_0_blst] + list(original_g1[1:]) + srs.srs.blst_g2 = [g2_0_blst, g2_1_blst] + list(original_g2[2:]) + + vk_uncompressed = ( + compressed_g1_to_uncompressed_bytes(vk_bytes[0:48]) + + compressed_g2_to_uncompressed_bytes(vk_bytes[48:144]) + + compressed_g2_to_uncompressed_bytes(vk_bytes[144:240]) + + compressed_g1_to_uncompressed_bytes(vk_bytes[240:288]) + + compressed_g1_to_uncompressed_bytes(vk_bytes[288:336]) + + compressed_g1_to_uncompressed_bytes(vk_bytes[336:384]) + ) + + quotient_compressed = bytes.fromhex(proof_data["proof"]["quotient_commitment"]) + quotient_uncompressed = compressed_g1_to_uncompressed_bytes(quotient_compressed) + raw_bytes["quotient_commitment_uncompressed"] = quotient_uncompressed + + verifier = Verify( + proof=proof_tuple, + vk=vk_uncompressed, + fixed_cols=fixed_cols, + rl_to_proove=result_ark_bytes, + rps=result_plus_seed, + seed_point=seed_point, + Domain=domain, + raw_proof_bytes=raw_bytes, + transcript_challenge=b"w3f-ring-proof-test", + padding_rows=padding_rows, + ) + + assert verifier.is_valid(), f"Verification failed for {vector_path.name}" + + +def get_vector_paths() -> list[Path]: + vectors_dir = Path(__file__).parent / "vectors" / "others" + paths = sorted(p for p in vectors_dir.glob("*.json") if p.name != "test_parameters.json") + if not paths: + pytest.skip(f"No JSON vectors found in {vectors_dir}") + return paths + + +@pytest.mark.parametrize("vector_path", get_vector_paths(), ids=lambda p: p.name) +def test_verify_ring_sig_vectors(vector_path: Path) -> None: + print(f"Testing vector: {vector_path.name}") + verify_vector(vector_path) diff --git a/tests/test_verify_rust_proof.py b/tests/test_verify_rust_proof.py deleted file mode 100644 index 37d6326..0000000 --- a/tests/test_verify_rust_proof.py +++ /dev/null @@ -1,257 +0,0 @@ -import json -import sys -from pathlib import Path - -import pytest - -# Add project root to path -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from dot_ring import blst -from dot_ring.ring_proof.constants import D_512, D_2048, OMEGA_2048, S_PRIME -from dot_ring.ring_proof.curve.bandersnatch import TwistedEdwardCurve -from dot_ring.ring_proof.pcs import srs -from dot_ring.ring_proof.verify import Verify - -# Import serialization utilities -from tests.utils.arkworks_serde import ( - compressed_g1_to_uncompressed_bytes, - compressed_g2_to_uncompressed_bytes, - deserialize_bandersnatch_point, - deserialize_bls12_381_g1, - deserialize_fq_field_element, -) - - -def load_test_parameters() -> dict: - """ - Load deterministic test parameters from JSON file. - - These parameters were extracted once from Rust's test_rng() and saved - for deterministic test execution without needing to rebuild Rust code. - - Returns: - Dictionary with h, seed, result, domain_size, etc. - """ - params_file = Path(__file__).parent / "vectors" / "others" / "test_parameters.json" - with open(params_file) as f: - return json.load(f) - - -def parse_proof_from_json(proof_json: dict) -> tuple: - """ - Parse proof from Rust-generated JSON. - - Returns (proof_tuple, raw_bytes_dict) where: - - proof_tuple: deserialized proof compatible with Python Verify class - - raw_bytes_dict: raw arkworks-serialized bytes for transcript - """ - proof = proof_json["proof"] - - # Parse column commitments (4 × 48-byte G1 points) - col_cmts_hex = proof["column_commitments"] - col_cmts_bytes = bytes.fromhex(col_cmts_hex) - - c_b = deserialize_bls12_381_g1(col_cmts_bytes[0:48]) - c_accip = deserialize_bls12_381_g1(col_cmts_bytes[48:96]) - c_accx = deserialize_bls12_381_g1(col_cmts_bytes[96:144]) - c_accy = deserialize_bls12_381_g1(col_cmts_bytes[144:192]) - - # Parse evaluations at zeta (7 × 32-byte Fq field elements) - cols_at_zeta_hex = proof["columns_at_zeta"] - cols_at_zeta_bytes = bytes.fromhex(cols_at_zeta_hex) - - px_zeta = deserialize_fq_field_element(cols_at_zeta_bytes[0:32]) - py_zeta = deserialize_fq_field_element(cols_at_zeta_bytes[32:64]) - s_zeta = deserialize_fq_field_element(cols_at_zeta_bytes[64:96]) - b_zeta = deserialize_fq_field_element(cols_at_zeta_bytes[96:128]) - accip_zeta = deserialize_fq_field_element(cols_at_zeta_bytes[128:160]) - accx_zeta = deserialize_fq_field_element(cols_at_zeta_bytes[160:192]) - accy_zeta = deserialize_fq_field_element(cols_at_zeta_bytes[192:224]) - - # Parse other proof components - cq_bytes = bytes.fromhex(proof["quotient_commitment"]) - c_q = deserialize_bls12_381_g1(cq_bytes) - l_zeta_omega = deserialize_fq_field_element(bytes.fromhex(proof["lin_at_zeta_omega"])) - - # KZG opening proofs (48-byte G1 points) - phi_zeta = deserialize_bls12_381_g1(bytes.fromhex(proof["agg_at_zeta_proof"])) - phi_zeta_omega = deserialize_bls12_381_g1(bytes.fromhex(proof["lin_at_zeta_omega_proof"])) - - proof_tuple = ( - c_b, - c_accip, - c_accx, - c_accy, - px_zeta, - py_zeta, - s_zeta, - b_zeta, - accip_zeta, - accx_zeta, - accy_zeta, - c_q, - l_zeta_omega, - phi_zeta, - phi_zeta_omega, - ) - - # Store raw bytes for transcript (matching arkworks serialization) - raw_bytes = { - "col_commitments": [ - col_cmts_bytes[0:48], - col_cmts_bytes[48:96], - col_cmts_bytes[96:144], - col_cmts_bytes[144:192], - ], - "quotient_commitment": cq_bytes, - } - - return (proof_tuple, raw_bytes) - - -def parse_verifier_key(vk_json: dict, use_global_srs: bool = True) -> dict: - """ - Parse verifier key from JSON. - - VerifierKey serialization order (384 bytes): - 1. pcs_raw_vk (RawKzgVerifierKey, 240 bytes): - - g1: 48 bytes G1 - - g2: 96 bytes G2 - - tau_g2: 96 bytes G2 - 2. fixed_columns_committed (FixedColumnsCommitted, 144 bytes): - - points[0]: C_px (48 bytes G1) - - points[1]: C_py (48 bytes G1) - - ring_selector: C_s (48 bytes G1) - - If use_global_srs is True, uses the global SRS instead of parsing G1/G2 from vk. - """ - vk_hex = vk_json["verifier_key"]["verification_key"] - vk_bytes = bytes.fromhex(vk_hex) - - # Fixed columns commitments (offsets 240-384) - c_px = deserialize_bls12_381_g1(vk_bytes[240:288]) - c_py = deserialize_bls12_381_g1(vk_bytes[288:336]) - c_s = deserialize_bls12_381_g1(vk_bytes[336:384]) - - # Convert to format expected by Python verifier - from py_ecc.optimized_bls12_381 import normalize as nm - - from dot_ring.ring_proof.helpers import Helpers as H - - if use_global_srs: - # Use the global SRS which has already been updated with Rust values - g1_int = H.to_int(srs.srs.g1_points[0]) - g2_altered = H.altered_points(srs.srs.g2_points) - else: - # Parse from verifier key bytes - kzg_g1 = deserialize_bls12_381_g1(vk_bytes[0:48]) - g1_normalized = nm(kzg_g1) - g1_int = H.to_int(g1_normalized) - - # G2 parsing would go here but is complex - # For now, fall back to using global SRS - g2_altered = H.altered_points(srs.srs.g2_points) - - # Create verifier_key dict in the format expected by Verify class - verifier_key_dict = {"g1": g1_int, "g2": g2_altered, "commitments": [H.to_int(nm(c_px)), H.to_int(nm(c_py)), H.to_int(nm(c_s))]} - - return {"fixed_cols": [c_px, c_py, c_s], "verifier_key": verifier_key_dict} - - -@pytest.fixture(scope="module") -def rust_parameters(): - """Load deterministic test parameters from JSON.""" - return load_test_parameters() - - -@pytest.fixture(scope="module") -def proof_data(): - """Load Rust-generated proof.""" - proof_path = Path(__file__).parent / "vectors" / "others" / "ring_proof_rust_generated.json" - with open(proof_path) as f: - return json.load(f) - - -def test_verify_rust_generated_proof(rust_parameters, proof_data): - """ - Test verification of Rust-generated ring proof. - - This test demonstrates that the Python verifier can verify - proofs generated by the Rust reference implementation. - """ - params = rust_parameters - - # Extract and update SRS from verifier key - vk_compressed = bytes.fromhex(proof_data["verifier_key"]["verification_key"]) - g1_0_bytes = vk_compressed[0:48] - g2_0_bytes = vk_compressed[48:144] - g2_1_bytes = vk_compressed[144:240] - - g1_0_blst = blst.P1(blst.P1_Affine(g1_0_bytes)) - g2_0_blst = blst.P2(blst.P2_Affine(g2_0_bytes)) - g2_1_blst = blst.P2(blst.P2_Affine(g2_1_bytes)) - - original_g1 = srs.srs.blst_g1 - original_g2 = srs.srs.blst_g2 - - srs.srs.blst_g1 = [g1_0_blst] + list(original_g1[1:]) - srs.srs.blst_g2 = [g2_0_blst, g2_1_blst] + list(original_g2[2:]) - - # Compute domain based on size - domain_size = params["domain_size"] - if domain_size == 512: - domain = D_512 - elif domain_size == 1024: - omega_1024 = pow(OMEGA_2048, 2048 // 1024, S_PRIME) - domain = [pow(omega_1024, i, S_PRIME) for i in range(1024)] - elif domain_size == 2048: - domain = D_2048 - else: - raise ValueError(f"Unsupported domain size: {domain_size}") - - # Deserialize Bandersnatch points - seed_x_bytes = bytes.fromhex(params["seed"]["x"]) - seed_y_bytes = bytes.fromhex(params["seed"]["y"]) - seed_point = deserialize_bandersnatch_point(seed_x_bytes, seed_y_bytes) - - result_x_bytes = bytes.fromhex(params["result"]["x"]) - result_y_bytes = bytes.fromhex(params["result"]["y"]) - result_point = deserialize_bandersnatch_point(result_x_bytes, result_y_bytes) - - result_ark_bytes = result_x_bytes + result_y_bytes - result_plus_seed = TwistedEdwardCurve.add(result_point, seed_point) - - # Parse proof and verifier key - proof_tuple, raw_bytes = parse_proof_from_json(proof_data) - vk_dict = parse_verifier_key(proof_data) - - # Prepare raw bytes for transcript - vk_uncompressed = ( - compressed_g1_to_uncompressed_bytes(vk_compressed[0:48]) - + compressed_g2_to_uncompressed_bytes(vk_compressed[48:144]) - + compressed_g2_to_uncompressed_bytes(vk_compressed[144:240]) - + compressed_g1_to_uncompressed_bytes(vk_compressed[240:288]) - + compressed_g1_to_uncompressed_bytes(vk_compressed[288:336]) - + compressed_g1_to_uncompressed_bytes(vk_compressed[336:384]) - ) - - quotient_compressed = bytes.fromhex(proof_data["proof"]["quotient_commitment"]) - quotient_uncompressed = compressed_g1_to_uncompressed_bytes(quotient_compressed) - raw_bytes["quotient_commitment_uncompressed"] = quotient_uncompressed - - # Create verifier and verify - verifier = Verify( - proof=proof_tuple, - vk=vk_uncompressed, - fixed_cols=vk_dict["fixed_cols"], - rl_to_proove=result_ark_bytes, - rps=result_plus_seed, - seed_point=seed_point, - Domain=domain, - raw_proof_bytes=raw_bytes, - transcript_challenge=b"w3f-ring-proof-test", - ) - - # Verify proof - assert verifier.is_valid(), "Proof verification failed!" diff --git a/tests/utils/python_to_rust_serde.py b/tests/utils/python_to_rust_serde.py new file mode 100644 index 0000000..150f6b3 --- /dev/null +++ b/tests/utils/python_to_rust_serde.py @@ -0,0 +1,132 @@ +""" +Arkworks-compatible serialization utilities. + +These helpers mirror the formats expected by tests.utils.arkworks_serde +and are intended for exporting Python-generated proofs to arkworks. +""" + +from __future__ import annotations + +from typing import Any + +from py_ecc.bls12_381 import bls12_381_pairing as pairing +from py_ecc.optimized_bls12_381 import FQ +from py_ecc.optimized_bls12_381 import normalize as nm + +FIELD_MODULUS = pairing.field_modulus + + +def _to_int(value: Any) -> int: + return int(value) + + +def serialize_fq_field_element(value: int | FQ) -> bytes: + """Serialize a field element as 32-byte little-endian.""" + return _to_int(value).to_bytes(32, "little") + + +def _is_zero_fq2(value: Any) -> bool: + if hasattr(value, "coeffs"): + c0, c1 = value.coeffs + return int(c0) == 0 and int(c1) == 0 + if isinstance(value, tuple) and len(value) == 2: + return int(value[0]) == 0 and int(value[1]) == 0 + return False + + +def serialize_bls12_381_g1(point: tuple) -> bytes: + """Serialize a BLS12-381 G1 point in arkworks compressed format.""" + if len(point) == 3: + # Jacobian + x, y, z = point + if int(z) == 0: + # Point at infinity + x_bytes = b"\x00" * 48 + flags = 0xC0 # compressed + infinity + return bytes([x_bytes[0] | flags]) + x_bytes[1:] + x_aff, y_aff = nm(point) + x = x_aff + y = y_aff + elif len(point) == 2: + x, y = point + else: + raise ValueError("Invalid G1 point format") + + x_int = _to_int(x) + y_int = _to_int(y) + + x_bytes = bytearray(x_int.to_bytes(48, "big")) + # Clear top 3 bits before setting flags + x_bytes[0] &= 0x1F + + flags = 0x80 # compressed + if y_int > (FIELD_MODULUS - 1) // 2: + flags |= 0x20 # lexicographically largest + + x_bytes[0] |= flags + return bytes(x_bytes) + + +def serialize_bls12_381_g2(point: tuple) -> bytes: + """Serialize a BLS12-381 G2 point in arkworks compressed format.""" + if len(point) == 3: + x, y, z = point + if _is_zero_fq2(z): + x_bytes = b"\x00" * 48 + flags = 0xC0 + return bytes([x_bytes[0] | flags]) + x_bytes[1:] + b"\x00" * 48 + x_aff, y_aff = nm(point) + x = x_aff + y = y_aff + elif len(point) == 2: + x, y = point + else: + raise ValueError("Invalid G2 point format") + + if hasattr(x, "coeffs"): + x_c0, x_c1 = x.coeffs + y_c0, y_c1 = y.coeffs + x_c0_int = _to_int(x_c0) + x_c1_int = _to_int(x_c1) + y_c1_int = _to_int(y_c1) + elif isinstance(x, tuple) and len(x) == 2: + # Assume (c0, c1) ordering as used by py_ecc and SRS loader + x_c0_int = _to_int(x[0]) + x_c1_int = _to_int(x[1]) + y_c1_int = _to_int(y[1]) + else: + raise ValueError("Unsupported G2 coordinate format") + + x_c1_bytes = bytearray(x_c1_int.to_bytes(48, "big")) + x_c0_bytes = x_c0_int.to_bytes(48, "big") + + # Clear top 3 bits before setting flags + x_c1_bytes[0] &= 0x1F + + flags = 0x80 # compressed + if y_c1_int > (FIELD_MODULUS - 1) // 2: + flags |= 0x20 + + x_c1_bytes[0] |= flags + return bytes(x_c1_bytes) + x_c0_bytes + + +def serialize_ring_proof( + column_commitments: list[tuple], + columns_at_zeta: list[int], + quotient_commitment: tuple, + lin_at_zeta_omega: int, + agg_at_zeta_proof: tuple, + lin_at_zeta_omega_proof: tuple, +) -> bytes: + """Serialize the ring proof in arkworks-compatible order.""" + out = bytearray() + for c in column_commitments: + out.extend(serialize_bls12_381_g1(c)) + for v in columns_at_zeta: + out.extend(serialize_fq_field_element(v)) + out.extend(serialize_bls12_381_g1(quotient_commitment)) + out.extend(serialize_fq_field_element(lin_at_zeta_omega)) + out.extend(serialize_bls12_381_g1(agg_at_zeta_proof)) + out.extend(serialize_bls12_381_g1(lin_at_zeta_omega_proof)) + return bytes(out) diff --git a/tests/vectors/others/ring_proof_ring1024_domain2048.json b/tests/vectors/others/ring_proof_ring1024_domain2048.json new file mode 100644 index 0000000..d0acff7 --- /dev/null +++ b/tests/vectors/others/ring_proof_ring1024_domain2048.json @@ -0,0 +1,30 @@ +{ + "proof": { + "agg_at_zeta_proof": "a297530ba64ced505499cf9ac265dfd222787185a3283aa17108addaab61a13c6222cc840f92302ac5ea3b714e304e51", + "column_commitments": "a838f6f9af9d51145ebd02d8d1713b214d276874b77dd602e76a5fcb637d3885fb894fb18d8e69897acfa10594ca6cd08ef751f3a9c0cfbdba81010b8c5416c30ac411eb69eaf3e8264743ca60df87d5496d60c66420b9bd5f3109010989b76e837d2c4826863339e90352c3b55ac93480c14247526c609bf3f0d3a32105b15f890458a338f588aa4b5d1cafdcbf877c98dffbca8d569ccae3646dc36ec64a4b525cb9a463c67e9349c8f42cc1873a5d4a9878397bfe9ea3ff8409d6ebbcf9d3", + "columns_at_zeta": "684f6d0c6cbca9c8580a336aed2a211bc6e5ebedfdcc46b844759ae5c9d31a228e41e6d63687c51a07ff2cd4c8b33b7efe42952432bbd22e79f8209c8b4d185f164c14c10b0a7e88fab17bdeff206f7de0c74704392b33fc41512f726bba9d2991737e9ecc6204544bc744b4ba1d7e89821a13959979c8000db8ca9f40ce7869e940b7c6f857d4cb6f71860ac543ecc12c97be589c7eb1ba9cf97a42c4a96a6f22e17040844d22b2ea7a23bcd73352bc761c777123161aeea6916f92373c0a1f3c9031738af240af76fdfda0190792cb67fff91f1e2e0a49a04d1652b29fae28", + "lin_at_zeta_omega": "d642e8af584adce31313fad5fbfee4b7c1dedd4d68454a82bb0a6f37422f963a", + "lin_at_zeta_omega_proof": "ab01aa27b84e9a942ab58f88a3a7d2aeff348ae475374250032732e92496f3317890dc9f05ef8675c9407ee3de1ed033", + "quotient_commitment": "8f2dbb016bf5832b17aca4dfe5985e8d87a93df805938d463b560bc1eaf9f235f13469c77c13c0341261bc8f4764f459" + }, + "verifier_key": { + "verification_key": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8916b2250fc4b3098ccadf9ec58526aecf307d21e233f65110554ee04460f72a616f4d070941b0796de5defda29196e40013645b8518e5568e75d320da8b83a87e12406956b9c74491ee5ecf06dc297ba1186016209d78fd832c16ef62fc49eff93c04054587d5ea60dfff114be3f7a8ab463f361f1f400dbdb0c2759c34d440a8888b528294a0c6a8da39724a6dd7a2a8d35eb6cee0b6d59e5bd1f1fc47bace04bbd87d0363cdcd418a7d7d693b0d8ff2ebc2ba2738d4478c9fc95c150445d498c2773899d5bcedaacc80bba69a0c9a13d9385acc4f1926c0403b86bd4052a091d06d6cd32b581fdfda0ee285d233189" + }, + "metadata": { + "parameters": { + "domain_size": 2048, + "h": { + "x": "e8c5e337ffbd7839ed5aaee576faae32eea01bff684125758d874fa909e8980d", + "y": "e93da06b869766b158d20b843ec648cc68e0b7ba2f7083acf0f154205d04e23e" + }, + "seed": { + "x": "20f354ea2af5f890e0cfac3b044aca2335fc26fa900fbe429fb059b0df319553", + "y": "6e5574f9077fb76c885c36196a832dbadd64142d305be5487724967acf959520" + }, + "result": { + "x": "1314846abc3f7185946c62c0317a19feffebd3de51dda9c966936a9ae40b4a54", + "y": "43421133efdea576dabc032c2b689247856ad17803a9c2a3763aa50e02b3fd0b" + } + } + } +} \ No newline at end of file diff --git a/tests/vectors/others/ring_proof_ring128_domain512.json b/tests/vectors/others/ring_proof_ring128_domain512.json new file mode 100644 index 0000000..1e16c07 --- /dev/null +++ b/tests/vectors/others/ring_proof_ring128_domain512.json @@ -0,0 +1,30 @@ +{ + "proof": { + "agg_at_zeta_proof": "9850b39035b9268bd51693277ed1a6922b85f078229990a514ca4e56cbf0d18d7e470c35dea7c8b218ea7a495dfd4633", + "column_commitments": "950b30a2a53734f6a2da31763eefc4937f46fde96ef9f0dcb7a2fbd7dc6d95881cbc2ec3e704b7c007506fa8987f09d8851709c52f7949ccde7b4de47584d3fb25bb9c45d1a68efdce12fbda03c43cc5460f3730957046d11126a0069de3d18989089a53d66afd4c88c4a8098087905ba83d6e0b6862e01eb68b7933968ec9844342436c0352241a12fadcfecad614f9b0deaa406000f18fd488fba877f8b8046cfa0d3fb4e297187a389db3f2f00b28bcd0ce2dcf6ee95ebb4846434c4da5ae", + "columns_at_zeta": "66c1ab6af489af22617427a02ac2449f617080b98ab259cb5e3d7a0783bf49132e37f9f8dd34ef129ee3a05ee13195f9cc3552cbe3c399c831804665adc11155eca5e4f6216b56d09a28a86b4b30ecf8d2b56804272a7962278a2cc8b87bc04359875e9c1d10800a2a3a9647b34eb6f50414e89f1e259f42ea6257a5140d08490c59a8f9f3431ffa76cfa6ef38a8fa5c0856155c1067dac06eb1e94092acd012415da915285336626f96a678a7b13f4223a82bdbaf4dd5e2ef247691d624755cd6a19f2a554b78af5426fbeafe0e7e7baca1bf6659dd40292193fed757f43732", + "lin_at_zeta_omega": "1913d81c107ea42071a251d4bcad302dcc5f352de2b783148e02ba1a4706cd2b", + "lin_at_zeta_omega_proof": "ad88f4ed563374d290c081a7169a6d9ef4483e857a177b719b0805bab6aab2995e5af68471f5ba1c9a8de08fab1da1fc", + "quotient_commitment": "8a60ba6adb3e7b99ef08bd51ed814980e610f58cf204b7d6c0ea627d9ccdf93833699f93311173360c5a5f2526d7b399" + }, + "verifier_key": { + "verification_key": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8916b2250fc4b3098ccadf9ec58526aecf307d21e233f65110554ee04460f72a616f4d070941b0796de5defda29196e40013645b8518e5568e75d320da8b83a87e12406956b9c74491ee5ecf06dc297ba1186016209d78fd832c16ef62fc49eff87fcc9986de2fff4b57582f384559754ac0c920e6bc45d973db6a59f3fa43b8ecc318407c0f942c527079744f3291b2ab02c298da9e071e614dec5b65a8e81cfb91c4e7703d0c65792dcfb1f99cf4b7fb933279886240a246e9324cbf77a5b19947be6aa7957e5c87436d9a4e308eb641a1565f3bfe69cae9c3c501baefa729b2e66e188a26ec541718fdb0778d957a4" + }, + "metadata": { + "parameters": { + "domain_size": 512, + "h": { + "x": "e8c5e337ffbd7839ed5aaee576faae32eea01bff684125758d874fa909e8980d", + "y": "e93da06b869766b158d20b843ec648cc68e0b7ba2f7083acf0f154205d04e23e" + }, + "seed": { + "x": "20f354ea2af5f890e0cfac3b044aca2335fc26fa900fbe429fb059b0df319553", + "y": "6e5574f9077fb76c885c36196a832dbadd64142d305be5487724967acf959520" + }, + "result": { + "x": "1314846abc3f7185946c62c0317a19feffebd3de51dda9c966936a9ae40b4a54", + "y": "43421133efdea576dabc032c2b689247856ad17803a9c2a3763aa50e02b3fd0b" + } + } + } +} \ No newline at end of file diff --git a/tests/vectors/others/ring_proof_ring256_domain1024.json b/tests/vectors/others/ring_proof_ring256_domain1024.json new file mode 100644 index 0000000..cd4fb3b --- /dev/null +++ b/tests/vectors/others/ring_proof_ring256_domain1024.json @@ -0,0 +1,30 @@ +{ + "proof": { + "agg_at_zeta_proof": "a9bea63ad8852f6bf5f725dbfc6f834b38783ea4d0a981d79541b3068aee37a023ee228f5d98d007b597616ef6ae2226", + "column_commitments": "8c07be6d7c25e00da7301a412fdc05949c1df18770fc2dfa853ce8e5177d128c09e83c1af1930eace0059e5340a719dd8207f67480e0d20c1e0fbef9dbec7998629174e18683afafdf2ddf86d735cf6bee3106b4b106b99b84599ad75bf4553ea47f8b9d6ed9565268d789e117cec2ae99e48bd16c988ff45c714fa12c0a1f90c92bfff70d3630ffd138ccf2eb98b1c0a66ef2efa2ca62393ca964e840c8574a07dad807f6de38ad372a18382543fbf67130306c9eb5190babec56f32ac11634", + "columns_at_zeta": "5ee83405715a54a15e7c3b4e6a96caab94032267a226341e6b59b4c7585dfc336f9b08d8750a7bbc238229884e372d267b4c5ecfb1b420e3cae21bc06dd3e855d5d811daae73e378f75dc9f5517e7bbea9bec574554b79a6d92e7b6fcfbc6c0ee40f476165bd7f6010b75c75c9b76c928727e70ac8ef0ff48b1baa65bfb2532bcd5fb86d9a6ba9998662bd0ca77d4cd7c90e06f0af7354558fb4c1f45b4dda62e49bdcb61c2a8fd46a4077e067fe690bf5e86c821b6647e991c4db61968d96057232e130ef870650ec7d4f2b4b7e4a58762787aef923a7fe48fd8253cd08ce30", + "lin_at_zeta_omega": "b901ded9ed32ee42c5ebd83a93aab1247450af956da3c3c6770c5994f093d832", + "lin_at_zeta_omega_proof": "a7012e3b10dd35255f56de7263d281557d6ce6c6d35d47382d7776c190aedf02a065236192607710ea5b7e105fa916ed", + "quotient_commitment": "ad1882d854cdfe5dfa89cb9f9be72a24d3dabd6a2472b98f276432517bda9372494f0826636b0583ffbdb742770b37bc" + }, + "verifier_key": { + "verification_key": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8916b2250fc4b3098ccadf9ec58526aecf307d21e233f65110554ee04460f72a616f4d070941b0796de5defda29196e40013645b8518e5568e75d320da8b83a87e12406956b9c74491ee5ecf06dc297ba1186016209d78fd832c16ef62fc49effa38a57aee668a9f5e62e06bda7929eb54c6d12abfc199eac185e2e3d91ca124ccdf2532cac1977d6937a95f8f02e1c799431a9f97877cc7474456441bc54436a441d2ba608a98bc1528f3975cf9551d46057ca438a54b52a6a76c5d089892b95aab73fc7b5faf29dc6b9c17af312582b036a08d442eb50779adf3739bb20b473c8bcb6fbbcce14518cd174b1e6d87101" + }, + "metadata": { + "parameters": { + "domain_size": 1024, + "h": { + "x": "e8c5e337ffbd7839ed5aaee576faae32eea01bff684125758d874fa909e8980d", + "y": "e93da06b869766b158d20b843ec648cc68e0b7ba2f7083acf0f154205d04e23e" + }, + "seed": { + "x": "20f354ea2af5f890e0cfac3b044aca2335fc26fa900fbe429fb059b0df319553", + "y": "6e5574f9077fb76c885c36196a832dbadd64142d305be5487724967acf959520" + }, + "result": { + "x": "1314846abc3f7185946c62c0317a19feffebd3de51dda9c966936a9ae40b4a54", + "y": "43421133efdea576dabc032c2b689247856ad17803a9c2a3763aa50e02b3fd0b" + } + } + } +} \ No newline at end of file diff --git a/tests/vectors/others/ring_proof_ring64_domain512.json b/tests/vectors/others/ring_proof_ring64_domain512.json new file mode 100644 index 0000000..2b8cb70 --- /dev/null +++ b/tests/vectors/others/ring_proof_ring64_domain512.json @@ -0,0 +1,30 @@ +{ + "proof": { + "agg_at_zeta_proof": "b94af18784cac87de3715a9dcc8420041ef0904d49906a2d7d69b52cee814376515dbfc0129a251fa8bde4871a22dce9", + "column_commitments": "8ec21bf13daeae321c3d43eb066c65582f9e33a2d191b0edbb941907827e637d236e550363c9c5ecd4459d52136383ea851709c52f7949ccde7b4de47584d3fb25bb9c45d1a68efdce12fbda03c43cc5460f3730957046d11126a0069de3d18984f13c9ec5ef4f2b7d87d4df526abd2c21e5c6918a737d58930f333fe8553a314dc83389643d106702e249e6f21af5ee9667b948dffac4b2d4c6ba7f836da03f801de9d9e86a53c2061dbb25ea41605ae8d12fd3d5f7b42751efb721539cb313", + "columns_at_zeta": "a0ae8f54199b7d136292343b8b56976fb26fa1b6368017e506114fc2405003391582032ef90763e361fcf3ff81f329317f6041e20af32b8697a93a48e79e475c220de7e583c7d933ecd7f9d1208f80efcc914e3a6edb1bc6f38248cbc062f472ee0355a16fe3b90e5e1674a33d26fc130c3e30827f26baf37e8dc2dacaea940e9c99cb32ae480be378e0a99aadec274ceb0acff05eb3984c8f699a86ae921569b4dd62ffe13ad765c77bd930cba0b3691da76162edfeb7a6a8edee2532c2c367f9c4b11e3bf2efe017e76ff2a42b21affbda7e2c1b0e243f7a6a5e2ca1e49373", + "lin_at_zeta_omega": "62751da990e973a4e802aa313e58e62651c8f94f9c5061870fd49334604a2b14", + "lin_at_zeta_omega_proof": "867630b38b6707a8244fefd3eb20e4e78ffda5bb0ac88eb6ddddd8aa3e4f31349107a1fc58aaafd343234a309f0f0514", + "quotient_commitment": "a6fcfa5136d9185a84c7f04fd1958e66651fc4190fc45d453ce73194bc7724edf55865978fdee514c7dc46778f2464f6" + }, + "verifier_key": { + "verification_key": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8916b2250fc4b3098ccadf9ec58526aecf307d21e233f65110554ee04460f72a616f4d070941b0796de5defda29196e40013645b8518e5568e75d320da8b83a87e12406956b9c74491ee5ecf06dc297ba1186016209d78fd832c16ef62fc49eff9507ea9f6571e9b2885457a322cfc19924f255a0c163118f922a3ba1d8faf7dccfb6aa299a29c46a2b3f08d6fee3c441b722df40ab79ec1f97ffef61f3dcf041a7e07600f0cc9e15038450de9977bbf4c31645ff7c3d9dd6cf1483f50a8eec02ab5c8b57ab8aa423638cb0868412a3cd94afdc3655891105d04150af5a7dc37f0b62d940cd37142b7cf74b5687343a3e" + }, + "metadata": { + "parameters": { + "domain_size": 512, + "h": { + "x": "e8c5e337ffbd7839ed5aaee576faae32eea01bff684125758d874fa909e8980d", + "y": "e93da06b869766b158d20b843ec648cc68e0b7ba2f7083acf0f154205d04e23e" + }, + "seed": { + "x": "20f354ea2af5f890e0cfac3b044aca2335fc26fa900fbe429fb059b0df319553", + "y": "6e5574f9077fb76c885c36196a832dbadd64142d305be5487724967acf959520" + }, + "result": { + "x": "1314846abc3f7185946c62c0317a19feffebd3de51dda9c966936a9ae40b4a54", + "y": "43421133efdea576dabc032c2b689247856ad17803a9c2a3763aa50e02b3fd0b" + } + } + } +} \ No newline at end of file diff --git a/tests/vectors/others/ring_proof_rust_generated.json b/tests/vectors/others/ring_proof_rust_generated.json index 15d1f03..0263125 100644 --- a/tests/vectors/others/ring_proof_rust_generated.json +++ b/tests/vectors/others/ring_proof_rust_generated.json @@ -9,5 +9,22 @@ }, "verifier_key": { "verification_key": "97ae15a6d3f5898bfa5a96b54e4e7f44d9001a1a43e218868df899e4961a439f4b753e9ee919c93e6d17697d8a6f197f96159f6d905433a175f63610c51aad73b1b2bc024d20d912cea026fc66bf337b2e405ef86caec6103f35de713d67244812f9249c0a49939658b01b1212e7017e4767c498f38e6e989be3b56894ef2114155aa5c881c732c79f051ad711ab90feaec75b7848db3eacb0068a38379fc73aaee4488f2a12207b70757a5d56567b18939e9a02961001e1ca277c9f84b7ee13193de85d4f349d23486b4c2037f4a04f37c5d07499a59f02b84205926ead8b0cb07433774f65e72233fc566196cbf91883d6c1d680317287b4d84cc0078ef5c042387f15c6bd797f863cb45eb7273d825303d54c1d690468c8261ae6c14d0a0298d0795b11446cb4849c06b624c4953c364298643168b0e31f277379650152601413c032da7b0598edc0d77c28c7ac73a3971d9f9a9f4d4e684f81a915c2527d08800b85452dcae5d9df92d965e17fd005f17b2e2eedd48029227fdce670751a" + }, + "metadata": { + "parameters": { + "domain_size": 1024, + "h": { + "x": "fc08f51fded9706747d310226bacbe992452f1c2248a82813e768f0199fbc824", + "y": "d64dcb4cca8d299d9248582b3c473094c6784f7b60be06d4a754ee6e3b73a241" + }, + "result": { + "x": "d3cc691ca41245cd5f2b0f296fa9e92160c0c1487a0284dd2b0ee0c5f38cc962", + "y": "34ece6c3d54f571e5e0129673b49b3ac6df406cf84551f2c400111123ecec941" + }, + "seed": { + "x": "99b606929820569a878cb565828cfb458540987bf1d0f32e2397b09d6c652d4e", + "y": "1f4f86a282b593962e4b48ecce56f77adb3ed6fdf3effbf75adb06d6b19ce462" + } + } } } \ No newline at end of file diff --git a/tests/vectors/others/test_parameters.json b/tests/vectors/others/test_parameters.json deleted file mode 100644 index 5f7b9e8..0000000 --- a/tests/vectors/others/test_parameters.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "domain_size": 1024, - "h": { - "x": "fc08f51fded9706747d310226bacbe992452f1c2248a82813e768f0199fbc824", - "y": "d64dcb4cca8d299d9248582b3c473094c6784f7b60be06d4a754ee6e3b73a241" - }, - "result": { - "x": "d3cc691ca41245cd5f2b0f296fa9e92160c0c1487a0284dd2b0ee0c5f38cc962", - "y": "34ece6c3d54f571e5e0129673b49b3ac6df406cf84551f2c400111123ecec941" - }, - "ring_size": 1024, - "seed": { - "x": "99b606929820569a878cb565828cfb458540987bf1d0f32e2397b09d6c652d4e", - "y": "1f4f86a282b593962e4b48ecce56f77adb3ed6fdf3effbf75adb06d6b19ce462" - } -}