Skip to content

Commit

Permalink
Merge pull request #50 from namnc/issue-31-PrefixOp
Browse files Browse the repository at this point in the history
Support `PrefixOp`
  • Loading branch information
voltrevo authored May 9, 2024
2 parents 2d351a8 + 677c381 commit 16de04d
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 17 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ This library enables the creation of arithmetic circuits from circom programs.
| | `InfixOp` ||
| | `Number` ||
| | `Variable` ||
| | `PrefixOp` | |
| | `PrefixOp` | |
| | `InlineSwitchOp` ||
| | `ParallelOp` ||
| | `AnonymousComp` ||
Expand Down
3 changes: 3 additions & 0 deletions src/circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub enum AGateType {
AMul,
ANeq,
ASub,
AXor,
}

impl From<&ExpressionInfixOpcode> for AGateType {
Expand All @@ -47,6 +48,7 @@ impl From<&ExpressionInfixOpcode> for AGateType {
ExpressionInfixOpcode::Mul => AGateType::AMul,
ExpressionInfixOpcode::NotEq => AGateType::ANeq,
ExpressionInfixOpcode::Sub => AGateType::ASub,
ExpressionInfixOpcode::BitXor => AGateType::AXor,
_ => unimplemented!("Unsupported opcode"),
}
}
Expand All @@ -65,6 +67,7 @@ impl From<&AGateType> for Operation {
AGateType::ALEq => Operation::LessOrEqual,
AGateType::AGt => Operation::GreaterThan,
AGateType::AGEq => Operation::GreaterOrEqual,
AGateType::AXor => Operation::XorBitwise,
}
}
}
Expand Down
112 changes: 96 additions & 16 deletions src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::runtime::{
};
use circom_circom_algebra::num_traits::ToPrimitive;
use circom_program_structure::ast::{
Access, AssignOp, Expression, ExpressionInfixOpcode, Statement,
Access, AssignOp, Expression, ExpressionInfixOpcode, ExpressionPrefixOpcode, Statement
};
use circom_program_structure::program_archive::ProgramArchive;
use std::cell::RefCell;
Expand Down Expand Up @@ -272,6 +272,9 @@ pub fn process_expression(
Expression::InfixOp {
lhe, infix_op, rhe, ..
} => handle_infix_op(ac, runtime, program_archive, infix_op, lhe, rhe),
Expression::PrefixOp {
prefix_op, rhe, ..
} => handle_prefix_op(ac, runtime, program_archive, prefix_op, rhe),
Expression::Number(_, value) => {
let signal_gen = runtime.get_signal_gen();
let access = runtime
Expand Down Expand Up @@ -457,6 +460,60 @@ fn handle_infix_op(
Ok(output_signal)
}

/// Handles a prefix operation.
/// - If input is a variable, it directly computes the operation.
/// - If input is a signal, it handles it like an infix op against a constant.
/// Returns the access to a variable containing the result of the operation or the signal of the output gate.
fn handle_prefix_op(
ac: &mut ArithmeticCircuit,
runtime: &mut Runtime,
program_archive: &ProgramArchive,
op: &ExpressionPrefixOpcode,
rhe: &Expression,
) -> Result<DataAccess, ProgramError> {
let rhe_access = process_expression(ac, runtime, program_archive, rhe)?;

let signal_gen: Rc<RefCell<u32>> = runtime.get_signal_gen();
let ctx = runtime.current_context()?;

// Determine the data type of the operand
let rhs_data_type = ctx.get_item_data_type(&rhe_access.get_name())?;

// Handle the variable case
if rhs_data_type == DataType::Variable {
let rhs_value = ctx
.get_variable_value(&rhe_access)?
.ok_or(ProgramError::EmptyDataItem)?;

let op_res = execute_prefix_op(op, rhs_value)?;
let item_access = ctx.declare_random_item(signal_gen, DataType::Variable)?;
ctx.set_variable(&item_access, Some(op_res))?;

return Ok(item_access);
}

let (lhs_value, infix_op) = to_equivalent_infix(op);
let lhs_id = make_constant(ac, ctx, signal_gen.clone(), lhs_value)?;

// Handle signal input
let rhs_id = get_signal_for_access(ac, ctx, signal_gen.clone(), &rhe_access)?;

// Construct the corresponding circuit gate
let gate_type = AGateType::from(&infix_op);
let output_signal = ctx.declare_random_item(signal_gen, DataType::Signal)?;
let output_id = ctx.get_signal_id(&output_signal)?;

// Add output signal and gate to the circuit
ac.add_signal(
output_id,
output_signal.access_str(ctx.get_ctx_name()),
None,
)?;
ac.add_gate(gate_type, lhs_id, rhs_id, output_id)?;

Ok(output_signal)
}

/// Returns a signal id for a given access
/// - If the access is a signal or a component, it returns the corresponding signal id.
/// - If the access is a variable, it adds a constant variable to the circuit and returns the corresponding signal id.
Expand All @@ -474,26 +531,35 @@ fn get_signal_for_access(
.get_variable_value(access)?
.ok_or(ProgramError::EmptyDataItem)?;

let signal_access = DataAccess::new(&format!("const_signal_{}", value), vec![]);
// Try to get signal id if it exists
if let Ok(id) = ctx.get_signal_id(&signal_access) {
Ok(id)
} else {
// If it doesn't exist, declare it and add it to the circuit
ctx.declare_item(DataType::Signal, &signal_access.get_name(), &[], signal_gen)?;
let signal_id = ctx.get_signal_id(&signal_access)?;
ac.add_signal(
signal_id,
signal_access.access_str(ctx.get_ctx_name()),
Some(value),
)?;
Ok(signal_id)
}
make_constant(ac, ctx, signal_gen, value)
}
DataType::Component => Ok(ctx.get_component_signal_id(access)?),
}
}

fn make_constant(
ac: &mut ArithmeticCircuit,
ctx: &mut Context,
signal_gen: Rc<RefCell<u32>>,
value: u32,
) -> Result<u32, ProgramError> {
let signal_access = DataAccess::new(&format!("const_signal_{}", value), vec![]);
// Try to get signal id if it exists
if let Ok(id) = ctx.get_signal_id(&signal_access) {
Ok(id)
} else {
// If it doesn't exist, declare it and add it to the circuit
ctx.declare_item(DataType::Signal, &signal_access.get_name(), &[], signal_gen)?;
let signal_id = ctx.get_signal_id(&signal_access)?;
ac.add_signal(
signal_id,
signal_access.access_str(ctx.get_ctx_name()),
Some(value),
)?;
Ok(signal_id)
}
}

/// Returns the content of a signal for a given access
fn get_signal_content_for_access(
ctx: &Context,
Expand Down Expand Up @@ -656,3 +722,17 @@ fn execute_op(lhs: u32, rhs: u32, op: &ExpressionInfixOpcode) -> Result<u32, Pro

Ok(res)
}

/// Executes a prefix operation on a u32 value, performing the specified arithmetic or logical computation.
fn execute_prefix_op(op: &ExpressionPrefixOpcode, rhs: u32) -> Result<u32, ProgramError> {
let (lhs_value, infix_op) = to_equivalent_infix(op);
execute_op(lhs_value, rhs, &infix_op)
}

fn to_equivalent_infix(op: &ExpressionPrefixOpcode) -> (u32, ExpressionInfixOpcode) {
match op {
ExpressionPrefixOpcode::Sub => (0, ExpressionInfixOpcode::Sub),
ExpressionPrefixOpcode::BoolNot => (0, ExpressionInfixOpcode::Eq),
ExpressionPrefixOpcode::Complement => (u32::MAX, ExpressionInfixOpcode::BitXor),
}
}
19 changes: 19 additions & 0 deletions tests/add_zero.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use circom_2_arithc::{circom::input::Input, program::build_circuit};

const TEST_FILE_PATH: &str = "./tests/circuits/addZero.circom";

#[test]
fn test_add_zero() {
let input = Input::new(TEST_FILE_PATH.into(), "./".into()).unwrap();
let circuit = build_circuit(&input).unwrap();
let sim_circuit = circuit.build_sim_circuit().unwrap();

let circuit_input = vec![
42, // actual input
0, // constant - FIXME: should not need to provide this
];

let res = sim_circuit.execute(&circuit_input).unwrap();

assert_eq!(res, vec![42]);
}
10 changes: 10 additions & 0 deletions tests/circuits/addZero.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pragma circom 2.0.0;

template addZero() {
signal input in;
signal output out;

out <== in + 0;
}

component main = addZero();
9 changes: 9 additions & 0 deletions tests/circuits/constantSum.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pragma circom 2.0.0;

template constantSum() {
signal output out;

out <== 3 + 5;
}

component main = constantSum();
29 changes: 29 additions & 0 deletions tests/circuits/prefixOps.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
pragma circom 2.0.0;

template prefixOps() {
signal input a;
signal input b;
signal input c;

signal output negateA;

signal output notA;
signal output notB;
signal output notC;

signal output complementA;
signal output complementB;
signal output complementC;

negateA <== -a;

notA <== !a;
notB <== !b;
notC <== !c;

complementA <== ~a;
complementB <== ~b;
complementC <== ~c;
}

component main = prefixOps();
18 changes: 18 additions & 0 deletions tests/constant_sum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use circom_2_arithc::{circom::input::Input, program::build_circuit};

const TEST_FILE_PATH: &str = "./tests/circuits/constantSum.circom";

#[test]
fn test_constant_sum() {
let input = Input::new(TEST_FILE_PATH.into(), "./".into()).unwrap();
let circuit = build_circuit(&input).unwrap();
let sim_circuit = circuit.build_sim_circuit().unwrap();

let circuit_input = vec![];
let res = sim_circuit.execute(&circuit_input).unwrap();

assert_eq!(
res,
Vec::<u32>::new(), // FIXME: Should be vec![8]
);
}
29 changes: 29 additions & 0 deletions tests/prefix_ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use circom_2_arithc::{circom::input::Input, program::build_circuit};

const TEST_FILE_PATH: &str = "./tests/circuits/prefixOps.circom";

#[test]
fn test_prefix_ops() {
let input = Input::new(TEST_FILE_PATH.into(), "./".into()).unwrap();
let circuit = build_circuit(&input).unwrap();
let sim_circuit = circuit.build_sim_circuit().unwrap();

let circuit_input = vec![
0, 1, 2, // actual inputs
0, u32::MAX // constants - FIXME: should not need to provide these
];

let res = sim_circuit.execute(&circuit_input).unwrap();

assert_eq!(res, vec![
0, // -0

1, // !0
0, // !1
0, // !2

0b_11111111_11111111_11111111_11111111, // ~0
0b_11111111_11111111_11111111_11111110, // ~1
0b_11111111_11111111_11111111_11111101, // ~2
]);
}

0 comments on commit 16de04d

Please sign in to comment.