diff --git a/src/benchmarks.py b/src/benchmarks.py new file mode 100644 index 00000000..4b282358 --- /dev/null +++ b/src/benchmarks.py @@ -0,0 +1,111 @@ +from src.definitions import STARK, CurveID, CURVES, PyFelt, Curve +from random import randint +from src.extension_field_modulo_circuit import ExtensionFieldModuloCircuit +from src.precompiled_circuits.final_exp import FinalExpCircuit + + +def test_extf_mul_circuit_amortized(curve_id: CurveID, extension_degree: int): + curve: Curve = CURVES[curve_id.value] + p = curve.p + X = [PyFelt(randint(0, p - 1), p) for _ in range(extension_degree)] + circuit = ExtensionFieldModuloCircuit( + f"{curve_id.name}_mul_amortized_Fp{extension_degree}", + curve_id=curve_id.value, + extension_degree=extension_degree, + ) + circuit.create_powers_of_Z(PyFelt(2, STARK), mock=True) + X = circuit.write_elements(X) + circuit.extf_mul(X, X, extension_degree) + return circuit.summarize() + + +def test_extf_square_amortized(curve_id: CurveID, extension_degree: int): + curve: Curve = CURVES[curve_id.value] + p = curve.p + X = [PyFelt(randint(0, p - 1), p) for _ in range(extension_degree)] + circuit = ExtensionFieldModuloCircuit( + f"{curve_id.name}_square_amortized_Fp{extension_degree}", + curve_id=curve_id.value, + extension_degree=extension_degree, + ) + circuit.create_powers_of_Z(PyFelt(2, STARK), mock=True) + X = circuit.write_elements(X) + circuit.extf_square(X, extension_degree) + return circuit.summarize() + + +def test_extf_mul_circuit_full(curve_id: CurveID, extension_degree: int): + curve: Curve = CURVES[curve_id.value] + p = curve.p + X = [PyFelt(randint(0, p - 1), p) for _ in range(extension_degree)] + circuit = ExtensionFieldModuloCircuit( + f"{curve_id.name}_mul_zpow_verif_Fp{extension_degree}", + curve_id=curve_id.value, + extension_degree=extension_degree, + ) + circuit.create_powers_of_Z(PyFelt(2, STARK), mock=False) + X = circuit.write_elements(X) + circuit.extf_mul(X, X, extension_degree) + circuit.finalize_circuit() + return circuit.summarize() + + +def test_square_torus_amortized(curve_id: CurveID, extension_degree: int): + curve: Curve = CURVES[curve_id.value] + p = curve.p + X = [PyFelt(randint(0, p - 1), p) for _ in range(extension_degree)] + circuit = FinalExpCircuit( + f"{curve_id.name}_square_torus_amortized_Fp{extension_degree}", + curve_id=curve_id.value, + extension_degree=extension_degree, + ) + circuit.create_powers_of_Z(PyFelt(2, STARK), mock=True) + X = circuit.write_elements(X) + circuit.square_torus(X) + return circuit.summarize() + + +def test_mul_torus_amortized(curve_id: CurveID, extension_degree: int): + curve: Curve = CURVES[curve_id.value] + p = curve.p + X = [PyFelt(randint(0, p - 1), p) for _ in range(extension_degree)] + circuit = FinalExpCircuit( + f"{curve_id.name}_mul_torus_amortized_Fp{extension_degree}", + curve_id=curve_id.value, + extension_degree=extension_degree, + ) + circuit.create_powers_of_Z(PyFelt(2, STARK), mock=True) + X = circuit.write_elements(X) + circuit.mul_torus(X, X) + return circuit.summarize() + + +if __name__ == "__main__": + data = [] + for curveID in [CurveID.BLS12_381]: + data.append(test_extf_mul_circuit_amortized(curveID, 6)) + data.append(test_extf_mul_circuit_full(curveID, 6)) + data.append(test_extf_mul_circuit_amortized(curveID, 12)) + data.append(test_extf_mul_circuit_full(curveID, 12)) + data.append(test_square_torus_amortized(curveID, 6)) + data.append(test_extf_square_amortized(curveID, 12)) + data.append(test_mul_torus_amortized(curveID, 6)) + + import pandas as pd + + df = pd.DataFrame(data) + + costs = {"MULMOD": 8, "ADDMOD": 4, "POSEIDON": 7} # , "ASSERT_EQ": 3} + acc = pd.Series([0] * len(df)) + + for column, multiplier in costs.items(): + tmp = df[column] * multiplier + df[f"{column}_w"] = tmp + acc += tmp + + df[f"Total with weights"] = acc + + print("\n\n") + print(f"Weights: {costs}") + print(df.sort_values(by="Total with weights")) + print("\n") diff --git a/src/definitions.py b/src/definitions.py index 9cedeefa..8c893bca 100644 --- a/src/definitions.py +++ b/src/definitions.py @@ -1,6 +1,7 @@ from src.algebra import Polynomial from src.algebra import BaseField, PyFelt, ModuloCircuitElement from dataclasses import dataclass +from enum import Enum N_LIMBS = 4 BASE = 2**96 @@ -9,6 +10,11 @@ BLS12_381_ID = int.from_bytes(b"bls12_381", "big") +class CurveID(Enum): + BN254 = int.from_bytes(b"bn254", "big") + BLS12_381 = int.from_bytes(b"bls12_381", "big") + + @dataclass(slots=True, frozen=True) class Curve: id: int @@ -47,10 +53,10 @@ def get_base_field(curve_id: int) -> BaseField: def get_irreducible_poly(curve_id: int, extension_degree: int) -> Polynomial: + field = BaseField(CURVES[curve_id].p) return Polynomial( coefficients=[ - PyFelt(x, CURVES[curve_id].p) - for x in CURVES[curve_id].irreducible_polys[extension_degree] + field(x) for x in CURVES[curve_id].irreducible_polys[extension_degree] ], raw_init=True, ) diff --git a/src/extension_field_modulo_circuit.py b/src/extension_field_modulo_circuit.py index a3206042..bedbc11a 100644 --- a/src/extension_field_modulo_circuit.py +++ b/src/extension_field_modulo_circuit.py @@ -3,9 +3,12 @@ from src.poseidon_transcript import CairoPoseidonTranscript from src.hints.extf_mul import ( nondeterministic_extension_field_mul_divmod, + nondeterministic_extension_field_div, ) from dataclasses import dataclass -from src.definitions import get_irreducible_poly +from src.definitions import get_irreducible_poly, CurveID, STARK, CURVES, Curve +from pprint import pprint +from random import randint # Accumulates equations of the form c_i * X_i(Z)*Y_i(z) = Q_i*P + R_i @@ -31,37 +34,46 @@ def __init__( self.transcript: CairoPoseidonTranscript = CairoPoseidonTranscript( init_hash=int.from_bytes(name.encode(), "big") ) + self.z_powers: list[ModuloCircuitElement] = [] def _init_accumulator(self): return EuclideanPolyAccumulator( - xy=self.write_element(self.field.zero()), + xy=self.constants["ZERO"], nondeterministic_Q=Polynomial([self.field.zero()]), - R=[ - self.write_element(self.field.zero()) - for _ in range(self.extension_degree) - ], + R=[self.constants["ZERO"]] * self.extension_degree, ) - def write_commitment(self, elmt: PyFelt) -> ModuloCircuitElement: + def write_commitment( + self, elmt: PyFelt, add_to_transcript: bool = True + ) -> ModuloCircuitElement: """ 1) Add the commitment to the list of commitments 2) Add to transcript to precompute Z 3) Write the commitment to the values segment and return the ModuloElement """ self.commitments.append(elmt) - self.transcript.hash_limbs(elmt) + if add_to_transcript: + self.transcript.hash_limbs(elmt) return self.write_element(elmt, WriteOps.COMMIT) def write_commitments( - self, elmts: list[PyFelt] + self, elmts: list[PyFelt], add_to_transcript: bool = True ) -> (list[ModuloCircuitElement], PyFelt, PyFelt): - vals = [self.write_commitment(elmt) for elmt in elmts] + vals = [self.write_commitment(elmt, add_to_transcript) for elmt in elmts] return vals, self.transcript.continuable_hash, self.transcript.s1 - def create_powers_of_Z(self, Z: PyFelt) -> list[ModuloCircuitElement]: - powers = [self.write_cairo_native_felt(Z.value)] - for _ in range(1, self.extension_degree): - powers.append(self.mul(powers[-1], powers[0])) + def create_powers_of_Z( + self, Z: PyFelt, mock: bool = False + ) -> list[ModuloCircuitElement]: + powers = [self.write_cairo_native_felt(Z)] + if not mock: + for _ in range(1, self.extension_degree + 1): + powers.append(self.mul(powers[-1], powers[0])) + else: + powers = powers + [ + self.write_cairo_native_felt(self.field(Z.value**i)) + for i in range(1, self.extension_degree + 1) + ] self.z_powers = powers return powers @@ -75,7 +87,7 @@ def eval_poly_in_precomputed_Z( assert len(X) <= len(self.z_powers), f"{len(X)} > {len(self.z_powers)}" X_of_z = X[0] for i in range(1, len(X)): - X_of_z = self.add(X_of_z, self.mul(X[i], self.z_powers[i])) + X_of_z = self.add(X_of_z, self.mul(X[i], self.z_powers[i - 1])) return X_of_z def extf_add( @@ -102,24 +114,36 @@ def extf_scalar_mul( assert type(c) == ModuloCircuitElement return [self.mul(x_i, c) for x_i in X] + def extf_neg(self, X: list[ModuloCircuitElement]) -> list[ModuloCircuitElement]: + """ + Negates a polynomial with coefficients `X`. + Returns R = [-x0, -x1, -x2, ... -xn-1] mod p + """ + return [self.neg(x_i) for x_i in X] + + def extf_sub( + self, X: list[ModuloCircuitElement], Y: list[ModuloCircuitElement] + ) -> list[ModuloCircuitElement]: + return self.extf_add(X, self.extf_neg(Y)) + def extf_mul( self, X: list[ModuloCircuitElement], Y: list[ModuloCircuitElement], + extension_degree: int, ) -> list[ModuloCircuitElement]: """ Multiply in the extension field X * Y mod irreducible_poly Commit to R and accumulates Q. """ + assert len(X) == len(Y) == extension_degree + Q, R = nondeterministic_extension_field_mul_divmod( + X, Y, self.curve_id, self.extension_degree + ) + R, s0, s1 = self.write_commitments(R) - def commit_to_R_and_accumulate_Q(X, Y, Q, R): - Q, R = nondeterministic_extension_field_mul_divmod( - X, Y, self.curve_id, self.extension_degree - ) - R = self.write_commitments(R) - - (_, s1) = self.transcript.hash_limbs(R) - s1 = PyFelt(s1, self.field) + Q = Polynomial(Q) + s1 = self.field(s1) Q_acc: Polynomial = self.acc.nondeterministic_Q + s1 * Q s1 = self.write_cairo_native_felt(s1) @@ -138,23 +162,101 @@ def commit_to_R_and_accumulate_Q(X, Y, Q, R): ) return R + def extf_square( + self, + X: list[ModuloCircuitElement], + extension_degree: int, + ) -> list[ModuloCircuitElement]: + """ + Multiply in the extension field X * Y mod irreducible_poly + Commit to R and accumulates Q. + """ + assert len(X) == extension_degree + Q, R = nondeterministic_extension_field_mul_divmod( + X, X, self.curve_id, self.extension_degree + ) + R, s0, s1 = self.write_commitments(R) + + Q = Polynomial(Q) + s1 = self.field(s1) + Q_acc: Polynomial = self.acc.nondeterministic_Q + s1 * Q + s1 = self.write_cairo_native_felt(s1) + + # Evaluate polynomials X(z), Y(z) inside circuit. + X_of_z = self.eval_poly_in_precomputed_Z(X) + XY_of_z = self.mul(X_of_z, X_of_z) + ci_XY_of_z = self.mul(s1, XY_of_z) + XY_acc = self.add(self.acc.xy, ci_XY_of_z) + + # Computes R_acc = R_acc + s1 * R as a Polynomial inside circuit + R_acc = [self.add(r_acc, self.mul(s1, r)) for r_acc, r in zip(self.acc.R, R)] + + self.acc = EuclideanPolyAccumulator( + xy=XY_acc, nondeterministic_Q=Q_acc, R=R_acc + ) + return R + + def extf_div( + self, + X: list[ModuloCircuitElement], + Y: list[ModuloCircuitElement], + extension_degree: int, + ) -> list[ModuloCircuitElement]: + assert len(X) == len(Y) == extension_degree + x_over_y = nondeterministic_extension_field_div( + X, Y, self.curve_id, extension_degree + ) + add_to_transcript = True + x_over_y, _, _ = self.write_commitments( + x_over_y, add_to_transcript + ) # Is it really necessary to hash this in addition to what is hashed in extf_mul right after ? + should_be_X = self.extf_mul(x_over_y, Y, extension_degree) + self.extf_assert_eq(should_be_X, X) + + return x_over_y + + def extf_assert_eq( + self, X: list[ModuloCircuitElement], Y: list[ModuloCircuitElement] + ): + assert len(X) == len(Y) + for x, y in zip(X, Y): + self.assert_eq(x, y) + def finalize_circuit(self): + # print("\n Finalize Circuit") Q = self.acc.nondeterministic_Q.get_coeffs() Q = Q + [self.field.zero()] * (self.extension_degree - 1 - len(Q)) - Q = self.write_commitments(Q) + Q, s0, s1 = self.write_commitments(Q) Q_of_Z = self.eval_poly_in_precomputed_Z(Q) P, sparsity = self.write_sparse_elements( - get_irreducible_poly(self.curve_id, self.extension_degree).get_coeffs() + get_irreducible_poly(self.curve_id, self.extension_degree).get_coeffs(), + WriteOps.CONSTANT, ) - P_of_z = P[0] - for i in range(1, len(P)): + sparse_p_index = 1 + for i in range(1, len(sparsity)): if sparsity[i] == 1: - P_of_z = self.add(P_of_z, self.mul(P[i], self.z_powers[i])) + P_of_z = self.add( + P_of_z, self.mul(P[sparse_p_index], self.z_powers[i - 1]) + ) + sparse_p_index += 1 R_of_Z = self.eval_poly_in_precomputed_Z(self.acc.R) lhs = self.acc.xy - rhs = self.add(Q_of_Z, self.mul(P_of_z, R_of_Z)) - assert lhs.emulated_felt == rhs.emulated_felt + rhs = self.add(self.mul(Q_of_Z, P_of_z), R_of_Z) + assert lhs.value == rhs.value, f"{lhs.value} != {rhs.value}" return rhs + + def summarize(self): + add_count, mul_count = self.values_segment.summarize() + summary = { + "circuit": self.name, + "MULMOD": mul_count, + "ADDMOD": add_count, + "POSEIDON": self.transcript.permutations_count, + } + # TODO : add Number of poseidon. + + # pprint(summary, ) + return summary diff --git a/src/modulo_circuit.py b/src/modulo_circuit.py index cb106ac7..7a939785 100644 --- a/src/modulo_circuit.py +++ b/src/modulo_circuit.py @@ -1,5 +1,6 @@ from src.algebra import PyFelt, ModuloCircuitElement, BaseField -from src.definitions import STARK, CURVES, N_LIMBS +from src.hints.io import bigint_split +from src.definitions import STARK, CURVES, N_LIMBS, BASE from dataclasses import dataclass from enum import Enum, auto @@ -7,7 +8,7 @@ class WriteOps(Enum): """ Enum for the source of a write operation in the value segment. - -LIMBS: The value was written by coping N_LIMBS from the allocation pointer to the value segment. + -CONSTANT | INPUT: The value was written by coping N_LIMBS from the allocation pointer to the value segment. -COMMIT: The value was written as a result of a hint value from the prover. -FELT: The value was written by coping N_LIMBS from the allocation pointer, as a result of a non-deterministic felt252 to UInt384 decomposition. @@ -15,7 +16,8 @@ class WriteOps(Enum): -BUILTIN: The value was written by the modulo builtin as result of a ADD or MUL instruction. """ - LIMBS = auto() + CONSTANT = auto() + INPUT = auto() COMMIT = auto() FELT = auto() BUILTIN = auto() @@ -30,6 +32,10 @@ class ModBuiltinOps(Enum): MUL = "*" +class CairoVMOps(Enum): + ASSERT_EQ = "==" + + @dataclass(slots=True, frozen=True) class ModuloCircuitInstruction: operation: ModBuiltinOps @@ -38,6 +44,12 @@ class ModuloCircuitInstruction: result_offset: int +@dataclass(slots=True, frozen=True) +class AssertEqInstruction: + segment_left_offset: int + segment_right_offset: int + + @dataclass(slots=True, frozen=True) class ValueSegmentItem: emulated_felt: PyFelt @@ -60,15 +72,21 @@ def felt(self): @dataclass(slots=True, init=False) class ValueSegment: segment: dict[int, ValueSegmentItem] + assert_eq_instructions: list[AssertEqInstruction] offset: int + n_limbs: int debug: bool - stacks: dict[WriteOps, dict[int, ValueSegmentItem]] + name: str + segment_stacks: dict[WriteOps, dict[int, ValueSegmentItem]] - def __init__(self, debug: bool = True): + def __init__(self, name: str, debug: bool = True): self.segment: dict[int, ValueSegmentItem] = dict() + self.assert_eq_instructions: list[AssertEqInstruction] = [] self.offset = 0 + self.n_limbs = N_LIMBS self.debug = debug - self.stacks = {key: dict() for key in WriteOps} + self.name = name + self.segment_stacks = {key: dict() for key in WriteOps} def __len__(self) -> int: return len(self.segment) @@ -76,73 +94,100 @@ def __len__(self) -> int: def __getitem__(self, key: int) -> ValueSegmentItem: return self.segment[key] - def get_add_mul_offsets(self): - add_offsets = [ - ( - item.instruction.left_offset, - item.instruction.right_offset, - result_offset, - ) - for result_offset, item in self.stacks[WriteOps.BUILTIN].items() - if item.instruction.operation == ModBuiltinOps.ADD - ] - mul_offsets = [ - ( - item.instruction.left_offset, - item.instruction.right_offset, - result_offset, - ) - for result_offset, item in self.stacks[WriteOps.BUILTIN].items() - if item.instruction.operation == ModBuiltinOps.MUL - ] - return add_offsets, mul_offsets - - def write(self, item: ValueSegmentItem) -> int: + def write_to_segment(self, item: ValueSegmentItem) -> int: offset = self.offset self.segment[offset] = item - self.stacks[item.write_source][self.offset] = item + self.segment_stacks[item.write_source][offset] = item self.offset += N_LIMBS if item.write_source == WriteOps.FELT: self.offset += 1 return offset - def fiat_shamir(self) -> "ValueSegment": + def assert_eq( + self, + left_offset: int, + right_offset: int, + ): + self.assert_eq_instructions.append( + AssertEqInstruction(left_offset, right_offset) + ) + + def non_interactive_transform(self) -> "ValueSegment": """ Rebuild a new ValueSegment by re-ordering the current one in this order: - LIMBS (inputs) - COMMITS + CONSTANT + INPUT + COMMIT FELT BUILTIN Order matters! """ - res = ValueSegment() + res = ValueSegment(self.name + "_non_interactive") offset_map = {} for stacks_key in [ - WriteOps.LIMBS, + WriteOps.CONSTANT, + WriteOps.INPUT, WriteOps.COMMIT, WriteOps.FELT, WriteOps.BUILTIN, ]: - print(stacks_key, len(self.stacks[stacks_key])) - for old_offset, item in self.stacks[stacks_key].items(): - new_offset = res.write( - ValueSegmentItem( + if self.debug: + print(stacks_key, len(self.segment_stacks[stacks_key])) + for old_offset, item in self.segment_stacks[stacks_key].items(): + new_offset = res.write_to_segment( + item + if stacks_key is not WriteOps.BUILTIN + else ValueSegmentItem( item.emulated_felt, item.write_source, ModuloCircuitInstruction( - item.instruction.operation, - offset_map[item.instruction.left_offset], - offset_map[item.instruction.right_offset], - res.offset, + operation=item.instruction.operation, + left_offset=offset_map[item.instruction.left_offset], + right_offset=offset_map[item.instruction.right_offset], + result_offset=res.offset, ), ) - if stacks_key == WriteOps.BUILTIN - else item ) offset_map[old_offset] = new_offset + for assert_eq_instruction in self.assert_eq_instructions: + res.assert_eq_instructions.append( + AssertEqInstruction( + offset_map[assert_eq_instruction.segment_left_offset], + offset_map[assert_eq_instruction.segment_right_offset], + ) + ) return res + def get_dw_lookups(self) -> dict: + dw_arrays = { + "constants": [], + "add_offsets": [], + "mul_offsets": [], + "left_assert_eq_offsets": [], + "right_assert_eq_offsets": [], + "poseidon_ptr_indexes": [], + } + for _, item in self.segment_stacks[WriteOps.CONSTANT].items(): + dw_arrays["constants"].append(bigint_split(item.value, self.n_limbs, BASE)) + for result_offset, item in self.segment_stacks[WriteOps.BUILTIN].items(): + dw_arrays[item.instruction.operation.name.lower() + "_offsets"].append( + ( + item.instruction.left_offset, + item.instruction.right_offset, + result_offset, + ) + ) + for assert_eq_instruction in self.assert_eq_instructions: + dw_arrays["left_assert_eq_offsets"].append( + assert_eq_instruction.segment_left_offset + ) + dw_arrays["right_assert_eq_offsets"].append( + assert_eq_instruction.segment_right_offset + ) + + return dw_arrays + def print(self): # ANSI escape codes for some colors RED = "\033[31m" # Red text @@ -157,7 +202,7 @@ def print(self): for i, (offset, val) in enumerate(self.segment.items()): row = "" row += f"{BLUE}{'['+str(i)+']':^7}{RESET}|{ORANGE}{'['+str(offset)+']':^7}{RESET}|" - if val.write_source in [WriteOps.BUILTIN, WriteOps.BUILTIN]: + if val.write_source in [WriteOps.BUILTIN]: row += f"{str(val.instruction.left_offset)+str(val.instruction.operation.value)+str(val.instruction.right_offset):^9}|" else: row += f"{'_':^9}|" @@ -166,6 +211,21 @@ def print(self): print(row) + def summarize(self): + add_count = sum( + 1 + for item in self.segment_stacks[WriteOps.BUILTIN].values() + if item.instruction.operation.name == "ADD" + ) + mul_count = sum( + 1 + for item in self.segment_stacks[WriteOps.BUILTIN].values() + if item.instruction.operation.name == "MUL" + ) + assert add_count + mul_count == len(self.segment_stacks[WriteOps.BUILTIN]) + # print(f"Total FpMul : {mul_count} Total FpAdd : {add_count}") + return add_count, mul_count + class ModuloCircuit: """ @@ -183,42 +243,41 @@ class ModuloCircuit: """ def __init__(self, name: str, curve_id: int) -> None: - self.circuit_name = name + self.name = name self.curve_id = curve_id self.field = BaseField(CURVES[curve_id].p) - self.values_offset = 0 - self.number_of_values = 0 self.N_LIMBS = 4 - - self.values_segment: ValueSegment = ValueSegment() + self.values_segment: ValueSegment = ValueSegment(name) self.constants: dict[str, ModuloCircuitElement] = dict() - self.z_powers: list[ModuloCircuitElement] = [] - # self.add_offsets: list[ModuloCircuitInstruction] = [] - # self.mul_offsets: list[ModuloCircuitInstruction] = [] + self.add_constant("ZERO", self.field.zero()) + self.add_constant("ONE", self.field.one()) + self.add_constant("MINUS_ONE", self.field(-1)) + + @property + def values_offset(self): + return self.values_segment.offset def write_element( self, elmt: PyFelt, - write_source: WriteOps = WriteOps.LIMBS, + write_source: WriteOps = WriteOps.INPUT, instruction: ModuloCircuitInstruction | None = None, ) -> ModuloCircuitElement: assert type(elmt) == PyFelt, f"Expected PyFelt, got {type(elmt)}" - self.values_segment.write( + value_offset = self.values_segment.write_to_segment( ValueSegmentItem( elmt, write_source, instruction, ) ) - res = ModuloCircuitElement(elmt, self.values_offset) - self.values_offset += self.N_LIMBS - self.number_of_values += 1 + res = ModuloCircuitElement(elmt, value_offset) return res def write_elements( self, elmts: list[PyFelt], - operation: WriteOps = WriteOps.LIMBS, + operation: WriteOps = WriteOps.INPUT, ) -> list[ModuloCircuitElement]: assert operation not in [ WriteOps.BUILTIN, @@ -226,30 +285,49 @@ def write_elements( ], f"Builtin {operation} not supported in this context. Use add and mul directly." return [self.write_element(elmt, operation, None) for elmt in elmts] - def write_cairo_native_felt(self, value: int): - assert 0 <= value < STARK - res = self.write_element(self.field(value), WriteOps.FELT) - self.values_offset += 1 + def write_cairo_native_felt(self, native_felt: PyFelt): + assert 0 <= native_felt.value < STARK + res = self.write_element(elmt=native_felt, write_source=WriteOps.FELT) return res - def write_sparse_elements(self, elmts: list[PyFelt]) -> list[ModuloCircuitElement]: + def write_sparse_elements( + self, elmts: list[PyFelt], operation: WriteOps + ) -> list[ModuloCircuitElement]: sparsity = [1 if elmt != self.field.zero() else 0 for elmt in elmts] return [ - self.write_element(elmt, "sparse_write") + self.write_element(elmt, operation) for elmt, not_sparse in zip(elmts, sparsity) if not_sparse ], sparsity def add_constant(self, name: str, value: PyFelt) -> None: if name in self.constants: - raise ValueError(f"Constant '{name}' already exists.") - self.constants[name] = self.write_element(value, WriteOps.LIMBS) + print((f"/!\ Constant '{name}' already exists.")) + return self.constants[name] + self.constants[name] = self.write_element(value, WriteOps.CONSTANT) + return self.constants[name] def get_constant(self, name: str) -> ModuloCircuitElement: if name not in self.constants: - raise ValueError(f"Constant '{name}' does not exist.") + raise ValueError( + f"Constant '{name}' does not exist. Available constants : {list(self.constants.keys())}" + ) return self.constants[name] + def assert_eq(self, a: ModuloCircuitElement, b: ModuloCircuitElement): + self.values_segment.assert_eq(a.offset, b.offset) + + def assert_eq_zero(self, a: ModuloCircuitElement): + zero = self.constants["ZERO"] + for i in range(N_LIMBS): + self.values_segment.assert_eq(a.offset + i, zero.offset) + + def assert_eq_one(self, a: ModuloCircuitElement): + one, zero = self.constants["ONE"], self.constants["ZERO"] + self.values_segment.assert_eq(a.offset, one.offset) + for i in range(1, N_LIMBS): + self.values_segment.assert_eq(a.offset + i, zero.offset) + def add( self, a: ModuloCircuitElement, b: ModuloCircuitElement ) -> ModuloCircuitElement: @@ -260,7 +338,6 @@ def add( instruction = ModuloCircuitInstruction( ModBuiltinOps.ADD, a.offset, b.offset, self.values_offset ) - # self.add_offsets.append(instruction) return self.write_element( a.emulated_felt + b.emulated_felt, WriteOps.BUILTIN, instruction ) @@ -274,37 +351,21 @@ def mul( instruction = ModuloCircuitInstruction( ModBuiltinOps.MUL, a.offset, b.offset, self.values_offset ) - # self.mul_offsets.append(instruction) return self.write_element( a.emulated_felt * b.emulated_felt, WriteOps.BUILTIN, instruction ) - def compile_offsets(self) -> str: - add_offsets, mul_offsets = self.values_segment.get_add_mul_offsets() - add_offsets_lookup = f"{self.circuit_name}_add_offsets:\n" - mul_offsets_lookup = f"{self.circuit_name}_mul_offsets:\n" - for left, right, result in add_offsets: - add_offsets_lookup += ( - f"\t dw {left}; // {self.values_segment[left].value}\n" - + f"\t dw {right}; // {self.values_segment[right].value}\n" - + f"\t dw {result}; // {self.values_segment[result].value}\n\n" - ) - for left, right, result in mul_offsets: - mul_offsets_lookup += ( - f"\t dw {left}; // {self.values_segment[left].value}\n" - + f"\t dw {right}; // {self.values_segment[right].value}\n" - + f"\t dw {result}; // {self.values_segment[result].value}\n\n" - ) - return add_offsets_lookup + mul_offsets_lookup + def neg(self, a: ModuloCircuitElement) -> ModuloCircuitElement: + return self.mul(a, self.constants["MINUS_ONE"]) def sub(self, a: ModuloCircuitElement, b: ModuloCircuitElement): - raise NotImplementedError + return self.add(a, self.neg(b)) def inv(self, a: ModuloCircuitElement): raise NotImplementedError def div(self, a: ModuloCircuitElement, b: ModuloCircuitElement): - raise NotImplementedError + return self.mul(a, self.inv(b)) def _check_sanity(self): for a_offset, b_offset, result_offset in self.add_offsets: @@ -322,6 +383,54 @@ def _check_sanity(self): def print_value_segment(self): self.values_segment.print() + def compile_circuit( + self, + returns: list[str] = [ + "constants", + "add_offsets", + "mul_offsets", + "left_assert_eq_offsets", + "right_assert_eq_offsets", + "poseidon_ptr_indexes", + ], + ) -> str: + values_segment_non_interactive = self.values_segment.non_interactive_transform() + dw_arrays = values_segment_non_interactive.get_dw_lookups() + name = values_segment_non_interactive.name + code = f"func get_{name}_circuit()->({':felt*, '.join(returns)})" + "{" + "\n" + + for dw_array_name in returns: + code += f"let ({dw_array_name}_ptr:felt*) = get_label_location({dw_array_name}_loc);\n" + + code += f"return ({'_ptr, '.join(returns)});\n" + + for dw_array_name in returns: + dw_values = dw_arrays[dw_array_name] + code += f"\t {dw_array_name}_loc:\n" + if dw_array_name == "constants": + for bigint in dw_values: + for limb in bigint: + code += f"\t dw {limb};\n" + code += "\n" + + elif dw_array_name in ["add_offsets", "mul_offsets"]: + for left, right, result in dw_values: + code += ( + f"\t dw {left};\n" + f"\t dw {right};\n" + f"\t dw {result};\n" + ) + code += "\n" + elif dw_array_name in [ + "left_assert_eq_offsets", + "right_assert_eq_offsets", + "poseidon_ptr_indexes", + ]: + for val in dw_values: + code += f"\t dw {val};\n" + + code += "\n" + code += "}\n" + return code + if __name__ == "__main__": from src.algebra import BaseField diff --git a/tools/make/requirements.txt b/tools/make/requirements.txt index 06db8925..87ae579b 100644 --- a/tools/make/requirements.txt +++ b/tools/make/requirements.txt @@ -1,3 +1,4 @@ cairo-lang==0.13.0 protobuf==3.20.3 -inquirer \ No newline at end of file +inquirer +pandas \ No newline at end of file diff --git a/tools/make/setup.sh b/tools/make/setup.sh index 3475144f..500e0f4c 100755 --- a/tools/make/setup.sh +++ b/tools/make/setup.sh @@ -7,9 +7,9 @@ echo 'export PYTHONPATH="$PWD:$PYTHONPATH"' >> venv/bin/activate source venv/bin/activate pip install -r tools/make/requirements.txt echo "Patching poseidon_utils.py" -patch venv/lib/python3.9/site-packages/starkware/cairo/common/poseidon_utils.py tools/make/poseidon_utils.patch +patch venv/lib/python3.10/site-packages/starkware/cairo/common/poseidon_utils.py tools/make/poseidon_utils.patch echo "Copying Modulo Builtin files into venv..." -rsync -avh --progress tools/make/cairo/ venv/lib/python3.9/site-packages/starkware/cairo/ +rsync -avh --progress tools/make/cairo/ venv/lib/python3.10/site-packages/starkware/cairo/ if [ "$system" = "Darwin" ]; then brew install llvm