diff --git a/Cargo.lock b/Cargo.lock index 5c80a482..a9e4ac65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "acir" -version = "0.10.3" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510b65efd4d20bf266185ce0a5dc7d29bcdd196a6a1835c20908fd88040de76c" +checksum = "084577e67b44c72d1cdfabe286d48adac6f5e0ad441ef134c5c467f4b6eee291" dependencies = [ "acir_field", "flate2", @@ -16,9 +16,9 @@ dependencies = [ [[package]] name = "acir_field" -version = "0.10.3" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f032e710c67fd146caedc8fe1dea6e95f01ab59453e42d59b604a51fef3dfe" +checksum = "a267ef529f4b132293199ecdf8c232ade817f01d916039f2d34562cab39e75e9" dependencies = [ "ark-bn254", "ark-ff", @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "acvm" -version = "0.10.3" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2611266039740ffd1978f23258bd6ce3166c22cf15b8227685c2f3bb20ae2ee0" +checksum = "4e1d6795105b50b13fa0dd1779b5191c4d8e9cd98b357b0b9a0b04a847baacf0" dependencies = [ "acir", "acvm_stdlib", @@ -43,6 +43,7 @@ dependencies = [ "num-bigint", "num-traits", "sha2 0.9.9", + "sha3", "thiserror", ] @@ -60,18 +61,18 @@ dependencies = [ "pkg-config", "reqwest", "rust-embed", - "sha3", "sled", "tempfile", + "thiserror", "tokio", "wasmer", ] [[package]] name = "acvm_stdlib" -version = "0.10.3" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ec51160c66eba75dc15a028a2391675386fd395b3897478d89a386c64a48dd" +checksum = "3131af53d17ac12340c0ff50f8555d8e040321f8078b8ee3cd8846560b6a44a9" dependencies = [ "acir", ] diff --git a/Cargo.toml b/Cargo.toml index 7ae36cb7..1290ce6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,10 +9,10 @@ license = "MIT OR Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -acvm = { version = "0.10.3", features = ["bn254"] } +acvm = { version = "0.11.0", features = ["bn254"] } +thiserror = "1.0.21" blake2 = "0.9.1" -sha3 = "0.9.1" dirs = { version = "3.0", optional = true } reqwest = { version = "0.11.16", optional = true, default-features = false, features = [ "stream", diff --git a/cspell.json b/cspell.json index 56cb8b03..bdc8f643 100644 --- a/cspell.json +++ b/cspell.json @@ -54,6 +54,7 @@ "nixpkgs", "envrc", "subshell", + "thiserror", // In Solidity // "addmod", diff --git a/flake.lock b/flake.lock index 428f4797..8d6445d3 100644 --- a/flake.lock +++ b/flake.lock @@ -10,11 +10,11 @@ ] }, "locked": { - "lastModified": 1683235108, - "narHash": "sha256-2NN/pb9hwM5/qJ5mpJfGso1H3fc2RRaZ4sZhVPvT0Dw=", + "lastModified": 1683314474, + "narHash": "sha256-gfHYpOnVTfS+4fhScBhfkB/e5z+jPFCi8zSy+aEh+8s=", "owner": "AztecProtocol", "repo": "barretenberg", - "rev": "e66f1ef38c3c87c223456d8a77878c2bd3d346eb", + "rev": "ad615ee7dc931d3dbea041e47c96b9d8dccebf98", "type": "github" }, "original": { diff --git a/src/acvm_interop/proof_system.rs b/src/acvm_interop/proof_system.rs index 7a88dad0..613bc25f 100644 --- a/src/acvm_interop/proof_system.rs +++ b/src/acvm_interop/proof_system.rs @@ -5,15 +5,20 @@ use std::collections::BTreeMap; use crate::barretenberg_structures::Assignments; use crate::composer::Composer; -use crate::Barretenberg; +use crate::{BackendError, Barretenberg}; impl ProofSystemCompiler for Barretenberg { + type Error = BackendError; + fn np_language(&self) -> Language { Language::PLONKCSat { width: 3 } } - fn get_exact_circuit_size(&self, circuit: &Circuit) -> u32 { - Composer::get_exact_circuit_size(self, &circuit.into()) + fn get_exact_circuit_size(&self, circuit: &Circuit) -> Result { + Ok(Composer::get_exact_circuit_size( + self, + &circuit.try_into()?, + )?) } fn black_box_function_supported(&self, opcode: &BlackBoxFunc) -> bool { @@ -31,17 +36,17 @@ impl ProofSystemCompiler for Barretenberg { | BlackBoxFunc::EcdsaSecp256k1 | BlackBoxFunc::FixedBaseScalarMul => true, - BlackBoxFunc::AES => false, + BlackBoxFunc::AES => false, } } - fn preprocess(&self, circuit: &Circuit) -> (Vec, Vec) { - let constraint_system = &circuit.into(); + fn preprocess(&self, circuit: &Circuit) -> Result<(Vec, Vec), Self::Error> { + let constraint_system = &circuit.try_into()?; - let proving_key = self.compute_proving_key(constraint_system); - let verification_key = self.compute_verification_key(constraint_system, &proving_key); + let proving_key = self.compute_proving_key(constraint_system)?; + let verification_key = self.compute_verification_key(constraint_system, &proving_key)?; - (proving_key, verification_key) + Ok((proving_key, verification_key)) } fn prove_with_pk( @@ -49,10 +54,10 @@ impl ProofSystemCompiler for Barretenberg { circuit: &Circuit, witness_values: BTreeMap, proving_key: &[u8], - ) -> Vec { + ) -> Result, Self::Error> { let assignments = flatten_witness_map(circuit, witness_values); - self.create_proof_with_pk(&circuit.into(), assignments, proving_key) + Ok(self.create_proof_with_pk(&circuit.try_into()?, assignments, proving_key)?) } fn verify_with_vk( @@ -61,18 +66,18 @@ impl ProofSystemCompiler for Barretenberg { public_inputs: BTreeMap, circuit: &Circuit, verification_key: &[u8], - ) -> bool { + ) -> Result { // Unlike when proving, we omit any unassigned witnesses. // Witness values should be ordered by their index but we skip over any indices without an assignment. let flattened_public_inputs: Vec = public_inputs.into_values().collect(); - Composer::verify_with_vk( + Ok(Composer::verify_with_vk( self, - &circuit.into(), + &circuit.try_into()?, proof, flattened_public_inputs.into(), verification_key, - ) + )?) } } diff --git a/src/acvm_interop/pwg.rs b/src/acvm_interop/pwg.rs index 6731f160..3e6df850 100644 --- a/src/acvm_interop/pwg.rs +++ b/src/acvm_interop/pwg.rs @@ -24,7 +24,7 @@ impl PartialWitnessGenerator for Barretenberg { match func_call.name { BlackBoxFunc::SHA256 => hash::sha256(initial_witness, func_call), BlackBoxFunc::Blake2s => hash::blake2s(initial_witness, func_call), - BlackBoxFunc::Keccak256 => keccak256(initial_witness, func_call), + BlackBoxFunc::Keccak256 => hash::keccak256(initial_witness, func_call), BlackBoxFunc::EcdsaSecp256k1 => { signature::ecdsa::secp256k1_prehashed(initial_witness, func_call) } @@ -54,7 +54,10 @@ impl PartialWitnessGenerator for Barretenberg { hash_path?, index, leaf, - ); + ) + .map_err(|err| { + OpcodeResolutionError::BlackBoxFunctionFailed(func_call.name, err.to_string()) + })?; initial_witness.insert(func_call.outputs[0], computed_merkle_root); Ok(OpcodeResolution::Solved) @@ -82,25 +85,66 @@ impl PartialWitnessGenerator for Barretenberg { .copied() .chain(pub_key_y.to_vec()) .collect(); - let pub_key: [u8; 64] = pub_key_bytes.try_into().unwrap(); - - let mut signature = [0u8; 64]; - for (i, sig) in signature.iter_mut().enumerate() { - let _sig_i = inputs_iter.next().unwrap_or_else(|| { - panic!("signature should be 64 bytes long, found only {i} bytes") - }); + let pub_key: [u8; 64] = pub_key_bytes.try_into().map_err(|v: Vec| { + OpcodeResolutionError::BlackBoxFunctionFailed( + func_call.name, + format!("expected pubkey size {} but received {}", 64, v.len()), + ) + })?; + + let mut sig_s: [u8; 32] = [0u8; 32]; + for (i, sig) in sig_s.iter_mut().enumerate() { + let _sig_i = inputs_iter.next().ok_or_else(|| { + OpcodeResolutionError::BlackBoxFunctionFailed( + func_call.name, + format!("sig_s should be 32 bytes long, found only {i} bytes"), + ) + })?; + let sig_i = witness_to_value(initial_witness, _sig_i.witness)?; + *sig = *sig_i.to_be_bytes().last().ok_or_else(|| { + OpcodeResolutionError::BlackBoxFunctionFailed( + func_call.name, + "could not get last bytes".into(), + ) + })?; + } + let mut sig_e: [u8; 32] = [0u8; 32]; + for (i, sig) in sig_e.iter_mut().enumerate() { + let _sig_i = inputs_iter.next().ok_or_else(|| { + OpcodeResolutionError::BlackBoxFunctionFailed( + func_call.name, + format!("sig_e should be 32 bytes long, found only {i} bytes"), + ) + })?; let sig_i = witness_to_value(initial_witness, _sig_i.witness)?; - *sig = *sig_i.to_be_bytes().last().unwrap() + *sig = *sig_i.to_be_bytes().last().ok_or_else(|| { + OpcodeResolutionError::BlackBoxFunctionFailed( + func_call.name, + "could not get last bytes".into(), + ) + })?; } let mut message = Vec::new(); for msg in inputs_iter { let msg_i_field = witness_to_value(initial_witness, msg.witness)?; - let msg_i = *msg_i_field.to_be_bytes().last().unwrap(); + let msg_i = *msg_i_field.to_be_bytes().last().ok_or_else(|| { + OpcodeResolutionError::BlackBoxFunctionFailed( + func_call.name, + "could not get last bytes".into(), + ) + })?; message.push(msg_i); } - let valid_signature = self.verify_signature(pub_key, signature, &message); + let valid_signature = self + .verify_signature(pub_key, sig_s, sig_e, &message) + .map_err(|err| { + OpcodeResolutionError::BlackBoxFunctionFailed( + func_call.name, + err.to_string(), + ) + })?; if !valid_signature { dbg!("signature has failed to verify"); } @@ -122,7 +166,9 @@ impl PartialWitnessGenerator for Barretenberg { .collect(); let scalars: Vec<_> = scalars?.into_iter().cloned().collect(); - let (res_x, res_y) = self.encrypt(scalars); + let (res_x, res_y) = self.encrypt(scalars).map_err(|err| { + OpcodeResolutionError::BlackBoxFunctionFailed(func_call.name, err.to_string()) + })?; initial_witness.insert(func_call.outputs[0], res_x); initial_witness.insert(func_call.outputs[1], res_y); Ok(OpcodeResolution::Solved) @@ -152,7 +198,9 @@ impl PartialWitnessGenerator for Barretenberg { BlackBoxFunc::FixedBaseScalarMul => { let scalar = witness_to_value(initial_witness, func_call.inputs[0].witness)?; - let (pub_x, pub_y) = self.fixed_base(scalar); + let (pub_x, pub_y) = self.fixed_base(scalar).map_err(|err| { + OpcodeResolutionError::BlackBoxFunctionFailed(func_call.name, err.to_string()) + })?; initial_witness.insert(func_call.outputs[0], pub_x); initial_witness.insert(func_call.outputs[1], pub_y); @@ -161,59 +209,3 @@ impl PartialWitnessGenerator for Barretenberg { } } } - -// All of the code below can be removed once we update to acvm 0.11 or greater. -use sha3::Keccak256; -fn keccak256( - initial_witness: &mut BTreeMap, - func_call: &BlackBoxFuncCall, -) -> Result { - let hash = generic_hash_256::(initial_witness, func_call)?; - - for (output_witness, value) in func_call.outputs.iter().zip(hash.iter()) { - insert_value( - output_witness, - FieldElement::from_be_bytes_reduce(&[*value]), - initial_witness, - )?; - } - - Ok(OpcodeResolution::Solved) -} -fn insert_value( - witness: &Witness, - value_to_insert: FieldElement, - initial_witness: &mut BTreeMap, -) -> Result<(), OpcodeResolutionError> { - let optional_old_value = initial_witness.insert(*witness, value_to_insert); - - let old_value = match optional_old_value { - Some(old_value) => old_value, - None => return Ok(()), - }; - - if old_value != value_to_insert { - return Err(OpcodeResolutionError::UnsatisfiedConstrain); - } - - Ok(()) -} -fn generic_hash_256( - initial_witness: &mut BTreeMap, - func_call: &BlackBoxFuncCall, -) -> Result<[u8; 32], OpcodeResolutionError> { - let mut hasher = D::new(); - - // Read witness assignments into hasher. - for input in func_call.inputs.iter() { - let witness = input.witness; - let num_bits = input.num_bits as usize; - - let witness_assignment = witness_to_value(initial_witness, witness)?; - let bytes = witness_assignment.fetch_nearest_bytes(num_bits); - hasher.update(bytes); - } - - let result = hasher.finalize().as_slice().try_into().unwrap(); - Ok(result) -} diff --git a/src/acvm_interop/pwg/merkle.rs b/src/acvm_interop/pwg/merkle.rs index 0bf5ebc1..da85ab59 100644 --- a/src/acvm_interop/pwg/merkle.rs +++ b/src/acvm_interop/pwg/merkle.rs @@ -1,11 +1,13 @@ use acvm::FieldElement; +use crate::Error; + pub(super) fn compute_merkle_root( - hash_func: impl Fn(&FieldElement, &FieldElement) -> FieldElement, + hash_func: impl Fn(&FieldElement, &FieldElement) -> Result, hash_path: Vec<&FieldElement>, index: &FieldElement, leaf: &FieldElement, -) -> FieldElement { +) -> Result { let mut index_bits: Vec = index.bits(); index_bits.reverse(); @@ -14,14 +16,17 @@ pub(super) fn compute_merkle_root( "hash path exceeds max depth of tree" ); index_bits.into_iter().zip(hash_path.into_iter()).fold( - *leaf, - |current_node, (path_bit, path_elem)| { - let (left, right) = if !path_bit { - (¤t_node, path_elem) - } else { - (path_elem, ¤t_node) - }; - hash_func(left, right) + Ok(*leaf), + |current_node, (path_bit, path_elem)| match current_node { + Ok(current_node) => { + let (left, right) = if !path_bit { + (¤t_node, path_elem) + } else { + (path_elem, ¤t_node) + }; + hash_func(left, right) + } + Err(_) => current_node, }, ) } @@ -29,11 +34,12 @@ pub(super) fn compute_merkle_root( #[cfg(test)] mod tests { use crate::merkle::{MerkleTree, MessageHasher}; + use crate::Error; use crate::{pedersen::Pedersen, Barretenberg}; use acvm::FieldElement; #[test] - fn test_check_membership() { + fn test_check_membership() -> Result<(), Error> { struct Test<'a> { // Index of the leaf in the MerkleTree index: &'a str, @@ -49,42 +55,42 @@ mod tests { // Note these test cases are not independent. // i.e. If you update index 0, then this will be saved for the next test let tests = vec![ - Test { - index : "0", - result : true, - message : vec![0;64], - should_update_tree: false, - error_msg : "this should always be true, since the tree is initialized with 64 zeroes" - }, - Test { - index : "0", - result : false, - message : vec![10;64], - should_update_tree: false, - error_msg : "this should be false, since the tree was not updated, however the message which derives the leaf has changed" - }, - Test { - index : "0", - result : true, - message : vec![1;64], - should_update_tree: true, - error_msg : "this should be true, since we are updating the tree" - }, - Test { - index : "0", - result : true, - message : vec![1;64], - should_update_tree: false, - error_msg : "this should be true since the index at 4 has not been changed yet, so it would be [0;64]" - }, - Test { - index : "4", - result : true, - message : vec![0;64], - should_update_tree: false, - error_msg : "this should be true since the index at 4 has not been changed yet, so it would be [0;64]" - }, - ]; + Test { + index : "0", + result : true, + message : vec![0;64], + should_update_tree: false, + error_msg : "this should always be true, since the tree is initialized with 64 zeroes" + }, + Test { + index : "0", + result : false, + message : vec![10;64], + should_update_tree: false, + error_msg : "this should be false, since the tree was not updated, however the message which derives the leaf has changed" + }, + Test { + index : "0", + result : true, + message : vec![1;64], + should_update_tree: true, + error_msg : "this should be true, since we are updating the tree" + }, + Test { + index : "0", + result : true, + message : vec![1;64], + should_update_tree: false, + error_msg : "this should be true since the index at 4 has not been changed yet, so it would be [0;64]" + }, + Test { + index : "4", + result : true, + message : vec![0;64], + should_update_tree: false, + error_msg : "this should be true since the index at 4 has not been changed yet, so it would be [0;64]" + }, + ]; use tempfile::tempdir; let temp_dir = tempdir().unwrap(); @@ -102,7 +108,7 @@ mod tests { let mut root = tree.root(); if test_vector.should_update_tree { - root = tree.update_message(index_as_usize, &test_vector.message); + root = tree.update_message(index_as_usize, &test_vector.message)?; } let hash_path = tree.get_hash_path(index_as_usize); @@ -120,7 +126,7 @@ mod tests { hash_path_ref, &index, &leaf, - ); + )?; let is_leaf_in_tree = root == computed_merkle_root; assert_eq!( @@ -129,11 +135,13 @@ mod tests { test_vector.error_msg ); } + + Ok(()) } // This test uses `update_leaf` directly rather than `update_message` #[test] - fn simple_shield() { + fn simple_shield() -> Result<(), Error> { use tempfile::tempdir; let temp_dir = tempdir().unwrap(); @@ -148,7 +156,7 @@ mod tests { "0x2a5d7253a6ed48462fedb2d350cc768d13956310f54e73a8a47914f34a34c5c4", ) .unwrap(); - let (note_commitment_x, _) = barretenberg.encrypt(vec![pubkey_x, pubkey_y]); + let (note_commitment_x, _) = barretenberg.encrypt(vec![pubkey_x, pubkey_y])?; dbg!(note_commitment_x.to_hex()); let leaf = note_commitment_x; @@ -157,7 +165,7 @@ mod tests { let mut index_bits = index.bits(); index_bits.reverse(); - let root = tree.update_leaf(index_as_usize, leaf); + let root = tree.update_leaf(index_as_usize, leaf)?; let hash_path = tree.get_hash_path(index_as_usize); let mut hash_path_ref = Vec::new(); @@ -173,8 +181,10 @@ mod tests { hash_path_ref, &index, &leaf, - ); + )?; + + assert_eq!(root, computed_merkle_root); - assert_eq!(root, computed_merkle_root) + Ok(()) } } diff --git a/src/acvm_interop/smart_contract.rs b/src/acvm_interop/smart_contract.rs index 5ef524d3..34d64efc 100644 --- a/src/acvm_interop/smart_contract.rs +++ b/src/acvm_interop/smart_contract.rs @@ -1,14 +1,16 @@ use acvm::SmartContract; use crate::crs::G2; -use crate::Barretenberg; +use crate::{BackendError, Barretenberg}; /// Embed the Solidity verifier file const ULTRA_VERIFIER_CONTRACT: &str = include_str!("contract.sol"); #[cfg(feature = "native")] impl SmartContract for Barretenberg { - fn eth_contract_from_vk(&self, verification_key: &[u8]) -> String { + type Error = BackendError; + + fn eth_contract_from_vk(&self, verification_key: &[u8]) -> Result { use std::slice; let g2 = G2::new(); @@ -28,46 +30,46 @@ impl SmartContract for Barretenberg { }; let verification_key_library: String = sc_as_bytes.iter().map(|b| *b as char).collect(); - format!("{verification_key_library}{ULTRA_VERIFIER_CONTRACT}") + Ok(format!( + "{verification_key_library}{ULTRA_VERIFIER_CONTRACT}" + )) } } #[cfg(not(feature = "native"))] impl SmartContract for Barretenberg { - fn eth_contract_from_vk(&self, verification_key: &[u8]) -> String { - use crate::wasm::POINTER_BYTES; + type Error = BackendError; + fn eth_contract_from_vk(&self, verification_key: &[u8]) -> Result { let g2 = G2::new(); - let g2_ptr = self.allocate(&g2.data); - let vk_ptr = self.allocate(verification_key); + let g2_ptr = self.allocate(&g2.data)?; + let vk_ptr = self.allocate(verification_key)?; // The smart contract string is not actually written to this pointer. // `contract_ptr_ptr` is a pointer to a pointer which holds the smart contract string. let contract_ptr_ptr: usize = 0; - let contract_size = self - .call_multiple( - "acir_proofs_get_solidity_verifier", - vec![&g2_ptr, &vk_ptr, &contract_ptr_ptr.into()], - ) - .value(); - let contract_size: usize = contract_size.unwrap_i32() as usize; + let contract_size = self.call_multiple( + "acir_proofs_get_solidity_verifier", + vec![&g2_ptr, &vk_ptr, &contract_ptr_ptr.into()], + )?; // We then need to read the pointer at `contract_ptr_ptr` to get the smart contract's location // and then slice memory again at `contract_ptr_ptr` to get the smart contract string. - let contract_ptr: [u8; POINTER_BYTES] = self.read_memory(contract_ptr_ptr); - let contract_ptr: usize = u32::from_le_bytes(contract_ptr) as usize; + let contract_ptr = self.get_pointer(contract_ptr_ptr); - let sc_as_bytes = self.read_memory_variable_length(contract_ptr, contract_size); + let sc_as_bytes = self.read_memory_variable_length(contract_ptr, contract_size.try_into()?); let verification_key_library: String = sc_as_bytes.iter().map(|b| *b as char).collect(); - format!("{verification_key_library}{ULTRA_VERIFIER_CONTRACT}") + Ok(format!( + "{verification_key_library}{ULTRA_VERIFIER_CONTRACT}" + )) } } #[test] -fn test_smart_contract() { +fn test_smart_contract() -> Result<(), BackendError> { use crate::barretenberg_structures::{Constraint, ConstraintSystem}; use crate::composer::Composer; use crate::Barretenberg; @@ -91,12 +93,14 @@ fn test_smart_contract() { let bb = Barretenberg::new(); - let proving_key = bb.compute_proving_key(&constraint_system); - let verification_key = bb.compute_verification_key(&constraint_system, &proving_key); + let proving_key = bb.compute_proving_key(&constraint_system)?; + let verification_key = bb.compute_verification_key(&constraint_system, &proving_key)?; - let contract = bb.eth_contract_from_vk(&verification_key); + let contract = bb.eth_contract_from_vk(&verification_key)?; assert!(contract.contains("contract BaseUltraVerifier")); assert!(contract.contains("contract UltraVerifier")); assert!(contract.contains("library UltraVerificationKey")); + + Ok(()) } diff --git a/src/barretenberg_structures.rs b/src/barretenberg_structures.rs index ee64814d..ec2334a8 100644 --- a/src/barretenberg_structures.rs +++ b/src/barretenberg_structures.rs @@ -3,6 +3,8 @@ use acvm::acir::native_types::Expression; use acvm::acir::BlackBoxFunc; use acvm::FieldElement; +use crate::Error; + #[derive(Debug, Default, Clone)] pub(crate) struct Assignments(Vec); @@ -643,9 +645,11 @@ impl ConstraintSystem { } } -impl From<&Circuit> for ConstraintSystem { +impl TryFrom<&Circuit> for ConstraintSystem { + type Error = Error; + /// Converts an `IR` into the `StandardFormat` constraint system - fn from(circuit: &Circuit) -> Self { + fn try_from(circuit: &Circuit) -> Result { // Create constraint system let mut constraints: Vec = Vec::new(); let mut range_constraints: Vec = Vec::new(); @@ -730,11 +734,12 @@ impl From<&Circuit> for ConstraintSystem { let mut outputs_iter = gadget_call.outputs.iter(); let mut result = [0i32; 32]; for (i, res) in result.iter_mut().enumerate() { - let out_byte = outputs_iter.next().unwrap_or_else(|| { - panic!( - "missing rest of output. Tried to get byte {i} but failed" + let out_byte = outputs_iter.next().ok_or_else(|| { + Error::MalformedBlackBoxFunc( + gadget_call.name, + format!("Missing rest of output. Tried to get byte {i} but failed"), ) - }); + })?; let out_byte_index = out_byte.witness_index() as i32; *res = out_byte_index @@ -759,11 +764,13 @@ impl From<&Circuit> for ConstraintSystem { let mut outputs_iter = gadget_call.outputs.iter(); let mut result = [0i32; 32]; for (i, res) in result.iter_mut().enumerate() { - let out_byte = outputs_iter.next().unwrap_or_else(|| { - panic!( - "missing rest of output. Tried to get byte {i} but failed" - ) - }); + let out_byte = + outputs_iter.next().ok_or_else(|| { + Error::MalformedBlackBoxFunc( + gadget_call.name, + format!("Missing rest of output. Tried to get byte {i} but failed"), + ) + })?; let out_byte_index = out_byte.witness_index() as i32; *res = out_byte_index @@ -780,15 +787,22 @@ impl From<&Circuit> for ConstraintSystem { // leaf let leaf = { - let leaf_input = inputs_iter - .next() - .expect("missing leaf to check membership for"); + let leaf_input = inputs_iter.next().ok_or_else(|| { + Error::MalformedBlackBoxFunc( + gadget_call.name, + "Missing leaf to check membership for".into(), + ) + })?; leaf_input.witness.witness_index() as i32 }; // index let index = { - let index_input = - inputs_iter.next().expect("missing index for leaf"); + let index_input = inputs_iter.next().ok_or_else(|| { + Error::MalformedBlackBoxFunc( + gadget_call.name, + "Missing index for leaf".into(), + ) + })?; index_input.witness.witness_index() as i32 }; @@ -820,26 +834,35 @@ impl From<&Circuit> for ConstraintSystem { // pub_key_x let public_key_x = { - let pub_key_x = inputs_iter - .next() - .expect("missing `x` component for public key"); + let pub_key_x = inputs_iter.next().ok_or_else(|| { + Error::MalformedBlackBoxFunc( + gadget_call.name, + "Missing `x` component for public key".into(), + ) + })?; pub_key_x.witness.witness_index() as i32 }; // pub_key_y let public_key_y = { - let pub_key_y = inputs_iter - .next() - .expect("missing `y` component for public key"); + let pub_key_y = inputs_iter.next().ok_or_else(|| { + Error::MalformedBlackBoxFunc( + gadget_call.name, + "Missing `y` component for public key".into(), + ) + })?; pub_key_y.witness.witness_index() as i32 }; // signature + let mut signature = [0i32; 64]; for (i, sig) in signature.iter_mut().enumerate() { - let sig_byte = inputs_iter.next().unwrap_or_else(|| { - panic!( - "missing rest of signature. Tried to get byte {i} but failed", - ) - }); + let sig_byte = + inputs_iter.next().ok_or_else(|| { + Error::MalformedBlackBoxFunc( + gadget_call.name, + format!("Missing rest of signature. Tried to get byte {i} but failed"), + ) + })?; let sig_byte_index = sig_byte.witness.witness_index() as i32; *sig = sig_byte_index } @@ -907,7 +930,13 @@ impl From<&Circuit> for ConstraintSystem { // public key x let mut public_key_x = [0i32; 32]; for (i, pkx) in public_key_x.iter_mut().enumerate() { - let x_byte = inputs_iter.next().unwrap_or_else(|| panic!("missing rest of x component for public key. Tried to get byte {i} but failed")); + let x_byte = + inputs_iter.next().ok_or_else(|| { + Error::MalformedBlackBoxFunc( + gadget_call.name, + format!("Missing rest of `x` component for public key. Tried to get byte {i} but failed"), + ) + })?; let x_byte_index = x_byte.witness.witness_index() as i32; *pkx = x_byte_index; } @@ -915,7 +944,13 @@ impl From<&Circuit> for ConstraintSystem { // public key y let mut public_key_y = [0i32; 32]; for (i, pky) in public_key_y.iter_mut().enumerate() { - let y_byte = inputs_iter.next().unwrap_or_else(|| panic!("missing rest of y component for public key. Tried to get byte {i} but failed")); + let y_byte = + inputs_iter.next().ok_or_else(|| { + Error::MalformedBlackBoxFunc( + gadget_call.name, + format!("Missing rest of `y` component for public key. Tried to get byte {i} but failed"), + ) + })?; let y_byte_index = y_byte.witness.witness_index() as i32; *pky = y_byte_index; } @@ -923,11 +958,13 @@ impl From<&Circuit> for ConstraintSystem { // signature let mut signature = [0i32; 64]; for (i, sig) in signature.iter_mut().enumerate() { - let sig_byte = inputs_iter.next().unwrap_or_else(|| { - panic!( - "missing rest of signature. Tried to get byte {i} but failed", - ) - }); + let sig_byte = + inputs_iter.next().ok_or_else(|| { + Error::MalformedBlackBoxFunc( + gadget_call.name, + format!("Missing rest of signature. Tried to get byte {i} but failed"), + ) + })?; let sig_byte_index = sig_byte.witness.witness_index() as i32; *sig = sig_byte_index; } @@ -981,11 +1018,13 @@ impl From<&Circuit> for ConstraintSystem { let mut outputs_iter = gadget_call.outputs.iter(); let mut result = [0i32; 32]; for (i, res) in result.iter_mut().enumerate() { - let out_byte = outputs_iter.next().unwrap_or_else(|| { - panic!( - "missing rest of output. Tried to get byte {i} but failed" - ) - }); + let out_byte = + outputs_iter.next().ok_or_else(|| { + Error::MalformedBlackBoxFunc( + gadget_call.name, + format!("Missing rest of output. Tried to get byte {i} but failed"), + ) + })?; let out_byte_index = out_byte.witness_index() as i32; *res = out_byte_index @@ -997,7 +1036,9 @@ impl From<&Circuit> for ConstraintSystem { keccak_constraints.push(keccak_constraint); } - BlackBoxFunc::AES => panic!("AES has not yet been implemented"), + BlackBoxFunc::AES => { + return Err(Error::UnsupportedBlackBoxFunc(gadget_call.name)) + } }; } Opcode::Directive(_) | Opcode::Oracle(_) => { @@ -1010,7 +1051,7 @@ impl From<&Circuit> for ConstraintSystem { } // Create constraint system - ConstraintSystem { + Ok(ConstraintSystem { var_num: circuit.current_witness_index + 1, // number of witnesses is the witness index + 1; public_inputs: circuit.public_inputs().indices(), logic_constraints, @@ -1025,7 +1066,7 @@ impl From<&Circuit> for ConstraintSystem { hash_to_field_constraints, constraints, fixed_base_scalar_mul_constraints, - } + }) } } diff --git a/src/composer.rs b/src/composer.rs index ce699c12..b3ac376d 100644 --- a/src/composer.rs +++ b/src/composer.rs @@ -1,26 +1,27 @@ use crate::barretenberg_structures::{Assignments, ConstraintSystem}; use crate::crs::{CRS, G2}; -use crate::{Barretenberg, FIELD_BYTES}; +use crate::{Barretenberg, Error, FIELD_BYTES}; const NUM_RESERVED_GATES: u32 = 4; // this must be >= num_roots_cut_out_of_vanishing_polynomial (found under prover settings in barretenberg) pub(crate) trait Composer { - fn get_circuit_size(&self, constraint_system: &ConstraintSystem) -> u32; - fn get_exact_circuit_size(&self, constraint_system: &ConstraintSystem) -> u32; + fn get_circuit_size(&self, constraint_system: &ConstraintSystem) -> Result; - fn compute_proving_key(&self, constraint_system: &ConstraintSystem) -> Vec; + fn get_exact_circuit_size(&self, constraint_system: &ConstraintSystem) -> Result; + + fn compute_proving_key(&self, constraint_system: &ConstraintSystem) -> Result, Error>; fn compute_verification_key( &self, constraint_system: &ConstraintSystem, proving_key: &[u8], - ) -> Vec; + ) -> Result, Error>; fn create_proof_with_pk( &self, constraint_system: &ConstraintSystem, witness: Assignments, proving_key: &[u8], - ) -> Vec; + ) -> Result, Error>; fn verify_with_vk( &self, @@ -30,7 +31,7 @@ pub(crate) trait Composer { proof: &[u8], public_inputs: Assignments, verification_key: &[u8], - ) -> bool; + ) -> Result; } #[cfg(feature = "native")] @@ -45,7 +46,7 @@ impl Composer for Barretenberg { // This method is primarily used to determine how many group // elements we need from the CRS. So using 2^19 on an error // should be an overestimation. - fn get_circuit_size(&self, constraint_system: &ConstraintSystem) -> u32 { + fn get_circuit_size(&self, constraint_system: &ConstraintSystem) -> Result { let cs_buf = constraint_system.to_bytes(); let circuit_size; @@ -57,13 +58,19 @@ impl Composer for Barretenberg { pow2ceil(circuit_size + NUM_RESERVED_GATES) } - fn get_exact_circuit_size(&self, constraint_system: &ConstraintSystem) -> u32 { + fn get_exact_circuit_size(&self, constraint_system: &ConstraintSystem) -> Result { let cs_buf = constraint_system.to_bytes(); - unsafe { barretenberg_sys::composer::get_exact_circuit_size(cs_buf.as_slice().as_ptr()) } + let circuit_size; + unsafe { + circuit_size = + barretenberg_sys::composer::get_exact_circuit_size(cs_buf.as_slice().as_ptr()) + } + + Ok(circuit_size) } - fn compute_proving_key(&self, constraint_system: &ConstraintSystem) -> Vec { + fn compute_proving_key(&self, constraint_system: &ConstraintSystem) -> Result, Error> { let cs_buf = constraint_system.to_bytes(); let mut pk_addr: *mut u8 = std::ptr::null_mut(); @@ -80,19 +87,19 @@ impl Composer for Barretenberg { unsafe { result = Vec::from_raw_parts(pk_addr, pk_size, pk_size); } - result + Ok(result) } fn compute_verification_key( &self, constraint_system: &ConstraintSystem, proving_key: &[u8], - ) -> Vec { - let circuit_size = self.get_circuit_size(constraint_system); + ) -> Result, Error> { + let circuit_size = self.get_circuit_size(constraint_system)?; let CRS { g1_data, g2_data, .. } = CRS::new(circuit_size as usize); - let pippenger_ptr = self.get_pippenger(&g1_data).pointer(); + let pippenger_ptr = self.get_pippenger(&g1_data)?.pointer(); let mut vk_addr: *mut u8 = std::ptr::null_mut(); let vk_ptr = &mut vk_addr as *mut *mut u8; @@ -115,7 +122,7 @@ impl Composer for Barretenberg { unsafe { result = Vec::from_raw_parts(vk_addr, vk_size, vk_size); } - result.to_vec() + Ok(result.to_vec()) } fn create_proof_with_pk( @@ -123,12 +130,12 @@ impl Composer for Barretenberg { constraint_system: &ConstraintSystem, witness: Assignments, proving_key: &[u8], - ) -> Vec { - let circuit_size = self.get_circuit_size(constraint_system); + ) -> Result, Error> { + let circuit_size = self.get_circuit_size(constraint_system)?; let CRS { g1_data, g2_data, .. } = CRS::new(circuit_size as usize); - let pippenger_ptr = self.get_pippenger(&g1_data).pointer(); + let pippenger_ptr = self.get_pippenger(&g1_data)?.pointer(); let cs_buf: Vec = constraint_system.to_bytes(); let witness_buf = witness.to_bytes(); @@ -160,7 +167,10 @@ impl Composer for Barretenberg { // Barretenberg returns proofs which are prepended with the public inputs. // This behavior is nonstandard so we strip the public inputs from the proof. - remove_public_inputs(constraint_system.public_inputs_size(), &result) + Ok(remove_public_inputs( + constraint_system.public_inputs_size(), + &result, + )) } fn verify_with_vk( @@ -171,7 +181,7 @@ impl Composer for Barretenberg { proof: &[u8], public_inputs: Assignments, verification_key: &[u8], - ) -> bool { + ) -> Result { let g2_data = G2::new().data; // Barretenberg expects public inputs to be prepended onto the proof @@ -189,7 +199,7 @@ impl Composer for Barretenberg { &proof, ); } - verified + Ok(verified) } } @@ -205,41 +215,35 @@ impl Composer for Barretenberg { // This method is primarily used to determine how many group // elements we need from the CRS. So using 2^19 on an error // should be an overestimation. - fn get_circuit_size(&self, constraint_system: &ConstraintSystem) -> u32 { + fn get_circuit_size(&self, constraint_system: &ConstraintSystem) -> Result { let cs_buf = constraint_system.to_bytes(); - let cs_ptr = self.allocate(&cs_buf); + let cs_ptr = self.allocate(&cs_buf)?; - let circuit_size = self - .call("acir_proofs_get_total_circuit_size", &cs_ptr) - .i32(); - let circuit_size = - u32::try_from(circuit_size).expect("circuit cannot have negative number of gates"); + // This doesn't unwrap the result because we need to free even if there is a failure + let circuit_size = self.call("acir_proofs_get_total_circuit_size", &cs_ptr); - self.free(cs_ptr); + self.free(cs_ptr)?; - pow2ceil(circuit_size + NUM_RESERVED_GATES) + let size: u32 = circuit_size?.try_into()?; + + pow2ceil(size + NUM_RESERVED_GATES) } - fn get_exact_circuit_size(&self, constraint_system: &ConstraintSystem) -> u32 { + fn get_exact_circuit_size(&self, constraint_system: &ConstraintSystem) -> Result { let cs_buf = constraint_system.to_bytes(); - let cs_ptr = self.allocate(&cs_buf); + let cs_ptr = self.allocate(&cs_buf)?; - let circuit_size = self - .call("acir_proofs_get_exact_circuit_size", &cs_ptr) - .i32(); - let circuit_size = - u32::try_from(circuit_size).expect("circuit cannot have negative number of gates"); + // This doesn't unwrap the result because we need to free even if there is a failure + let circuit_size = self.call("acir_proofs_get_exact_circuit_size", &cs_ptr); - self.free(cs_ptr); + self.free(cs_ptr)?; - circuit_size + Ok(circuit_size?.try_into()?) } - fn compute_proving_key(&self, constraint_system: &ConstraintSystem) -> Vec { - use super::wasm::POINTER_BYTES; - + fn compute_proving_key(&self, constraint_system: &ConstraintSystem) -> Result, Error> { let cs_buf = constraint_system.to_bytes(); - let cs_ptr = self.allocate(&cs_buf); + let cs_ptr = self.allocate(&cs_buf)?; // The proving key is not actually written to this pointer. // `pk_ptr_ptr` is a pointer to a pointer which holds the proving key. @@ -248,51 +252,43 @@ impl Composer for Barretenberg { let pk_size = self.call_multiple( "acir_proofs_init_proving_key", vec![&cs_ptr, &pk_ptr_ptr.into()], - ); - let pk_size: usize = pk_size.i32() as usize; + )?; // We then need to read the pointer at `pk_ptr_ptr` to get the key's location // and then slice memory again at `pk_ptr` to get the proving key. - let pk_ptr: [u8; POINTER_BYTES] = self.read_memory(pk_ptr_ptr); - let pk_ptr: usize = u32::from_le_bytes(pk_ptr) as usize; + let pk_ptr = self.get_pointer(pk_ptr_ptr); - self.read_memory_variable_length(pk_ptr, pk_size) + Ok(self.read_memory_variable_length(pk_ptr, pk_size.try_into()?)) } fn compute_verification_key( &self, constraint_system: &ConstraintSystem, proving_key: &[u8], - ) -> Vec { - use super::wasm::POINTER_BYTES; - - let circuit_size = self.get_circuit_size(constraint_system); + ) -> Result, Error> { + let circuit_size = self.get_circuit_size(constraint_system)?; let CRS { g1_data, g2_data, .. } = CRS::new(circuit_size as usize); - let pippenger_ptr = self.get_pippenger(&g1_data).pointer(); + let pippenger_ptr = self.get_pippenger(&g1_data)?.pointer(); - let g2_ptr = self.allocate(&g2_data); - let pk_ptr = self.allocate(proving_key); + let g2_ptr = self.allocate(&g2_data)?; + let pk_ptr = self.allocate(proving_key)?; // The verification key is not actually written to this pointer. // `vk_ptr_ptr` is a pointer to a pointer which holds the verification key. let vk_ptr_ptr: usize = 0; - let vk_size = self - .call_multiple( - "acir_proofs_init_verification_key", - vec![&pippenger_ptr, &g2_ptr, &pk_ptr, &vk_ptr_ptr.into()], - ) - .value(); - let vk_size: usize = vk_size.unwrap_i32() as usize; + let vk_size = self.call_multiple( + "acir_proofs_init_verification_key", + vec![&pippenger_ptr, &g2_ptr, &pk_ptr, &vk_ptr_ptr.into()], + )?; // We then need to read the pointer at `vk_ptr_ptr` to get the key's location // and then slice memory again at `vk_ptr` to get the verification key. - let vk_ptr: [u8; POINTER_BYTES] = self.read_memory(vk_ptr_ptr); - let vk_ptr: usize = u32::from_le_bytes(vk_ptr) as usize; + let vk_ptr = self.get_pointer(vk_ptr_ptr); - self.read_memory_variable_length(vk_ptr, vk_size) + Ok(self.read_memory_variable_length(vk_ptr, vk_size.try_into()?)) } fn create_proof_with_pk( @@ -300,51 +296,48 @@ impl Composer for Barretenberg { constraint_system: &ConstraintSystem, witness: Assignments, proving_key: &[u8], - ) -> Vec { - use super::wasm::POINTER_BYTES; - - let circuit_size = self.get_circuit_size(constraint_system); + ) -> Result, Error> { + let circuit_size = self.get_circuit_size(constraint_system)?; let CRS { g1_data, g2_data, .. } = CRS::new(circuit_size as usize); - let pippenger_ptr = self.get_pippenger(&g1_data).pointer(); + let pippenger_ptr = self.get_pippenger(&g1_data)?.pointer(); let cs_buf: Vec = constraint_system.to_bytes(); let witness_buf = witness.to_bytes(); - let cs_ptr = self.allocate(&cs_buf); - let witness_ptr = self.allocate(&witness_buf); - let g2_ptr = self.allocate(&g2_data); - let pk_ptr = self.allocate(proving_key); + let cs_ptr = self.allocate(&cs_buf)?; + let witness_ptr = self.allocate(&witness_buf)?; + let g2_ptr = self.allocate(&g2_data)?; + let pk_ptr = self.allocate(proving_key)?; // The proof data is not actually written to this pointer. // `proof_ptr_ptr` is a pointer to a pointer which holds the proof data. let proof_ptr_ptr: usize = 0; - let proof_size = self - .call_multiple( - "acir_proofs_new_proof", - vec![ - &pippenger_ptr, - &g2_ptr, - &pk_ptr, - &cs_ptr, - &witness_ptr, - &0.into(), - ], - ) - .value(); - let proof_size: usize = proof_size.unwrap_i32() as usize; + let proof_size = self.call_multiple( + "acir_proofs_new_proof", + vec![ + &pippenger_ptr, + &g2_ptr, + &pk_ptr, + &cs_ptr, + &witness_ptr, + &proof_ptr_ptr.into(), + ], + )?; // We then need to read the pointer at `proof_ptr_ptr` to get the proof's location // and then slice memory again at `proof_ptr` to get the proof data. - let proof_ptr: [u8; POINTER_BYTES] = self.read_memory(proof_ptr_ptr); - let proof_ptr: usize = u32::from_le_bytes(proof_ptr) as usize; + let proof_ptr = self.get_pointer(proof_ptr_ptr); - let result = self.read_memory_variable_length(proof_ptr, proof_size); + let result = self.read_memory_variable_length(proof_ptr, proof_size.try_into()?); // Barretenberg returns proofs which are prepended with the public inputs. // This behavior is nonstandard so we strip the public inputs from the proof. - remove_public_inputs(constraint_system.public_inputs_size(), &result) + Ok(remove_public_inputs( + constraint_system.public_inputs_size(), + &result, + )) } fn verify_with_vk( @@ -355,45 +348,40 @@ impl Composer for Barretenberg { proof: &[u8], public_inputs: Assignments, verification_key: &[u8], - ) -> bool { + ) -> Result { let g2_data = G2::new().data; // Barretenberg expects public inputs to be prepended onto the proof let proof = prepend_public_inputs(proof.to_vec(), public_inputs); let cs_buf = constraint_system.to_bytes(); - let cs_ptr = self.allocate(&cs_buf); - let proof_ptr = self.allocate(&proof); - let g2_ptr = self.allocate(&g2_data); - let vk_ptr = self.allocate(verification_key); + let cs_ptr = self.allocate(&cs_buf)?; + let proof_ptr = self.allocate(&proof)?; + let g2_ptr = self.allocate(&g2_data)?; + let vk_ptr = self.allocate(verification_key)?; - let verified = self - .call_multiple( - "acir_proofs_verify_proof", - vec![&g2_ptr, &vk_ptr, &cs_ptr, &proof_ptr, &proof.len().into()], - ) - .value(); + // This doesn't unwrap the result because we need to free even if there is a failure + let verified = self.call_multiple( + "acir_proofs_verify_proof", + vec![&g2_ptr, &vk_ptr, &cs_ptr, &proof_ptr, &proof.len().into()], + ); - self.free(proof_ptr); + self.free(proof_ptr)?; - match verified.unwrap_i32() { - 0 => false, - 1 => true, - _ => panic!("Expected a 1 or a zero for the verification result"), - } + Ok(verified?.try_into()?) } } -fn pow2ceil(v: u32) -> u32 { +fn pow2ceil(v: u32) -> Result { if v > (u32::MAX >> 1) { - panic!("pow2ceil overflow"); - } - - let mut p = 1; - while p < v { - p <<= 1; + Err(Error::Pow2CeilOverflow(v)) + } else { + let mut p = 1; + while p < v { + p <<= 1; + } + Ok(p) } - p } /// Removes the public inputs which are prepended to a proof by Barretenberg. @@ -431,7 +419,7 @@ mod test { }; #[test] - fn test_no_constraints_no_pub_inputs() { + fn test_no_constraints_no_pub_inputs() -> Result<(), Error> { let constraint_system = ConstraintSystem::new(); let case_1 = WitnessResult { @@ -441,11 +429,11 @@ mod test { }; let test_cases = vec![case_1]; - test_composer_with_pk_vk(constraint_system, test_cases); + test_composer_with_pk_vk(constraint_system, test_cases) } #[test] - fn test_a_single_constraint_no_pub_inputs() { + fn test_a_single_constraint_no_pub_inputs() -> Result<(), Error> { let constraint = Constraint { a: 1, b: 2, @@ -498,10 +486,10 @@ mod test { }; let test_cases = vec![case_1, case_2, case_3, case_4, case_5]; - test_composer_with_pk_vk(constraint_system, test_cases); + test_composer_with_pk_vk(constraint_system, test_cases) } #[test] - fn test_a_single_constraint_with_pub_inputs() { + fn test_a_single_constraint_with_pub_inputs() -> Result<(), Error> { let constraint = Constraint { a: 1, b: 2, @@ -570,11 +558,11 @@ mod test { /*case_1,*/ case_2, case_3, /*case_4,*/ case_5, case_6, ]; - test_composer_with_pk_vk(constraint_system, test_cases); + test_composer_with_pk_vk(constraint_system, test_cases) } #[test] - fn test_multiple_constraints() { + fn test_multiple_constraints() -> Result<(), Error> { let constraint = Constraint { a: 1, b: 2, @@ -612,11 +600,11 @@ mod test { result: false, }; - test_composer_with_pk_vk(constraint_system, vec![case_1, case_2]); + test_composer_with_pk_vk(constraint_system, vec![case_1, case_2]) } #[test] - fn test_schnorr_constraints() { + fn test_schnorr_constraints() -> Result<(), Error> { let mut signature_indices = [0i32; 64]; for i in 13..(13 + 64) { signature_indices[i - 13] = i as i32; @@ -689,11 +677,11 @@ mod test { result: true, }; - test_composer_with_pk_vk(constraint_system, vec![case_1]); + test_composer_with_pk_vk(constraint_system, vec![case_1]) } #[test] - fn test_keccak256_constraint() { + fn test_keccak256_constraint() -> Result<(), Error> { let input_value: u128 = 0xbd; let input_index = 1; @@ -740,10 +728,10 @@ mod test { result: true, }; - test_composer_with_pk_vk(constraint_system, vec![case_1]); + test_composer_with_pk_vk(constraint_system, vec![case_1]) } #[test] - fn test_ped_constraints() { + fn test_ped_constraints() -> Result<(), Error> { let constraint = PedersenConstraint { inputs: vec![1, 2], result_x: 3, @@ -792,11 +780,11 @@ mod test { result: true, }; - test_composer_with_pk_vk(constraint_system, vec![case_1]); + test_composer_with_pk_vk(constraint_system, vec![case_1]) } #[test] - fn test_compute_merkle_root_constraint() { + fn test_compute_merkle_root_constraint() -> Result<(), Error> { use tempfile::tempdir; let temp_dir = tempdir().unwrap(); let mut msg_hasher: blake2::Blake2s = MessageHasher::new(); @@ -843,11 +831,11 @@ mod test { result: true, }; - test_composer_with_pk_vk(constraint_system, vec![case_1]); + test_composer_with_pk_vk(constraint_system, vec![case_1]) } #[test] - fn test_logic_constraints() { + fn test_logic_constraints() -> Result<(), Error> { /* * constraints produced by Noir program: * fn main(x : u32, y : pub u32) { @@ -933,7 +921,7 @@ mod test { result: true, }; - test_composer_with_pk_vk(constraint_system, vec![case_1]); + test_composer_with_pk_vk(constraint_system, vec![case_1]) } #[derive(Clone, Debug)] @@ -946,22 +934,23 @@ mod test { fn test_composer_with_pk_vk( constraint_system: ConstraintSystem, test_cases: Vec, - ) { + ) -> Result<(), Error> { let bb = Barretenberg::new(); - let proving_key = bb.compute_proving_key(&constraint_system); - let verification_key = bb.compute_verification_key(&constraint_system, &proving_key); + let proving_key = bb.compute_proving_key(&constraint_system)?; + let verification_key = bb.compute_verification_key(&constraint_system, &proving_key)?; for test_case in test_cases.into_iter() { let proof = - bb.create_proof_with_pk(&constraint_system, test_case.witness, &proving_key); + bb.create_proof_with_pk(&constraint_system, test_case.witness, &proving_key)?; let verified = bb.verify_with_vk( &constraint_system, &proof, test_case.public_inputs, &verification_key, - ); + )?; assert_eq!(verified, test_case.result); } + Ok(()) } } diff --git a/src/lib.rs b/src/lib.rs index 0bca3f4a..0ca43c27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,9 +20,80 @@ mod pippenger; mod scalar_mul; mod schnorr; +use acvm::acir::BlackBoxFunc; +use thiserror::Error; + +#[cfg(feature = "native")] +#[derive(Debug, Error)] +enum FeatureError { + #[error("Could not slice field element")] + FieldElementSlice { + source: std::array::TryFromSliceError, + }, + #[error("Expected a Vec of length {0} but it was {1}")] + FieldToArray(usize, usize), +} + +#[cfg(not(feature = "native"))] +#[derive(Debug, Error)] +enum FeatureError { + #[error("Trying to call {name} resulted in an error")] + FunctionCallFailed { + name: String, + source: wasmer::RuntimeError, + }, + #[error("Could not find function export named {name}")] + InvalidExport { + name: String, + source: wasmer::ExportError, + }, + #[error("No value available when value was expected")] + NoValue, + #[error("Value expected to be i32")] + InvalidI32, + #[error("Could not convert value {value} from i32 to u32")] + InvalidU32 { + value: i32, + source: std::num::TryFromIntError, + }, + #[error("Could not convert value {value} from i32 to usize")] + InvalidUsize { + value: i32, + source: std::num::TryFromIntError, + }, + #[error("Value expected to be 0 or 1 representing a boolean")] + InvalidBool, +} + +#[derive(Debug, Error)] +enum Error { + #[error("The value {0} overflows in the pow2ceil function")] + Pow2CeilOverflow(u32), + + #[error("Malformed Black Box Function: {0} - {1}")] + MalformedBlackBoxFunc(BlackBoxFunc, String), + + #[error("Unsupported Black Box Function: {0}")] + UnsupportedBlackBoxFunc(BlackBoxFunc), + + #[error(transparent)] + FromFeature(#[from] FeatureError), +} + +#[derive(Debug, Error)] +#[error(transparent)] +pub struct BackendError(#[from] Error); + +impl From for BackendError { + fn from(value: FeatureError) -> Self { + value.into() + } +} + /// The number of bytes necessary to store a `FieldElement`. const FIELD_BYTES: usize = 32; +#[derive(Debug)] pub struct Barretenberg { #[cfg(feature = "wasm")] memory: wasmer::Memory, @@ -37,17 +108,18 @@ impl Default for Barretenberg { } #[test] -fn smoke() { +fn smoke() -> Result<(), Error> { use crate::pedersen::Pedersen; let b = Barretenberg::new(); - let (x, y) = b.encrypt(vec![acvm::FieldElement::zero(), acvm::FieldElement::one()]); + let (x, y) = b.encrypt(vec![acvm::FieldElement::zero(), acvm::FieldElement::one()])?; dbg!(x.to_hex(), y.to_hex()); + Ok(()) } #[cfg(feature = "native")] mod native { - use super::Barretenberg; + use super::{Barretenberg, Error, FeatureError}; impl Barretenberg { pub(crate) fn new() -> Barretenberg { @@ -55,12 +127,12 @@ mod native { } } - pub(super) fn field_to_array(f: &acvm::FieldElement) -> [u8; 32] { + pub(super) fn field_to_array(f: &acvm::FieldElement) -> Result<[u8; 32], Error> { let v = f.to_be_bytes(); - let result: [u8; 32] = v.try_into().unwrap_or_else(|v: Vec| { - panic!("Expected a Vec of length {} but it was {}", 32, v.len()) - }); - result + let result: [u8; 32] = v + .try_into() + .map_err(|v: Vec| FeatureError::FieldToArray(32, v.len()))?; + Ok(result) } } @@ -69,7 +141,7 @@ mod wasm { use std::cell::Cell; use wasmer::{imports, Function, Instance, Memory, MemoryType, Module, Store, Value}; - use super::Barretenberg; + use super::{Barretenberg, Error, FeatureError}; /// The number of bytes necessary to represent a pointer to memory inside the wasm. pub(super) const POINTER_BYTES: usize = 4; @@ -107,24 +179,6 @@ mod wasm { #[derive(Debug, Clone)] pub(super) struct WASMValue(Option); - impl WASMValue { - pub(super) fn value(self) -> Value { - self.0.unwrap() - } - - pub(super) fn i32(self) -> i32 { - i32::try_from(self.0.unwrap()).expect("expected an i32 value") - } - - pub(super) fn bool(self) -> bool { - match self.i32() { - 0 => false, - 1 => true, - _ => panic!("expected a boolean value"), - } - } - } - impl From for WASMValue { fn from(value: usize) -> Self { WASMValue(Some(Value::I32(value as i32))) @@ -143,6 +197,56 @@ mod wasm { } } + impl TryFrom for bool { + type Error = FeatureError; + + fn try_from(value: WASMValue) -> Result { + match value.try_into()? { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(FeatureError::InvalidBool), + } + } + } + + impl TryFrom for usize { + type Error = FeatureError; + + fn try_from(value: WASMValue) -> Result { + let value: i32 = value.try_into()?; + value + .try_into() + .map_err(|source| FeatureError::InvalidUsize { value, source }) + } + } + + impl TryFrom for u32 { + type Error = FeatureError; + + fn try_from(value: WASMValue) -> Result { + let value = value.try_into()?; + u32::try_from(value).map_err(|source| FeatureError::InvalidU32 { value, source }) + } + } + + impl TryFrom for i32 { + type Error = FeatureError; + + fn try_from(value: WASMValue) -> Result { + value.0.map_or(Err(FeatureError::NoValue), |val| { + val.i32().ok_or(FeatureError::InvalidI32) + }) + } + } + + impl TryFrom for Value { + type Error = FeatureError; + + fn try_from(value: WASMValue) -> Result { + value.0.ok_or(FeatureError::NoValue) + } + } + impl Barretenberg { /// Transfer bytes to WASM heap pub(super) fn transfer_to_heap(&self, arr: &[u8], offset: usize) { @@ -168,6 +272,7 @@ mod wasm { } } + // TODO: Consider making this Result-returning pub(super) fn read_memory(&self, start: usize) -> [u8; SIZE] { self.read_memory_variable_length(start, SIZE) .try_into() @@ -188,40 +293,61 @@ mod wasm { .collect(); } - pub(super) fn call(&self, name: &str, param: &WASMValue) -> WASMValue { + pub(super) fn get_pointer(&self, ptr_ptr: usize) -> usize { + let ptr: [u8; POINTER_BYTES] = self.read_memory(ptr_ptr); + u32::from_le_bytes(ptr) as usize + } + + pub(super) fn call(&self, name: &str, param: &WASMValue) -> Result { self.call_multiple(name, vec![param]) } - pub(super) fn call_multiple(&self, name: &str, params: Vec<&WASMValue>) -> WASMValue { + pub(super) fn call_multiple( + &self, + name: &str, + params: Vec<&WASMValue>, + ) -> Result { // We take in a reference to values, since they do not implement Copy. // We then clone them inside of this function, so that the API does not have a bunch of Clones everywhere - let params: Vec = params - .into_iter() - .map(|param| param.clone().value()) - .collect(); - let func = self.instance.exports.get_function(name).unwrap(); - let option_value = func.call(¶ms).unwrap().first().cloned(); - - WASMValue(option_value) + let mut args: Vec = vec![]; + for param in params.into_iter().cloned() { + args.push(param.try_into()?) + } + let func = self.instance.exports.get_function(name).map_err(|source| { + FeatureError::InvalidExport { + name: name.to_string(), + source, + } + })?; + let boxed_value = + func.call(&args) + .map_err(|source| FeatureError::FunctionCallFailed { + name: name.to_string(), + source, + })?; + let option_value = boxed_value.first().cloned(); + + Ok(WASMValue(option_value)) } /// Creates a pointer and allocates the bytes that the pointer references to, to the heap - pub(super) fn allocate(&self, bytes: &[u8]) -> WASMValue { - let ptr = self.call("bbmalloc", &bytes.len().into()).value(); + pub(super) fn allocate(&self, bytes: &[u8]) -> Result { + let ptr: i32 = self.call("bbmalloc", &bytes.len().into())?.try_into()?; - let i32_bytes = ptr.unwrap_i32().to_be_bytes(); + let i32_bytes = ptr.to_be_bytes(); let u32_bytes = u32::from_be_bytes(i32_bytes); self.transfer_to_heap(bytes, u32_bytes as usize); - ptr.into() + Ok(ptr.into()) } /// Frees a pointer. /// Notice we consume the Value, if you clone the value before passing it to free /// It most likely is a bug - pub(super) fn free(&self, pointer: WASMValue) { - self.call("bbfree", &pointer); + pub(super) fn free(&self, pointer: WASMValue) -> Result<(), Error> { + self.call("bbfree", &pointer)?; + Ok(()) } } diff --git a/src/merkle.rs b/src/merkle.rs index 7558ee8c..395610c4 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -1,16 +1,17 @@ +// TODO(#166): Rework this module to return results use acvm::FieldElement; use std::{convert::TryInto, path::Path}; -use crate::{pedersen::Pedersen, Barretenberg}; +use crate::{pedersen::Pedersen, Barretenberg, Error}; // Hashes the leaves up the path, on the way to the root pub(crate) trait PathHasher { fn new() -> Self; - fn hash(&self, left: &FieldElement, right: &FieldElement) -> FieldElement; + fn hash(&self, left: &FieldElement, right: &FieldElement) -> Result; } impl PathHasher for Barretenberg { - fn hash(&self, left: &FieldElement, right: &FieldElement) -> FieldElement { + fn hash(&self, left: &FieldElement, right: &FieldElement) -> Result { self.compress_native(left, right) } @@ -202,7 +203,7 @@ impl MerkleTree { for i in 0..layer_size { hashes[offset + i] = current; } - current = barretenberg.hash(¤t, ¤t); + current = barretenberg.hash(¤t, ¤t).unwrap(); offset += layer_size; layer_size /= 2; @@ -248,7 +249,11 @@ impl MerkleTree { path } /// Updates the message at index and computes the new tree root - pub(crate) fn update_message(&mut self, index: usize, new_message: &[u8]) -> FieldElement { + pub(crate) fn update_message( + &mut self, + index: usize, + new_message: &[u8], + ) -> Result { let current = self.msg_hasher.hash(new_message); insert_preimage(&mut self.db, index as u32, new_message.to_vec()); @@ -285,7 +290,7 @@ impl MerkleTree { &mut self, mut index: usize, mut current: FieldElement, - ) -> FieldElement { + ) -> Result { // Note that this method does not update the list of messages [preimages]| // use `update_message` to do this self.check_if_index_valid_and_increment(index); @@ -299,7 +304,7 @@ impl MerkleTree { current = self.barretenberg.hash( &fetch_hash(&self.db, offset + index), &fetch_hash(&self.db, offset + index + 1), - ); + )?; offset += layer_size as usize; layer_size /= 2; @@ -307,7 +312,7 @@ impl MerkleTree { } insert_root(&mut self.db, current); - current + Ok(current) } #[allow(dead_code)] @@ -368,20 +373,20 @@ fn basic_interop_hashpath() { } #[test] -fn basic_interop_update() { +fn basic_interop_update() -> Result<(), Error> { // Test that computing the HashPath is correct use tempfile::tempdir; let temp_dir = tempdir().unwrap(); let mut tree: MerkleTree = MerkleTree::new(3, &temp_dir); - tree.update_message(0, &[0; 64]); - tree.update_message(1, &[1; 64]); - tree.update_message(2, &[2; 64]); - tree.update_message(3, &[3; 64]); - tree.update_message(4, &[4; 64]); - tree.update_message(5, &[5; 64]); - tree.update_message(6, &[6; 64]); - let root = tree.update_message(7, &[7; 64]); + tree.update_message(0, &[0; 64])?; + tree.update_message(1, &[1; 64])?; + tree.update_message(2, &[2; 64])?; + tree.update_message(3, &[3; 64])?; + tree.update_message(4, &[4; 64])?; + tree.update_message(5, &[5; 64])?; + tree.update_message(6, &[6; 64])?; + let root = tree.update_message(7, &[7; 64])?; assert_eq!( "0ef8e14db4762ebddadb23b2225f93ca200a4c9bd37130b4d028c971bbad16b5", @@ -409,4 +414,6 @@ fn basic_interop_update() { assert_eq!(got.0.to_hex().as_str(), expected_segment.0); assert_eq!(got.1.to_hex().as_str(), expected_segment.1) } + + Ok(()) } diff --git a/src/pedersen.rs b/src/pedersen.rs index 6526c8aa..1297c4e9 100644 --- a/src/pedersen.rs +++ b/src/pedersen.rs @@ -1,56 +1,77 @@ use acvm::FieldElement; -use super::Barretenberg; +use super::{Barretenberg, Error}; pub(crate) trait Pedersen { - fn compress_native(&self, left: &FieldElement, right: &FieldElement) -> FieldElement; - fn compress_many(&self, inputs: Vec) -> FieldElement; - fn encrypt(&self, inputs: Vec) -> (FieldElement, FieldElement); + fn compress_native( + &self, + left: &FieldElement, + right: &FieldElement, + ) -> Result; + fn compress_many(&self, inputs: Vec) -> Result; + fn encrypt(&self, inputs: Vec) -> Result<(FieldElement, FieldElement), Error>; } #[cfg(feature = "native")] impl Pedersen for Barretenberg { - fn compress_native(&self, left: &FieldElement, right: &FieldElement) -> FieldElement { + fn compress_native( + &self, + left: &FieldElement, + right: &FieldElement, + ) -> Result { + use super::FeatureError; + let result_bytes = barretenberg_sys::pedersen::compress_native( - left.to_be_bytes().as_slice().try_into().unwrap(), - right.to_be_bytes().as_slice().try_into().unwrap(), + left.to_be_bytes() + .as_slice() + .try_into() + .map_err(|source| FeatureError::FieldElementSlice { source })?, + right + .to_be_bytes() + .as_slice() + .try_into() + .map_err(|source| FeatureError::FieldElementSlice { source })?, ); - FieldElement::from_be_bytes_reduce(&result_bytes) + Ok(FieldElement::from_be_bytes_reduce(&result_bytes)) } #[allow(dead_code)] - fn compress_many(&self, inputs: Vec) -> FieldElement { + fn compress_many(&self, inputs: Vec) -> Result { use super::native::field_to_array; let mut inputs_buf = Vec::new(); for f in inputs { - inputs_buf.push(field_to_array(&f)); + inputs_buf.push(field_to_array(&f)?); } let result_bytes = barretenberg_sys::pedersen::compress_many(&inputs_buf); - FieldElement::from_be_bytes_reduce(&result_bytes) + Ok(FieldElement::from_be_bytes_reduce(&result_bytes)) } - fn encrypt(&self, inputs: Vec) -> (FieldElement, FieldElement) { + fn encrypt(&self, inputs: Vec) -> Result<(FieldElement, FieldElement), Error> { use super::native::field_to_array; let mut inputs_buf = Vec::new(); for f in inputs { - inputs_buf.push(field_to_array(&f)); + inputs_buf.push(field_to_array(&f)?); } let (point_x_bytes, point_y_bytes) = barretenberg_sys::pedersen::encrypt(&inputs_buf); let point_x = FieldElement::from_be_bytes_reduce(&point_x_bytes); let point_y = FieldElement::from_be_bytes_reduce(&point_y_bytes); - (point_x, point_y) + Ok((point_x, point_y)) } } #[cfg(not(feature = "native"))] impl Pedersen for Barretenberg { - fn compress_native(&self, left: &FieldElement, right: &FieldElement) -> FieldElement { + fn compress_native( + &self, + left: &FieldElement, + right: &FieldElement, + ) -> Result { use super::FIELD_BYTES; let lhs_ptr: usize = 0; @@ -63,42 +84,42 @@ impl Pedersen for Barretenberg { self.call_multiple( "pedersen_plookup_compress_fields", vec![&lhs_ptr.into(), &rhs_ptr.into(), &result_ptr.into()], - ); + )?; let result_bytes: [u8; FIELD_BYTES] = self.read_memory(result_ptr); - FieldElement::from_be_bytes_reduce(&result_bytes) + Ok(FieldElement::from_be_bytes_reduce(&result_bytes)) } #[allow(dead_code)] - fn compress_many(&self, inputs: Vec) -> FieldElement { + fn compress_many(&self, inputs: Vec) -> Result { use super::FIELD_BYTES; use crate::barretenberg_structures::Assignments; let input_buf = Assignments::from(inputs).to_bytes(); - let input_ptr = self.allocate(&input_buf); + let input_ptr = self.allocate(&input_buf)?; let result_ptr: usize = 0; self.call_multiple( "pedersen_plookup_compress", vec![&input_ptr, &result_ptr.into()], - ); + )?; let result_bytes: [u8; FIELD_BYTES] = self.read_memory(result_ptr); - FieldElement::from_be_bytes_reduce(&result_bytes) + Ok(FieldElement::from_be_bytes_reduce(&result_bytes)) } - fn encrypt(&self, inputs: Vec) -> (FieldElement, FieldElement) { + fn encrypt(&self, inputs: Vec) -> Result<(FieldElement, FieldElement), Error> { use super::FIELD_BYTES; use crate::barretenberg_structures::Assignments; let input_buf = Assignments::from(inputs).to_bytes(); - let input_ptr = self.allocate(&input_buf); + let input_ptr = self.allocate(&input_buf)?; let result_ptr: usize = 0; self.call_multiple( "pedersen_plookup_commit", vec![&input_ptr, &result_ptr.into()], - ); + )?; let result_bytes: [u8; 2 * FIELD_BYTES] = self.read_memory(result_ptr); let (point_x_bytes, point_y_bytes) = result_bytes.split_at(FIELD_BYTES); @@ -106,12 +127,12 @@ impl Pedersen for Barretenberg { let point_x = FieldElement::from_be_bytes_reduce(point_x_bytes); let point_y = FieldElement::from_be_bytes_reduce(point_y_bytes); - (point_x, point_y) + Ok((point_x, point_y)) } } #[test] -fn basic_interop() { +fn basic_interop() -> Result<(), Error> { // Expected values were taken from Barretenberg by running `crypto::pedersen::compress_native` // printing the result in hex to `std::cout` and copying struct Test<'a> { @@ -142,17 +163,18 @@ fn basic_interop() { for test in tests { let expected = FieldElement::from_hex(test.expected_hex).unwrap(); - let got = barretenberg.compress_native(&test.input_left, &test.input_right); - let got_many = barretenberg.compress_many(vec![test.input_left, test.input_right]); + let got = barretenberg.compress_native(&test.input_left, &test.input_right)?; + let got_many = barretenberg.compress_many(vec![test.input_left, test.input_right])?; assert_eq!(got, expected); assert_eq!(got, got_many); } + Ok(()) } #[test] -fn pedersen_hash_to_point() { +fn pedersen_hash_to_point() -> Result<(), Error> { let barretenberg = Barretenberg::new(); - let (x, y) = barretenberg.encrypt(vec![FieldElement::zero(), FieldElement::one()]); + let (x, y) = barretenberg.encrypt(vec![FieldElement::zero(), FieldElement::one()])?; let expected_x = FieldElement::from_hex( "0x11831f49876c313f2a9ec6d8d521c7ce0b6311c852117e340bfe27fd1ac096ef", ) @@ -164,4 +186,5 @@ fn pedersen_hash_to_point() { assert_eq!(expected_x.to_hex(), x.to_hex()); assert_eq!(expected_y.to_hex(), y.to_hex()); + Ok(()) } diff --git a/src/pippenger.rs b/src/pippenger.rs index 66e05fa8..a0aa3e14 100644 --- a/src/pippenger.rs +++ b/src/pippenger.rs @@ -1,4 +1,4 @@ -use crate::Barretenberg; +use crate::{Barretenberg, Error}; pub(crate) struct Pippenger { #[cfg(feature = "native")] @@ -23,26 +23,29 @@ impl Pippenger { #[cfg(feature = "native")] impl Barretenberg { - pub(crate) fn get_pippenger(&self, crs_data: &[u8]) -> Pippenger { + pub(crate) fn get_pippenger(&self, crs_data: &[u8]) -> Result { let pippenger_ptr = barretenberg_sys::pippenger::new(crs_data); - Pippenger { pippenger_ptr } + Ok(Pippenger { pippenger_ptr }) } } #[cfg(not(feature = "native"))] impl Barretenberg { - pub(crate) fn get_pippenger(&self, crs_data: &[u8]) -> Pippenger { + pub(crate) fn get_pippenger(&self, crs_data: &[u8]) -> Result { use super::FIELD_BYTES; let num_points = crs_data.len() / (2 * FIELD_BYTES); - let crs_ptr = self.allocate(crs_data); + let crs_ptr = self.allocate(crs_data)?; + // This doesn't unwrap the result because we need to free even if there is a failure let pippenger_ptr = self.call_multiple("new_pippenger", vec![&crs_ptr, &num_points.into()]); - self.free(crs_ptr); + self.free(crs_ptr)?; - Pippenger { pippenger_ptr } + Ok(Pippenger { + pippenger_ptr: pippenger_ptr?, + }) } } diff --git a/src/scalar_mul.rs b/src/scalar_mul.rs index 838c957d..af5e10ea 100644 --- a/src/scalar_mul.rs +++ b/src/scalar_mul.rs @@ -1,17 +1,17 @@ use acvm::FieldElement; -use super::{Barretenberg, FIELD_BYTES}; +use super::{Barretenberg, Error, FIELD_BYTES}; pub(crate) trait ScalarMul { - fn fixed_base(&self, input: &FieldElement) -> (FieldElement, FieldElement); + fn fixed_base(&self, input: &FieldElement) -> Result<(FieldElement, FieldElement), Error>; } #[cfg(feature = "native")] impl ScalarMul for Barretenberg { - fn fixed_base(&self, input: &FieldElement) -> (FieldElement, FieldElement) { + fn fixed_base(&self, input: &FieldElement) -> Result<(FieldElement, FieldElement), Error> { use super::native::field_to_array; - let result_bytes = barretenberg_sys::schnorr::construct_public_key(&field_to_array(input)); + let result_bytes = barretenberg_sys::schnorr::construct_public_key(&field_to_array(input)?); let (pubkey_x_bytes, pubkey_y_bytes) = result_bytes.split_at(FIELD_BYTES); assert!(pubkey_x_bytes.len() == FIELD_BYTES); @@ -19,13 +19,13 @@ impl ScalarMul for Barretenberg { let pubkey_x = FieldElement::from_be_bytes_reduce(pubkey_x_bytes); let pubkey_y = FieldElement::from_be_bytes_reduce(pubkey_y_bytes); - (pubkey_x, pubkey_y) + Ok((pubkey_x, pubkey_y)) } } #[cfg(not(feature = "native"))] impl ScalarMul for Barretenberg { - fn fixed_base(&self, input: &FieldElement) -> (FieldElement, FieldElement) { + fn fixed_base(&self, input: &FieldElement) -> Result<(FieldElement, FieldElement), Error> { let lhs_ptr: usize = 0; let result_ptr: usize = lhs_ptr + FIELD_BYTES; self.transfer_to_heap(&input.to_be_bytes(), lhs_ptr); @@ -33,7 +33,7 @@ impl ScalarMul for Barretenberg { self.call_multiple( "compute_public_key", vec![&lhs_ptr.into(), &result_ptr.into()], - ); + )?; let result_bytes: [u8; 2 * FIELD_BYTES] = self.read_memory(result_ptr); let (pubkey_x_bytes, pubkey_y_bytes) = result_bytes.split_at(FIELD_BYTES); @@ -43,7 +43,7 @@ impl ScalarMul for Barretenberg { let pubkey_x = FieldElement::from_be_bytes_reduce(pubkey_x_bytes); let pubkey_y = FieldElement::from_be_bytes_reduce(pubkey_y_bytes); - (pubkey_x, pubkey_y) + Ok((pubkey_x, pubkey_y)) } } @@ -51,15 +51,16 @@ impl ScalarMul for Barretenberg { mod test { use super::*; #[test] - fn smoke_test() { + fn smoke_test() -> Result<(), Error> { let barretenberg = Barretenberg::new(); let input = FieldElement::one(); - let res = barretenberg.fixed_base(&input); + let res = barretenberg.fixed_base(&input)?; let x = "0000000000000000000000000000000000000000000000000000000000000001"; let y = "0000000000000002cf135e7506a45d632d270d45f1181294833fc48d823f272c"; assert_eq!(x, res.0.to_hex()); assert_eq!(y, res.1.to_hex()); + Ok(()) } } diff --git a/src/schnorr.rs b/src/schnorr.rs index d58affd8..cc5c7f69 100644 --- a/src/schnorr.rs +++ b/src/schnorr.rs @@ -1,31 +1,50 @@ -use super::Barretenberg; +use super::{Barretenberg, Error}; pub(crate) trait SchnorrSig { - fn construct_signature(&self, message: &[u8], private_key: [u8; 32]) -> [u8; 64]; - fn construct_public_key(&self, private_key: [u8; 32]) -> [u8; 64]; - fn verify_signature(&self, pub_key: [u8; 64], sig: [u8; 64], message: &[u8]) -> bool; + fn construct_signature( + &self, + message: &[u8], + private_key: [u8; 32], + ) -> Result<([u8; 32], [u8; 32]), Error>; + fn construct_public_key(&self, private_key: [u8; 32]) -> Result<[u8; 64], Error>; + fn verify_signature( + &self, + pub_key: [u8; 64], + sig_s: [u8; 32], + sig_e: [u8; 32], + message: &[u8], + ) -> Result; } #[cfg(feature = "native")] impl SchnorrSig for Barretenberg { - fn construct_signature(&self, message: &[u8], private_key: [u8; 32]) -> [u8; 64] { - let (sig_s, sig_e) = barretenberg_sys::schnorr::construct_signature(message, private_key); - - let sig_bytes: [u8; 64] = [sig_s, sig_e].concat().try_into().unwrap(); - sig_bytes + fn construct_signature( + &self, + message: &[u8], + private_key: [u8; 32], + ) -> Result<([u8; 32], [u8; 32]), Error> { + Ok(barretenberg_sys::schnorr::construct_signature( + message, + private_key, + )) } - fn construct_public_key(&self, private_key: [u8; 32]) -> [u8; 64] { - barretenberg_sys::schnorr::construct_public_key(&private_key) + fn construct_public_key(&self, private_key: [u8; 32]) -> Result<[u8; 64], Error> { + Ok(barretenberg_sys::schnorr::construct_public_key( + &private_key, + )) } - fn verify_signature(&self, pub_key: [u8; 64], sig: [u8; 64], message: &[u8]) -> bool { - let (sig_s, sig_e) = sig.split_at(32); - - let sig_s: [u8; 32] = sig_s.try_into().unwrap(); - let sig_e: [u8; 32] = sig_e.try_into().unwrap(); - - barretenberg_sys::schnorr::verify_signature(pub_key, sig_s, sig_e, message) + fn verify_signature( + &self, + pub_key: [u8; 64], + sig_s: [u8; 32], + sig_e: [u8; 32], + message: &[u8], + ) -> Result { + Ok(barretenberg_sys::schnorr::verify_signature( + pub_key, sig_s, sig_e, message, + )) // Note, currently for Barretenberg plonk, if the signature fails // then the whole circuit fails. @@ -34,7 +53,11 @@ impl SchnorrSig for Barretenberg { #[cfg(not(feature = "native"))] impl SchnorrSig for Barretenberg { - fn construct_signature(&self, message: &[u8], private_key: [u8; 32]) -> [u8; 64] { + fn construct_signature( + &self, + message: &[u8], + private_key: [u8; 32], + ) -> Result<([u8; 32], [u8; 32]), Error> { use super::{wasm::WASM_SCRATCH_BYTES, FIELD_BYTES}; let sig_s_ptr: usize = 0; @@ -57,17 +80,16 @@ impl SchnorrSig for Barretenberg { &sig_s_ptr.into(), &sig_e_ptr.into(), ], - ); + )?; let sig_s: [u8; FIELD_BYTES] = self.read_memory(sig_s_ptr); let sig_e: [u8; FIELD_BYTES] = self.read_memory(sig_e_ptr); - let sig_bytes: [u8; 64] = [sig_s, sig_e].concat().try_into().unwrap(); - sig_bytes + Ok((sig_s, sig_e)) } #[allow(dead_code)] - fn construct_public_key(&self, private_key: [u8; 32]) -> [u8; 64] { + fn construct_public_key(&self, private_key: [u8; 32]) -> Result<[u8; 64], Error> { use super::FIELD_BYTES; let private_key_ptr: usize = 0; @@ -78,15 +100,19 @@ impl SchnorrSig for Barretenberg { self.call_multiple( "compute_public_key", vec![&private_key_ptr.into(), &result_ptr.into()], - ); + )?; - self.read_memory(result_ptr) + Ok(self.read_memory(result_ptr)) } - fn verify_signature(&self, pub_key: [u8; 64], sig: [u8; 64], message: &[u8]) -> bool { - use super::{wasm::WASM_SCRATCH_BYTES, FIELD_BYTES}; - - let (sig_s, sig_e) = sig.split_at(FIELD_BYTES); + fn verify_signature( + &self, + pub_key: [u8; 64], + sig_s: [u8; 32], + sig_e: [u8; 32], + message: &[u8], + ) -> Result { + use super::wasm::WASM_SCRATCH_BYTES; let public_key_ptr: usize = 0; let sig_s_ptr: usize = public_key_ptr + pub_key.len(); @@ -98,11 +124,11 @@ impl SchnorrSig for Barretenberg { ); self.transfer_to_heap(&pub_key, public_key_ptr); - self.transfer_to_heap(sig_s, sig_s_ptr); - self.transfer_to_heap(sig_e, sig_e_ptr); + self.transfer_to_heap(&sig_s, sig_s_ptr); + self.transfer_to_heap(&sig_e, sig_e_ptr); self.transfer_to_heap(message, message_ptr); - let wasm_value = self.call_multiple( + let verified = self.call_multiple( "verify_signature", vec![ &message_ptr.into(), @@ -111,43 +137,44 @@ impl SchnorrSig for Barretenberg { &sig_s_ptr.into(), &sig_e_ptr.into(), ], - ); + )?; - wasm_value.bool() // Note, currently for Barretenberg plonk, if the signature fails // then the whole circuit fails. + Ok(verified.try_into()?) } } #[test] -fn basic_interop() { +fn basic_interop() -> Result<(), Error> { let barretenberg = Barretenberg::new(); // First case should pass, standard procedure for Schnorr let private_key = [2; 32]; let message = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - let public_key = barretenberg.construct_public_key(private_key); - let signature = barretenberg.construct_signature(&message, private_key); - let valid_signature = barretenberg.verify_signature(public_key, signature, &message); + let public_key = barretenberg.construct_public_key(private_key)?; + let (sig_s, sig_e) = barretenberg.construct_signature(&message, private_key)?; + let valid_signature = barretenberg.verify_signature(public_key, sig_s, sig_e, &message)?; assert!(valid_signature); // Should fail, since the messages are different let private_key = [2; 32]; let message = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - let public_key = barretenberg.construct_public_key(private_key); - let signature = barretenberg.construct_signature(&message, private_key); - let valid_signature = barretenberg.verify_signature(public_key, signature, &[0, 2]); + let public_key = barretenberg.construct_public_key(private_key)?; + let (sig_s, sig_e) = barretenberg.construct_signature(&message, private_key)?; + let valid_signature = barretenberg.verify_signature(public_key, sig_s, sig_e, &[0, 2])?; assert!(!valid_signature); // Should fail, since the signature is not valid let private_key = [2; 32]; let message = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - let signature = [1; 64]; + let sig_s = [1; 32]; + let sig_e = [1; 32]; - let public_key = barretenberg.construct_public_key(private_key); - let valid_signature = barretenberg.verify_signature(public_key, signature, &message); + let public_key = barretenberg.construct_public_key(private_key)?; + let valid_signature = barretenberg.verify_signature(public_key, sig_s, sig_e, &message)?; assert!(!valid_signature); // Should fail, since the public key does not match @@ -155,17 +182,18 @@ fn basic_interop() { let private_key_b = [2; 32]; let message = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - let public_key_b = barretenberg.construct_public_key(private_key_b); - let signature_a = barretenberg.construct_signature(&message, private_key_a); - let valid_signature = barretenberg.verify_signature(public_key_b, signature_a, &message); + let public_key_b = barretenberg.construct_public_key(private_key_b)?; + let (sig_s, sig_e) = barretenberg.construct_signature(&message, private_key_a)?; + let valid_signature = barretenberg.verify_signature(public_key_b, sig_s, sig_e, &message)?; assert!(!valid_signature); // Test the first case again, to check if memory is being freed and overwritten properly let private_key = [2; 32]; let message = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - let public_key = barretenberg.construct_public_key(private_key); - let signature = barretenberg.construct_signature(&message, private_key); - let valid_signature = barretenberg.verify_signature(public_key, signature, &message); + let public_key = barretenberg.construct_public_key(private_key)?; + let (sig_s, sig_e) = barretenberg.construct_signature(&message, private_key)?; + let valid_signature = barretenberg.verify_signature(public_key, sig_s, sig_e, &message)?; assert!(valid_signature); + Ok(()) }