Skip to content
This repository has been archived by the owner on Apr 9, 2024. It is now read-only.

Commit

Permalink
feat: Brillig oracle opcode (#170)
Browse files Browse the repository at this point in the history
* Brillig OracleWait and test for it

* cleanup and add stalled brillig data to unresolved_opcodes

* wip

---------

Co-authored-by: Kevaundray Wedderburn <[email protected]>
  • Loading branch information
vezenovm and kevaundray authored Mar 29, 2023
1 parent d3bdeec commit a9b00f4
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 36 deletions.
160 changes: 146 additions & 14 deletions acvm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ pub mod pwg;

use crate::pwg::{arithmetic::ArithmeticSolver, brillig::BrilligSolver, oracle::OracleSolver};
use acir::{
brillig_bytecode,
circuit::{
directives::Directive,
opcodes::{BlackBoxFuncCall, OracleData},
opcodes::{BlackBoxFuncCall, Brillig, OracleData},
Circuit, Opcode,
},
native_types::{Expression, Witness},
Expand Down Expand Up @@ -49,7 +50,7 @@ pub enum OpcodeResolutionError {
#[error("could not satisfy all constraints")]
UnsatisfiedConstrain,
#[error("unexpected opcode, expected {0}, but got {1}")]
UnexpectedOpcode(&'static str, BlackBoxFunc),
UnexpectedOpcode(&'static str, &'static str),
#[error("expected {0} inputs for function {1}, but got {2}")]
IncorrectNumFunctionArguments(usize, BlackBoxFunc, usize),
}
Expand All @@ -62,6 +63,8 @@ pub enum OpcodeResolution {
Stalled(OpcodeNotSolvable),
/// The opcode is not solvable but could resolved some witness
InProgress,
/// The brillig oracle opcode is not solved but could be resolved given some values
InProgessBrillig(brillig_bytecode::OracleData),
}

pub trait Backend: SmartContract + ProofSystemCompiler + PartialWitnessGenerator {}
Expand All @@ -75,15 +78,17 @@ pub trait PartialWitnessGenerator {
initial_witness: &mut BTreeMap<Witness, FieldElement>,
blocks: &mut Blocks,
mut opcode_to_solve: Vec<Opcode>,
) -> Result<(Vec<Opcode>, Vec<OracleData>), OpcodeResolutionError> {
) -> Result<UnresolvedData, OpcodeResolutionError> {
let mut unresolved_opcodes: Vec<Opcode> = Vec::new();
let mut unresolved_oracles: Vec<OracleData> = Vec::new();
let mut unresolved_brillig_oracles: Vec<brillig_bytecode::OracleData> = Vec::new();
while !opcode_to_solve.is_empty() || !unresolved_oracles.is_empty() {
unresolved_opcodes.clear();
let mut stalled = true;
let mut opcode_not_solvable = None;
for opcode in &opcode_to_solve {
let mut solved_oracle_data = None;
let mut solved_brillig_data = None;
let resolution = match opcode {
Opcode::Arithmetic(expr) => ArithmeticSolver::solve(initial_witness, expr),
Opcode::BlackBoxFuncCall(bb_func) => {
Expand Down Expand Up @@ -112,7 +117,7 @@ pub trait PartialWitnessGenerator {
Opcode::Brillig(brillig) => {
let mut brillig_clone = brillig.clone();
let result = BrilligSolver::solve(initial_witness, &mut brillig_clone)?;
// TODO: add oracle logic
solved_brillig_data = Some(brillig_clone);
Ok(result)
}
};
Expand All @@ -129,6 +134,11 @@ pub trait PartialWitnessGenerator {
unresolved_opcodes.push(opcode.clone());
}
}
Ok(OpcodeResolution::InProgessBrillig(oracle_data)) => {
stalled = false;
// InProgressBrillig Oracles must be externally re-solved
unresolved_brillig_oracles.push(oracle_data);
}
Ok(OpcodeResolution::Stalled(not_solvable)) => {
if opcode_not_solvable.is_none() {
// we keep track of the first unsolvable opcode
Expand All @@ -141,6 +151,10 @@ pub trait PartialWitnessGenerator {
Some(oracle_data) => Opcode::Oracle(oracle_data),
None => opcode.clone(),
});
unresolved_opcodes.push(match solved_brillig_data {
Some(brillig) => Opcode::Brillig(brillig),
None => opcode.clone(),
})
}
Err(OpcodeResolutionError::OpcodeNotSolvable(_)) => {
unreachable!("ICE - Result should have been converted to GateResolution")
Expand All @@ -149,8 +163,12 @@ pub trait PartialWitnessGenerator {
}
}
// We have oracles that must be externally resolved
if !unresolved_oracles.is_empty() {
return Ok((unresolved_opcodes, unresolved_oracles));
if !unresolved_oracles.is_empty() | !unresolved_brillig_oracles.is_empty() {
return Ok(UnresolvedData {
unresolved_opcodes,
unresolved_oracles,
unresolved_brillig_oracles,
});
}
// We are stalled because of an opcode being bad
if stalled && !unresolved_opcodes.is_empty() {
Expand All @@ -161,7 +179,11 @@ pub trait PartialWitnessGenerator {
}
std::mem::swap(&mut opcode_to_solve, &mut unresolved_opcodes);
}
Ok((Vec::new(), Vec::new()))
Ok(UnresolvedData {
unresolved_opcodes: Vec::new(),
unresolved_oracles: Vec::new(),
unresolved_brillig_oracles: Vec::new(),
})
}

fn solve_black_box_function_call(
Expand Down Expand Up @@ -198,6 +220,12 @@ pub trait PartialWitnessGenerator {
}
}

pub struct UnresolvedData {
pub unresolved_opcodes: Vec<Opcode>,
pub unresolved_oracles: Vec<OracleData>,
pub unresolved_brillig_oracles: Vec<brillig_bytecode::OracleData>,
}

pub trait SmartContract {
// TODO: Allow a backend to support multiple smart contract platforms

Expand Down Expand Up @@ -320,9 +348,11 @@ mod test {
use std::collections::BTreeMap;

use acir::{
brillig_bytecode,
brillig_bytecode::{RegisterIndex, RegisterMemIndex},
circuit::{
directives::Directive,
opcodes::{BlackBoxFuncCall, OracleData},
opcodes::{BlackBoxFuncCall, Brillig, OracleData},
Opcode,
},
native_types::{Expression, Witness},
Expand All @@ -331,6 +361,7 @@ mod test {

use crate::{
pwg::block::Blocks, OpcodeResolution, OpcodeResolutionError, PartialWitnessGenerator,
UnresolvedData,
};

struct StubbedPwg;
Expand Down Expand Up @@ -395,22 +426,123 @@ mod test {
(Witness(2), FieldElement::from(3u128)),
]);
let mut blocks = Blocks::default();
let (unsolved_opcodes, mut unresolved_oracles) = pwg
let UnresolvedData { unresolved_opcodes, mut unresolved_oracles, .. } = pwg
.solve(&mut witness_assignments, &mut blocks, opcodes)
.expect("should stall on oracle");
assert!(unsolved_opcodes.is_empty(), "oracle should be removed");
assert!(unresolved_opcodes.is_empty(), "oracle should be removed");
assert_eq!(unresolved_oracles.len(), 1, "should have an oracle request");
let mut oracle_data = unresolved_oracles.remove(0);
assert_eq!(oracle_data.input_values.len(), 1, "Should have solved a single input");

// Filling data request and continue solving
oracle_data.output_values = vec![oracle_data.input_values.last().unwrap().inverse()];
let mut next_opcodes_for_solving = vec![Opcode::Oracle(oracle_data)];
next_opcodes_for_solving.extend_from_slice(&unsolved_opcodes[..]);
let (unsolved_opcodes, unresolved_oracles) = pwg
next_opcodes_for_solving.extend_from_slice(&unresolved_opcodes[..]);
let UnresolvedData { unresolved_opcodes, .. } = pwg
.solve(&mut witness_assignments, &mut blocks, next_opcodes_for_solving)
.expect("should be solvable");
assert!(unsolved_opcodes.is_empty(), "should be fully solved");
.expect("should not stall on oracle");
assert!(unresolved_opcodes.is_empty(), "should be fully solved");
assert!(unresolved_oracles.is_empty(), "should have no unresolved oracles");
}

#[test]
fn inversion_brillig_oracle_equivalence() {
// Opcodes below describe the following:
// fn main(x : Field, y : pub Field) {
// let z = x + y;
// constrain 1/z == Oracle("inverse", x + y);
// }
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 brillig_bytecode = brillig_bytecode::Opcode::Oracle(brillig_bytecode::OracleData {
name: "invert".into(),
inputs: vec![RegisterMemIndex::Register(RegisterIndex(0))],
input_values: vec![],
outputs: vec![RegisterIndex(1)],
output_values: vec![],
});

let brillig_opcode = Opcode::Brillig(Brillig {
inputs: vec![
Expression {
mul_terms: vec![],
linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)],
q_c: fe_0,
},
Expression::default(),
],
outputs: vec![w_x_plus_y, w_oracle],
bytecode: vec![brillig_bytecode],
});

let opcodes = vec![
brillig_opcode,
Opcode::Arithmetic(Expression {
mul_terms: vec![],
linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)],
q_c: fe_0,
}),
Opcode::Directive(Directive::Invert { x: w_z, result: w_z_inverse }),
Opcode::Arithmetic(Expression {
mul_terms: vec![(fe_1, w_z, w_z_inverse)],
linear_combinations: vec![],
q_c: -fe_1,
}),
Opcode::Arithmetic(Expression {
mul_terms: vec![],
linear_combinations: vec![(-fe_1, w_oracle), (fe_1, w_z_inverse)],
q_c: fe_0,
}),
];

let pwg = StubbedPwg;

let mut witness_assignments = BTreeMap::from([
(Witness(1), FieldElement::from(2u128)),
(Witness(2), FieldElement::from(3u128)),
(Witness(6), FieldElement::from(5u128)),
]);
let mut blocks = Blocks::default();
let UnresolvedData { unresolved_opcodes, mut unresolved_brillig_oracles, .. } = pwg
.solve(&mut witness_assignments, &mut blocks, opcodes)
.expect("should stall on oracle");

assert!(unresolved_opcodes.is_empty(), "opcode should be removed");
assert_eq!(unresolved_brillig_oracles.len(), 1, "should have a brillig oracle request");

let mut oracle_data = unresolved_brillig_oracles.remove(0);
assert_eq!(oracle_data.inputs.len(), 1, "Should have solved a single input");

// Filling data request and continue solving
oracle_data.output_values = vec![oracle_data.input_values.last().unwrap().inverse()];
let brillig_bytecode = brillig_bytecode::Opcode::Oracle(oracle_data);

let mut next_opcodes_for_solving = vec![Opcode::Brillig(Brillig {
inputs: vec![
Expression {
mul_terms: vec![],
linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)],
q_c: fe_0,
},
Expression::default(),
],
outputs: vec![w_x_plus_y, w_oracle],
bytecode: vec![brillig_bytecode],
})];

next_opcodes_for_solving.extend_from_slice(&unresolved_opcodes[..]);
let UnresolvedData { unresolved_opcodes, unresolved_brillig_oracles, .. } = pwg
.solve(&mut witness_assignments, &mut blocks, next_opcodes_for_solving)
.expect("should not stall on oracle");

assert!(unresolved_opcodes.is_empty(), "should be fully solved");
assert!(unresolved_brillig_oracles.is_empty(), "should have no unresolved oracles");
}
}
62 changes: 54 additions & 8 deletions acvm/src/pwg/brillig.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
use std::collections::BTreeMap;

use acir::{circuit::opcodes::Brillig, native_types::Witness, FieldElement};
use acir::{
brillig_bytecode::{Opcode, RegisterMemIndex, Registers, VMStatus, Value, VM},
circuit::opcodes::Brillig,
native_types::Witness,
FieldElement,
};

use crate::{OpcodeNotSolvable, OpcodeResolution, OpcodeResolutionError};
use crate::{
pwg::arithmetic::ArithmeticSolver, OpcodeNotSolvable, OpcodeResolution, OpcodeResolutionError,
};

use super::{directives::insert_witness, get_value};

Expand All @@ -13,16 +20,55 @@ impl BrilligSolver {
initial_witness: &mut BTreeMap<Witness, FieldElement>,
brillig: &mut Brillig,
) -> Result<OpcodeResolution, OpcodeResolutionError> {
let mut input_register_values: Vec<acir::brillig_bytecode::Value> = Vec::new();
// Set input values
let mut input_register_values: Vec<Value> = Vec::new();
for expr in &brillig.inputs {
let expr_value = get_value(expr, initial_witness)?;
input_register_values.push(expr_value.into())
// Break from setting the inputs values if unable to solve the arithmetic expression inputs
let solve = ArithmeticSolver::evaluate(expr, initial_witness);
if let Some(value) = solve.to_const() {
input_register_values.push(value.into())
} else {
break;
}
}
let input_registers = acir::brillig_bytecode::Registers { inner: input_register_values };

let vm = acir::brillig_bytecode::VM::new(input_registers, brillig.bytecode.clone());
if input_register_values.len() != brillig.inputs.len() {
return Ok(OpcodeResolution::Stalled(OpcodeNotSolvable::ExpressionHasTooManyUnknowns(
brillig
.inputs
.last()
.expect("Infallible: cannot reach this point if no inputs")
.clone(),
)));
}

let input_registers = Registers { inner: input_register_values };
let vm = VM::new(input_registers, brillig.bytecode.clone());

let (output_registers, status) = vm.clone().process_opcodes();

let output_registers = vm.process_opcodes();
if status == VMStatus::OracleWait {
let pc = vm.program_counter();
let current_opcode = &brillig.bytecode[pc];
let mut data = match current_opcode.clone() {
Opcode::Oracle(data) => data,
_ => {
return Err(OpcodeResolutionError::UnexpectedOpcode(
"brillig oracle",
current_opcode.name(),
))
}
};
let input_values = data
.clone()
.inputs
.into_iter()
.map(|register_mem_index| output_registers.get(register_mem_index).inner)
.collect::<Vec<_>>();
data.input_values = input_values;

return Ok(OpcodeResolution::InProgessBrillig(data.clone()));
}

let output_register_values: Vec<FieldElement> =
output_registers.inner.into_iter().map(|v| v.inner).collect::<Vec<_>>();
Expand Down
2 changes: 1 addition & 1 deletion acvm/src/pwg/logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub fn solve_logic_opcode(
match func_call.name {
BlackBoxFunc::AND => LogicSolver::solve_and_gate(initial_witness, func_call),
BlackBoxFunc::XOR => LogicSolver::solve_xor_gate(initial_witness, func_call),
_ => Err(OpcodeResolutionError::UnexpectedOpcode("logic opcode", func_call.name)),
_ => Err(OpcodeResolutionError::UnexpectedOpcode("logic opcode", func_call.name.name())),
}
}

Expand Down
Loading

0 comments on commit a9b00f4

Please sign in to comment.