diff --git a/extensions/algebra/rvr/ffi/common/src/lib.rs b/extensions/algebra/rvr/ffi/common/src/lib.rs index d1e5e5e309..81b849e649 100644 --- a/extensions/algebra/rvr/ffi/common/src/lib.rs +++ b/extensions/algebra/rvr/ffi/common/src/lib.rs @@ -2,7 +2,7 @@ use std::ffi::c_void; -use halo2curves_axiom::ff::PrimeField; +use halo2curves_axiom::ff::{Field, PrimeField}; use num_bigint::{BigInt, BigUint}; use num_integer::Integer; use rvr_openvm_ext_ffi_common::{rd_mem_words_traced, wr_mem_words_traced, WORD_SIZE}; @@ -37,6 +37,58 @@ pub trait FieldArith { fn is_eq(&self, a: &Self::Elem, b: &Self::Elem) -> bool; } +// ── KnownFieldArith trait ───────────────────────────────────────────────────── + +/// Narrower trait for known (native) field types whose element type supports +/// standard arithmetic via operator overloads. Implementing this trait +/// automatically provides a full [`FieldArith`] impl via a blanket impl — +/// only `read_elem` and `write_elem` need to be supplied. +pub trait KnownFieldArith { + type Elem: Field; + + /// # Safety + /// `state` must be a valid pointer to the C `RvState` struct. + unsafe fn read_elem(&self, state: *mut c_void, ptr: u32) -> Self::Elem; + /// # Safety + /// `state` must be a valid pointer to the C `RvState` struct. + unsafe fn write_elem(&self, state: *mut c_void, ptr: u32, val: &Self::Elem); +} + +impl FieldArith for T { + type Elem = T::Elem; + + #[inline(always)] + unsafe fn read_elem(&self, state: *mut c_void, ptr: u32) -> Self::Elem { + KnownFieldArith::read_elem(self, state, ptr) + } + + #[inline(always)] + unsafe fn write_elem(&self, state: *mut c_void, ptr: u32, val: &Self::Elem) { + KnownFieldArith::write_elem(self, state, ptr, val) + } + + #[inline(always)] + fn add(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { + a + b + } + #[inline(always)] + fn sub(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { + a - b + } + #[inline(always)] + fn mul(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { + a * b + } + #[inline(always)] + fn div(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { + a * b.invert().unwrap() + } + #[inline(always)] + fn is_eq(&self, a: &Self::Elem, b: &Self::Elem) -> bool { + a == b + } +} + // ── 256-bit / 384-bit field I/O ───────────────────────────────────────────── /// Read a 256-bit field element from guest memory (traced). @@ -182,6 +234,57 @@ pub fn mod_inverse(a: &BigUint, p: &BigUint) -> BigUint { .unwrap() } +// ── FFI generation macros (shared by modular and fp2) ─────────────────────── + +/// Generate a known-field FFI function. `$wrapper` must be a +/// `PhantomData`-newtype that implements [`KnownFieldArith`] for `$field`. +#[macro_export] +macro_rules! known_field_op_fn { + ($name:ident, $wrapper:ident, $field:ty, $op:ident) => { + /// # Safety + /// `state` must be a valid `RvState` pointer. + #[no_mangle] + pub unsafe extern "C" fn $name( + state: *mut ::std::ffi::c_void, + rd: u32, + rs1: u32, + rs2: u32, + ) { + let f = $wrapper::<$field>(::std::marker::PhantomData); + $crate::exec_op(&f, state, rd, rs1, rs2, |f, a, b| f.$op(a, b)); + } + }; +} + +/// Generate a generic (unknown-modulus) field-op FFI function. +/// +/// `$field_ty` must be a struct with `modulus: BigUint` and `num_limbs: u32` +/// fields that implements [`FieldArith`]. +#[macro_export] +macro_rules! unknown_field_op_fn { + ($name:ident, $field_ty:ident, $op:ident) => { + /// # Safety + /// `state` must be a valid `RvState` pointer. `modulus_ptr` must point + /// to `num_limbs` bytes. + #[no_mangle] + pub unsafe extern "C" fn $name( + state: *mut ::std::ffi::c_void, + rd_ptr: u32, + rs1_ptr: u32, + rs2_ptr: u32, + num_limbs: u32, + modulus_ptr: *const u8, + ) { + let modulus = ::num_bigint::BigUint::from_bytes_le(::std::slice::from_raw_parts( + modulus_ptr, + num_limbs as usize, + )); + let f = $field_ty { modulus, num_limbs }; + $crate::exec_op(&f, state, rd_ptr, rs1_ptr, rs2_ptr, |f, a, b| f.$op(a, b)); + } + }; +} + // ── Instruction execution helper ───────────────────────────────────────────── /// Read both operands, apply `op`, write the result. diff --git a/extensions/algebra/rvr/ffi/fp2/src/lib.rs b/extensions/algebra/rvr/ffi/fp2/src/lib.rs index 857cc4d5c2..9fef4e9152 100644 --- a/extensions/algebra/rvr/ffi/fp2/src/lib.rs +++ b/extensions/algebra/rvr/ffi/fp2/src/lib.rs @@ -7,11 +7,11 @@ use std::{ffi::c_void, marker::PhantomData}; -use halo2curves_axiom::ff::Field; use num_bigint::BigUint; use rvr_openvm_ext_algebra_ffi_common::{ - exec_op, mod_inverse, read_bigint, read_bls12_381_fq, read_field_256, write_bigint, - write_bls12_381_fq, write_field_256, FieldArith, BLS12_381_ELEM_BYTES, FIELD_256_BYTES, + known_field_op_fn, mod_inverse, read_bigint, read_bls12_381_fq, read_field_256, write_bigint, + write_bls12_381_fq, write_field_256, FieldArith, KnownFieldArith, BLS12_381_ELEM_BYTES, + FIELD_256_BYTES, }; use rvr_openvm_ext_ffi_common::{ rd_mem_words_traced, trace_mem_access_range, wr_mem_words_traced, AS_MEMORY, WORD_SIZE, @@ -28,7 +28,7 @@ struct UnknownComplexField { // ── KnownComplexField impls ───────────────────────────────────────────────── -impl FieldArith for KnownComplexField { +impl KnownFieldArith for KnownComplexField { type Elem = halo2curves_axiom::bn256::Fq2; #[inline(always)] @@ -48,30 +48,9 @@ impl FieldArith for KnownComplexField { &val.c1, ); } - - #[inline(always)] - fn add(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { - a + b - } - #[inline(always)] - fn sub(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { - a - b - } - #[inline(always)] - fn mul(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { - a * b - } - #[inline(always)] - fn div(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { - a * b.invert().unwrap() - } - #[inline(always)] - fn is_eq(&self, a: &Self::Elem, b: &Self::Elem) -> bool { - a == b - } } -impl FieldArith for KnownComplexField { +impl KnownFieldArith for KnownComplexField { type Elem = blstrs::Fp2; #[inline(always)] @@ -87,27 +66,6 @@ impl FieldArith for KnownComplexField { write_bls12_381_fq(state, ptr, &val.c0()); write_bls12_381_fq(state, ptr + BLS12_381_ELEM_BYTES as u32, &val.c1()); } - - #[inline(always)] - fn add(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { - a + b - } - #[inline(always)] - fn sub(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { - a - b - } - #[inline(always)] - fn mul(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { - a * b - } - #[inline(always)] - fn div(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { - a * b.invert().unwrap() - } - #[inline(always)] - fn is_eq(&self, a: &Self::Elem, b: &Self::Elem) -> bool { - a == b - } } // ── UnknownComplexField impl ──────────────────────────────────────────────── @@ -162,25 +120,13 @@ impl FieldArith for UnknownComplexField { // ── Macros ────────────────────────────────────────────────────────────────── -macro_rules! field_op_fn { - ($name:ident, $field:ty, $op:ident) => { - /// # Safety - /// `state` must be a valid `RvState` pointer. - #[no_mangle] - pub unsafe extern "C" fn $name(state: *mut c_void, rd: u32, rs1: u32, rs2: u32) { - let f = KnownComplexField::<$field>(PhantomData); - exec_op(&f, state, rd, rs1, rs2, |f, a, b| f.$op(a, b)); - } - }; -} - macro_rules! define_fp2_ffi { ($field:ty, $suffix:ident) => { paste::paste! { - field_op_fn!([], $field, add); - field_op_fn!([], $field, sub); - field_op_fn!([], $field, mul); - field_op_fn!([], $field, div); + known_field_op_fn!([], KnownComplexField, $field, add); + known_field_op_fn!([], KnownComplexField, $field, sub); + known_field_op_fn!([], KnownComplexField, $field, mul); + known_field_op_fn!([], KnownComplexField, $field, div); } }; } @@ -190,32 +136,12 @@ define_fp2_ffi!(blstrs::Fp2, bls12_381); // ── Generic FFI (fallback for unknown moduli) ──────────────────────────────── -macro_rules! unknown_field_op_fn { - ($name:ident, $op:ident) => { - /// # Safety - /// `state` must be a valid `RvState` pointer. `modulus_ptr` must point - /// to `num_limbs` bytes. - #[no_mangle] - pub unsafe extern "C" fn $name( - state: *mut c_void, - rd_ptr: u32, - rs1_ptr: u32, - rs2_ptr: u32, - num_limbs: u32, - modulus_ptr: *const u8, - ) { - let modulus = - BigUint::from_bytes_le(std::slice::from_raw_parts(modulus_ptr, num_limbs as usize)); - let f = UnknownComplexField { modulus, num_limbs }; - exec_op(&f, state, rd_ptr, rs1_ptr, rs2_ptr, |f, a, b| f.$op(a, b)); - } - }; -} +use rvr_openvm_ext_algebra_ffi_common::unknown_field_op_fn; -unknown_field_op_fn!(rvr_ext_fp2_add, add); -unknown_field_op_fn!(rvr_ext_fp2_sub, sub); -unknown_field_op_fn!(rvr_ext_fp2_mul, mul); -unknown_field_op_fn!(rvr_ext_fp2_div, div); +unknown_field_op_fn!(rvr_ext_fp2_add, UnknownComplexField, add); +unknown_field_op_fn!(rvr_ext_fp2_sub, UnknownComplexField, sub); +unknown_field_op_fn!(rvr_ext_fp2_mul, UnknownComplexField, mul); +unknown_field_op_fn!(rvr_ext_fp2_div, UnknownComplexField, div); /// # Safety /// `state` must be a valid `RvState` pointer. diff --git a/extensions/algebra/rvr/ffi/modular/src/lib.rs b/extensions/algebra/rvr/ffi/modular/src/lib.rs index 44f5a18f9b..d7db3af0b3 100644 --- a/extensions/algebra/rvr/ffi/modular/src/lib.rs +++ b/extensions/algebra/rvr/ffi/modular/src/lib.rs @@ -12,12 +12,12 @@ use std::{ffi::c_void, marker::PhantomData}; -use halo2curves_axiom::ff::{Field, PrimeField}; +use halo2curves_axiom::ff::PrimeField; use num_bigint::BigUint; use num_traits::One; use rvr_openvm_ext_algebra_ffi_common::{ - exec_op, mod_inverse, read_bigint, read_bls12_381_fq, read_field_256, write_bigint, - write_bls12_381_fq, write_field_256, FieldArith, + known_field_op_fn, mod_inverse, read_bigint, read_bls12_381_fq, read_field_256, write_bigint, + write_bls12_381_fq, write_field_256, FieldArith, KnownFieldArith, }; use rvr_openvm_ext_ffi_common::{ ext_hint_stream_set, rd_mem_u64_range_wrapper, rd_mem_words_traced, trace_mem_access_range, @@ -45,70 +45,30 @@ struct UnknownPrimeField { // ── KnownPrimeField impl (256-bit via generic bound) ──────────────────────── -impl> FieldArith for KnownPrimeField { +impl> KnownFieldArith for KnownPrimeField { type Elem = F; #[inline(always)] - unsafe fn read_elem(&self, state: *mut c_void, ptr: u32) -> Self::Elem { + unsafe fn read_elem(&self, state: *mut c_void, ptr: u32) -> F { read_field_256(state, ptr) } #[inline(always)] - unsafe fn write_elem(&self, state: *mut c_void, ptr: u32, val: &Self::Elem) { + unsafe fn write_elem(&self, state: *mut c_void, ptr: u32, val: &F) { write_field_256(state, ptr, val) } - #[inline(always)] - fn add(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { - a + b - } - #[inline(always)] - fn sub(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { - a - b - } - #[inline(always)] - fn mul(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { - a * b - } - #[inline(always)] - fn div(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { - a * b.invert().unwrap() - } - #[inline(always)] - fn is_eq(&self, a: &Self::Elem, b: &Self::Elem) -> bool { - a == b - } } // ── KnownPrimeField impl (BLS12-381 Fq, 48 bytes) ────────────────────────── -impl FieldArith for KnownPrimeField { +impl KnownFieldArith for KnownPrimeField { type Elem = blstrs::Fp; #[inline(always)] - unsafe fn read_elem(&self, state: *mut c_void, ptr: u32) -> Self::Elem { + unsafe fn read_elem(&self, state: *mut c_void, ptr: u32) -> blstrs::Fp { read_bls12_381_fq(state, ptr) } #[inline(always)] - unsafe fn write_elem(&self, state: *mut c_void, ptr: u32, val: &Self::Elem) { + unsafe fn write_elem(&self, state: *mut c_void, ptr: u32, val: &blstrs::Fp) { write_bls12_381_fq(state, ptr, val) } - #[inline(always)] - fn add(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { - a + b - } - #[inline(always)] - fn sub(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { - a - b - } - #[inline(always)] - fn mul(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { - a * b - } - #[inline(always)] - fn div(&self, a: Self::Elem, b: Self::Elem) -> Self::Elem { - a * b.invert().unwrap() - } - #[inline(always)] - fn is_eq(&self, a: &Self::Elem, b: &Self::Elem) -> bool { - a == b - } } // ── UnknownPrimeField impl ────────────────────────────────────────────────── @@ -161,25 +121,13 @@ unsafe fn exec_iseq(f: &F, state: *mut c_void, rs1_ptr: u32, rs2_ // ── FFI generation macros ──────────────────────────────────────────────────── -macro_rules! field_op_fn { - ($name:ident, $field:ty, $op:ident) => { - /// # Safety - /// `state` must be a valid `RvState` pointer. - #[no_mangle] - pub unsafe extern "C" fn $name(state: *mut c_void, rd: u32, rs1: u32, rs2: u32) { - let f = KnownPrimeField::<$field>(PhantomData); - exec_op(&f, state, rd, rs1, rs2, |f, a, b| f.$op(a, b)); - } - }; -} - macro_rules! define_mod_ffi { ($field:ty, $suffix:ident) => { paste::paste! { - field_op_fn!([], $field, add); - field_op_fn!([], $field, sub); - field_op_fn!([], $field, mul); - field_op_fn!([], $field, div); + known_field_op_fn!([], KnownPrimeField, $field, add); + known_field_op_fn!([], KnownPrimeField, $field, sub); + known_field_op_fn!([], KnownPrimeField, $field, mul); + known_field_op_fn!([], KnownPrimeField, $field, div); /// # Safety /// `state` must be a valid `RvState` pointer. #[no_mangle] @@ -203,32 +151,12 @@ define_mod_ffi!(halo2curves_axiom::bls12_381::Fr, bls12_381_fr); // ── Generic FFI (fallback for unknown moduli) ──────────────────────────────── -macro_rules! unknown_field_op_fn { - ($name:ident, $op:ident) => { - /// # Safety - /// `state` must be a valid `RvState` pointer. `modulus_ptr` must point - /// to `num_limbs` bytes. - #[no_mangle] - pub unsafe extern "C" fn $name( - state: *mut c_void, - rd_ptr: u32, - rs1_ptr: u32, - rs2_ptr: u32, - num_limbs: u32, - modulus_ptr: *const u8, - ) { - let modulus = - BigUint::from_bytes_le(std::slice::from_raw_parts(modulus_ptr, num_limbs as usize)); - let f = UnknownPrimeField { modulus, num_limbs }; - exec_op(&f, state, rd_ptr, rs1_ptr, rs2_ptr, |f, a, b| f.$op(a, b)); - } - }; -} +use rvr_openvm_ext_algebra_ffi_common::unknown_field_op_fn; -unknown_field_op_fn!(rvr_ext_mod_add, add); -unknown_field_op_fn!(rvr_ext_mod_sub, sub); -unknown_field_op_fn!(rvr_ext_mod_mul, mul); -unknown_field_op_fn!(rvr_ext_mod_div, div); +unknown_field_op_fn!(rvr_ext_mod_add, UnknownPrimeField, add); +unknown_field_op_fn!(rvr_ext_mod_sub, UnknownPrimeField, sub); +unknown_field_op_fn!(rvr_ext_mod_mul, UnknownPrimeField, mul); +unknown_field_op_fn!(rvr_ext_mod_div, UnknownPrimeField, div); /// # Safety /// `state` must be a valid `RvState` pointer. `modulus_ptr` must point to `num_limbs` bytes. diff --git a/extensions/algebra/rvr/src/common.rs b/extensions/algebra/rvr/src/common.rs new file mode 100644 index 0000000000..f7efaee06d --- /dev/null +++ b/extensions/algebra/rvr/src/common.rs @@ -0,0 +1,215 @@ +use std::{fmt::Debug, marker::PhantomData}; + +use rvr_openvm_ir::{ExtEmitCtx, ExtInstr, Reg}; + +use crate::{detect_known_field, format_c_byte_array, KnownField, ModOp}; + +/// Base trait for zero-sized marker types that identify a field kind (modular +/// or Fp2). Provides the C function prefix and known-field suffix lookup used +/// by all three instruction families (arith, iseq, setup). +pub(crate) trait FieldKind: Clone + Debug + Send + Sync + 'static { + fn c_prefix() -> &'static str; + /// Returns the C suffix for a known field, or `None` to fall through to the + /// generic path. + fn known_suffix(field: KnownField) -> Option<&'static str>; +} + +/// Extension of [`FieldKind`] for the arithmetic instruction family. Adds the +/// IR opname used by [`FieldArithInstr`]. +pub(crate) trait ArithKind: FieldKind { + fn opname() -> &'static str; +} + +/// Extension of [`FieldKind`] for the setup instruction family. Adds the +/// IR opname used by [`FieldSetupInstr`]. +pub(crate) trait SetupKind: FieldKind { + fn opname() -> &'static str; +} + +/// Extension of [`FieldKind`] for the IS_EQ instruction family. Adds the +/// IR opname used by [`FieldIsEqInstr`]. Not implemented for Fp2 — fp2 has no IS_EQ. +pub(crate) trait IsEqKind: FieldKind { + fn opname() -> &'static str; +} + +/// Generic IR node for field arithmetic (ADD, SUB, MUL, DIV). +/// Use the type aliases [`crate::ModArithInstr`] / [`crate::Fp2ArithInstr`] +/// rather than naming this type directly. +#[derive(Debug, Clone)] +pub(crate) struct FieldArithInstr { + _kind: PhantomData, + pub op: ModOp, + pub rd_reg: Reg, + pub rs1_reg: Reg, + pub rs2_reg: Reg, + pub num_limbs: u32, + pub modulus: Vec, +} + +impl FieldArithInstr { + pub fn new( + op: ModOp, + rd_reg: Reg, + rs1_reg: Reg, + rs2_reg: Reg, + num_limbs: u32, + modulus: Vec, + ) -> Self { + Self { + _kind: PhantomData, + op, + rd_reg, + rs1_reg, + rs2_reg, + num_limbs, + modulus, + } + } +} + +/// Generic IR node for field IS_EQ. +/// Use the type alias [`crate::ModIsEqInstr`] rather than naming this type directly. +#[derive(Debug, Clone)] +pub(crate) struct FieldIsEqInstr { + _kind: PhantomData, + pub rd_reg: Reg, + pub rs1_reg: Reg, + pub rs2_reg: Reg, + pub num_limbs: u32, + pub modulus: Vec, +} + +impl FieldIsEqInstr { + pub fn new(rd_reg: Reg, rs1_reg: Reg, rs2_reg: Reg, num_limbs: u32, modulus: Vec) -> Self { + Self { + _kind: PhantomData, + rd_reg, + rs1_reg, + rs2_reg, + num_limbs, + modulus, + } + } +} + +/// Generic IR node for field setup (SETUP_ADDSUB, SETUP_MULDIV, SETUP_ISEQ). +/// The C function name is derived from `K::c_prefix()` as `rvr_ext_{prefix}_setup`. +/// Use the type aliases [`crate::ModSetupInstr`] / [`crate::Fp2SetupInstr`] +/// rather than naming this type directly. +#[derive(Debug, Clone)] +pub(crate) struct FieldSetupInstr { + _kind: PhantomData, + pub rd_reg: Reg, + pub rs1_reg: Reg, + pub rs2_reg: Reg, + pub num_limbs: u32, +} + +impl FieldSetupInstr { + pub fn new(rd_reg: Reg, rs1_reg: Reg, rs2_reg: Reg, num_limbs: u32) -> Self { + Self { + _kind: PhantomData, + rd_reg, + rs1_reg, + rs2_reg, + num_limbs, + } + } +} + +impl ExtInstr for FieldSetupInstr { + fn opname(&self) -> &str { + K::opname() + } + + fn emit_c(&self, ctx: &mut dyn ExtEmitCtx) { + let rd = ctx.read_reg(self.rd_reg); + let rs1 = ctx.read_reg(self.rs1_reg); + let rs2 = ctx.read_reg(self.rs2_reg); + let num_limbs = format!("{}u", self.num_limbs); + let name = format!("rvr_ext_{}_setup", K::c_prefix()); + ctx.extern_call(&name, &["state", &rd, &rs1, &rs2, &num_limbs]); + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + + fn is_block_end(&self) -> bool { + false + } +} + +impl ExtInstr for FieldIsEqInstr { + fn opname(&self) -> &str { + K::opname() + } + + fn emit_c(&self, ctx: &mut dyn ExtEmitCtx) { + let rs1 = ctx.read_reg(self.rs1_reg); + let rs2 = ctx.read_reg(self.rs2_reg); + let prefix = K::c_prefix(); + let known_suffix = detect_known_field(&self.modulus).and_then(K::known_suffix); + if let Some(suffix) = known_suffix { + let name = format!("rvr_ext_{prefix}_iseq_{suffix}"); + let val = ctx.extern_call_expr("uint32_t", &name, &["state", &rs1, &rs2]); + ctx.write_reg(self.rd_reg, &val); + } else { + let mod_literal = format_c_byte_array(&self.modulus); + ctx.write_line("{"); + ctx.write_line(&format!("static const uint8_t mod_[] = {mod_literal};")); + let name = format!("rvr_ext_{prefix}_iseq"); + let num_limbs = format!("{}u", self.num_limbs); + let val = ctx.extern_call_expr( + "uint32_t", + &name, + &["state", &rs1, &rs2, &num_limbs, "mod_"], + ); + ctx.write_reg(self.rd_reg, &val); + ctx.write_line("}"); + } + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + + fn is_block_end(&self) -> bool { + false + } +} + +impl ExtInstr for FieldArithInstr { + fn opname(&self) -> &str { + K::opname() + } + + fn emit_c(&self, ctx: &mut dyn ExtEmitCtx) { + let rd = ctx.read_reg(self.rd_reg); + let rs1 = ctx.read_reg(self.rs1_reg); + let rs2 = ctx.read_reg(self.rs2_reg); + let op_name = self.op.c_name(); + let prefix = K::c_prefix(); + let known_suffix = detect_known_field(&self.modulus).and_then(K::known_suffix); + if let Some(suffix) = known_suffix { + let name = format!("rvr_ext_{prefix}_{op_name}_{suffix}"); + ctx.extern_call(&name, &["state", &rd, &rs1, &rs2]); + } else { + let mod_literal = format_c_byte_array(&self.modulus); + ctx.write_line("{"); + ctx.write_line(&format!("static const uint8_t mod_[] = {mod_literal};")); + let name = format!("rvr_ext_{prefix}_{op_name}"); + let num_limbs = format!("{}u", self.num_limbs); + ctx.extern_call(&name, &["state", &rd, &rs1, &rs2, &num_limbs, "mod_"]); + ctx.write_line("}"); + } + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + + fn is_block_end(&self) -> bool { + false + } +} diff --git a/extensions/algebra/rvr/src/fp2.rs b/extensions/algebra/rvr/src/fp2.rs index b3ba48ed4f..898c1fe851 100644 --- a/extensions/algebra/rvr/src/fp2.rs +++ b/extensions/algebra/rvr/src/fp2.rs @@ -4,11 +4,14 @@ use num_bigint::BigUint; use openvm_algebra_transpiler::Fp2Opcode; use openvm_instructions::{instruction::Instruction, LocalOpcode}; use openvm_stark_backend::p3_field::PrimeField32; -use rvr_openvm_ir::{ExtEmitCtx, ExtInstr, Instr, InstrAt, LiftedInstr, Reg}; +use rvr_openvm_ir::{Instr, InstrAt, LiftedInstr}; use rvr_openvm_lift::{helpers::decode_reg, RvrExtension}; use strum::EnumCount; -use crate::{detect_known_field, format_c_byte_array, ModOp}; +use crate::{ + pad_modulus, ArithKind, FieldArithInstr, FieldKind, FieldSetupInstr, KnownField, ModOp, + SetupKind, +}; /// Per-modulus info for the Fp2 extension. Fp2 lifting never consults a /// non-QR, so we only carry the padded modulus and limb count. @@ -22,14 +25,7 @@ fn make_moduli(moduli: Vec) -> Vec { } fn make_modulus_info(modulus: BigUint) -> ModulusInfo { - let bytes = modulus.bits().div_ceil(8) as usize; - assert!( - bytes <= 48, - "modulus exceeds maximum supported size of 384 bits" - ); - let num_limbs = if bytes <= 32 { 32u32 } else { 48u32 }; - let mut modulus_bytes = modulus.to_bytes_le(); - modulus_bytes.resize(num_limbs as usize, 0); + let (modulus_bytes, num_limbs) = pad_modulus(&modulus); ModulusInfo { modulus_bytes, num_limbs, @@ -38,81 +34,35 @@ fn make_modulus_info(modulus: BigUint) -> ModulusInfo { // ── Fp2 arithmetic IR ──────────────────────────────────────────────────────── -/// IR node for Fp2 arithmetic (ADD, SUB, MUL, DIV). #[derive(Debug, Clone)] -pub struct Fp2ArithInstr { - pub op: ModOp, - pub rd_reg: Reg, - pub rs1_reg: Reg, - pub rs2_reg: Reg, - pub num_limbs: u32, - pub modulus: Vec, -} +pub(crate) struct Fp2ArithKind; -impl ExtInstr for Fp2ArithInstr { - fn opname(&self) -> &str { - "fp2_arith" +impl FieldKind for Fp2ArithKind { + fn c_prefix() -> &'static str { + "fp2" } - - fn emit_c(&self, ctx: &mut dyn ExtEmitCtx) { - let rd = ctx.read_reg(self.rd_reg); - let rs1 = ctx.read_reg(self.rs1_reg); - let rs2 = ctx.read_reg(self.rs2_reg); - let op_name = self.op.c_name(); - let fp2_suffix = detect_known_field(&self.modulus).and_then(|f| f.fp2_c_suffix()); - if let Some(suffix) = fp2_suffix { - let name = format!("rvr_ext_fp2_{op_name}_{suffix}"); - ctx.extern_call(&name, &["state", &rd, &rs1, &rs2]); - } else { - let mod_literal = format_c_byte_array(&self.modulus); - ctx.write_line("{"); - ctx.write_line(&format!("static const uint8_t mod_[] = {mod_literal};")); - let name = format!("rvr_ext_fp2_{op_name}"); - let num_limbs = format!("{}u", self.num_limbs); - ctx.extern_call(&name, &["state", &rd, &rs1, &rs2, &num_limbs, "mod_"]); - ctx.write_line("}"); - } - } - - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } - - fn is_block_end(&self) -> bool { - false + fn known_suffix(field: KnownField) -> Option<&'static str> { + field.fp2_c_suffix() } } -/// IR node for Fp2 SETUP (SETUP_ADDSUB, SETUP_MULDIV). -#[derive(Debug, Clone)] -pub struct Fp2SetupInstr { - pub rd_reg: Reg, - pub rs1_reg: Reg, - pub rs2_reg: Reg, - pub num_limbs: u32, +impl ArithKind for Fp2ArithKind { + fn opname() -> &'static str { + "fp2_arith" + } } -impl ExtInstr for Fp2SetupInstr { - fn opname(&self) -> &str { +impl SetupKind for Fp2ArithKind { + fn opname() -> &'static str { "fp2_setup" } +} - fn emit_c(&self, ctx: &mut dyn ExtEmitCtx) { - let rd = ctx.read_reg(self.rd_reg); - let rs1 = ctx.read_reg(self.rs1_reg); - let rs2 = ctx.read_reg(self.rs2_reg); - let num_limbs = format!("{}u", self.num_limbs); - ctx.extern_call("rvr_ext_fp2_setup", &["state", &rd, &rs1, &rs2, &num_limbs]); - } - - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } +/// IR node for Fp2 arithmetic (ADD, SUB, MUL, DIV). +pub(crate) type Fp2ArithInstr = FieldArithInstr; - fn is_block_end(&self) -> bool { - false - } -} +/// IR node for Fp2 SETUP (SETUP_ADDSUB, SETUP_MULDIV). +pub(crate) type Fp2SetupInstr = FieldSetupInstr; // ── Fp2 extension ──────────────────────────────────────────────────────────── @@ -175,53 +125,48 @@ impl Fp2RvrExtension { let rs1_reg = decode_reg(insn.b); let rs2_reg = decode_reg(insn.c); - let instr: Instr = match local { - x if x == Fp2Opcode::ADD as usize => Instr::Ext(Box::new(Fp2ArithInstr { - op: ModOp::Add, - rd_reg, - rs1_reg, - rs2_reg, - num_limbs: info.num_limbs, - modulus: info.modulus_bytes.clone(), - })), - x if x == Fp2Opcode::SUB as usize => Instr::Ext(Box::new(Fp2ArithInstr { - op: ModOp::Sub, - rd_reg, - rs1_reg, - rs2_reg, - num_limbs: info.num_limbs, - modulus: info.modulus_bytes.clone(), - })), - x if x == Fp2Opcode::SETUP_ADDSUB as usize => Instr::Ext(Box::new(Fp2SetupInstr { - rd_reg, - rs1_reg, - rs2_reg, - num_limbs: info.num_limbs, - })), - x if x == Fp2Opcode::MUL as usize => Instr::Ext(Box::new(Fp2ArithInstr { - op: ModOp::Mul, - rd_reg, - rs1_reg, - rs2_reg, - num_limbs: info.num_limbs, - modulus: info.modulus_bytes.clone(), - })), - x if x == Fp2Opcode::DIV as usize => Instr::Ext(Box::new(Fp2ArithInstr { - op: ModOp::Div, - rd_reg, - rs1_reg, - rs2_reg, - num_limbs: info.num_limbs, - modulus: info.modulus_bytes.clone(), - })), - x if x == Fp2Opcode::SETUP_MULDIV as usize => Instr::Ext(Box::new(Fp2SetupInstr { - rd_reg, - rs1_reg, - rs2_reg, - num_limbs: info.num_limbs, - })), - _ => return None, - }; + let instr: Instr = + match local { + x if x == Fp2Opcode::ADD as usize => Instr::Ext(Box::new(Fp2ArithInstr::new( + ModOp::Add, + rd_reg, + rs1_reg, + rs2_reg, + info.num_limbs, + info.modulus_bytes.clone(), + ))), + x if x == Fp2Opcode::SUB as usize => Instr::Ext(Box::new(Fp2ArithInstr::new( + ModOp::Sub, + rd_reg, + rs1_reg, + rs2_reg, + info.num_limbs, + info.modulus_bytes.clone(), + ))), + x if x == Fp2Opcode::SETUP_ADDSUB as usize => Instr::Ext(Box::new( + Fp2SetupInstr::new(rd_reg, rs1_reg, rs2_reg, info.num_limbs), + )), + x if x == Fp2Opcode::MUL as usize => Instr::Ext(Box::new(Fp2ArithInstr::new( + ModOp::Mul, + rd_reg, + rs1_reg, + rs2_reg, + info.num_limbs, + info.modulus_bytes.clone(), + ))), + x if x == Fp2Opcode::DIV as usize => Instr::Ext(Box::new(Fp2ArithInstr::new( + ModOp::Div, + rd_reg, + rs1_reg, + rs2_reg, + info.num_limbs, + info.modulus_bytes.clone(), + ))), + x if x == Fp2Opcode::SETUP_MULDIV as usize => Instr::Ext(Box::new( + Fp2SetupInstr::new(rd_reg, rs1_reg, rs2_reg, info.num_limbs), + )), + _ => return None, + }; Some(LiftedInstr::Body(InstrAt { pc, diff --git a/extensions/algebra/rvr/src/lib.rs b/extensions/algebra/rvr/src/lib.rs index d5152b9016..d555181db6 100644 --- a/extensions/algebra/rvr/src/lib.rs +++ b/extensions/algebra/rvr/src/lib.rs @@ -6,13 +6,31 @@ //! (modular + phantoms; ships the lift-time C and libsecp256k1 inputs for //! k256) and [`Fp2RvrExtension`] (fp2 ops only; Rust-only). +mod common; mod fp2; mod modular; -pub use fp2::{Fp2ArithInstr, Fp2RvrExtension, Fp2SetupInstr}; -pub use modular::{ - HintNonQrInstr, HintSqrtInstr, ModArithInstr, ModIsEqInstr, ModSetupInstr, ModularRvrExtension, +pub(crate) use common::{ + ArithKind, FieldArithInstr, FieldIsEqInstr, FieldKind, FieldSetupInstr, IsEqKind, SetupKind, }; +pub use fp2::Fp2RvrExtension; +pub use modular::{HintNonQrInstr, HintSqrtInstr, ModularRvrExtension}; +use num_bigint::BigUint; + +/// Zero-pad `modulus` to the canonical limb boundary (32 or 48 bytes). +/// +/// Returns `(padded_bytes_le, num_limbs)`. Panics if the modulus exceeds 384 bits. +fn pad_modulus(modulus: &BigUint) -> (Vec, u32) { + let bytes = modulus.bits().div_ceil(8) as usize; + assert!( + bytes <= 48, + "modulus exceeds maximum supported size of 384 bits" + ); + let num_limbs = if bytes <= 32 { 32u32 } else { 48u32 }; + let mut modulus_bytes = modulus.to_bytes_le(); + modulus_bytes.resize(num_limbs as usize, 0); + (modulus_bytes, num_limbs) +} // ── Modular arithmetic operations ──────────────────────────────────────────── diff --git a/extensions/algebra/rvr/src/modular.rs b/extensions/algebra/rvr/src/modular.rs index db92684485..5e19b55fc7 100644 --- a/extensions/algebra/rvr/src/modular.rs +++ b/extensions/algebra/rvr/src/modular.rs @@ -11,7 +11,10 @@ use rvr_openvm_ir::{ExtEmitCtx, ExtInstr, Instr, InstrAt, LiftedInstr, Reg}; use rvr_openvm_lift::{helpers::decode_reg, RvrExtension}; use strum::EnumCount; -use crate::{detect_known_field, format_c_byte_array, ModOp}; +use crate::{ + format_c_byte_array, pad_modulus, ArithKind, FieldArithInstr, FieldIsEqInstr, FieldKind, + FieldSetupInstr, IsEqKind, KnownField, ModOp, SetupKind, +}; include!(concat!(env!("OUT_DIR"), "/secp256k1_files.rs")); @@ -35,14 +38,7 @@ fn make_moduli(moduli: Vec) -> Vec { } fn make_modulus_info(modulus: &BigUint, rng: &mut StdRng) -> ModulusInfo { - let bytes = modulus.bits().div_ceil(8) as usize; - assert!( - bytes <= 48, - "modulus exceeds maximum supported size of 384 bits" - ); - let num_limbs = if bytes <= 32 { 32u32 } else { 48u32 }; - let mut modulus_bytes = modulus.to_bytes_le(); - modulus_bytes.resize(num_limbs as usize, 0); + let (modulus_bytes, num_limbs) = pad_modulus(modulus); let non_qr = find_non_qr(modulus, rng); let mut non_qr_bytes = non_qr.to_bytes_le(); non_qr_bytes.resize(num_limbs as usize, 0); @@ -55,128 +51,44 @@ fn make_modulus_info(modulus: &BigUint, rng: &mut StdRng) -> ModulusInfo { // ── Modular arithmetic IR ──────────────────────────────────────────────────── -/// IR node for modular arithmetic (ADD, SUB, MUL, DIV). #[derive(Debug, Clone)] -pub struct ModArithInstr { - pub op: ModOp, - pub rd_reg: Reg, - pub rs1_reg: Reg, - pub rs2_reg: Reg, - pub num_limbs: u32, - pub modulus: Vec, -} +pub(crate) struct ModArithKind; -impl ExtInstr for ModArithInstr { - fn opname(&self) -> &str { - "mod_arith" +impl FieldKind for ModArithKind { + fn c_prefix() -> &'static str { + "mod" } - - fn emit_c(&self, ctx: &mut dyn ExtEmitCtx) { - let rd = ctx.read_reg(self.rd_reg); - let rs1 = ctx.read_reg(self.rs1_reg); - let rs2 = ctx.read_reg(self.rs2_reg); - let op_name = self.op.c_name(); - if let Some(field) = detect_known_field(&self.modulus) { - let suffix = field.c_suffix(); - let name = format!("rvr_ext_mod_{op_name}_{suffix}"); - ctx.extern_call(&name, &["state", &rd, &rs1, &rs2]); - } else { - let mod_literal = format_c_byte_array(&self.modulus); - ctx.write_line("{"); - ctx.write_line(&format!("static const uint8_t mod_[] = {mod_literal};")); - let name = format!("rvr_ext_mod_{op_name}"); - let num_limbs = format!("{}u", self.num_limbs); - ctx.extern_call(&name, &["state", &rd, &rs1, &rs2, &num_limbs, "mod_"]); - ctx.write_line("}"); - } - } - - fn clone_box(&self) -> Box { - Box::new(self.clone()) + fn known_suffix(field: KnownField) -> Option<&'static str> { + Some(field.c_suffix()) } +} - fn is_block_end(&self) -> bool { - false +impl ArithKind for ModArithKind { + fn opname() -> &'static str { + "mod_arith" } } -/// IR node for modular IS_EQ. -#[derive(Debug, Clone)] -pub struct ModIsEqInstr { - pub rd_reg: Reg, - pub rs1_reg: Reg, - pub rs2_reg: Reg, - pub num_limbs: u32, - pub modulus: Vec, +impl SetupKind for ModArithKind { + fn opname() -> &'static str { + "mod_setup" + } } -impl ExtInstr for ModIsEqInstr { - fn opname(&self) -> &str { +impl IsEqKind for ModArithKind { + fn opname() -> &'static str { "mod_iseq" } - - fn emit_c(&self, ctx: &mut dyn ExtEmitCtx) { - let rs1 = ctx.read_reg(self.rs1_reg); - let rs2 = ctx.read_reg(self.rs2_reg); - if let Some(field) = detect_known_field(&self.modulus) { - let suffix = field.c_suffix(); - let name = format!("rvr_ext_mod_iseq_{suffix}"); - let val = ctx.extern_call_expr("uint32_t", &name, &["state", &rs1, &rs2]); - ctx.write_reg(self.rd_reg, &val); - } else { - let mod_literal = format_c_byte_array(&self.modulus); - ctx.write_line("{"); - ctx.write_line(&format!("static const uint8_t mod_[] = {mod_literal};")); - let num_limbs = format!("{}u", self.num_limbs); - let val = ctx.extern_call_expr( - "uint32_t", - "rvr_ext_mod_iseq", - &["state", &rs1, &rs2, &num_limbs, "mod_"], - ); - ctx.write_reg(self.rd_reg, &val); - ctx.write_line("}"); - } - } - - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } - - fn is_block_end(&self) -> bool { - false - } -} - -/// IR node for modular SETUP (SETUP_ADDSUB, SETUP_MULDIV, SETUP_ISEQ). -#[derive(Debug, Clone)] -pub struct ModSetupInstr { - pub rd_reg: Reg, - pub rs1_reg: Reg, - pub rs2_reg: Reg, - pub num_limbs: u32, } -impl ExtInstr for ModSetupInstr { - fn opname(&self) -> &str { - "mod_setup" - } - - fn emit_c(&self, ctx: &mut dyn ExtEmitCtx) { - let rd = ctx.read_reg(self.rd_reg); - let rs1 = ctx.read_reg(self.rs1_reg); - let rs2 = ctx.read_reg(self.rs2_reg); - let num_limbs = format!("{}u", self.num_limbs); - ctx.extern_call("rvr_ext_mod_setup", &["state", &rd, &rs1, &rs2, &num_limbs]); - } +/// IR node for modular arithmetic (ADD, SUB, MUL, DIV). +pub(crate) type ModArithInstr = FieldArithInstr; - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } +/// IR node for modular IS_EQ. +pub(crate) type ModIsEqInstr = FieldIsEqInstr; - fn is_block_end(&self) -> bool { - false - } -} +/// IR node for modular SETUP (SETUP_ADDSUB, SETUP_MULDIV, SETUP_ISEQ). +pub(crate) type ModSetupInstr = FieldSetupInstr; // ── Phantom instructions (HintNonQr, HintSqrt) ────────────────────────────── @@ -366,78 +278,63 @@ impl ModularRvrExtension { let instr: Instr = match local { x if x == Rv64ModularArithmeticOpcode::ADD as usize => { - Instr::Ext(Box::new(ModArithInstr { - op: ModOp::Add, + Instr::Ext(Box::new(ModArithInstr::new( + ModOp::Add, rd_reg, rs1_reg, rs2_reg, - num_limbs: info.num_limbs, - modulus: info.modulus_bytes.clone(), - })) + info.num_limbs, + info.modulus_bytes.clone(), + ))) } x if x == Rv64ModularArithmeticOpcode::SUB as usize => { - Instr::Ext(Box::new(ModArithInstr { - op: ModOp::Sub, + Instr::Ext(Box::new(ModArithInstr::new( + ModOp::Sub, rd_reg, rs1_reg, rs2_reg, - num_limbs: info.num_limbs, - modulus: info.modulus_bytes.clone(), - })) - } - x if x == Rv64ModularArithmeticOpcode::SETUP_ADDSUB as usize => { - Instr::Ext(Box::new(ModSetupInstr { - rd_reg, - rs1_reg, - rs2_reg, - num_limbs: info.num_limbs, - })) + info.num_limbs, + info.modulus_bytes.clone(), + ))) } + x if x == Rv64ModularArithmeticOpcode::SETUP_ADDSUB as usize => Instr::Ext(Box::new( + ModSetupInstr::new(rd_reg, rs1_reg, rs2_reg, info.num_limbs), + )), x if x == Rv64ModularArithmeticOpcode::MUL as usize => { - Instr::Ext(Box::new(ModArithInstr { - op: ModOp::Mul, + Instr::Ext(Box::new(ModArithInstr::new( + ModOp::Mul, rd_reg, rs1_reg, rs2_reg, - num_limbs: info.num_limbs, - modulus: info.modulus_bytes.clone(), - })) + info.num_limbs, + info.modulus_bytes.clone(), + ))) } x if x == Rv64ModularArithmeticOpcode::DIV as usize => { - Instr::Ext(Box::new(ModArithInstr { - op: ModOp::Div, + Instr::Ext(Box::new(ModArithInstr::new( + ModOp::Div, rd_reg, rs1_reg, rs2_reg, - num_limbs: info.num_limbs, - modulus: info.modulus_bytes.clone(), - })) - } - x if x == Rv64ModularArithmeticOpcode::SETUP_MULDIV as usize => { - Instr::Ext(Box::new(ModSetupInstr { - rd_reg, - rs1_reg, - rs2_reg, - num_limbs: info.num_limbs, - })) + info.num_limbs, + info.modulus_bytes.clone(), + ))) } + x if x == Rv64ModularArithmeticOpcode::SETUP_MULDIV as usize => Instr::Ext(Box::new( + ModSetupInstr::new(rd_reg, rs1_reg, rs2_reg, info.num_limbs), + )), x if x == Rv64ModularArithmeticOpcode::IS_EQ as usize => { - Instr::Ext(Box::new(ModIsEqInstr { - rd_reg, - rs1_reg, - rs2_reg, - num_limbs: info.num_limbs, - modulus: info.modulus_bytes.clone(), - })) - } - x if x == Rv64ModularArithmeticOpcode::SETUP_ISEQ as usize => { - Instr::Ext(Box::new(ModSetupInstr { + Instr::Ext(Box::new(ModIsEqInstr::new( rd_reg, rs1_reg, rs2_reg, - num_limbs: info.num_limbs, - })) + info.num_limbs, + info.modulus_bytes.clone(), + ))) } + x if x == Rv64ModularArithmeticOpcode::SETUP_ISEQ as usize => Instr::Ext(Box::new( + ModSetupInstr::new(rd_reg, rs1_reg, rs2_reg, info.num_limbs), + )), _ => return None, }; diff --git a/extensions/pairing/rvr/ffi/src/lib.rs b/extensions/pairing/rvr/ffi/src/lib.rs index fd478227aa..2cd3492155 100644 --- a/extensions/pairing/rvr/ffi/src/lib.rs +++ b/extensions/pairing/rvr/ffi/src/lib.rs @@ -6,7 +6,10 @@ use std::ffi::c_void; use halo2curves_axiom::{bls12_381, bn256, ff::PrimeField}; -use openvm_ecc_guest::{algebra::field::FieldExtension, AffinePoint}; +use openvm_ecc_guest::{ + algebra::{field::FieldExtension, Field}, + AffinePoint, +}; use openvm_pairing_guest::{ halo2curves_shims::{bls12_381::Bls12_381, bn254::Bn254}, pairing::{FinalExp, MultiMillerLoop}, @@ -73,9 +76,21 @@ fn point_base(ptr: u64, idx: u64, words_per_point: u32) -> Option { ptr.checked_add(offset)?.try_into().ok() } -// ── BN254 pairing hint ────────────────────────────────────────────────── +// ── Shared scaffolding ────────────────────────────────────────────────── -unsafe fn hint_bn254(state: *mut c_void, rs1_val: u32, rs2_val: u32) -> Option> { +/// Read G1 and G2 point vectors from guest memory. +/// +/// `read_fq` reads one base-field element. `make_fq2` constructs an Fq2 +/// from two consecutive Fq elements; use `Fq2::new` or `|c0, c1| Fq2 { c0, c1 }` +/// depending on the curve's API. +unsafe fn read_pairing_points( + state: *mut c_void, + rs1_val: u32, + rs2_val: u32, + fq_bytes: u32, + read_fq: impl Fn(*mut c_void, u32) -> Option, + make_fq2: impl Fn(Fq, Fq) -> Fq2, +) -> Option<(Vec>, Vec>)> { let p_ptr = rd_mem_u64_wrapper(state, rs1_val); let p_len = rd_mem_u64_wrapper(state, rs1_val + SLICE_LEN_OFFSET); let q_ptr = rd_mem_u64_wrapper(state, rs2_val); @@ -87,34 +102,42 @@ unsafe fn hint_bn254(state: *mut c_void, rs1_val: u32, rs2_val: u32) -> Option = (0..p_len) .map(|i| { - let base = point_base(p_ptr, i, G1_AFFINE_COORDS * BN254_FQ_BYTES)?; + let base = point_base(p_ptr, i, G1_AFFINE_COORDS * fq_bytes)?; Some(AffinePoint::new( - read_bn254_fq(state, base)?, - read_bn254_fq(state, base + BN254_FQ_BYTES)?, + read_fq(state, base)?, + read_fq(state, base + fq_bytes)?, )) }) .collect::>()?; let q: Vec<_> = (0..q_len) .map(|i| { - let base = point_base(q_ptr, i, G2_AFFINE_COORDS * BN254_FQ_BYTES)?; - // BN254 Fq2 exposes a constructor helper. - let x = bn256::Fq2::new( - read_bn254_fq(state, base)?, - read_bn254_fq(state, base + BN254_FQ_BYTES)?, - ); - let y = bn256::Fq2::new( - read_bn254_fq(state, base + 2 * BN254_FQ_BYTES)?, - read_bn254_fq(state, base + 3 * BN254_FQ_BYTES)?, + let base = point_base(q_ptr, i, G2_AFFINE_COORDS * fq_bytes)?; + let x = make_fq2(read_fq(state, base)?, read_fq(state, base + fq_bytes)?); + let y = make_fq2( + read_fq(state, base + 2 * fq_bytes)?, + read_fq(state, base + 3 * fq_bytes)?, ); Some(AffinePoint::new(x, y)) }) .collect::>()?; + Some((p, q)) +} + +// ── BN254 pairing hint ────────────────────────────────────────────────── + +unsafe fn hint_bn254(state: *mut c_void, rs1_val: u32, rs2_val: u32) -> Option> { + let (p, q) = read_pairing_points( + state, + rs1_val, + rs2_val, + BN254_FQ_BYTES, + |s, ptr| unsafe { read_bn254_fq(s, ptr) }, + bn256::Fq2::new, + )?; let f: bn256::Fq12 = Bn254::multi_miller_loop(&p, &q); let (c, u) = Bn254::final_exp_hint(&f); - - // Serialize hint: c (Fp12) then u (Fp12), each as 12 Fq elements Some( c.to_coeffs() .into_iter() @@ -128,44 +151,16 @@ unsafe fn hint_bn254(state: *mut c_void, rs1_val: u32, rs2_val: u32) -> Option Option> { - let p_ptr = rd_mem_u64_wrapper(state, rs1_val); - let p_len = rd_mem_u64_wrapper(state, rs1_val + SLICE_LEN_OFFSET); - let q_ptr = rd_mem_u64_wrapper(state, rs2_val); - let q_len = rd_mem_u64_wrapper(state, rs2_val + SLICE_LEN_OFFSET); - - if p_len != q_len { - return None; - } - - let p: Vec<_> = (0..p_len) - .map(|i| { - let base = point_base(p_ptr, i, G1_AFFINE_COORDS * BLS12_381_FQ_BYTES)?; - Some(AffinePoint::new( - read_bls12_381_fq(state, base)?, - read_bls12_381_fq(state, base + BLS12_381_FQ_BYTES)?, - )) - }) - .collect::>()?; - - let q: Vec<_> = (0..q_len) - .map(|i| { - let base = point_base(q_ptr, i, G2_AFFINE_COORDS * BLS12_381_FQ_BYTES)?; - // BLS12-381 Fq2 uses struct fields instead of an `Fq2::new` helper. - let x = bls12_381::Fq2 { - c0: read_bls12_381_fq(state, base)?, - c1: read_bls12_381_fq(state, base + BLS12_381_FQ_BYTES)?, - }; - let y = bls12_381::Fq2 { - c0: read_bls12_381_fq(state, base + 2 * BLS12_381_FQ_BYTES)?, - c1: read_bls12_381_fq(state, base + 3 * BLS12_381_FQ_BYTES)?, - }; - Some(AffinePoint::new(x, y)) - }) - .collect::>()?; - + let (p, q) = read_pairing_points( + state, + rs1_val, + rs2_val, + BLS12_381_FQ_BYTES, + |s, ptr| unsafe { read_bls12_381_fq(s, ptr) }, + |c0, c1| bls12_381::Fq2 { c0, c1 }, + )?; let f: bls12_381::Fq12 = Bls12_381::multi_miller_loop(&p, &q); let (c, u) = Bls12_381::final_exp_hint(&f); - Some( c.to_coeffs() .into_iter()