From 3adcab771d2648d0afb24631e212104373e4cdd0 Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Tue, 21 Mar 2023 12:22:43 +0000 Subject: [PATCH 01/11] add initial oracle opcode --- acir/src/circuit/mod.rs | 17 ++++++- acir/src/circuit/opcodes.rs | 58 +++++++++++++++++++++- acir/src/serialization.rs | 5 ++ acvm/src/compiler/transformers/fallback.rs | 8 ++- acvm/src/lib.rs | 3 ++ 5 files changed, 86 insertions(+), 5 deletions(-) diff --git a/acir/src/circuit/mod.rs b/acir/src/circuit/mod.rs index b3e22462c..bb7e8f422 100644 --- a/acir/src/circuit/mod.rs +++ b/acir/src/circuit/mod.rs @@ -184,7 +184,7 @@ mod test { opcodes::{BlackBoxFuncCall, FunctionInput}, Circuit, Opcode, PublicInputs, }; - use crate::native_types::Witness; + use crate::native_types::{Expression, Witness}; use acir_field::FieldElement; fn and_opcode() -> Opcode { @@ -204,12 +204,23 @@ mod test { outputs: vec![], }) } + fn oracle_opcode() -> Opcode { + Opcode::Oracle { + name: String::from("oracle-name"), + inputs: vec![Expression { + mul_terms: vec![(FieldElement::from(123u128), Witness(1), Witness(2))], + linear_combinations: vec![(FieldElement::from(456u128), Witness(34))], + q_c: FieldElement::from(12345678u128), + }], + outputs: vec![Witness(1), Witness(2), Witness(3)], + } + } #[test] fn serialization_roundtrip() { let circuit = Circuit { current_witness_index: 5, - opcodes: vec![and_opcode(), range_opcode()], + opcodes: vec![and_opcode(), range_opcode(), oracle_opcode()], public_parameters: PublicInputs(BTreeSet::from_iter(vec![Witness(2), Witness(12)])), return_values: PublicInputs(BTreeSet::from_iter(vec![Witness(4), Witness(12)])), }; @@ -237,6 +248,7 @@ mod test { }), range_opcode(), and_opcode(), + oracle_opcode(), ], public_parameters: PublicInputs(BTreeSet::from_iter(vec![Witness(2)])), return_values: PublicInputs(BTreeSet::from_iter(vec![Witness(2)])), @@ -261,6 +273,7 @@ mod test { }), range_opcode(), and_opcode(), + oracle_opcode(), ], public_parameters: PublicInputs(BTreeSet::from_iter(vec![Witness(2)])), return_values: PublicInputs(BTreeSet::from_iter(vec![Witness(2)])), diff --git a/acir/src/circuit/opcodes.rs b/acir/src/circuit/opcodes.rs index 31d06236b..85bcbb3f1 100644 --- a/acir/src/circuit/opcodes.rs +++ b/acir/src/circuit/opcodes.rs @@ -2,7 +2,9 @@ use std::io::{Read, Write}; use super::directives::{Directive, LogInfo}; use crate::native_types::{Expression, Witness}; -use crate::serialization::{read_n, read_u16, read_u32, write_bytes, write_u16, write_u32}; +use crate::serialization::{ + read_bytes, read_n, read_u16, read_u32, write_bytes, write_u16, write_u32, +}; use crate::BlackBoxFunc; use serde::{Deserialize, Serialize}; @@ -25,6 +27,7 @@ pub enum Opcode { Directive(Directive), // Abstract read/write operations on a block of data Block(BlockId, Vec), + Oracle { name: String, inputs: Vec, outputs: Vec }, } impl Opcode { @@ -36,6 +39,7 @@ impl Opcode { Opcode::Directive(directive) => directive.name(), Opcode::BlackBoxFuncCall(g) => g.name.name(), Opcode::Block(_, _) => "block", + Opcode::Oracle { name, .. } => name, } } @@ -47,6 +51,7 @@ impl Opcode { Opcode::BlackBoxFuncCall(_) => 1, Opcode::Directive(_) => 2, Opcode::Block(_, _) => 3, + Opcode::Oracle { .. } => 4, } } @@ -79,6 +84,25 @@ impl Opcode { } Ok(()) } + Opcode::Oracle { name, inputs, outputs } => { + let name_as_bytes = name.as_bytes(); + let name_len = name_as_bytes.len(); + write_u32(&mut writer, name_len as u32)?; + write_bytes(&mut writer, name_as_bytes)?; + + let inputs_len = inputs.len() as u32; + write_u32(&mut writer, inputs_len)?; + for input in inputs { + input.write(&mut writer)? + } + + let outputs_len = outputs.len() as u32; + write_u32(&mut writer, outputs_len)?; + for output in outputs { + write_u32(&mut writer, output.witness_index())?; + } + Ok(()) + } } } pub fn read(mut reader: R) -> std::io::Result { @@ -112,6 +136,28 @@ impl Opcode { } Ok(Opcode::Block(BlockId(id), trace)) } + 4 => { + let name_len = read_u32(&mut reader)?; + let name_as_bytes = read_bytes(&mut reader, name_len as usize)?; + let name: String = String::from_utf8(name_as_bytes) + .map_err(|_| std::io::Error::from(std::io::ErrorKind::InvalidData))?; + + let inputs_len = read_u32(&mut reader)?; + let mut inputs = Vec::with_capacity(inputs_len as usize); + for _ in 0..inputs_len { + let input = Expression::read(&mut reader)?; + inputs.push(input); + } + + let outputs_len = read_u32(&mut reader)?; + let mut outputs = Vec::with_capacity(outputs_len as usize); + for _ in 0..outputs_len { + let witness_index = read_u32(&mut reader)?; + outputs.push(Witness(witness_index)); + } + + Ok(Opcode::Oracle { name, inputs, outputs }) + } _ => Err(std::io::ErrorKind::InvalidData.into()), } } @@ -190,6 +236,16 @@ impl std::fmt::Display for Opcode { write!(f, "BLOCK ")?; write!(f, "(id: {}, len: {}) ", id.0, trace.len()) } + Opcode::Oracle { name, inputs, outputs } => { + write!(f, "ORACLE: {}", name)?; + write!(f, "Inputs: _{}..._{}", inputs.first().unwrap(), inputs.last().unwrap())?; + write!( + f, + "Outputs: _{}..._{}", + outputs.first().unwrap().witness_index(), + outputs.last().unwrap().witness_index() + ) + } } } } diff --git a/acir/src/serialization.rs b/acir/src/serialization.rs index 4c948e09c..01abcff49 100644 --- a/acir/src/serialization.rs +++ b/acir/src/serialization.rs @@ -20,6 +20,11 @@ pub(crate) fn write_n( pub(crate) fn write_bytes(mut w: W, bytes: &[u8]) -> std::io::Result { w.write(bytes) } +pub(crate) fn read_bytes(mut r: R, num_bytes: usize) -> std::io::Result> { + let mut bytes = vec![0u8; num_bytes]; + r.read_exact(&mut bytes[..])?; + Ok(bytes) +} pub(crate) fn write_u16(w: W, num: u16) -> std::io::Result { let bytes = num.to_le_bytes(); diff --git a/acvm/src/compiler/transformers/fallback.rs b/acvm/src/compiler/transformers/fallback.rs index 29247778c..7ded7a553 100644 --- a/acvm/src/compiler/transformers/fallback.rs +++ b/acvm/src/compiler/transformers/fallback.rs @@ -22,8 +22,12 @@ impl FallbackTransformer { for opcode in acir.opcodes { let bb_func_call = match &opcode { - Opcode::Arithmetic(_) | Opcode::Directive(_) | Opcode::Block(_, _) => { - // directive, arithmetic expression or block are handled by acvm + Opcode::Arithmetic(_) + | Opcode::Directive(_) + | Opcode::Block(_, _) + | Opcode::Oracle { .. } => { + // directive, arithmetic expression or block are handled by acvm + // The oracle opcode is assumed to be supported. acir_supported_opcodes.push(opcode); continue; } diff --git a/acvm/src/lib.rs b/acvm/src/lib.rs index ddcad6a53..a093b3086 100644 --- a/acvm/src/lib.rs +++ b/acvm/src/lib.rs @@ -88,6 +88,9 @@ pub trait PartialWitnessGenerator { Self::solve_directives(initial_witness, directive) } Opcode::Block(id, trace) => blocks.solve(*id, trace, initial_witness), + Opcode::Oracle { .. } => { + todo!("oracle opcode cannot be processed inside solve without extra information") + } }; match resolution { Ok(OpcodeResolution::Solved) => { From 60db12ae35d44059567bc7ec39a03bff5b625caa Mon Sep 17 00:00:00 2001 From: guipublic Date: Tue, 21 Mar 2023 15:30:50 +0000 Subject: [PATCH 02/11] Solver for Oracle opcodes --- acir/src/circuit/opcodes.rs | 172 ++++++++++++++++++++++++------------ acvm/src/lib.rs | 43 ++++++--- acvm/src/pwg.rs | 1 + acvm/src/pwg/oracle.rs | 54 +++++++++++ 4 files changed, 201 insertions(+), 69 deletions(-) create mode 100644 acvm/src/pwg/oracle.rs diff --git a/acir/src/circuit/opcodes.rs b/acir/src/circuit/opcodes.rs index 2b29de53d..2e70c3935 100644 --- a/acir/src/circuit/opcodes.rs +++ b/acir/src/circuit/opcodes.rs @@ -3,7 +3,7 @@ use std::io::{Read, Write}; use super::directives::{Directive, LogInfo}; use crate::native_types::{Expression, Witness}; use crate::serialization::{ - read_bytes, read_n, read_u16, read_u32, write_bytes, write_u16, write_u32, + read_bytes, read_field_element, read_n, read_u16, read_u32, write_bytes, write_u16, write_u32, }; use crate::BlackBoxFunc; use acir_field::FieldElement; @@ -87,6 +87,114 @@ impl MemoryBlock { } } +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct OracleData { + /// Name of the oracle + pub name: String, + /// Inputs + pub inputs: Vec, + /// Input values - they are progressively computed by the pwg + pub input_values: Vec, + /// Output witness + pub outputs: Vec, + /// Output values - they are computed by the (external) oracle once the input_values are kwown + pub output_values: Vec, +} + +impl OracleData { + pub(crate) fn write(&self, mut writer: W) -> std::io::Result<()> { + let name_as_bytes = self.name.as_bytes(); + let name_len = name_as_bytes.len(); + write_u32(&mut writer, name_len as u32)?; + write_bytes(&mut writer, name_as_bytes)?; + + let inputs_len = self.inputs.len() as u32; + write_u32(&mut writer, inputs_len)?; + for input in &self.inputs { + input.write(&mut writer)? + } + + let outputs_len = self.outputs.len() as u32; + write_u32(&mut writer, outputs_len)?; + for output in &self.outputs { + write_u32(&mut writer, output.witness_index())?; + } + + let inputs_len = self.input_values.len() as u32; + write_u32(&mut writer, inputs_len)?; + for input in &self.input_values { + write_bytes(&mut writer, &input.to_be_bytes())?; + } + + let outputs_len = self.output_values.len() as u32; + write_u32(&mut writer, outputs_len)?; + for output in &self.output_values { + write_bytes(&mut writer, &output.to_be_bytes())?; + } + Ok(()) + } + + pub(crate) fn read(mut reader: R) -> std::io::Result { + let name_len = read_u32(&mut reader)?; + let name_as_bytes = read_bytes(&mut reader, name_len as usize)?; + let name: String = String::from_utf8(name_as_bytes) + .map_err(|_| std::io::Error::from(std::io::ErrorKind::InvalidData))?; + + let inputs_len = read_u32(&mut reader)?; + let mut inputs = Vec::with_capacity(inputs_len as usize); + for _ in 0..inputs_len { + let input = Expression::read(&mut reader)?; + inputs.push(input); + } + + let outputs_len = read_u32(&mut reader)?; + let mut outputs = Vec::with_capacity(outputs_len as usize); + for _ in 0..outputs_len { + let witness_index = read_u32(&mut reader)?; + outputs.push(Witness(witness_index)); + } + + const FIELD_ELEMENT_NUM_BYTES: usize = FieldElement::max_num_bytes() as usize; + let inputs_len = read_u32(&mut reader)?; + let mut input_values = Vec::with_capacity(inputs_len as usize); + for _ in 0..inputs_len { + let value = read_field_element::(&mut reader)?; + input_values.push(value); + } + + let outputs_len = read_u32(&mut reader)?; + let mut output_values = Vec::with_capacity(outputs_len as usize); + for _ in 0..outputs_len { + let value = read_field_element::(&mut reader)?; + output_values.push(value); + } + + Ok(OracleData { name, inputs, outputs, input_values, output_values }) + } +} + +impl std::fmt::Display for OracleData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ORACLE: {}", self.name)?; + let solved = if self.input_values.len() == self.inputs.len() { "solved" } else { "" }; + + write!( + f, + "Inputs: _{}..._{}{solved}", + self.inputs.first().unwrap(), + self.inputs.last().unwrap() + )?; + + let solved = if self.output_values.len() == self.outputs.len() { "solved" } else { "" }; + write!( + f, + "Outputs: _{}..._{}{solved}", + self.outputs.first().unwrap().witness_index(), + self.outputs.last().unwrap().witness_index() + ) + } +} + #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Opcode { Arithmetic(Expression), @@ -106,11 +214,7 @@ pub enum Opcode { /// - after MemoryBlock.len, all operations are constant expressions (0 or 1) /// RAM is required for Aztec Backend as dynamic memory implementation in Barrentenberg requires an intialisation phase and can only handle constant values for operations. RAM(MemoryBlock), - Oracle { - name: String, - inputs: Vec, - outputs: Vec, - }, + Oracle(OracleData), } impl Opcode { @@ -124,7 +228,7 @@ impl Opcode { Opcode::Block(_) => "block", Opcode::RAM(_) => "ram", Opcode::ROM(_) => "rom", - Opcode::Oracle { name, .. } => name, + Opcode::Oracle(data) => &data.name, } } @@ -163,25 +267,7 @@ impl Opcode { Opcode::Block(mem_block) | Opcode::ROM(mem_block) | Opcode::RAM(mem_block) => { mem_block.write(writer) } - Opcode::Oracle { name, inputs, outputs } => { - let name_as_bytes = name.as_bytes(); - let name_len = name_as_bytes.len(); - write_u32(&mut writer, name_len as u32)?; - write_bytes(&mut writer, name_as_bytes)?; - - let inputs_len = inputs.len() as u32; - write_u32(&mut writer, inputs_len)?; - for input in inputs { - input.write(&mut writer)? - } - - let outputs_len = outputs.len() as u32; - write_u32(&mut writer, outputs_len)?; - for output in outputs { - write_u32(&mut writer, output.witness_index())?; - } - Ok(()) - } + Opcode::Oracle(data) => data.write(writer), } } pub fn read(mut reader: R) -> std::io::Result { @@ -216,26 +302,8 @@ impl Opcode { Ok(Opcode::RAM(block)) } 6 => { - let name_len = read_u32(&mut reader)?; - let name_as_bytes = read_bytes(&mut reader, name_len as usize)?; - let name: String = String::from_utf8(name_as_bytes) - .map_err(|_| std::io::Error::from(std::io::ErrorKind::InvalidData))?; - - let inputs_len = read_u32(&mut reader)?; - let mut inputs = Vec::with_capacity(inputs_len as usize); - for _ in 0..inputs_len { - let input = Expression::read(&mut reader)?; - inputs.push(input); - } - - let outputs_len = read_u32(&mut reader)?; - let mut outputs = Vec::with_capacity(outputs_len as usize); - for _ in 0..outputs_len { - let witness_index = read_u32(&mut reader)?; - outputs.push(Witness(witness_index)); - } - - Ok(Opcode::Oracle { name, inputs, outputs }) + let data = OracleData::read(reader)?; + Ok(Opcode::Oracle(data)) } _ => Err(std::io::ErrorKind::InvalidData.into()), } @@ -323,15 +391,9 @@ impl std::fmt::Display for Opcode { write!(f, "RAM ")?; write!(f, "(id: {}, len: {}) ", block.id.0, block.trace.len()) } - Opcode::Oracle { name, inputs, outputs } => { - write!(f, "ORACLE: {name}")?; - write!(f, "Inputs: _{}..._{}", inputs.first().unwrap(), inputs.last().unwrap())?; - write!( - f, - "Outputs: _{}..._{}", - outputs.first().unwrap().witness_index(), - outputs.last().unwrap().witness_index() - ) + Opcode::Oracle(data) => { + write!(f, "ORACLE: ")?; + write!(f, "{data}") } } } diff --git a/acvm/src/lib.rs b/acvm/src/lib.rs index b293313d6..680869c4d 100644 --- a/acvm/src/lib.rs +++ b/acvm/src/lib.rs @@ -7,7 +7,7 @@ pub mod compiler; pub mod pwg; -use crate::pwg::arithmetic::ArithmeticSolver; +use crate::pwg::{arithmetic::ArithmeticSolver, oracle::OracleSolver}; use acir::{ circuit::{directives::Directive, opcodes::BlackBoxFuncCall, Circuit, Opcode}, native_types::{Expression, Witness}, @@ -71,14 +71,16 @@ pub trait PartialWitnessGenerator { &self, initial_witness: &mut BTreeMap, mut opcode_to_solve: Vec, - ) -> Result<(), OpcodeResolutionError> { + ) -> Result<(Vec, Vec), OpcodeResolutionError> { let mut unresolved_opcodes: Vec = Vec::new(); let mut blocks = Blocks::default(); - while !opcode_to_solve.is_empty() { + let mut unresolved_oracles = 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 = None; let resolution = match opcode { Opcode::Arithmetic(expr) => ArithmeticSolver::solve(initial_witness, expr), Opcode::BlackBoxFuncCall(bb_func) => { @@ -90,8 +92,11 @@ pub trait PartialWitnessGenerator { Opcode::Block(block) | Opcode::ROM(block) | Opcode::RAM(block) => { blocks.solve(block.id, &block.trace, initial_witness) } - Opcode::Oracle { .. } => { - todo!("oracle opcode cannot be processed inside solve without extra information") + Opcode::Oracle(data) => { + let mut data_clone = data.clone(); + let result = OracleSolver::solve(initial_witness, &mut data_clone)?; + solved_oracle = Some(Opcode::Oracle(data_clone)); + Ok(result) } }; match resolution { @@ -100,17 +105,22 @@ pub trait PartialWitnessGenerator { } Ok(OpcodeResolution::InProgress) => { stalled = false; - unresolved_opcodes.push(opcode.clone()); + unresolved_opcodes.push(solved_oracle.unwrap_or(opcode.clone())); } Ok(OpcodeResolution::Stalled(not_solvable)) => { - if opcode_not_solvable.is_none() { - // we keep track of the first unsolvable opcode - opcode_not_solvable = Some(not_solvable); + // Stalled oracles must be externally re-solved + if let Some(oracle) = solved_oracle { + unresolved_oracles.push(oracle); + } else { + if opcode_not_solvable.is_none() { + // we keep track of the first unsolvable opcode + opcode_not_solvable = Some(not_solvable); + } + // We push those opcodes not solvable to the back as + // it could be because the opcodes are out of order, i.e. this assignment + // relies on a later opcodes' results + unresolved_opcodes.push(opcode.clone()); } - // We push those opcodes not solvable to the back as - // it could be because the opcodes are out of order, i.e. this assignment - // relies on a later opcodes' results - unresolved_opcodes.push(opcode.clone()); } Err(OpcodeResolutionError::OpcodeNotSolvable(_)) => { unreachable!("ICE - Result should have been converted to GateResolution") @@ -118,6 +128,11 @@ pub trait PartialWitnessGenerator { Err(err) => return Err(err), } } + // We are stalled because of an oracle opcode + if stalled && !unresolved_oracles.is_empty() { + return Ok((unresolved_opcodes, unresolved_oracles)); + } + // We are stalled because of an opcode being bad if stalled && !unresolved_opcodes.is_empty() { return Err(OpcodeResolutionError::OpcodeNotSolvable( opcode_not_solvable @@ -126,7 +141,7 @@ pub trait PartialWitnessGenerator { } std::mem::swap(&mut opcode_to_solve, &mut unresolved_opcodes); } - Ok(()) + Ok((Vec::new(), Vec::new())) } fn solve_black_box_function_call( diff --git a/acvm/src/pwg.rs b/acvm/src/pwg.rs index 1d8c66ffa..bf7bbdab5 100644 --- a/acvm/src/pwg.rs +++ b/acvm/src/pwg.rs @@ -17,6 +17,7 @@ pub mod directives; pub mod block; pub mod hash; pub mod logic; +pub mod oracle; pub mod range; pub mod signature; pub mod sorting; diff --git a/acvm/src/pwg/oracle.rs b/acvm/src/pwg/oracle.rs new file mode 100644 index 000000000..4e5e46b0a --- /dev/null +++ b/acvm/src/pwg/oracle.rs @@ -0,0 +1,54 @@ +use std::collections::BTreeMap; + +use acir::{circuit::opcodes::OracleData, native_types::Witness, FieldElement}; + +use crate::{OpcodeNotSolvable, OpcodeResolution, OpcodeResolutionError}; + +use super::{arithmetic::ArithmeticSolver, directives::insert_witness}; + +pub struct OracleSolver; + +#[allow(clippy::enum_variant_names)] +pub enum GateStatus { + GateSatisfied(FieldElement), + GateSolvable(FieldElement, (FieldElement, Witness)), + GateUnsolvable, +} + +impl OracleSolver { + /// Derives the rest of the witness based on the initial low level variables + pub fn solve( + initial_witness: &mut BTreeMap, + data: &mut OracleData, + ) -> Result { + // Set input values + for input in data.inputs.iter().skip(data.input_values.len()) { + let solve = ArithmeticSolver::evaluate(input, initial_witness); + if let Some(value) = solve.to_const() { + data.input_values.push(value); + } else { + break; + } + } + + // If all of the inputs to the oracle have assignments + if data.input_values.len() == data.inputs.len() { + if data.output_values.len() == data.outputs.len() { + for (out, value) in data.outputs.iter().zip(data.output_values.iter()) { + insert_witness(*out, *value, initial_witness)?; + } + Ok(OpcodeResolution::Solved) + } else { + // Missing output values + Ok(OpcodeResolution::Stalled(OpcodeNotSolvable::MissingAssignment( + data.outputs + .first() + .expect("Infallible: if there is not output, the opcode must be solved") + .0, + ))) + } + } else { + Ok(OpcodeResolution::InProgress) + } + } +} From 3984bbd142784636362ffe0bb10f58fc132a54b2 Mon Sep 17 00:00:00 2001 From: guipublic Date: Tue, 21 Mar 2023 15:53:25 +0000 Subject: [PATCH 03/11] invert InProgress/Stalled for Oracles --- acvm/src/lib.rs | 26 +++++++++++++------------- acvm/src/pwg/oracle.rs | 14 +++++++------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/acvm/src/lib.rs b/acvm/src/lib.rs index 680869c4d..429df3505 100644 --- a/acvm/src/lib.rs +++ b/acvm/src/lib.rs @@ -105,31 +105,31 @@ pub trait PartialWitnessGenerator { } Ok(OpcodeResolution::InProgress) => { stalled = false; - unresolved_opcodes.push(solved_oracle.unwrap_or(opcode.clone())); - } - Ok(OpcodeResolution::Stalled(not_solvable)) => { - // Stalled oracles must be externally re-solved + // InProgress Oracles must be externally re-solved if let Some(oracle) = solved_oracle { unresolved_oracles.push(oracle); } else { - if opcode_not_solvable.is_none() { - // we keep track of the first unsolvable opcode - opcode_not_solvable = Some(not_solvable); - } - // We push those opcodes not solvable to the back as - // it could be because the opcodes are out of order, i.e. this assignment - // relies on a later opcodes' results unresolved_opcodes.push(opcode.clone()); } } + Ok(OpcodeResolution::Stalled(not_solvable)) => { + if opcode_not_solvable.is_none() { + // we keep track of the first unsolvable opcode + opcode_not_solvable = Some(not_solvable); + } + // We push those opcodes not solvable to the back as + // it could be because the opcodes are out of order, i.e. this assignment + // relies on a later opcodes' results + unresolved_opcodes.push(solved_oracle.unwrap_or(opcode.clone())); + } Err(OpcodeResolutionError::OpcodeNotSolvable(_)) => { unreachable!("ICE - Result should have been converted to GateResolution") } Err(err) => return Err(err), } } - // We are stalled because of an oracle opcode - if stalled && !unresolved_oracles.is_empty() { + // We have oracles that must be externally resolved + if !unresolved_oracles.is_empty() { return Ok((unresolved_opcodes, unresolved_oracles)); } // We are stalled because of an opcode being bad diff --git a/acvm/src/pwg/oracle.rs b/acvm/src/pwg/oracle.rs index 4e5e46b0a..6958b1e0e 100644 --- a/acvm/src/pwg/oracle.rs +++ b/acvm/src/pwg/oracle.rs @@ -40,15 +40,15 @@ impl OracleSolver { Ok(OpcodeResolution::Solved) } else { // Missing output values - Ok(OpcodeResolution::Stalled(OpcodeNotSolvable::MissingAssignment( - data.outputs - .first() - .expect("Infallible: if there is not output, the opcode must be solved") - .0, - ))) + Ok(OpcodeResolution::InProgress) } } else { - Ok(OpcodeResolution::InProgress) + Ok(OpcodeResolution::Stalled(OpcodeNotSolvable::ExpressionHasTooManyUnknowns( + data.inputs + .last() + .expect("Infallible: cannot reach this point is no inputs") + .clone(), + ))) } } } From 068ee607e4147ad4d3f670d9820436484a83cbbb Mon Sep 17 00:00:00 2001 From: Joss Date: Wed, 22 Mar 2023 10:47:10 +0000 Subject: [PATCH 04/11] chore(acvm) add oracle test --- acir/src/circuit/opcodes.rs | 2 +- acvm/src/lib.rs | 82 +++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/acir/src/circuit/opcodes.rs b/acir/src/circuit/opcodes.rs index 2e70c3935..12ee35c55 100644 --- a/acir/src/circuit/opcodes.rs +++ b/acir/src/circuit/opcodes.rs @@ -97,7 +97,7 @@ pub struct OracleData { pub input_values: Vec, /// Output witness pub outputs: Vec, - /// Output values - they are computed by the (external) oracle once the input_values are kwown + /// Output values - they are computed by the (external) oracle once the input_values are known pub output_values: Vec, } diff --git a/acvm/src/lib.rs b/acvm/src/lib.rs index 429df3505..b0011372d 100644 --- a/acvm/src/lib.rs +++ b/acvm/src/lib.rs @@ -290,3 +290,85 @@ pub fn default_is_opcode_supported( Language::PLONKCSat { .. } => plonk_is_supported, } } + +#[cfg(test)] +mod test { + use std::collections::BTreeMap; + + use acir::{ + circuit::{ + directives::Directive, + opcodes::{BlackBoxFuncCall, OracleData}, + Opcode, + }, + native_types::{Expression, Witness}, + FieldElement, + }; + + use crate::{OpcodeResolution, OpcodeResolutionError, PartialWitnessGenerator}; + + struct StubbedPwg; + + impl PartialWitnessGenerator for StubbedPwg { + fn solve_black_box_function_call( + _initial_witness: &mut BTreeMap, + _func_call: &BlackBoxFuncCall, + ) -> Result { + panic!("Path not trodden by this test") + } + } + + #[test] + fn inversion_oracle_equivalence() { + let initial_witness = BTreeMap::from([(Witness(0), FieldElement::from(2u128))]); + let pwg = StubbedPwg; + + let opcodes_with_inversion_directive = + vec![Opcode::Directive(Directive::Invert { x: Witness(0), result: Witness(1) })]; + let mut witness_assignments_1 = initial_witness.clone(); + let (unsolved_opcodes, unresolved_oracles) = pwg + .solve(&mut witness_assignments_1, opcodes_with_inversion_directive) + .expect("should be solvable"); + assert!(unsolved_opcodes.is_empty(), "should be fully solved"); + assert!(unresolved_oracles.is_empty(), "should have no unresolved oracles"); + let ret_value_1 = + witness_assignments_1.get(&Witness(1)).expect("return value should exist"); + + let opcode_with_inversion_oracle = vec![Opcode::Oracle(OracleData { + name: "invert".into(), + inputs: vec![Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::one(), Witness(0))], + q_c: FieldElement::zero(), + }], + input_values: vec![], + outputs: vec![Witness(1)], + output_values: vec![], + })]; + let mut witness_assignments_2 = BTreeMap::from([(Witness(0), FieldElement::from(2u128))]); + let (unsolved_opcodes, mut unresolved_oracles) = pwg + .solve(&mut witness_assignments_2, opcode_with_inversion_oracle) + .expect("should stall on oracle"); + assert!(unsolved_opcodes.is_empty(), "oracle should be removed"); + assert_eq!(unresolved_oracles.len(), 1, "should have an oracle request"); + let mut oracle_data = match unresolved_oracles.remove(0) { + Opcode::Oracle(oracle_data) => oracle_data, + _ => panic!("Wrong opcode type"), + }; + 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(&unresolved_oracles[..]); + let (unsolved_opcodes, unresolved_oracles) = pwg + .solve(&mut witness_assignments_2, next_opcodes_for_solving) + .expect("should be solvable"); + assert!(unsolved_opcodes.is_empty(), "should be fully solved"); + assert!(unresolved_oracles.is_empty(), "should have no unresolved oracles"); + let ret_value_2 = + witness_assignments_2.get(&Witness(1)).expect("return value should exist"); + + assert_eq!(ret_value_1, ret_value_2, "Solving should be equivalent"); + } +} From 888b7ef89b1fa4d60cdc10615ac076423124808c Mon Sep 17 00:00:00 2001 From: Joss Date: Wed, 22 Mar 2023 14:35:41 +0000 Subject: [PATCH 05/11] chore(acvm) express oracle test as single circuit --- acvm/src/lib.rs | 81 ++++++++++++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 32 deletions(-) diff --git a/acvm/src/lib.rs b/acvm/src/lib.rs index b0011372d..caa8ecb81 100644 --- a/acvm/src/lib.rs +++ b/acvm/src/lib.rs @@ -320,35 +320,56 @@ mod test { #[test] fn inversion_oracle_equivalence() { - let initial_witness = BTreeMap::from([(Witness(0), FieldElement::from(2u128))]); - let pwg = StubbedPwg; + // 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 opcodes = vec![ + Opcode::Oracle(OracleData { + name: "invert".into(), + inputs: vec![Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], + q_c: fe_0, + }], + input_values: vec![], + outputs: vec![w_oracle], + output_values: vec![], + }), + 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 opcodes_with_inversion_directive = - vec![Opcode::Directive(Directive::Invert { x: Witness(0), result: Witness(1) })]; - let mut witness_assignments_1 = initial_witness.clone(); - let (unsolved_opcodes, unresolved_oracles) = pwg - .solve(&mut witness_assignments_1, opcodes_with_inversion_directive) - .expect("should be solvable"); - assert!(unsolved_opcodes.is_empty(), "should be fully solved"); - assert!(unresolved_oracles.is_empty(), "should have no unresolved oracles"); - let ret_value_1 = - witness_assignments_1.get(&Witness(1)).expect("return value should exist"); + let pwg = StubbedPwg; - let opcode_with_inversion_oracle = vec![Opcode::Oracle(OracleData { - name: "invert".into(), - inputs: vec![Expression { - mul_terms: vec![], - linear_combinations: vec![(FieldElement::one(), Witness(0))], - q_c: FieldElement::zero(), - }], - input_values: vec![], - outputs: vec![Witness(1)], - output_values: vec![], - })]; - let mut witness_assignments_2 = BTreeMap::from([(Witness(0), FieldElement::from(2u128))]); - let (unsolved_opcodes, mut unresolved_oracles) = pwg - .solve(&mut witness_assignments_2, opcode_with_inversion_oracle) - .expect("should stall on oracle"); + let mut witness_assignments = BTreeMap::from([ + (Witness(1), FieldElement::from(2u128)), + (Witness(2), FieldElement::from(3u128)), + ]); + let (unsolved_opcodes, mut unresolved_oracles) = + pwg.solve(&mut witness_assignments, opcodes).expect("should stall on oracle"); assert!(unsolved_opcodes.is_empty(), "oracle should be removed"); assert_eq!(unresolved_oracles.len(), 1, "should have an oracle request"); let mut oracle_data = match unresolved_oracles.remove(0) { @@ -362,13 +383,9 @@ mod test { let mut next_opcodes_for_solving = vec![Opcode::Oracle(oracle_data)]; next_opcodes_for_solving.extend_from_slice(&unresolved_oracles[..]); let (unsolved_opcodes, unresolved_oracles) = pwg - .solve(&mut witness_assignments_2, next_opcodes_for_solving) + .solve(&mut witness_assignments, next_opcodes_for_solving) .expect("should be solvable"); assert!(unsolved_opcodes.is_empty(), "should be fully solved"); assert!(unresolved_oracles.is_empty(), "should have no unresolved oracles"); - let ret_value_2 = - witness_assignments_2.get(&Witness(1)).expect("return value should exist"); - - assert_eq!(ret_value_1, ret_value_2, "Solving should be equivalent"); } } From 769352f4cd7a7fde533ecdce8fe21321dee58d0e Mon Sep 17 00:00:00 2001 From: Joss Date: Wed, 22 Mar 2023 15:53:57 +0000 Subject: [PATCH 06/11] chore(acvm) lift Blocks instance for reentry --- acvm/src/lib.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/acvm/src/lib.rs b/acvm/src/lib.rs index caa8ecb81..6ef4949d4 100644 --- a/acvm/src/lib.rs +++ b/acvm/src/lib.rs @@ -70,10 +70,10 @@ pub trait PartialWitnessGenerator { fn solve( &self, initial_witness: &mut BTreeMap, + blocks: &mut Blocks, mut opcode_to_solve: Vec, ) -> Result<(Vec, Vec), OpcodeResolutionError> { let mut unresolved_opcodes: Vec = Vec::new(); - let mut blocks = Blocks::default(); let mut unresolved_oracles = Vec::new(); while !opcode_to_solve.is_empty() || !unresolved_oracles.is_empty() { unresolved_opcodes.clear(); @@ -305,7 +305,9 @@ mod test { FieldElement, }; - use crate::{OpcodeResolution, OpcodeResolutionError, PartialWitnessGenerator}; + use crate::{ + pwg::block::Blocks, OpcodeResolution, OpcodeResolutionError, PartialWitnessGenerator, + }; struct StubbedPwg; @@ -368,8 +370,10 @@ mod test { (Witness(1), FieldElement::from(2u128)), (Witness(2), FieldElement::from(3u128)), ]); - let (unsolved_opcodes, mut unresolved_oracles) = - pwg.solve(&mut witness_assignments, opcodes).expect("should stall on oracle"); + let mut blocks = Blocks::default(); + let (unsolved_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_eq!(unresolved_oracles.len(), 1, "should have an oracle request"); let mut oracle_data = match unresolved_oracles.remove(0) { @@ -383,7 +387,7 @@ mod test { let mut next_opcodes_for_solving = vec![Opcode::Oracle(oracle_data)]; next_opcodes_for_solving.extend_from_slice(&unresolved_oracles[..]); let (unsolved_opcodes, unresolved_oracles) = pwg - .solve(&mut witness_assignments, next_opcodes_for_solving) + .solve(&mut witness_assignments, &mut blocks, next_opcodes_for_solving) .expect("should be solvable"); assert!(unsolved_opcodes.is_empty(), "should be fully solved"); assert!(unresolved_oracles.is_empty(), "should have no unresolved oracles"); From dda5713c54213150f6b2e4f0d591792e2d1ab7e8 Mon Sep 17 00:00:00 2001 From: Joss Date: Wed, 22 Mar 2023 16:54:38 +0000 Subject: [PATCH 07/11] chore(acvm) don't wrap unresolved OracleData --- acvm/src/lib.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/acvm/src/lib.rs b/acvm/src/lib.rs index 6ef4949d4..e25d96939 100644 --- a/acvm/src/lib.rs +++ b/acvm/src/lib.rs @@ -9,7 +9,11 @@ pub mod pwg; use crate::pwg::{arithmetic::ArithmeticSolver, oracle::OracleSolver}; use acir::{ - circuit::{directives::Directive, opcodes::BlackBoxFuncCall, Circuit, Opcode}, + circuit::{ + directives::Directive, + opcodes::{BlackBoxFuncCall, OracleData}, + Circuit, Opcode, + }, native_types::{Expression, Witness}, BlackBoxFunc, }; @@ -72,15 +76,15 @@ pub trait PartialWitnessGenerator { initial_witness: &mut BTreeMap, blocks: &mut Blocks, mut opcode_to_solve: Vec, - ) -> Result<(Vec, Vec), OpcodeResolutionError> { + ) -> Result<(Vec, Vec), OpcodeResolutionError> { let mut unresolved_opcodes: Vec = Vec::new(); - let mut unresolved_oracles = Vec::new(); + let mut unresolved_oracles: Vec = 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 = None; + let mut solved_oracle_data = None; let resolution = match opcode { Opcode::Arithmetic(expr) => ArithmeticSolver::solve(initial_witness, expr), Opcode::BlackBoxFuncCall(bb_func) => { @@ -95,7 +99,7 @@ pub trait PartialWitnessGenerator { Opcode::Oracle(data) => { let mut data_clone = data.clone(); let result = OracleSolver::solve(initial_witness, &mut data_clone)?; - solved_oracle = Some(Opcode::Oracle(data_clone)); + solved_oracle_data = Some(data_clone); Ok(result) } }; @@ -106,7 +110,7 @@ pub trait PartialWitnessGenerator { Ok(OpcodeResolution::InProgress) => { stalled = false; // InProgress Oracles must be externally re-solved - if let Some(oracle) = solved_oracle { + if let Some(oracle) = solved_oracle_data { unresolved_oracles.push(oracle); } else { unresolved_opcodes.push(opcode.clone()); @@ -120,7 +124,10 @@ pub trait PartialWitnessGenerator { // We push those opcodes not solvable to the back as // it could be because the opcodes are out of order, i.e. this assignment // relies on a later opcodes' results - unresolved_opcodes.push(solved_oracle.unwrap_or(opcode.clone())); + unresolved_opcodes.push(match solved_oracle_data { + Some(oracle_data) => Opcode::Oracle(oracle_data), + None => opcode.clone(), + }); } Err(OpcodeResolutionError::OpcodeNotSolvable(_)) => { unreachable!("ICE - Result should have been converted to GateResolution") @@ -376,16 +383,13 @@ mod test { .expect("should stall on oracle"); assert!(unsolved_opcodes.is_empty(), "oracle should be removed"); assert_eq!(unresolved_oracles.len(), 1, "should have an oracle request"); - let mut oracle_data = match unresolved_oracles.remove(0) { - Opcode::Oracle(oracle_data) => oracle_data, - _ => panic!("Wrong opcode type"), - }; + 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(&unresolved_oracles[..]); + next_opcodes_for_solving.extend_from_slice(&unsolved_opcodes[..]); let (unsolved_opcodes, unresolved_oracles) = pwg .solve(&mut witness_assignments, &mut blocks, next_opcodes_for_solving) .expect("should be solvable"); From db7bb931724cdf6bd51fb1fddc26dc25e11b458f Mon Sep 17 00:00:00 2001 From: Joss Date: Thu, 23 Mar 2023 12:01:08 +0000 Subject: [PATCH 08/11] chore(acvm) fix tests --- acir/src/circuit/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/acir/src/circuit/mod.rs b/acir/src/circuit/mod.rs index bb7e8f422..3c5710d2a 100644 --- a/acir/src/circuit/mod.rs +++ b/acir/src/circuit/mod.rs @@ -181,7 +181,7 @@ mod test { use std::collections::BTreeSet; use super::{ - opcodes::{BlackBoxFuncCall, FunctionInput}, + opcodes::{BlackBoxFuncCall, FunctionInput, OracleData}, Circuit, Opcode, PublicInputs, }; use crate::native_types::{Expression, Witness}; @@ -205,15 +205,17 @@ mod test { }) } fn oracle_opcode() -> Opcode { - Opcode::Oracle { + Opcode::Oracle(OracleData { name: String::from("oracle-name"), inputs: vec![Expression { mul_terms: vec![(FieldElement::from(123u128), Witness(1), Witness(2))], linear_combinations: vec![(FieldElement::from(456u128), Witness(34))], q_c: FieldElement::from(12345678u128), }], + input_values: vec![], outputs: vec![Witness(1), Witness(2), Witness(3)], - } + output_values: vec![], + }) } #[test] From ef2f002124dc86758352a8634a9608e12957ee43 Mon Sep 17 00:00:00 2001 From: Joss Date: Thu, 23 Mar 2023 14:47:33 +0000 Subject: [PATCH 09/11] chore(acvm) remove unneeded macro --- acvm/src/pwg/oracle.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/acvm/src/pwg/oracle.rs b/acvm/src/pwg/oracle.rs index 6958b1e0e..60a6fefba 100644 --- a/acvm/src/pwg/oracle.rs +++ b/acvm/src/pwg/oracle.rs @@ -8,7 +8,6 @@ use super::{arithmetic::ArithmeticSolver, directives::insert_witness}; pub struct OracleSolver; -#[allow(clippy::enum_variant_names)] pub enum GateStatus { GateSatisfied(FieldElement), GateSolvable(FieldElement, (FieldElement, Witness)), From 2a77c6f8b2f5be6c68d2683dc80e56ed10c1497a Mon Sep 17 00:00:00 2001 From: Joss Date: Thu, 23 Mar 2023 15:01:58 +0000 Subject: [PATCH 10/11] chore(acvm): remove unused enum --- acvm/src/pwg/oracle.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/acvm/src/pwg/oracle.rs b/acvm/src/pwg/oracle.rs index 60a6fefba..4ab5b300d 100644 --- a/acvm/src/pwg/oracle.rs +++ b/acvm/src/pwg/oracle.rs @@ -8,12 +8,6 @@ use super::{arithmetic::ArithmeticSolver, directives::insert_witness}; pub struct OracleSolver; -pub enum GateStatus { - GateSatisfied(FieldElement), - GateSolvable(FieldElement, (FieldElement, Witness)), - GateUnsolvable, -} - impl OracleSolver { /// Derives the rest of the witness based on the initial low level variables pub fn solve( From 91f6830ed96b16c6fcac2f3ed2ab674f2f8d7060 Mon Sep 17 00:00:00 2001 From: Joss Date: Thu, 23 Mar 2023 15:14:19 +0000 Subject: [PATCH 11/11] chore(acvm): fix typo --- acvm/src/pwg/oracle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acvm/src/pwg/oracle.rs b/acvm/src/pwg/oracle.rs index 4ab5b300d..7d427aac7 100644 --- a/acvm/src/pwg/oracle.rs +++ b/acvm/src/pwg/oracle.rs @@ -39,7 +39,7 @@ impl OracleSolver { Ok(OpcodeResolution::Stalled(OpcodeNotSolvable::ExpressionHasTooManyUnknowns( data.inputs .last() - .expect("Infallible: cannot reach this point is no inputs") + .expect("Infallible: cannot reach this point if no inputs") .clone(), ))) }