Skip to content

Commit

Permalink
[feat] miller double and add opcode (#727)
Browse files Browse the repository at this point in the history
* test pass

* chip

* fix

* Update vm/src/intrinsics/ecc/pairing/miller_double_and_add_step.rs

---------

Co-authored-by: Jonathan Wang <[email protected]>
  • Loading branch information
luffykai and jonathanpwang authored Nov 1, 2024
1 parent 6d4c0d0 commit f5bac96
Show file tree
Hide file tree
Showing 4 changed files with 274 additions and 14 deletions.
70 changes: 57 additions & 13 deletions vm/src/arch/chip_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use crate::{
},
intrinsics::{
ecc::{
pairing::{EcLineMul013By013Chip, MillerDoubleStepChip},
pairing::{EcLineMul013By013Chip, MillerDoubleAndAddStepChip, MillerDoubleStepChip},
sw::{EcAddNeChip, EcDoubleChip},
},
hashes::{keccak::hasher::KeccakVmChip, poseidon2::Poseidon2Chip},
Expand Down Expand Up @@ -873,6 +873,34 @@ impl VmConfig {
executors.insert(global_opcode_idx, chip.clone().into());
chips.push(AxVmChip::MillerDoubleStepRv32_48(chip));
}
ExecutorName::MillerDoubleAndAddStepRv32_32 => {
let chip = Rc::new(RefCell::new(MillerDoubleAndAddStepChip::new(
Rv32VecHeapAdapterChip::<F, 2, 4, 12, 32, 32>::new(
execution_bus,
program_bus,
memory_controller.clone(),
),
memory_controller.clone(),
config32,
class_offset,
)));
executors.insert(global_opcode_idx, chip.clone().into());
chips.push(AxVmChip::MillerDoubleAndAddStepRv32_32(chip));
}
ExecutorName::MillerDoubleAndAddStepRv32_48 => {
let chip = Rc::new(RefCell::new(MillerDoubleAndAddStepChip::new(
Rv32VecHeapAdapterChip::<F, 2, 12, 36, 16, 16>::new(
execution_bus,
program_bus,
memory_controller.clone(),
),
memory_controller.clone(),
config48,
class_offset,
)));
executors.insert(global_opcode_idx, chip.clone().into());
chips.push(AxVmChip::MillerDoubleAndAddStepRv32_48(chip));
}
_ => unreachable!("Unsupported executor"),
}
}
Expand Down Expand Up @@ -1096,19 +1124,35 @@ fn gen_pairing_executor_tuple(
let class_offset = PairingOpcode::default_offset() + i * PairingOpcode::COUNT;
let bytes = curve.prime().bits().div_ceil(8);
if bytes <= 32 {
vec![(
PairingOpcode::MILLER_DOUBLE_STEP as usize,
class_offset,
ExecutorName::MillerDoubleStepRv32_32,
curve.prime(),
)]
vec![
(
PairingOpcode::MILLER_DOUBLE_STEP as usize,
class_offset,
ExecutorName::MillerDoubleStepRv32_32,
curve.prime(),
),
(
PairingOpcode::MILLER_DOUBLE_AND_ADD_STEP as usize,
class_offset,
ExecutorName::MillerDoubleAndAddStepRv32_32,
curve.prime(),
),
]
} else if bytes <= 48 {
vec![(
PairingOpcode::MILLER_DOUBLE_STEP as usize,
class_offset,
ExecutorName::MillerDoubleStepRv32_48,
curve.prime(),
)]
vec![
(
PairingOpcode::MILLER_DOUBLE_STEP as usize,
class_offset,
ExecutorName::MillerDoubleStepRv32_48,
curve.prime(),
),
(
PairingOpcode::MILLER_DOUBLE_AND_ADD_STEP as usize,
class_offset,
ExecutorName::MillerDoubleAndAddStepRv32_48,
curve.prime(),
),
]
} else {
panic!("curve {:?} is not supported", curve);
}
Expand Down
6 changes: 5 additions & 1 deletion vm/src/arch/chips.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::{
arch::ExecutionState,
intrinsics::{
ecc::{
pairing::{EcLineMul013By013Chip, MillerDoubleStepChip},
pairing::{EcLineMul013By013Chip, MillerDoubleAndAddStepChip, MillerDoubleStepChip},
sw::{EcAddNeChip, EcDoubleChip},
},
hashes::{keccak::hasher::KeccakVmChip, poseidon2::Poseidon2Chip},
Expand Down Expand Up @@ -117,6 +117,8 @@ pub enum AxVmInstructionExecutor<F: PrimeField32> {
// 32-bytes or 48-bytes prime.
MillerDoubleStepRv32_32(Rc<RefCell<MillerDoubleStepChip<F, 4, 8, 32>>>),
MillerDoubleStepRv32_48(Rc<RefCell<MillerDoubleStepChip<F, 12, 24, 16>>>),
MillerDoubleAndAddStepRv32_32(Rc<RefCell<MillerDoubleAndAddStepChip<F, 4, 12, 32>>>),
MillerDoubleAndAddStepRv32_48(Rc<RefCell<MillerDoubleAndAddStepChip<F, 12, 36, 16>>>),
// TO BE REPLACED:
CastF(Rc<RefCell<CastFChip<F>>>),
ModularAddSub(Rc<RefCell<KernelModularAddSubChip<F, 32>>>),
Expand Down Expand Up @@ -169,6 +171,8 @@ pub enum AxVmChip<F: PrimeField32> {
// 32-bytes or 48-bytes prime.
MillerDoubleStepRv32_32(Rc<RefCell<MillerDoubleStepChip<F, 4, 8, 32>>>),
MillerDoubleStepRv32_48(Rc<RefCell<MillerDoubleStepChip<F, 12, 24, 16>>>),
MillerDoubleAndAddStepRv32_32(Rc<RefCell<MillerDoubleAndAddStepChip<F, 4, 12, 32>>>),
MillerDoubleAndAddStepRv32_48(Rc<RefCell<MillerDoubleAndAddStepChip<F, 12, 36, 16>>>),
// TO BE REPLACED:
CastF(Rc<RefCell<CastFChip<F>>>),
ModularAddSub(Rc<RefCell<KernelModularAddSubChip<F, 32>>>),
Expand Down
209 changes: 209 additions & 0 deletions vm/src/intrinsics/ecc/pairing/miller_double_and_add_step.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
use std::{cell::RefCell, rc::Rc};

use ax_circuit_derive::{Chip, ChipUsageGetter};
use ax_circuit_primitives::var_range::VariableRangeCheckerBus;
use ax_ecc_primitives::{
field_expression::{ExprBuilder, ExprBuilderConfig, FieldExpr},
field_extension::Fp2,
};
use axvm_circuit_derive::InstructionExecutor;
use p3_field::PrimeField32;

use crate::{
arch::{instructions::PairingOpcode, VmChipWrapper},
intrinsics::field_expression::FieldExpressionCoreChip,
rv32im::adapters::Rv32VecHeapAdapterChip,
system::memory::MemoryControllerRef,
};

// Input: two EcPoint<Fp2>: 4 field elements each
// Output: (EcPoint<Fp2>, UnevaluatedLine<Fp2>, UnevaluatedLine<Fp2>) -> 2*2 + 2*2 + 2*2 = 12 field elements
#[derive(Chip, ChipUsageGetter, InstructionExecutor)]
pub struct MillerDoubleAndAddStepChip<
F: PrimeField32,
const INPUT_BLOCKS: usize,
const OUTPUT_BLOCKS: usize,
const BLOCK_SIZE: usize,
>(
VmChipWrapper<
F,
Rv32VecHeapAdapterChip<F, 2, INPUT_BLOCKS, OUTPUT_BLOCKS, BLOCK_SIZE, BLOCK_SIZE>,
FieldExpressionCoreChip,
>,
);

impl<
F: PrimeField32,
const INPUT_BLOCKS: usize,
const OUTPUT_BLOCKS: usize,
const BLOCK_SIZE: usize,
> MillerDoubleAndAddStepChip<F, INPUT_BLOCKS, OUTPUT_BLOCKS, BLOCK_SIZE>
{
pub fn new(
adapter: Rv32VecHeapAdapterChip<F, 2, INPUT_BLOCKS, OUTPUT_BLOCKS, BLOCK_SIZE, BLOCK_SIZE>,
memory_controller: MemoryControllerRef<F>,
config: ExprBuilderConfig,
offset: usize,
) -> Self {
let expr =
miller_double_and_add_step_expr(config, memory_controller.borrow().range_checker.bus());
let core = FieldExpressionCoreChip::new(
expr,
offset,
vec![PairingOpcode::MILLER_DOUBLE_AND_ADD_STEP as usize],
vec![],
memory_controller.borrow().range_checker.clone(),
"MillerDoubleAndAddStep",
);
Self(VmChipWrapper::new(adapter, core, memory_controller))
}
}

// Ref: https://github.com/axiom-crypto/afs-prototype/blob/043968891d596acdc82c8e3a9126a555fe178d43/lib/ecc-execution/src/common/miller_step.rs#L72
pub fn miller_double_and_add_step_expr(
config: ExprBuilderConfig,
range_bus: VariableRangeCheckerBus,
) -> FieldExpr {
config.check_valid();
let builder = ExprBuilder::new(config, range_bus.range_max_bits);
let builder = Rc::new(RefCell::new(builder));

let mut x_s = Fp2::new(builder.clone());
let mut y_s = Fp2::new(builder.clone());
let mut x_q = Fp2::new(builder.clone());
let mut y_q = Fp2::new(builder.clone());

// λ1 = (y_s - y_q) / (x_s - x_q)
let mut lambda1 = y_s.sub(&mut y_q).div(&mut x_s.sub(&mut x_q));
let mut x_sq = lambda1.square().sub(&mut x_s).sub(&mut x_q);
// λ2 = -λ1 - 2y_s / (x_{s+q} - x_s)
let mut lambda2 = lambda1
.neg()
.sub(&mut y_s.int_mul([2, 0]).div(&mut x_sq.sub(&mut x_s)));
let mut x_sqs = lambda2.square().sub(&mut x_s).sub(&mut x_sq);
let mut y_sqs = lambda2.mul(&mut (x_s.sub(&mut x_sqs))).sub(&mut y_s);

x_sqs.save_output();
y_sqs.save_output();

let mut b0 = lambda1.neg();
let mut c0 = lambda1.mul(&mut x_s).sub(&mut y_s);
b0.save_output();
c0.save_output();

let mut b1 = lambda2.neg();
let mut c1 = lambda2.mul(&mut x_s).sub(&mut y_s);
b1.save_output();
c1.save_output();

let builder = builder.borrow().clone();
FieldExpr::new(builder, range_bus)
}

#[cfg(test)]
mod tests {
use ax_ecc_execution::common::{miller_double_and_add_step, EcPoint};
use ax_ecc_primitives::test_utils::bn254_fq_to_biguint;
use axvm_ecc_constants::BN254;
use axvm_instructions::UsizeOpcode;
use halo2curves_axiom::bn256::{Fq, Fq2, G2Affine};
use p3_baby_bear::BabyBear;
use p3_field::AbstractField;
use rand::{rngs::StdRng, SeedableRng};

use super::*;
use crate::{
arch::{instructions::PairingOpcode, testing::VmChipTestBuilder, VmChipWrapper},
intrinsics::field_expression::FieldExpressionCoreChip,
rv32im::adapters::Rv32VecHeapAdapterChip,
utils::{biguint_to_limbs, rv32_write_heap_default},
};

type F = BabyBear;
const NUM_LIMBS: usize = 32;
const LIMB_BITS: usize = 8;
#[test]
#[allow(non_snake_case)]
fn test_miller_double_and_add() {
let mut tester: VmChipTestBuilder<F> = VmChipTestBuilder::default();
let config = ExprBuilderConfig {
modulus: BN254.MODULUS.clone(),
limb_bits: LIMB_BITS,
num_limbs: NUM_LIMBS,
};
let expr = miller_double_and_add_step_expr(
config,
tester.memory_controller().borrow().range_checker.bus(),
);
let core = FieldExpressionCoreChip::new(
expr,
PairingOpcode::default_offset(),
vec![PairingOpcode::MILLER_DOUBLE_AND_ADD_STEP as usize],
vec![],
tester.memory_controller().borrow().range_checker.clone(),
"MillerDoubleAndAdd",
);
let adapter = Rv32VecHeapAdapterChip::<F, 2, 4, 12, NUM_LIMBS, NUM_LIMBS>::new(
tester.execution_bus(),
tester.program_bus(),
tester.memory_controller(),
);
let mut chip = VmChipWrapper::new(adapter, core, tester.memory_controller());

let mut rng0 = StdRng::seed_from_u64(2);
let Q = G2Affine::random(&mut rng0);
let Q2 = G2Affine::random(&mut rng0);
let inputs = [
Q.x.c0, Q.x.c1, Q.y.c0, Q.y.c1, Q2.x.c0, Q2.x.c1, Q2.y.c0, Q2.y.c1,
]
.map(|x| bn254_fq_to_biguint(&x));

let Q_ecpoint = EcPoint { x: Q.x, y: Q.y };
let Q_ecpoint2 = EcPoint { x: Q2.x, y: Q2.y };
let (Q_daa, l_qa, l_sqs) = miller_double_and_add_step::<Fq, Fq2>(Q_ecpoint, Q_ecpoint2);
let result = chip
.core
.expr()
.execute_with_output(inputs.to_vec(), vec![]);
assert_eq!(result.len(), 12); // EcPoint<Fp2> and 4 Fp2 coefficients
assert_eq!(result[0], bn254_fq_to_biguint(&Q_daa.x.c0));
assert_eq!(result[1], bn254_fq_to_biguint(&Q_daa.x.c1));
assert_eq!(result[2], bn254_fq_to_biguint(&Q_daa.y.c0));
assert_eq!(result[3], bn254_fq_to_biguint(&Q_daa.y.c1));
assert_eq!(result[4], bn254_fq_to_biguint(&l_qa.b.c0));
assert_eq!(result[5], bn254_fq_to_biguint(&l_qa.b.c1));
assert_eq!(result[6], bn254_fq_to_biguint(&l_qa.c.c0));
assert_eq!(result[7], bn254_fq_to_biguint(&l_qa.c.c1));
assert_eq!(result[8], bn254_fq_to_biguint(&l_sqs.b.c0));
assert_eq!(result[9], bn254_fq_to_biguint(&l_sqs.b.c1));
assert_eq!(result[10], bn254_fq_to_biguint(&l_sqs.c.c0));
assert_eq!(result[11], bn254_fq_to_biguint(&l_sqs.c.c1));

let input1_limbs = inputs[0..4]
.iter()
.map(|x| {
biguint_to_limbs::<NUM_LIMBS>(x.clone(), LIMB_BITS)
.map(BabyBear::from_canonical_u32)
})
.collect::<Vec<_>>();

let input2_limbs = inputs[4..8]
.iter()
.map(|x| {
biguint_to_limbs::<NUM_LIMBS>(x.clone(), LIMB_BITS)
.map(BabyBear::from_canonical_u32)
})
.collect::<Vec<_>>();

let instruction = rv32_write_heap_default(
&mut tester,
input1_limbs,
input2_limbs,
chip.core.air.offset + PairingOpcode::MILLER_DOUBLE_AND_ADD_STEP as usize,
);

tester.execute(&mut chip, instruction);
let tester = tester.build().load(chip).finalize();
tester.simple_test().expect("Verification failed");
}
}
3 changes: 3 additions & 0 deletions vm/src/intrinsics/ecc/pairing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ mod miller_double_step;

pub use line::*;
pub use miller_double_step::*;

mod miller_double_and_add_step;
pub use miller_double_and_add_step::*;

0 comments on commit f5bac96

Please sign in to comment.