From b8cd1beca1dac20db1023c0d32bc438a9efbe30e Mon Sep 17 00:00:00 2001 From: feltroidprime Date: Mon, 24 Jun 2024 10:40:48 +0200 Subject: [PATCH] wip: cairo1 compiler --- hydra/extension_field_modulo_circuit.py | 10 +- hydra/modulo_circuit.py | 151 ++++++++++++++---- .../precompiled_circuits/multi_miller_loop.py | 10 +- 3 files changed, 126 insertions(+), 45 deletions(-) diff --git a/hydra/extension_field_modulo_circuit.py b/hydra/extension_field_modulo_circuit.py index 13b2ad13..03a192ae 100644 --- a/hydra/extension_field_modulo_circuit.py +++ b/hydra/extension_field_modulo_circuit.py @@ -25,8 +25,9 @@ POSEIDON_OUTPUT_S1_INDEX = 4 -# Represents the state of the accumulation of the equation c_i * X_i(Z)*Y_i(z) = c_i*Q_i*P + c_i*R_i inside the circuit. -# Only store ci*X_i(Z)*Y_i(z) (as Emulated Field Element) and ci*R_i (as Polynomial) +# Represents the state of the accumulation of the equation +# c_i * Π(Pi(z)) = c_i*Q_i*P + c_i*R_i inside the circuit. +# Only store ci*Π(Pi(z)) (as Emulated Field Element) and ci*R_i (as Polynomial) @dataclass(slots=True) class EuclideanPolyAccumulator: lhs: ModuloCircuitElement @@ -279,11 +280,6 @@ def extf_div( extension_degree: int, acc_index: int = 0, ) -> list[ModuloCircuitElement]: - if extension_degree == 2: - return self.fp2_div(X, Y) - else: - assert len(X) == len(Y) == extension_degree - x_over_y = nondeterministic_extension_field_div( X, Y, self.curve_id, extension_degree ) diff --git a/hydra/modulo_circuit.py b/hydra/modulo_circuit.py index 1f68875b..94f80e1a 100644 --- a/hydra/modulo_circuit.py +++ b/hydra/modulo_circuit.py @@ -375,21 +375,21 @@ def add( b: ModuloCircuitElement, ) -> ModuloCircuitElement: - if a is None: - return b - elif b is None: - return a - else: - assert ( - type(a) == type(b) == ModuloCircuitElement - ), f"Expected ModuloElement, got {type(a)}, {a} and {type(b)}, {b}" + # if a is None: + # return b + # elif b is None: + # return a + # else: + assert ( + type(a) == type(b) == ModuloCircuitElement + ), f"Expected ModuloElement, got {type(a)}, {a} and {type(b)}, {b}" - instruction = ModuloCircuitInstruction( - ModBuiltinOps.ADD, a.offset, b.offset, self.values_offset - ) - return self.write_element( - a.emulated_felt + b.emulated_felt, WriteOps.BUILTIN, instruction - ) + instruction = ModuloCircuitInstruction( + ModBuiltinOps.ADD, a.offset, b.offset, self.values_offset + ) + return self.write_element( + a.emulated_felt + b.emulated_felt, WriteOps.BUILTIN, instruction + ) def double(self, a: ModuloCircuitElement) -> ModuloCircuitElement: return self.add(a, a) @@ -419,17 +419,23 @@ def sub(self, a: ModuloCircuitElement, b: ModuloCircuitElement): def inv(self, a: ModuloCircuitElement): instruction = ModuloCircuitInstruction( - ModBuiltinOps.MUL, a.offset, self.values_offset, self.get_constant(1).offset + ModBuiltinOps.MUL, + a.offset, + self.values_offset, + self.get_constant(1).offset, ) return self.write_element(a.felt.__inv__(), WriteOps.BUILTIN, instruction) def div(self, a: ModuloCircuitElement, b: ModuloCircuitElement): - instruction = ModuloCircuitInstruction( - ModBuiltinOps.MUL, b.offset, self.values_offset, a.offset - ) - return self.write_element( - a.felt * b.felt.__inv__(), WriteOps.BUILTIN, instruction - ) + if self.compilation_mode == 0: + instruction = ModuloCircuitInstruction( + ModBuiltinOps.MUL, b.offset, self.values_offset, a.offset + ) + return self.write_element( + a.felt * b.felt.__inv__(), WriteOps.BUILTIN, instruction + ) + elif self.compilation_mode == 1: + return self.mul(a, self.inv(b)) def fp2_mul(self, X: list[ModuloCircuitElement], Y: list[ModuloCircuitElement]): # Assumes the irreducible poly is X^2 + 1. @@ -452,18 +458,28 @@ def fp2_square(self, X: list[ModuloCircuitElement]): def fp2_div(self, X: list[ModuloCircuitElement], Y: list[ModuloCircuitElement]): assert len(X) == len(Y) == 2 - x_over_y = nondeterministic_extension_field_div(X, Y, self.curve_id, 2) - x_over_y = self.write_elements(x_over_y, WriteOps.WITNESS) - # x_over_y = d0 + i * d1 - # y = y0 + i * y1 - # x = x_over_y*y = d0*y0 - d1*y1 + i * (d0*y1 + d1*y0) - self.sub_and_assert( - a=self.mul(x_over_y[0], Y[0]), b=self.mul(x_over_y[1], Y[1]), c=X[0] - ) - self.add_and_assert( - a=self.mul(x_over_y[0], Y[1]), b=self.mul(x_over_y[1], Y[0]), c=X[1] - ) - return x_over_y + if self.compilation_mode == 0: + x_over_y = nondeterministic_extension_field_div(X, Y, self.curve_id, 2) + x_over_y = self.write_elements(x_over_y, WriteOps.WITNESS) + # x_over_y = d0 + i * d1 + # y = y0 + i * y1 + # x = x_over_y*y = d0*y0 - d1*y1 + i * (d0*y1 + d1*y0) + self.sub_and_assert( + a=self.mul(x_over_y[0], Y[0]), b=self.mul(x_over_y[1], Y[1]), c=X[0] + ) + self.add_and_assert( + a=self.mul(x_over_y[0], Y[1]), b=self.mul(x_over_y[1], Y[0]), c=X[1] + ) + return x_over_y + elif self.compilation_mode == 1: + # Todo : consider passing as calldata if possible. + t0 = self.mul(Y[0], Y[0]) + t1 = self.mul(Y[1], Y[1]) + t0 = self.add(t0, t1) + t1 = self.inv(t0) + inv0 = self.mul(Y[0], t1) + inv1 = self.neg(self.mul(Y[1], t1)) + return self.fp2_mul(X, [inv0, inv1]) def sub_and_assert( self, a: ModuloCircuitElement, b: ModuloCircuitElement, c: ModuloCircuitElement @@ -618,6 +634,75 @@ def compile_circuit( code += "}\n" return code + def compile_circuit_cairo_1( + self, + function_name: str = None, + ) -> str: + dw_arrays = self.values_segment.get_dw_lookups() + name = function_name or self.values_segment.name + function_name = f"get_{name}_circuit" + if self.generic_circuit: + code = ( + f"fn {function_name}(mut input: Array,curve_index:usize)->Array" + + "{" + + "\n" + ) + else: + code = ( + f"fn {function_name}(mut input: Array)->Array" + "{" + "\n" + ) + + def write_stack( + write_ops: WriteOps, code: str, start_index: int + ) -> tuple(str, int): + code += f"\n // {write_ops.name} stack\n" + for idx in range( + start_index, + start_index + len(self.values_segment.segment_stacks[write_ops]), + ): + code += ( + f"\t let in{idx} = CircuitElement::> {{}};\n" + ) + c + return code, start_index + len( + self.values_segment.segment_stacks[write_ops] + ) + + code, start_index = write_stack(WriteOps.CONSTANT, code, 0) + code, start_index = write_stack(WriteOps.INPUT, code, start_index) + code, start_index = write_stack(WriteOps.COMMIT, code, start_index) + code, start_index = write_stack(WriteOps.WITNESS, code, start_index) + code, start_index = write_stack(WriteOps.FELT, code, start_index) + + # elif dw_array_name in ["add_offsets_ptr", "mul_offsets_ptr"]: + # num_instructions = len(dw_values) + # instructions_needed = ( + # 8 - (num_instructions % 8) + # ) % 8 # Must be a multiple of 8 (currently) + # for left, right, result in dw_values: + # code += ( + # f"\t dw {left};\n" + f"\t dw {right};\n" + f"\t dw {result};\n" + # ) + # if instructions_needed > 0: + # first_triplet = dw_values[0] + # for _ in range(instructions_needed): + # code += ( + # f"\t dw {first_triplet[0]};\n" + # + f"\t dw {first_triplet[1]};\n" + # + f"\t dw {first_triplet[2]};\n" + # ) + # code += "\n" + # elif dw_array_name in ["output_offsets_ptr"]: + # if continuous_output: + # code += f"\t dw {dw_values[0]};\n" + # else: + # for val in dw_values: + # code += f"\t dw {val};\n" + + code += "\n" + code += "}\n" + return code + def summarize(self): add_count, mul_count, assert_eq_count = self.values_segment.summarize() summary = { diff --git a/hydra/precompiled_circuits/multi_miller_loop.py b/hydra/precompiled_circuits/multi_miller_loop.py index a6083363..4b230dee 100644 --- a/hydra/precompiled_circuits/multi_miller_loop.py +++ b/hydra/precompiled_circuits/multi_miller_loop.py @@ -114,7 +114,7 @@ def compute_doubling_slope( self.mul(num_tmp[1], self.get_constant(6)), ] den = self.extf_add(Q[1], Q[1]) - return self.extf_div(num, den, 2) + return self.fp2_div(num, den) def compute_adding_slope( self, @@ -131,7 +131,7 @@ def compute_adding_slope( # den = xa - xb num = self.extf_sub(Qa[1], Qb[1]) den = self.extf_sub(Qa[0], Qb[0]) - return self.extf_div(num, den, 2) + return self.fp2_div(num, den) def build_sparse_line( self, @@ -273,7 +273,7 @@ def double_and_add_step( num = self.extf_add(Qa[1], Qa[1]) den = self.extf_sub(x3, Qa[0]) - λ2 = self.extf_neg(self.extf_add(λ1, self.extf_div(num, den, 2))) + λ2 = self.extf_neg(self.extf_add(λ1, self.fp2_div(num, den))) # compute xr = λ2²-x1-x3 x4 = self.extf_sub(self.extf_sub(self.fp2_square(λ2), Qa[0]), x3) @@ -307,7 +307,7 @@ def triple_step( self.mul(num_tmp[1], self.get_constant(6)), ] den = self.extf_add(Q[1], Q[1]) - λ1 = self.extf_div(num, den, 2) + λ1 = self.fp2_div(num, den) line1 = self.build_sparse_line( R0=λ1, # Directly store λ as R0 @@ -322,7 +322,7 @@ def triple_step( # compute λ2 = 2y/(x2 − x) − λ1. # However in https://github.com/Consensys/gnark/blob/7cfcd5a723b0726dcfe75a5fc7249a23d690b00b/std/algebra/emulated/sw_bls12381/pairing.go#L548 # It's coded as x - x2. - λ2 = self.extf_sub(self.extf_div(den, self.extf_sub(Q[0], x2), 2), λ1) + λ2 = self.extf_sub(self.fp2_div(den, self.extf_sub(Q[0], x2)), λ1) line2 = self.build_sparse_line( R0=λ2,