use crate::helpers::VariableStore;
use acir::{
circuit::{
directives::Directive,
opcodes::{BlackBoxFuncCall, FunctionInput},
Opcode,
},
native_types::{Expression, Witness},
FieldElement,
};
fn round_to_nearest_mul_8(num_bits: u32) -> u32 {
let remainder = num_bits % 8;
if remainder == 0 {
return num_bits;
}
num_bits + 8 - remainder
}
pub(crate) fn round_to_nearest_byte(num_bits: u32) -> u32 {
round_to_nearest_mul_8(num_bits) / 8
}
pub(crate) fn boolean_expr(expr: &Expression, variables: &mut VariableStore) -> Expression {
&mul_with_witness(expr, expr, variables) - expr
}
pub(crate) fn mul_with_witness(
lhs: &Expression,
rhs: &Expression,
variables: &mut VariableStore,
) -> Expression {
use std::borrow::Cow;
let lhs_is_linear = lhs.is_linear();
let rhs_is_linear = rhs.is_linear();
if lhs_is_linear && rhs_is_linear {
return (lhs * rhs)
.expect("one of the expressions is a constant and so this should not fail");
}
let lhs_reduced = if lhs_is_linear {
Cow::Borrowed(lhs)
} else {
Cow::Owned(variables.new_variable().into())
};
if lhs == rhs {
return (&*lhs_reduced * &*lhs_reduced)
.expect("Both expressions are reduced to be degree<=1");
};
let rhs_reduced = if rhs_is_linear {
Cow::Borrowed(rhs)
} else {
Cow::Owned(variables.new_variable().into())
};
(&*lhs_reduced * &*rhs_reduced).expect("Both expressions are reduced to be degree<=1")
}
pub(crate) fn bit_decomposition(
opcode: Expression,
bit_size: u32,
mut num_witness: u32,
) -> (Vec<Opcode>, Vec<Witness>, u32) {
let mut new_opcodes = Vec::new();
let mut variables = VariableStore::new(&mut num_witness);
let mut bit_vector = Vec::with_capacity(bit_size as usize);
for _ in 0..bit_size {
bit_vector.push(variables.new_variable())
}
new_opcodes.push(Opcode::Directive(Directive::ToLeRadix {
a: opcode.clone(),
b: bit_vector.clone(),
radix: 2,
}));
let mut binary_exprs = Vec::new();
let mut bit_decomp_constraint = opcode;
let mut two_pow: FieldElement = FieldElement::one();
let two = FieldElement::from(2_i128);
for &bit in &bit_vector {
let expr = boolean_expr(&bit.into(), &mut variables);
binary_exprs.push(Opcode::Arithmetic(expr));
bit_decomp_constraint.push_addition_term(-two_pow, bit);
two_pow = two * two_pow;
}
new_opcodes.extend(binary_exprs);
bit_decomp_constraint.sort(); new_opcodes.push(Opcode::Arithmetic(bit_decomp_constraint));
(new_opcodes, bit_vector, variables.finalize())
}
pub(crate) fn byte_decomposition(
opcode: Expression,
num_bytes: u32,
mut num_witness: u32,
) -> (Vec<Opcode>, Vec<Witness>, u32) {
let mut new_opcodes = Vec::new();
let mut variables = VariableStore::new(&mut num_witness);
let mut vector = Vec::with_capacity(num_bytes as usize);
for _ in 0..num_bytes {
vector.push(variables.new_variable())
}
new_opcodes.push(Opcode::Directive(Directive::ToLeRadix {
a: opcode.clone(),
b: vector.clone(),
radix: 256,
}));
vector.reverse();
let mut byte_exprs = Vec::new();
let mut decomp_constraint = opcode;
let byte_shift: u128 = 256;
for (i, v) in vector.iter().enumerate() {
let range = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE {
input: FunctionInput { witness: *v, num_bits: 8 },
});
let scaling_factor_value = byte_shift.pow(num_bytes - 1 - i as u32);
let scaling_factor = FieldElement::from(scaling_factor_value);
decomp_constraint.push_addition_term(-scaling_factor, *v);
byte_exprs.push(range);
}
new_opcodes.extend(byte_exprs);
decomp_constraint.sort();
new_opcodes.push(Opcode::Arithmetic(decomp_constraint));
(new_opcodes, vector, variables.finalize())
}