From 12e02dfb3770c1b67b6258a5081927fc406179e8 Mon Sep 17 00:00:00 2001 From: malatrax Date: Fri, 27 Dec 2024 14:45:53 +0100 Subject: [PATCH 1/2] feat: add Processor constraints --- .../brainfuck_prover/src/brainfuck_air/mod.rs | 44 +++- .../src/components/processor/component.rs | 194 +++++++++++++++++- 2 files changed, 224 insertions(+), 14 deletions(-) diff --git a/crates/brainfuck_prover/src/brainfuck_air/mod.rs b/crates/brainfuck_prover/src/brainfuck_air/mod.rs index e20233f..ce197fa 100644 --- a/crates/brainfuck_prover/src/brainfuck_air/mod.rs +++ b/crates/brainfuck_prover/src/brainfuck_air/mod.rs @@ -14,7 +14,12 @@ use crate::components::{ component::{MemoryComponent, MemoryEval}, table::{MemoryElements, MemoryTable}, }, - InstructionClaim, IoClaim, MemoryClaim, + processor::{ + self, + component::{ProcessorComponent, ProcessorEval}, + table::ProcessorTable, + }, + InstructionClaim, IoClaim, MemoryClaim, ProcessorClaim, }; use brainfuck_vm::machine::Machine; use stwo_prover::{ @@ -54,6 +59,7 @@ pub struct BrainfuckClaim { pub output: IoClaim, pub memory: MemoryClaim, pub instruction: InstructionClaim, + pub processor: ProcessorClaim, } impl BrainfuckClaim { @@ -62,6 +68,7 @@ impl BrainfuckClaim { self.output.mix_into(channel); self.memory.mix_into(channel); self.instruction.mix_into(channel); + self.processor.mix_into(channel); } pub fn log_sizes(&self) -> TreeVec> { @@ -71,6 +78,7 @@ impl BrainfuckClaim { self.output.log_sizes(), self.memory.log_sizes(), self.instruction.log_sizes(), + self.processor.log_sizes(), ] .into_iter(), ); @@ -113,6 +121,7 @@ pub struct BrainfuckInteractionClaim { output: io::component::InteractionClaim, memory: memory::component::InteractionClaim, instruction: instruction::component::InteractionClaim, + processor: processor::component::InteractionClaim, } impl BrainfuckInteractionClaim { @@ -122,6 +131,7 @@ impl BrainfuckInteractionClaim { self.output.mix_into(channel); self.memory.mix_into(channel); self.instruction.mix_into(channel); + self.processor.mix_into(channel); } } @@ -143,6 +153,7 @@ pub struct BrainfuckComponents { output: OutputComponent, memory: MemoryComponent, instruction: InstructionComponent, + processor: ProcessorComponent, } impl BrainfuckComponents { @@ -183,13 +194,24 @@ impl BrainfuckComponents { ), (interaction_claim.instruction.claimed_sum, None), ); + let processor = ProcessorComponent::new( + tree_span_provider, + ProcessorEval::new( + &claim.processor, + interaction_elements.input_lookup_elements.clone(), + interaction_elements.output_lookup_elements.clone(), + interaction_elements.memory_lookup_elements.clone(), + interaction_elements.instruction_lookup_elements.clone(), + ), + (interaction_claim.processor.claimed_sum, None), + ); - Self { input, output, memory, instruction } + Self { input, output, memory, instruction, processor } } /// Returns the `ComponentProver` of each components, used by the prover. pub fn provers(&self) -> Vec<&dyn ComponentProver> { - vec![&self.input, &self.output, &self.memory, &self.instruction] + vec![&self.input, &self.output, &self.memory, &self.instruction, &self.processor] } /// Returns the `Component` of each components, used by the verifier. @@ -265,17 +287,21 @@ pub fn prove_brainfuck( let (memory_trace, memory_claim) = MemoryTable::from(&vm_trace).trace_evaluation().unwrap(); let (instruction_trace, instruction_claim) = InstructionTable::from((&vm_trace, inputs.program())).trace_evaluation().unwrap(); + let (processor_trace, processor_claim) = + ProcessorTable::from(vm_trace).trace_evaluation().unwrap(); tree_builder.extend_evals(input_trace.clone()); tree_builder.extend_evals(output_trace.clone()); tree_builder.extend_evals(memory_trace.clone()); tree_builder.extend_evals(instruction_trace.clone()); + tree_builder.extend_evals(processor_trace.clone()); let claim = BrainfuckClaim { input: input_claim, output: output_claim, memory: memory_claim, instruction: instruction_claim, + processor: processor_claim, }; // Mix the claim into the Fiat-Shamir channel. @@ -321,16 +347,28 @@ pub fn prove_brainfuck( ) .unwrap(); + let (processor_interaction_trace_eval, processor_interaction_claim) = + processor::table::interaction_trace_evaluation( + &processor_trace, + &interaction_elements.input_lookup_elements, + &interaction_elements.output_lookup_elements, + &interaction_elements.instruction_lookup_elements, + &interaction_elements.memory_lookup_elements, + ) + .unwrap(); + tree_builder.extend_evals(input_interaction_trace_eval); tree_builder.extend_evals(output_interaction_trace_eval); tree_builder.extend_evals(memory_interaction_trace_eval); tree_builder.extend_evals(instruction_interaction_trace_eval); + tree_builder.extend_evals(processor_interaction_trace_eval); let interaction_claim = BrainfuckInteractionClaim { input: input_interaction_claim, output: output_interaction_claim, memory: memory_interaction_claim, instruction: instruction_interaction_claim, + processor: processor_interaction_claim, }; // Mix the interaction claim into the Fiat-Shamir channel. diff --git a/crates/brainfuck_prover/src/components/processor/component.rs b/crates/brainfuck_prover/src/components/processor/component.rs index 51d5ea3..4bfba12 100644 --- a/crates/brainfuck_prover/src/components/processor/component.rs +++ b/crates/brainfuck_prover/src/components/processor/component.rs @@ -2,8 +2,12 @@ use crate::components::{ instruction::table::InstructionElements, io::table::IoElements, memory::table::MemoryElements, ProcessorClaim, }; +use num_traits::One; use stwo_prover::{ - constraint_framework::{EvalAtRow, FrameworkComponent, FrameworkEval}, + constraint_framework::{ + preprocessed_columns::PreprocessedColumn, EvalAtRow, FrameworkComponent, FrameworkEval, + RelationEntry, + }, core::{channel::Channel, fields::qm31::SecureField}, }; @@ -17,10 +21,10 @@ pub type ProcessorComponent = FrameworkComponent; /// provided by the constraint framework of Stwo. pub struct ProcessorEval { log_size: u32, - _input_lookup_elements: IoElements, - _output_lookup_elements: IoElements, - _memory_lookup_elements: MemoryElements, - _instruction_lookup_elements: InstructionElements, + input_lookup_elements: IoElements, + output_lookup_elements: IoElements, + memory_lookup_elements: MemoryElements, + instruction_lookup_elements: InstructionElements, } impl ProcessorEval { @@ -33,10 +37,10 @@ impl ProcessorEval { ) -> Self { Self { log_size: claim.log_size, - _input_lookup_elements: input_lookup_elements, - _output_lookup_elements: output_lookup_elements, - _memory_lookup_elements: memory_lookup_elements, - _instruction_lookup_elements: instruction_lookup_elements, + input_lookup_elements, + output_lookup_elements, + memory_lookup_elements, + instruction_lookup_elements, } } } @@ -67,8 +71,89 @@ impl FrameworkEval for ProcessorEval { /// Use `eval.add_to_relation` to define a global constraint for the logUp protocol. /// /// The logUp must be finalized with `eval.finalize_logup()`. - fn evaluate(&self, _eval: E) -> E { - todo!() + #[allow(clippy::similar_names)] + #[allow(clippy::too_many_lines)] + fn evaluate(&self, mut eval: E) -> E { + // Get the preprocessed column to check boundary constraints + let is_first = eval.get_preprocessed_column(PreprocessedColumn::IsFirst(self.log_size())); + + // Get all the required registers' values + let clk = eval.next_trace_mask(); + let ip = eval.next_trace_mask(); + let ci = eval.next_trace_mask(); + let ni = eval.next_trace_mask(); + let mp = eval.next_trace_mask(); + let mv = eval.next_trace_mask(); + let mvi = eval.next_trace_mask(); + let d = eval.next_trace_mask(); + let next_clk = eval.next_trace_mask(); + let _next_ip = eval.next_trace_mask(); + let _next_ci = eval.next_trace_mask(); + let _next_ni = eval.next_trace_mask(); + let next_mp = eval.next_trace_mask(); + let next_mv = eval.next_trace_mask(); + let _next_mvi = eval.next_trace_mask(); + let next_d = eval.next_trace_mask(); + + // Boundary constraints + // `clk` starts at 0 + eval.add_constraint(is_first.clone() * clk.clone()); + // `ip` starts at 0 + eval.add_constraint(is_first.clone() * ip.clone()); + // `mp` starts at 0 + eval.add_constraint(is_first.clone() * mp.clone()); + // `mv` starts at 0 + eval.add_constraint(is_first * mv.clone()); + + // Consistency constraints + // `mv` is the inverse of `mvi` + eval.add_constraint(mv.clone() * (mv.clone() * mvi.clone() - E::F::one())); + // `mvi` is the inverse of `mv` + eval.add_constraint(mvi.clone() * (mv.clone() * mvi - E::F::one())); + + // Transition constraints + + // `clk` increases by 1 + eval.add_constraint(next_clk.clone() - clk.clone() - E::F::one()); + + // Lookup arguments + + // Processor & Input + let multiplicity_row = E::EF::one() - E::EF::from(d); + let multiplicity_next_row = E::EF::one() - E::EF::from(next_d); + eval.add_to_relation(&[RelationEntry::new( + &self.input_lookup_elements, + multiplicity_row.clone(), + &[clk.clone(), ci.clone(), mv.clone()], + )]); + + // Processor & Output + eval.add_to_relation(&[RelationEntry::new( + &self.output_lookup_elements, + multiplicity_row.clone(), + &[clk.clone(), ci.clone(), mv.clone()], + )]); + + // Processor & Instruction + eval.add_to_relation(&[RelationEntry::new( + &self.instruction_lookup_elements, + multiplicity_row.clone(), + &[ip, ci, ni], + )]); + + // Processor & Memory + eval.add_to_relation(&[ + RelationEntry::new(&self.memory_lookup_elements, multiplicity_row, &[clk, mp, mv]), + RelationEntry::new( + &self.memory_lookup_elements, + multiplicity_next_row, + &[next_clk, next_mp, next_mv], + ), + ]); + + eval.finalize_logup(); + + eval } } @@ -93,3 +178,90 @@ impl InteractionClaim { channel.mix_felts(&[self.claimed_sum]); } } + +#[cfg(test)] +mod tests { + use crate::components::{ + instruction::table::InstructionElements, + io::table::IoElements, + memory::table::MemoryElements, + processor::{ + component::ProcessorEval, + table::{interaction_trace_evaluation, ProcessorTable}, + }, + }; + use brainfuck_vm::{compiler::Compiler, test_helper::create_test_machine}; + use stwo_prover::{ + constraint_framework::{ + assert_constraints, preprocessed_columns::gen_is_first, FrameworkEval, + }, + core::{ + pcs::TreeVec, + poly::circle::{CanonicCoset, CircleEvaluation}, + }, + }; + + #[test] + fn test_processor_constraints() { + const LOG_SIZE: u32 = 9; + + // Get an execution trace from a valid Brainfuck program + let code = "+++>,<[>+.<-]"; + let mut compiler = Compiler::new(code); + let instructions = compiler.compile(); + let (mut machine, _) = create_test_machine(&instructions, &[1]); + let () = machine.execute().expect("Failed to execute machine"); + + let trace_vm = machine.trace(); + + // Construct the IsFirst preprocessed column + let is_first_col = gen_is_first(LOG_SIZE); + let is_first_col_2 = gen_is_first(LOG_SIZE); + let preprocessed_trace = vec![is_first_col, is_first_col_2]; + + // Construct the main trace from the execution trace + let table = ProcessorTable::from(trace_vm); + let (main_trace, claim) = table.trace_evaluation().unwrap(); + + // Draw Interaction elements + let input_lookup_elements = IoElements::dummy(); + let output_lookup_elements = IoElements::dummy(); + let instruction_lookup_elements = InstructionElements::dummy(); + let memory_lookup_elements = MemoryElements::dummy(); + + // Generate interaction trace + let (interaction_trace, interaction_claim) = interaction_trace_evaluation( + &main_trace, + &input_lookup_elements, + &output_lookup_elements, + &instruction_lookup_elements, + &memory_lookup_elements, + ) + .unwrap(); + + // Create the trace evaluation TreeVec + let trace = TreeVec::new(vec![preprocessed_trace, main_trace, interaction_trace]); + + // Interpolate the trace for the evaluation + let trace_polys = trace.map_cols(CircleEvaluation::interpolate); + + // Get the Memory AIR evaluator + let processor_eval = ProcessorEval::new( + &claim, + input_lookup_elements, + output_lookup_elements, + memory_lookup_elements, + instruction_lookup_elements, + ); + + // Assert that the constraints are valid for a valid Brainfuck program. + assert_constraints( + &trace_polys, + CanonicCoset::new(LOG_SIZE), + |eval| { + processor_eval.evaluate(eval); + }, + (interaction_claim.claimed_sum, None), + ); + } +} From 933c0e562019c02a983cd7800cd74023f122db33 Mon Sep 17 00:00:00 2001 From: malatrax Date: Fri, 27 Dec 2024 14:59:38 +0100 Subject: [PATCH 2/2] feat: add instruction specific constraints to Processor component --- .../src/components/processor/component.rs | 258 +++++++++++++++++- 1 file changed, 254 insertions(+), 4 deletions(-) diff --git a/crates/brainfuck_prover/src/components/processor/component.rs b/crates/brainfuck_prover/src/components/processor/component.rs index 4bfba12..c3105ec 100644 --- a/crates/brainfuck_prover/src/components/processor/component.rs +++ b/crates/brainfuck_prover/src/components/processor/component.rs @@ -2,13 +2,17 @@ use crate::components::{ instruction::table::InstructionElements, io::table::IoElements, memory::table::MemoryElements, ProcessorClaim, }; +use brainfuck_vm::instruction::{InstructionType, VALID_INSTRUCTIONS_BF}; use num_traits::One; use stwo_prover::{ constraint_framework::{ preprocessed_columns::PreprocessedColumn, EvalAtRow, FrameworkComponent, FrameworkEval, RelationEntry, }, - core::{channel::Channel, fields::qm31::SecureField}, + core::{ + channel::Channel, + fields::{m31::BaseField, qm31::SecureField}, + }, }; /// Implementation of `Component` and `ComponentProver` @@ -45,6 +49,35 @@ impl ProcessorEval { } } +/// Computes the value of the deselector polynomial for a given `ci` value from the trace +/// and the wanted instruction. +/// +/// This polynomial allows to conditionally render AIR for the various instructions. +/// +/// # Arguments +/// * ci - The evaluated value on which the deselector polynomial is evaluated upon. +/// * instruction - The type of instruction which +/// +/// # Returns +/// It returns zero when `ci` is 0 or different from the available instructions except one. +/// If `ci` equals the `instruction`, then it returns something non-zero. +pub fn deselector_polynomial(ci: &E::F, instruction: &InstructionType) -> E::F { + VALID_INSTRUCTIONS_BF + .iter() + .filter(|&&val| val != instruction.to_base_field()) + .fold(ci.clone(), |acc, &val| acc * (ci.clone() - val.into())) +} + +#[cfg(test)] +/// Equivalent of `deselector_polynomial` with the [`BaseField`], +/// used to easily test the logic. +pub fn deselector_poly(ci: &BaseField, instruction: &InstructionType) -> BaseField { + VALID_INSTRUCTIONS_BF + .iter() + .filter(|&&val| val != instruction.to_base_field()) + .fold(*ci, |acc, &val| acc * (*ci - val)) +} + impl FrameworkEval for ProcessorEval { /// Returns the log size from the main claim. fn log_size(&self) -> u32 { @@ -87,7 +120,7 @@ impl FrameworkEval for ProcessorEval { let mvi = eval.next_trace_mask(); let d = eval.next_trace_mask(); let next_clk = eval.next_trace_mask(); - let _next_ip = eval.next_trace_mask(); + let next_ip = eval.next_trace_mask(); let _next_ci = eval.next_trace_mask(); let _next_ni = eval.next_trace_mask(); let next_mp = eval.next_trace_mask(); @@ -95,6 +128,20 @@ impl FrameworkEval for ProcessorEval { let _next_mvi = eval.next_trace_mask(); let next_d = eval.next_trace_mask(); + // TODO: Huge inefficiency (implies constraints of degree at least 8...) + // TODO: Remove this deselector by splitting all instructions into their own components. + let deselector_jump_if_zero = deselector_polynomial::(&ci, &InstructionType::JumpIfZero); + let deselector_jump_if_not_zero = + deselector_polynomial::(&ci, &InstructionType::JumpIfNotZero); + let deselector_left = deselector_polynomial::(&ci, &InstructionType::Left); + let deselector_right = deselector_polynomial::(&ci, &InstructionType::Right); + let deselector_minus = deselector_polynomial::(&ci, &InstructionType::Minus); + let deselector_plus = deselector_polynomial::(&ci, &InstructionType::Plus); + let deselector_input = deselector_polynomial::(&ci, &InstructionType::ReadChar); + let deselector_output = deselector_polynomial::(&ci, &InstructionType::PutChar); + + let is_mv_zero = E::F::one() - mv.clone() * mvi.clone(); + // Boundary constraints // `clk` starts at 0 eval.add_constraint(is_first.clone() * clk.clone()); @@ -109,13 +156,83 @@ impl FrameworkEval for ProcessorEval { // `mv` is the inverse of `mvi` eval.add_constraint(mv.clone() * (mv.clone() * mvi.clone() - E::F::one())); // `mvi` is the inverse of `mv` - eval.add_constraint(mvi.clone() * (mv.clone() * mvi - E::F::one())); + eval.add_constraint(mvi.clone() * (mv.clone() * mvi.clone() - E::F::one())); // Transition constraints // `clk` increases by 1 eval.add_constraint(next_clk.clone() - clk.clone() - E::F::one()); + // JumpIfZero - [ + // Jump to `ni` if `mv = 0`. Otherwise, skip two. + eval.add_constraint( + deselector_jump_if_zero.clone() * + (mvi.clone() * (next_ip.clone() - ip.clone() - BaseField::from(2).into()) + + is_mv_zero.clone() * (next_ip.clone() - ni.clone())), + ); + // `mp` remains the same + eval.add_constraint(deselector_jump_if_zero.clone() * (next_mp.clone() - mp.clone())); + // `mv` remains the same + eval.add_constraint(deselector_jump_if_zero * (next_mv.clone() - mv.clone())); + + // JumpIfNotZero - ] + // Jump to `ni` if `mv != 0`. Otherwise, skip two. + eval.add_constraint( + deselector_jump_if_not_zero.clone() * + (is_mv_zero * (next_ip.clone() - ip.clone() - BaseField::from(2).into()) + + mvi * (next_ip.clone() - ni.clone())), + ); + // `mp` remains the same + eval.add_constraint(deselector_jump_if_not_zero.clone() * (next_mp.clone() - mp.clone())); // `mv` remains the same + eval.add_constraint(deselector_jump_if_not_zero * (next_mv.clone() - mv.clone())); + + // ShiftLeft - < + + // `ip` increases by one + eval.add_constraint(deselector_left.clone() * (next_ip.clone() - ip.clone() - E::F::one())); // `mp` decreases by one + eval.add_constraint(deselector_left * (next_mp.clone() - mp.clone() + E::F::one())); + + // ShiftRight - > + // `ip` increases by one + eval.add_constraint( + deselector_right.clone() * (next_ip.clone() - ip.clone() - E::F::one()), + ); + // `mp` increases by one + eval.add_constraint(deselector_right * (next_mp.clone() - mp.clone() - E::F::one())); + + // MemoryDecrement - - + // `ip` increases by one + eval.add_constraint( + deselector_minus.clone() * (next_ip.clone() - ip.clone() - E::F::one()), + ); + // `mp` remains the same + eval.add_constraint(deselector_minus.clone() * (next_mp.clone() - mp.clone())); + // `mv` decreases by one + eval.add_constraint(deselector_minus * (next_mv.clone() - mv.clone() + E::F::one())); + + // MemoryIncrement - + + // `ip` increases by one + eval.add_constraint(deselector_plus.clone() * (next_ip.clone() - ip.clone() - E::F::one())); // `mp` remains the same + eval.add_constraint(deselector_plus.clone() * (next_mp.clone() - mp.clone())); + // `mv` increases by one + eval.add_constraint(deselector_plus * (next_mv.clone() - mv.clone() - E::F::one())); + + // Input - , + // `ip` increases by one + eval.add_constraint( + deselector_input.clone() * (next_ip.clone() - ip.clone() - E::F::one()), + ); + // `mp` remains the same + eval.add_constraint(deselector_input * (next_mp.clone() - mp.clone())); + + // Output - . + // `ip` increases by one + eval.add_constraint(deselector_output.clone() * (next_ip - ip.clone() - E::F::one())); + // `mp` remains the same + eval.add_constraint(deselector_output.clone() * (next_mp.clone() - mp.clone())); + // `mv` remains the same + eval.add_constraint(deselector_output * (next_mv.clone() - mv.clone())); + // Lookup arguments // Processor & Input @@ -181,6 +298,7 @@ impl InteractionClaim { #[cfg(test)] mod tests { + use super::deselector_poly; use crate::components::{ instruction::table::InstructionElements, io::table::IoElements, @@ -190,12 +308,16 @@ mod tests { table::{interaction_trace_evaluation, ProcessorTable}, }, }; - use brainfuck_vm::{compiler::Compiler, test_helper::create_test_machine}; + use brainfuck_vm::{ + compiler::Compiler, instruction::InstructionType, test_helper::create_test_machine, + }; + use num_traits::Zero; use stwo_prover::{ constraint_framework::{ assert_constraints, preprocessed_columns::gen_is_first, FrameworkEval, }, core::{ + fields::m31::BaseField, pcs::TreeVec, poly::circle::{CanonicCoset, CircleEvaluation}, }, @@ -264,4 +386,132 @@ mod tests { (interaction_claim.claimed_sum, None), ); } + + #[test] + fn deselector_jump_if_zero_valid() { + let ci = InstructionType::JumpIfZero.to_base_field(); + let deselector_jump_if_zero = deselector_poly(&ci, &InstructionType::JumpIfZero); + + assert_ne!(deselector_jump_if_zero, BaseField::zero()); + } + + #[test] + fn deselector_jump_if_zero_invalid() { + let ci = InstructionType::JumpIfZero.to_base_field(); + let deselector_jump_if_zero = deselector_poly(&ci, &InstructionType::Plus); + + assert_eq!(deselector_jump_if_zero, BaseField::zero()); + } + + #[test] + fn deselector_jump_if_not_zero_valid() { + let ci = InstructionType::JumpIfNotZero.to_base_field(); + let deselector_jump_if_not_zero = deselector_poly(&ci, &InstructionType::JumpIfNotZero); + + assert_ne!(deselector_jump_if_not_zero, BaseField::zero()); + } + + #[test] + fn deselector_jump_if_not_zero_invalid() { + let ci = InstructionType::JumpIfNotZero.to_base_field(); + let deselector_jump_if_not_zero = deselector_poly(&ci, &InstructionType::Left); + + assert_eq!(deselector_jump_if_not_zero, BaseField::zero()); + } + + #[test] + fn deselector_left_valid() { + let ci = InstructionType::Left.to_base_field(); + let deselector_left = deselector_poly(&ci, &InstructionType::Left); + + assert_ne!(deselector_left, BaseField::zero()); + } + + #[test] + fn deselector_left_invalid() { + let ci = InstructionType::Left.to_base_field(); + let deselector_left = deselector_poly(&ci, &InstructionType::JumpIfZero); + + assert_eq!(deselector_left, BaseField::zero()); + } + + #[test] + fn deselector_right_valid() { + let ci = InstructionType::Right.to_base_field(); + let deselector_right = deselector_poly(&ci, &InstructionType::Right); + + assert_ne!(deselector_right, BaseField::zero()); + } + + #[test] + fn deselector_right_invalid() { + let ci = InstructionType::Right.to_base_field(); + let deselector_right = deselector_poly(&ci, &InstructionType::JumpIfZero); + + assert_eq!(deselector_right, BaseField::zero()); + } + + #[test] + fn deselector_minus_valid() { + let ci = InstructionType::Minus.to_base_field(); + let deselector_minus = deselector_poly(&ci, &InstructionType::Minus); + + assert_ne!(deselector_minus, BaseField::zero()); + } + + #[test] + fn deselector_minus_invalid() { + let ci = InstructionType::Minus.to_base_field(); + let deselector_minus = deselector_poly(&ci, &InstructionType::JumpIfZero); + + assert_eq!(deselector_minus, BaseField::zero()); + } + + #[test] + fn deselector_plus_valid() { + let ci = InstructionType::Plus.to_base_field(); + let deselector_plus = deselector_poly(&ci, &InstructionType::Plus); + + assert_ne!(deselector_plus, BaseField::zero()); + } + + #[test] + fn deselector_plus_invalid() { + let ci = InstructionType::Plus.to_base_field(); + let deselector_plus = deselector_poly(&ci, &InstructionType::JumpIfZero); + + assert_eq!(deselector_plus, BaseField::zero()); + } + + #[test] + fn deselector_input_valid() { + let ci = InstructionType::ReadChar.to_base_field(); + let deselector_input = deselector_poly(&ci, &InstructionType::ReadChar); + + assert_ne!(deselector_input, BaseField::zero()); + } + + #[test] + fn deselector_input_invalid() { + let ci = InstructionType::ReadChar.to_base_field(); + let deselector_input = deselector_poly(&ci, &InstructionType::JumpIfZero); + + assert_eq!(deselector_input, BaseField::zero()); + } + + #[test] + fn deselector_output_valid() { + let ci = InstructionType::PutChar.to_base_field(); + let deselector_output = deselector_poly(&ci, &InstructionType::PutChar); + + assert_ne!(deselector_output, BaseField::zero()); + } + + #[test] + fn deselector_output_invalid() { + let ci = InstructionType::PutChar.to_base_field(); + let deselector_output = deselector_poly(&ci, &InstructionType::JumpIfZero); + + assert_eq!(deselector_output, BaseField::zero()); + } }