use std::collections::BTreeMap;
use acir::{
brillig::{BinaryFieldOp, HeapArray, MemoryAddress, Opcode as BrilligOpcode, ValueOrArray},
circuit::{
brillig::{BrilligBytecode, BrilligInputs, BrilligOutputs},
opcodes::{BlockId, MemOp},
Opcode, OpcodeLocation,
},
native_types::{Expression, Witness, WitnessMap},
FieldElement,
};
use acvm::pwg::{ACVMStatus, ErrorLocation, ForeignCallWaitInfo, OpcodeResolutionError, ACVM};
use acvm_blackbox_solver::StubbedBlackBoxSolver;
use brillig_vm::brillig::HeapValueType;
#[test]
fn inversion_brillig_oracle_equivalence() {
let fe_0 = FieldElement::zero();
let fe_1 = FieldElement::one();
let w_x = Witness(1);
let w_y = Witness(2);
let w_oracle = Witness(3);
let w_z = Witness(4);
let w_z_inverse = Witness(5);
let w_x_plus_y = Witness(6);
let w_equal_res = Witness(7);
let equal_opcode = BrilligOpcode::BinaryFieldOp {
op: BinaryFieldOp::Equals,
lhs: MemoryAddress::from(0),
rhs: MemoryAddress::from(1),
destination: MemoryAddress::from(2),
};
let opcodes = vec![
Opcode::BrilligCall {
id: 0,
inputs: vec![
BrilligInputs::Single(Expression {
mul_terms: vec![],
linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)],
q_c: fe_0,
}),
BrilligInputs::Single(Expression::default()), ],
outputs: vec![
BrilligOutputs::Simple(w_x_plus_y), BrilligOutputs::Simple(w_oracle), BrilligOutputs::Simple(w_equal_res), ],
predicate: None,
},
Opcode::AssertZero(Expression {
mul_terms: vec![],
linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)],
q_c: fe_0,
}),
Opcode::AssertZero(Expression {
mul_terms: vec![(fe_1, w_z, w_z_inverse)],
linear_combinations: vec![],
q_c: -fe_1,
}),
Opcode::AssertZero(Expression {
mul_terms: vec![],
linear_combinations: vec![(-fe_1, w_oracle), (fe_1, w_z_inverse)],
q_c: fe_0,
}),
];
let brillig_bytecode = BrilligBytecode {
bytecode: vec![
BrilligOpcode::CalldataCopy {
destination_address: MemoryAddress(0),
size: 2,
offset: 0,
},
equal_opcode,
BrilligOpcode::ForeignCall {
function: "invert".into(),
destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))],
destination_value_types: vec![HeapValueType::field()],
inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))],
input_value_types: vec![HeapValueType::field()],
},
BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 3 },
],
};
let witness_assignments = BTreeMap::from([
(Witness(1), FieldElement::from(2u128)),
(Witness(2), FieldElement::from(3u128)),
])
.into();
let unconstrained_functions = vec![brillig_bytecode];
let mut acvm = ACVM::new(
&StubbedBlackBoxSolver,
&opcodes,
witness_assignments,
&unconstrained_functions,
&[],
);
let solver_status = acvm.solve();
assert!(
matches!(solver_status, ACVMStatus::RequiresForeignCall(_)),
"should require foreign call response"
);
assert_eq!(acvm.instruction_pointer(), 0, "brillig should have been removed");
let foreign_call_wait_info: &ForeignCallWaitInfo =
acvm.get_pending_foreign_call().expect("should have a brillig foreign call request");
assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input");
let foreign_call_result = foreign_call_wait_info.inputs[0].unwrap_field().inverse();
acvm.resolve_pending_foreign_call(foreign_call_result.into());
let solver_status = acvm.solve();
assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved");
acvm.finalize();
}
#[test]
fn double_inversion_brillig_oracle() {
let fe_0 = FieldElement::zero();
let fe_1 = FieldElement::one();
let w_x = Witness(1);
let w_y = Witness(2);
let w_oracle = Witness(3);
let w_z = Witness(4);
let w_z_inverse = Witness(5);
let w_x_plus_y = Witness(6);
let w_equal_res = Witness(7);
let w_i = Witness(8);
let w_j = Witness(9);
let w_ij_oracle = Witness(10);
let w_i_plus_j = Witness(11);
let equal_opcode = BrilligOpcode::BinaryFieldOp {
op: BinaryFieldOp::Equals,
lhs: MemoryAddress::from(0),
rhs: MemoryAddress::from(1),
destination: MemoryAddress::from(4),
};
let opcodes = vec![
Opcode::BrilligCall {
id: 0,
inputs: vec![
BrilligInputs::Single(Expression {
mul_terms: vec![],
linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)],
q_c: fe_0,
}),
BrilligInputs::Single(Expression::default()), BrilligInputs::Single(Expression {
mul_terms: vec![],
linear_combinations: vec![(fe_1, w_i), (fe_1, w_j)],
q_c: fe_0,
}),
],
outputs: vec![
BrilligOutputs::Simple(w_x_plus_y), BrilligOutputs::Simple(w_oracle), BrilligOutputs::Simple(w_i_plus_j), BrilligOutputs::Simple(w_ij_oracle), BrilligOutputs::Simple(w_equal_res), ],
predicate: None,
},
Opcode::AssertZero(Expression {
mul_terms: vec![],
linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)],
q_c: fe_0,
}),
Opcode::AssertZero(Expression {
mul_terms: vec![(fe_1, w_z, w_z_inverse)],
linear_combinations: vec![],
q_c: -fe_1,
}),
Opcode::AssertZero(Expression {
mul_terms: vec![],
linear_combinations: vec![(-fe_1, w_oracle), (fe_1, w_z_inverse)],
q_c: fe_0,
}),
];
let brillig_bytecode = BrilligBytecode {
bytecode: vec![
BrilligOpcode::CalldataCopy {
destination_address: MemoryAddress(0),
size: 3,
offset: 0,
},
equal_opcode,
BrilligOpcode::ForeignCall {
function: "invert".into(),
destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))],
destination_value_types: vec![HeapValueType::field()],
inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))],
input_value_types: vec![HeapValueType::field()],
},
BrilligOpcode::ForeignCall {
function: "invert".into(),
destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(3))],
destination_value_types: vec![HeapValueType::field()],
inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(2))],
input_value_types: vec![HeapValueType::field()],
},
BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 5 },
],
};
let witness_assignments = BTreeMap::from([
(Witness(1), FieldElement::from(2u128)),
(Witness(2), FieldElement::from(3u128)),
(Witness(8), FieldElement::from(5u128)),
(Witness(9), FieldElement::from(10u128)),
])
.into();
let unconstrained_functions = vec![brillig_bytecode];
let mut acvm = ACVM::new(
&StubbedBlackBoxSolver,
&opcodes,
witness_assignments,
&unconstrained_functions,
&[],
);
let solver_status = acvm.solve();
assert!(
matches!(solver_status, ACVMStatus::RequiresForeignCall(_)),
"should require foreign call response"
);
assert_eq!(acvm.instruction_pointer(), 0, "should stall on brillig");
let foreign_call_wait_info: &ForeignCallWaitInfo =
acvm.get_pending_foreign_call().expect("should have a brillig foreign call request");
assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input");
let x_plus_y_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse();
acvm.resolve_pending_foreign_call(x_plus_y_inverse.into());
let solver_status = acvm.solve();
assert!(
matches!(solver_status, ACVMStatus::RequiresForeignCall(_)),
"should require foreign call response"
);
assert_eq!(acvm.instruction_pointer(), 0, "should stall on brillig");
let foreign_call_wait_info =
acvm.get_pending_foreign_call().expect("should have a brillig foreign call request");
assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input");
let i_plus_j_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse();
assert_ne!(x_plus_y_inverse, i_plus_j_inverse);
acvm.resolve_pending_foreign_call(i_plus_j_inverse.into());
let solver_status = acvm.solve();
assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved");
acvm.finalize();
}
#[test]
fn oracle_dependent_execution() {
let fe_0 = FieldElement::zero();
let fe_1 = FieldElement::one();
let w_x = Witness(1);
let w_y = Witness(2);
let w_x_inv = Witness(3);
let w_y_inv = Witness(4);
let brillig_bytecode = BrilligBytecode {
bytecode: vec![
BrilligOpcode::CalldataCopy {
destination_address: MemoryAddress(0),
size: 3,
offset: 0,
},
BrilligOpcode::ForeignCall {
function: "invert".into(),
destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))],
destination_value_types: vec![HeapValueType::field()],
inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))],
input_value_types: vec![HeapValueType::field()],
},
BrilligOpcode::ForeignCall {
function: "invert".into(),
destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(3))],
destination_value_types: vec![HeapValueType::field()],
inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(2))],
input_value_types: vec![HeapValueType::field()],
},
BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 4 },
],
};
let equality_check = Expression {
mul_terms: vec![],
linear_combinations: vec![(-fe_1, w_x), (fe_1, w_y)],
q_c: fe_0,
};
let inverse_equality_check = Expression {
mul_terms: vec![],
linear_combinations: vec![(-fe_1, w_x_inv), (fe_1, w_y_inv)],
q_c: fe_0,
};
let opcodes = vec![
Opcode::AssertZero(equality_check),
Opcode::BrilligCall {
id: 0,
inputs: vec![
BrilligInputs::Single(w_x.into()), BrilligInputs::Single(Expression::default()), BrilligInputs::Single(w_y.into()), ],
outputs: vec![
BrilligOutputs::Simple(w_x), BrilligOutputs::Simple(w_y_inv), BrilligOutputs::Simple(w_y), BrilligOutputs::Simple(w_y_inv), ],
predicate: None,
},
Opcode::AssertZero(inverse_equality_check),
];
let witness_assignments =
BTreeMap::from([(w_x, FieldElement::from(2u128)), (w_y, FieldElement::from(2u128))]).into();
let unconstrained_functions = vec![brillig_bytecode];
let mut acvm = ACVM::new(
&StubbedBlackBoxSolver,
&opcodes,
witness_assignments,
&unconstrained_functions,
&[],
);
let solver_status = acvm.solve();
assert!(
matches!(solver_status, ACVMStatus::RequiresForeignCall(_)),
"should require foreign call response"
);
assert_eq!(acvm.instruction_pointer(), 1, "should stall on brillig");
let foreign_call_wait_info: &ForeignCallWaitInfo =
acvm.get_pending_foreign_call().expect("should have a brillig foreign call request");
assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input");
let x_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse();
acvm.resolve_pending_foreign_call(x_inverse.into());
let solver_status = acvm.solve();
assert!(
matches!(solver_status, ACVMStatus::RequiresForeignCall(_)),
"should require foreign call response"
);
assert_eq!(acvm.instruction_pointer(), 1, "should stall on brillig");
let foreign_call_wait_info: &ForeignCallWaitInfo =
acvm.get_pending_foreign_call().expect("should have a brillig foreign call request");
assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input");
let y_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse();
acvm.resolve_pending_foreign_call(y_inverse.into());
let solver_status = acvm.solve();
assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved");
acvm.finalize();
}
#[test]
fn brillig_oracle_predicate() {
let fe_0 = FieldElement::zero();
let fe_1 = FieldElement::one();
let w_x = Witness(1);
let w_y = Witness(2);
let w_oracle = Witness(3);
let w_x_plus_y = Witness(4);
let w_equal_res = Witness(5);
let w_lt_res = Witness(6);
let equal_opcode = BrilligOpcode::BinaryFieldOp {
op: BinaryFieldOp::Equals,
lhs: MemoryAddress::from(0),
rhs: MemoryAddress::from(1),
destination: MemoryAddress::from(2),
};
let brillig_bytecode = BrilligBytecode {
bytecode: vec![
BrilligOpcode::CalldataCopy {
destination_address: MemoryAddress(0),
size: 2,
offset: 0,
},
equal_opcode,
BrilligOpcode::ForeignCall {
function: "invert".into(),
destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))],
destination_value_types: vec![HeapValueType::field()],
inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))],
input_value_types: vec![HeapValueType::field()],
},
],
};
let opcodes = vec![Opcode::BrilligCall {
id: 0,
inputs: vec![
BrilligInputs::Single(Expression {
mul_terms: vec![],
linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)],
q_c: fe_0,
}),
BrilligInputs::Single(Expression::default()),
],
outputs: vec![
BrilligOutputs::Simple(w_x_plus_y),
BrilligOutputs::Simple(w_oracle),
BrilligOutputs::Simple(w_equal_res),
BrilligOutputs::Simple(w_lt_res),
],
predicate: Some(Expression::default()),
}];
let witness_assignments = BTreeMap::from([
(Witness(1), FieldElement::from(2u128)),
(Witness(2), FieldElement::from(3u128)),
])
.into();
let unconstrained_functions = vec![brillig_bytecode];
let mut acvm = ACVM::new(
&StubbedBlackBoxSolver,
&opcodes,
witness_assignments,
&unconstrained_functions,
&[],
);
let solver_status = acvm.solve();
assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved");
acvm.finalize();
}
#[test]
fn unsatisfied_opcode_resolved() {
let a = Witness(0);
let b = Witness(1);
let c = Witness(2);
let d = Witness(3);
let opcode_a = Expression {
mul_terms: vec![],
linear_combinations: vec![
(FieldElement::one(), a),
(-FieldElement::one(), b),
(-FieldElement::one(), c),
(-FieldElement::one(), d),
],
q_c: FieldElement::zero(),
};
let mut values = WitnessMap::new();
values.insert(a, FieldElement::from(4_i128));
values.insert(b, FieldElement::from(2_i128));
values.insert(c, FieldElement::from(1_i128));
values.insert(d, FieldElement::from(2_i128));
let opcodes = vec![Opcode::AssertZero(opcode_a)];
let unconstrained_functions = vec![];
let mut acvm =
ACVM::new(&StubbedBlackBoxSolver, &opcodes, values, &unconstrained_functions, &[]);
let solver_status = acvm.solve();
assert_eq!(
solver_status,
ACVMStatus::Failure(OpcodeResolutionError::UnsatisfiedConstrain {
opcode_location: ErrorLocation::Resolved(OpcodeLocation::Acir(0)),
payload: None
}),
"The first opcode is not satisfiable, expected an error indicating this"
);
}
#[test]
fn unsatisfied_opcode_resolved_brillig() {
let a = Witness(0);
let b = Witness(1);
let c = Witness(2);
let d = Witness(3);
let fe_1 = FieldElement::one();
let fe_0 = FieldElement::zero();
let w_x = Witness(4);
let w_y = Witness(5);
let w_result = Witness(6);
let calldata_copy_opcode =
BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), size: 2, offset: 0 };
let equal_opcode = BrilligOpcode::BinaryFieldOp {
op: BinaryFieldOp::Equals,
lhs: MemoryAddress::from(0),
rhs: MemoryAddress::from(1),
destination: MemoryAddress::from(2),
};
let location_of_stop = 3;
let jmp_if_opcode =
BrilligOpcode::JumpIf { condition: MemoryAddress::from(2), location: location_of_stop };
let trap_opcode = BrilligOpcode::Trap { revert_data: HeapArray::default() };
let stop_opcode = BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 };
let brillig_bytecode = BrilligBytecode {
bytecode: vec![calldata_copy_opcode, equal_opcode, jmp_if_opcode, trap_opcode, stop_opcode],
};
let opcode_a = Expression {
mul_terms: vec![],
linear_combinations: vec![
(FieldElement::one(), a),
(-FieldElement::one(), b),
(-FieldElement::one(), c),
(-FieldElement::one(), d),
],
q_c: FieldElement::zero(),
};
let mut values = WitnessMap::new();
values.insert(a, FieldElement::from(4_i128));
values.insert(b, FieldElement::from(2_i128));
values.insert(c, FieldElement::from(1_i128));
values.insert(d, FieldElement::from(2_i128));
values.insert(w_x, FieldElement::from(0_i128));
values.insert(w_y, FieldElement::from(1_i128));
values.insert(w_result, FieldElement::from(0_i128));
let opcodes = vec![
Opcode::BrilligCall {
id: 0,
inputs: vec![
BrilligInputs::Single(Expression {
mul_terms: vec![],
linear_combinations: vec![(fe_1, w_x)],
q_c: fe_0,
}),
BrilligInputs::Single(Expression {
mul_terms: vec![],
linear_combinations: vec![(fe_1, w_y)],
q_c: fe_0,
}),
],
outputs: vec![BrilligOutputs::Simple(w_result)],
predicate: Some(Expression::one()),
},
Opcode::AssertZero(opcode_a),
];
let unconstrained_functions = vec![brillig_bytecode];
let mut acvm =
ACVM::new(&StubbedBlackBoxSolver, &opcodes, values, &unconstrained_functions, &[]);
let solver_status = acvm.solve();
assert_eq!(
solver_status,
ACVMStatus::Failure(OpcodeResolutionError::BrilligFunctionFailed {
payload: None,
call_stack: vec![OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 }]
}),
"The first opcode is not satisfiable, expected an error indicating this"
);
}
#[test]
fn memory_operations() {
let initial_witness = WitnessMap::from(BTreeMap::from_iter([
(Witness(1), FieldElement::from(1u128)),
(Witness(2), FieldElement::from(2u128)),
(Witness(3), FieldElement::from(3u128)),
(Witness(4), FieldElement::from(4u128)),
(Witness(5), FieldElement::from(5u128)),
(Witness(6), FieldElement::from(4u128)),
]));
let block_id = BlockId(0);
let init = Opcode::MemoryInit { block_id, init: (1..6).map(Witness).collect() };
let read_op = Opcode::MemoryOp {
block_id,
op: MemOp::read_at_mem_index(Witness(6).into(), Witness(7)),
predicate: None,
};
let expression = Opcode::AssertZero(Expression {
mul_terms: Vec::new(),
linear_combinations: vec![
(FieldElement::one(), Witness(7)),
(-FieldElement::one(), Witness(8)),
],
q_c: FieldElement::one(),
});
let opcodes = vec![init, read_op, expression];
let unconstrained_functions = vec![];
let mut acvm =
ACVM::new(&StubbedBlackBoxSolver, &opcodes, initial_witness, &unconstrained_functions, &[]);
let solver_status = acvm.solve();
assert_eq!(solver_status, ACVMStatus::Solved);
let witness_map = acvm.finalize();
assert_eq!(witness_map[&Witness(8)], FieldElement::from(6u128));
}