diff --git a/Cargo.lock b/Cargo.lock index 86df9e1a13b..7fadf637e31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -453,9 +453,11 @@ version = "0.28.0" dependencies = [ "acir", "acvm_blackbox_solver", + "ark-ec", + "ark-ff", "flate2", "getrandom", - "hex", + "grumpkin", "js-sys", "num-bigint", "pkg-config", @@ -1828,6 +1830,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "grumpkin" +version = "0.1.0" +source = "git+https://github.com/noir-lang/grumpkin?rev=56d99799381f79e42148aaef0de2b0cf9a4b9a5d#56d99799381f79e42148aaef0de2b0cf9a4b9a5d" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-std", +] + [[package]] name = "h2" version = "0.3.20" diff --git a/acvm-repo/acir_field/src/generic_ark.rs b/acvm-repo/acir_field/src/generic_ark.rs index 63e0d3d0d98..0f4be21ad54 100644 --- a/acvm-repo/acir_field/src/generic_ark.rs +++ b/acvm-repo/acir_field/src/generic_ark.rs @@ -248,6 +248,10 @@ impl FieldElement { self.0.inverse_in_place().map(|f| FieldElement(*f)) } + pub fn from_repr(field: F) -> Self { + Self(field) + } + // XXX: This method is used while this field element // implementation is not generic. pub fn into_repr(self) -> F { diff --git a/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs b/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs index 975025971dc..582ed56584b 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs @@ -2,11 +2,9 @@ use acir::{ circuit::opcodes::FunctionInput, native_types::{Witness, WitnessMap}, }; +use acvm_blackbox_solver::BlackBoxFunctionSolver; -use crate::{ - pwg::{insert_value, witness_to_value, OpcodeResolutionError}, - BlackBoxFunctionSolver, -}; +use crate::pwg::{insert_value, witness_to_value, OpcodeResolutionError}; pub(super) fn fixed_base_scalar_mul( backend: &impl BlackBoxFunctionSolver, diff --git a/acvm-repo/barretenberg_blackbox_solver/Cargo.toml b/acvm-repo/barretenberg_blackbox_solver/Cargo.toml index acecb24c142..97e58c2804b 100644 --- a/acvm-repo/barretenberg_blackbox_solver/Cargo.toml +++ b/acvm-repo/barretenberg_blackbox_solver/Cargo.toml @@ -16,8 +16,6 @@ repository.workspace = true acir.workspace = true acvm_blackbox_solver.workspace = true thiserror.workspace = true -hex.workspace = true -num-bigint.workspace = true rust-embed = { version = "6.6.0", features = [ "debug-embed", @@ -25,6 +23,12 @@ rust-embed = { version = "6.6.0", features = [ "include-exclude", ] } +# BN254 fixed base scalar multiplication solver +grumpkin = { git = "https://github.com/noir-lang/grumpkin", rev = "56d99799381f79e42148aaef0de2b0cf9a4b9a5d", features = ["std"] } +ark-ec = { version = "^0.4.0", default-features = false } +ark-ff = { version = "^0.4.0", default-features = false } +num-bigint.workspace = true + [target.'cfg(target_arch = "wasm32")'.dependencies] wasmer = { version = "3.3", default-features = false, features = [ "js-default", diff --git a/acvm-repo/barretenberg_blackbox_solver/src/fixed_base_scalar_mul.rs b/acvm-repo/barretenberg_blackbox_solver/src/fixed_base_scalar_mul.rs new file mode 100644 index 00000000000..7f004de0fe9 --- /dev/null +++ b/acvm-repo/barretenberg_blackbox_solver/src/fixed_base_scalar_mul.rs @@ -0,0 +1,115 @@ +use ark_ec::AffineRepr; +use ark_ff::MontConfig; +use num_bigint::BigUint; + +use acir::{BlackBoxFunc, FieldElement}; + +use crate::BlackBoxResolutionError; + +pub fn fixed_base_scalar_mul( + low: &FieldElement, + high: &FieldElement, +) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { + let low: u128 = low.try_into_u128().ok_or_else(|| { + BlackBoxResolutionError::Failed( + BlackBoxFunc::FixedBaseScalarMul, + format!("Limb {} is not less than 2^128", low.to_hex()), + ) + })?; + + let high: u128 = high.try_into_u128().ok_or_else(|| { + BlackBoxResolutionError::Failed( + BlackBoxFunc::FixedBaseScalarMul, + format!("Limb {} is not less than 2^128", high.to_hex()), + ) + })?; + + let mut bytes = high.to_be_bytes().to_vec(); + bytes.extend_from_slice(&low.to_be_bytes()); + + // Check if this is smaller than the grumpkin modulus + let grumpkin_integer = BigUint::from_bytes_be(&bytes); + + if grumpkin_integer >= grumpkin::FrConfig::MODULUS.into() { + return Err(BlackBoxResolutionError::Failed( + BlackBoxFunc::FixedBaseScalarMul, + format!("{} is not a valid grumpkin scalar", grumpkin_integer.to_str_radix(16)), + )); + } + + let result = grumpkin::SWAffine::from( + grumpkin::SWAffine::generator().mul_bigint(grumpkin_integer.to_u64_digits()), + ); + if let Some((res_x, res_y)) = result.xy() { + Ok((FieldElement::from_repr(*res_x), FieldElement::from_repr(*res_y))) + } else { + Ok((FieldElement::zero(), FieldElement::zero())) + } +} + +#[cfg(test)] +mod grumpkin_fixed_base_scalar_mul { + use ark_ff::BigInteger; + + use super::*; + #[test] + fn smoke_test() -> Result<(), BlackBoxResolutionError> { + let input = FieldElement::one(); + + let res = fixed_base_scalar_mul(&input, &FieldElement::zero())?; + let x = "0000000000000000000000000000000000000000000000000000000000000001"; + let y = "0000000000000002cf135e7506a45d632d270d45f1181294833fc48d823f272c"; + + assert_eq!(x, res.0.to_hex()); + assert_eq!(y, res.1.to_hex()); + Ok(()) + } + #[test] + fn low_high_smoke_test() -> Result<(), BlackBoxResolutionError> { + let low = FieldElement::one(); + let high = FieldElement::from(2u128); + + let res = fixed_base_scalar_mul(&low, &high)?; + let x = "0702ab9c7038eeecc179b4f209991bcb68c7cb05bf4c532d804ccac36199c9a9"; + let y = "23f10e9e43a3ae8d75d24154e796aae12ae7af546716e8f81a2564f1b5814130"; + + assert_eq!(x, res.0.to_hex()); + assert_eq!(y, res.1.to_hex()); + Ok(()) + } + + #[test] + fn rejects_invalid_limbs() { + let max_limb = FieldElement::from(u128::MAX); + let invalid_limb = max_limb + FieldElement::one(); + + let expected_error = Err(BlackBoxResolutionError::Failed( + BlackBoxFunc::FixedBaseScalarMul, + "Limb 0000000000000000000000000000000100000000000000000000000000000000 is not less than 2^128".into() + )); + + let res = fixed_base_scalar_mul(&invalid_limb, &FieldElement::zero()); + assert_eq!(res, expected_error); + + let res = fixed_base_scalar_mul(&FieldElement::zero(), &invalid_limb); + assert_eq!(res, expected_error); + } + + #[test] + fn rejects_grumpkin_modulus() { + let x = grumpkin::FrConfig::MODULUS.to_bytes_be(); + + let high = FieldElement::from_be_bytes_reduce(&x[0..16]); + let low = FieldElement::from_be_bytes_reduce(&x[16..32]); + + let res = fixed_base_scalar_mul(&low, &high); + + assert_eq!( + res, + Err(BlackBoxResolutionError::Failed( + BlackBoxFunc::FixedBaseScalarMul, + "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 is not a valid grumpkin scalar".into() + )) + ); + } +} diff --git a/acvm-repo/barretenberg_blackbox_solver/src/lib.rs b/acvm-repo/barretenberg_blackbox_solver/src/lib.rs index c3a1d457897..b9486e97bd9 100644 --- a/acvm-repo/barretenberg_blackbox_solver/src/lib.rs +++ b/acvm-repo/barretenberg_blackbox_solver/src/lib.rs @@ -5,11 +5,13 @@ use acir::{BlackBoxFunc, FieldElement}; use acvm_blackbox_solver::{BlackBoxFunctionSolver, BlackBoxResolutionError}; +mod fixed_base_scalar_mul; mod wasm; +pub use fixed_base_scalar_mul::fixed_base_scalar_mul; use wasm::Barretenberg; -use self::wasm::{Pedersen, ScalarMul, SchnorrSig}; +use self::wasm::{Pedersen, SchnorrSig}; #[deprecated = "The `BarretenbergSolver` is a temporary solution and will be removed in future."] pub struct BarretenbergSolver { @@ -77,9 +79,6 @@ impl BlackBoxFunctionSolver for BarretenbergSolver { low: &FieldElement, high: &FieldElement, ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { - #[allow(deprecated)] - self.blackbox_vendor.fixed_base(low, high).map_err(|err| { - BlackBoxResolutionError::Failed(BlackBoxFunc::FixedBaseScalarMul, err.to_string()) - }) + fixed_base_scalar_mul(low, high) } } diff --git a/acvm-repo/barretenberg_blackbox_solver/src/wasm/mod.rs b/acvm-repo/barretenberg_blackbox_solver/src/wasm/mod.rs index 22520725dca..10b1ab22a8d 100644 --- a/acvm-repo/barretenberg_blackbox_solver/src/wasm/mod.rs +++ b/acvm-repo/barretenberg_blackbox_solver/src/wasm/mod.rs @@ -6,13 +6,11 @@ mod barretenberg_structures; mod pedersen; -mod scalar_mul; mod schnorr; use barretenberg_structures::Assignments; pub(crate) use pedersen::Pedersen; -pub(crate) use scalar_mul::ScalarMul; pub(crate) use schnorr::SchnorrSig; /// The number of bytes necessary to store a `FieldElement`. @@ -34,10 +32,6 @@ pub(crate) enum FeatureError { NoValue, #[error("Value expected to be i32")] InvalidI32, - #[error("Value {scalar_as_hex} is not a valid grumpkin scalar")] - InvalidGrumpkinScalar { scalar_as_hex: String }, - #[error("Limb {limb_as_hex} is not less than 2^128")] - InvalidGrumpkinScalarLimb { limb_as_hex: String }, #[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")] diff --git a/acvm-repo/barretenberg_blackbox_solver/src/wasm/scalar_mul.rs b/acvm-repo/barretenberg_blackbox_solver/src/wasm/scalar_mul.rs deleted file mode 100644 index 71e1701bc30..00000000000 --- a/acvm-repo/barretenberg_blackbox_solver/src/wasm/scalar_mul.rs +++ /dev/null @@ -1,98 +0,0 @@ -use acir::FieldElement; -use num_bigint::BigUint; - -use crate::wasm::FeatureError; - -use super::{Barretenberg, Error, FIELD_BYTES}; - -pub(crate) trait ScalarMul { - fn fixed_base( - &self, - low: &FieldElement, - high: &FieldElement, - ) -> Result<(FieldElement, FieldElement), Error>; -} - -impl ScalarMul for Barretenberg { - fn fixed_base( - &self, - low: &FieldElement, - high: &FieldElement, - ) -> Result<(FieldElement, FieldElement), Error> { - let lhs_ptr: usize = 0; - let result_ptr: usize = lhs_ptr + FIELD_BYTES; - - let low: u128 = low.try_into_u128().ok_or_else(|| { - Error::FromFeature(FeatureError::InvalidGrumpkinScalarLimb { - limb_as_hex: low.to_hex(), - }) - })?; - - let high: u128 = high.try_into_u128().ok_or_else(|| { - Error::FromFeature(FeatureError::InvalidGrumpkinScalarLimb { - limb_as_hex: high.to_hex(), - }) - })?; - - let mut bytes = high.to_be_bytes().to_vec(); - bytes.extend_from_slice(&low.to_be_bytes()); - - // Check if this is smaller than the grumpkin modulus - let grumpkin_integer = BigUint::from_bytes_be(&bytes); - let grumpkin_modulus = BigUint::from_bytes_be(&[ - 48, 100, 78, 114, 225, 49, 160, 41, 184, 80, 69, 182, 129, 129, 88, 93, 151, 129, 106, - 145, 104, 113, 202, 141, 60, 32, 140, 22, 216, 124, 253, 71, - ]); - - if grumpkin_integer >= grumpkin_modulus { - return Err(Error::FromFeature(FeatureError::InvalidGrumpkinScalar { - scalar_as_hex: hex::encode(grumpkin_integer.to_bytes_be()), - })); - } - - self.transfer_to_heap(&bytes, lhs_ptr); - 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); - - assert!(pubkey_x_bytes.len() == FIELD_BYTES); - assert!(pubkey_y_bytes.len() == FIELD_BYTES); - - let pubkey_x = FieldElement::from_be_bytes_reduce(pubkey_x_bytes); - let pubkey_y = FieldElement::from_be_bytes_reduce(pubkey_y_bytes); - Ok((pubkey_x, pubkey_y)) - } -} - -#[cfg(test)] -mod test { - use super::*; - #[test] - fn smoke_test() -> Result<(), Error> { - let barretenberg = Barretenberg::new(); - let input = FieldElement::one(); - - let res = barretenberg.fixed_base(&input, &FieldElement::zero())?; - let x = "0000000000000000000000000000000000000000000000000000000000000001"; - let y = "0000000000000002cf135e7506a45d632d270d45f1181294833fc48d823f272c"; - - assert_eq!(x, res.0.to_hex()); - assert_eq!(y, res.1.to_hex()); - Ok(()) - } - #[test] - fn low_high_smoke_test() -> Result<(), Error> { - let barretenberg = Barretenberg::new(); - let low = FieldElement::one(); - let high = FieldElement::from(2u128); - - let res = barretenberg.fixed_base(&low, &high)?; - let x = "0702ab9c7038eeecc179b4f209991bcb68c7cb05bf4c532d804ccac36199c9a9"; - let y = "23f10e9e43a3ae8d75d24154e796aae12ae7af546716e8f81a2564f1b5814130"; - - assert_eq!(x, res.0.to_hex()); - assert_eq!(y, res.1.to_hex()); - Ok(()) - } -} diff --git a/acvm-repo/blackbox_solver/Cargo.toml b/acvm-repo/blackbox_solver/Cargo.toml index 2e09452c492..f0295456977 100644 --- a/acvm-repo/blackbox_solver/Cargo.toml +++ b/acvm-repo/blackbox_solver/Cargo.toml @@ -34,6 +34,7 @@ p256 = { version = "0.11.0", features = [ "arithmetic", ] } + [features] default = ["bn254"] bn254 = ["acir/bn254"] diff --git a/cspell.json b/cspell.json index a667fc6d7aa..ac7953e0653 100644 --- a/cspell.json +++ b/cspell.json @@ -49,6 +49,7 @@ "fxhash", "getrandom", "gloo", + "grumpkin", "Guillaume", "hasher", "hexdigit", diff --git a/deny.toml b/deny.toml index e19406592d8..fc60154c628 100644 --- a/deny.toml +++ b/deny.toml @@ -99,4 +99,7 @@ unknown-registry = "warn" # Lint level for what to happen when a crate from a git repository that is not # in the allow list is encountered unknown-git = "deny" -allow-git = ["https://github.com/jfecher/chumsky"] +allow-git = [ + "https://github.com/noir-lang/grumpkin", + "https://github.com/jfecher/chumsky" +] diff --git a/tooling/backend_interface/src/cli/mod.rs b/tooling/backend_interface/src/cli/mod.rs index 848c5e11e81..d1eebb1ba8e 100644 --- a/tooling/backend_interface/src/cli/mod.rs +++ b/tooling/backend_interface/src/cli/mod.rs @@ -5,6 +5,7 @@ mod gates; mod info; mod prove; mod verify; +mod version; mod write_vk; pub(crate) use contract::ContractCommand; @@ -12,6 +13,7 @@ pub(crate) use gates::GatesCommand; pub(crate) use info::InfoCommand; pub(crate) use prove::ProveCommand; pub(crate) use verify::VerifyCommand; +pub(crate) use version::VersionCommand; pub(crate) use write_vk::WriteVkCommand; #[test] diff --git a/tooling/backend_interface/src/cli/version.rs b/tooling/backend_interface/src/cli/version.rs new file mode 100644 index 00000000000..83ab72a870e --- /dev/null +++ b/tooling/backend_interface/src/cli/version.rs @@ -0,0 +1,29 @@ +use std::path::Path; + +use crate::BackendError; + +use super::string_from_stderr; + +/// VersionCommand will call the backend binary +/// to query installed version. +pub(crate) struct VersionCommand; + +impl VersionCommand { + pub(crate) fn run(self, binary_path: &Path) -> Result { + let mut command = std::process::Command::new(binary_path); + + command.arg("--version"); + + let output = command.output()?; + if output.status.success() { + match String::from_utf8(output.stdout) { + Ok(result) => Ok(result), + Err(_) => Err(BackendError::CommandFailed( + "Unexpected output from --version check.".to_owned(), + )), + } + } else { + Err(BackendError::CommandFailed(string_from_stderr(&output.stderr))) + } + } +} diff --git a/tooling/backend_interface/src/lib.rs b/tooling/backend_interface/src/lib.rs index d6a9cd0d293..6c91c181a92 100644 --- a/tooling/backend_interface/src/lib.rs +++ b/tooling/backend_interface/src/lib.rs @@ -10,6 +10,8 @@ mod smart_contract; use acvm::acir::circuit::Opcode; use bb_abstraction_leaks::ACVM_BACKEND_BARRETENBERG; +use bb_abstraction_leaks::BB_VERSION; +use cli::VersionCommand; pub use download::download_backend; const BACKENDS_DIR: &str = ".nargo/backends"; @@ -104,6 +106,33 @@ impl Backend { fn crs_directory(&self) -> PathBuf { self.backend_directory().join("crs") } + + fn assert_correct_version(&self) -> Result<&PathBuf, BackendError> { + let binary_path = self.binary_path(); + if binary_path.to_string_lossy().contains(ACVM_BACKEND_BARRETENBERG) { + match VersionCommand.run(binary_path) { + // If version matches then do nothing. + Ok(version_string) if version_string == BB_VERSION => (), + + // If version doesn't match then download the correct version. + Ok(version_string) => { + println!("`{ACVM_BACKEND_BARRETENBERG}` version `{version_string}` is different from expected `{BB_VERSION}`. Downloading expected version..."); + let bb_url = std::env::var("BB_BINARY_URL") + .unwrap_or_else(|_| bb_abstraction_leaks::BB_DOWNLOAD_URL.to_owned()); + download_backend(&bb_url, binary_path)?; + } + + // If `bb` fails to report its version, then attempt to fix it by re-downloading the binary. + Err(_) => { + println!("Could not determine version of `{ACVM_BACKEND_BARRETENBERG}`. Downloading expected version..."); + let bb_url = std::env::var("BB_BINARY_URL") + .unwrap_or_else(|_| bb_abstraction_leaks::BB_DOWNLOAD_URL.to_owned()); + download_backend(&bb_url, binary_path)?; + } + } + } + Ok(binary_path) + } } pub struct BackendOpcodeSupport { diff --git a/tooling/backend_interface/src/proof_system.rs b/tooling/backend_interface/src/proof_system.rs index ffdb7531ed1..cbb4a61be8a 100644 --- a/tooling/backend_interface/src/proof_system.rs +++ b/tooling/backend_interface/src/proof_system.rs @@ -13,6 +13,7 @@ use crate::{Backend, BackendError, BackendOpcodeSupport}; impl Backend { pub fn get_exact_circuit_size(&self, circuit: &Circuit) -> Result { let binary_path = self.assert_binary_exists()?; + self.assert_correct_version()?; let temp_directory = tempdir().expect("could not create a temporary directory"); let temp_directory = temp_directory.path().to_path_buf(); @@ -28,6 +29,7 @@ impl Backend { pub fn get_backend_info(&self) -> Result<(Language, BackendOpcodeSupport), BackendError> { let binary_path = self.assert_binary_exists()?; + self.assert_correct_version()?; InfoCommand { crs_path: self.crs_directory() }.run(binary_path) } @@ -38,6 +40,7 @@ impl Backend { is_recursive: bool, ) -> Result, BackendError> { let binary_path = self.assert_binary_exists()?; + self.assert_correct_version()?; let temp_directory = tempdir().expect("could not create a temporary directory"); let temp_directory = temp_directory.path().to_path_buf(); @@ -78,6 +81,7 @@ impl Backend { is_recursive: bool, ) -> Result { let binary_path = self.assert_binary_exists()?; + self.assert_correct_version()?; let temp_directory = tempdir().expect("could not create a temporary directory"); let temp_directory = temp_directory.path().to_path_buf(); diff --git a/tooling/backend_interface/src/smart_contract.rs b/tooling/backend_interface/src/smart_contract.rs index 5f56557cad4..50afd47287b 100644 --- a/tooling/backend_interface/src/smart_contract.rs +++ b/tooling/backend_interface/src/smart_contract.rs @@ -9,6 +9,7 @@ use tempfile::tempdir; impl Backend { pub fn eth_contract(&self, circuit: &Circuit) -> Result { let binary_path = self.assert_binary_exists()?; + self.assert_correct_version()?; let temp_directory = tempdir().expect("could not create a temporary directory"); let temp_directory_path = temp_directory.path().to_path_buf(); diff --git a/tooling/bb_abstraction_leaks/build.rs b/tooling/bb_abstraction_leaks/build.rs index baf3cfdf6a6..1ae5a28f5a8 100644 --- a/tooling/bb_abstraction_leaks/build.rs +++ b/tooling/bb_abstraction_leaks/build.rs @@ -35,6 +35,7 @@ fn main() -> Result<(), String> { }; println!("cargo:rustc-env=BB_BINARY_URL={}", get_bb_download_url(arch, os)); + println!("cargo:rustc-env=BB_VERSION={}", VERSION); Ok(()) } diff --git a/tooling/bb_abstraction_leaks/src/lib.rs b/tooling/bb_abstraction_leaks/src/lib.rs index 799e14c36f0..e0fdc467c53 100644 --- a/tooling/bb_abstraction_leaks/src/lib.rs +++ b/tooling/bb_abstraction_leaks/src/lib.rs @@ -5,6 +5,7 @@ use acvm::FieldElement; pub const ACVM_BACKEND_BARRETENBERG: &str = "acvm-backend-barretenberg"; pub const BB_DOWNLOAD_URL: &str = env!("BB_BINARY_URL"); +pub const BB_VERSION: &str = env!("BB_VERSION"); /// Embed the Solidity verifier file const ULTRA_VERIFIER_CONTRACT: &str = include_str!("contract.sol"); diff --git a/tooling/lsp/Cargo.toml b/tooling/lsp/Cargo.toml index 53e10ba26dd..ca34267f55a 100644 --- a/tooling/lsp/Cargo.toml +++ b/tooling/lsp/Cargo.toml @@ -29,4 +29,4 @@ async-lsp = { version = "0.0.5", default-features = false, features = ["omni-tra wasm-bindgen.workspace = true [dev-dependencies] -tokio = { version = "1.0", features = ["macros"] } +tokio = { version = "1.0", features = ["macros", "rt"] } diff --git a/tooling/lsp/src/lib.rs b/tooling/lsp/src/lib.rs index 48ffefb7f7a..9cb3db8a9cf 100644 --- a/tooling/lsp/src/lib.rs +++ b/tooling/lsp/src/lib.rs @@ -13,67 +13,32 @@ use std::{ use acvm::BlackBoxFunctionSolver; use async_lsp::{ - router::Router, AnyEvent, AnyNotification, AnyRequest, ClientSocket, Error, ErrorCode, - LanguageClient, LspService, ResponseError, + router::Router, AnyEvent, AnyNotification, AnyRequest, ClientSocket, Error, LspService, + ResponseError, }; -use codelens::{on_code_lens_request, on_test_run_request, on_tests_request}; use codespan_reporting::files; use fm::FILE_EXTENSION; -use nargo::prepare_package; -use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection}; -use noirc_driver::check_crate; -use noirc_errors::{DiagnosticKind, FileDiagnostic}; use noirc_frontend::{ graph::{CrateId, CrateName}, hir::{Context, FunctionNameMatch}, }; +use notifications::{ + on_did_change_configuration, on_did_change_text_document, on_did_close_text_document, + on_did_open_text_document, on_did_save_text_document, on_exit, on_initialized, +}; +use requests::{ + on_code_lens_request, on_initialize, on_shutdown, on_test_run_request, on_tests_request, +}; use serde_json::Value as JsonValue; use tower::Service; -mod codelens; +mod notifications; +mod requests; +mod solver; mod types; -use types::{ - notification, request, CodeLensOptions, Diagnostic, DiagnosticSeverity, - DidChangeConfigurationParams, DidChangeTextDocumentParams, DidCloseTextDocumentParams, - DidOpenTextDocumentParams, DidSaveTextDocumentParams, InitializeParams, InitializeResult, - InitializedParams, LogMessageParams, MessageType, NargoCapability, NargoPackageTests, - NargoTest, NargoTestId, NargoTestsOptions, Position, PublishDiagnosticsParams, Range, - ServerCapabilities, TextDocumentSyncOptions, Url, -}; - -// This is a struct that wraps a dynamically dispatched `BlackBoxFunctionSolver` -// where we proxy the unimplemented stuff to the wrapped backend, but it -// allows us to avoid changing function signatures to include the `Box` -struct WrapperSolver(Box); - -impl BlackBoxFunctionSolver for WrapperSolver { - fn schnorr_verify( - &self, - public_key_x: &acvm::FieldElement, - public_key_y: &acvm::FieldElement, - signature: &[u8], - message: &[u8], - ) -> Result { - self.0.schnorr_verify(public_key_x, public_key_y, signature, message) - } - - fn pedersen( - &self, - inputs: &[acvm::FieldElement], - domain_separator: u32, - ) -> Result<(acvm::FieldElement, acvm::FieldElement), acvm::BlackBoxResolutionError> { - self.0.pedersen(inputs, domain_separator) - } - - fn fixed_base_scalar_mul( - &self, - low: &acvm::FieldElement, - high: &acvm::FieldElement, - ) -> Result<(acvm::FieldElement, acvm::FieldElement), acvm::BlackBoxResolutionError> { - self.0.fixed_base_scalar_mul(low, high) - } -} +use solver::WrapperSolver; +use types::{notification, request, NargoTest, NargoTestId, Position, Range, Url}; // State for the LSP gets implemented on this struct and is internal to the implementation pub struct LspState { @@ -141,210 +106,6 @@ impl LspService for NargoLspService { } } -// Handlers -// The handlers for `request` are not `async` because it compiles down to lifetimes that can't be added to -// the router. To return a future that fits the trait, it is easiest wrap your implementations in an `async {}` -// block but you can also use `std::future::ready`. -// -// Additionally, the handlers for `notification` aren't async at all. -// -// They are not attached to the `NargoLspService` struct so they can be unit tested with only `LspState` -// and params passed in. - -fn on_initialize( - state: &mut LspState, - params: InitializeParams, -) -> impl Future> { - state.root_path = params.root_uri.and_then(|root_uri| root_uri.to_file_path().ok()); - - async { - let text_document_sync = - TextDocumentSyncOptions { save: Some(true.into()), ..Default::default() }; - - let code_lens = CodeLensOptions { resolve_provider: Some(false) }; - - let nargo = NargoCapability { - tests: Some(NargoTestsOptions { - fetch: Some(true), - run: Some(true), - update: Some(true), - }), - }; - - Ok(InitializeResult { - capabilities: ServerCapabilities { - text_document_sync: Some(text_document_sync.into()), - code_lens_provider: Some(code_lens), - nargo: Some(nargo), - }, - server_info: None, - }) - } -} - -fn on_shutdown( - _state: &mut LspState, - _params: (), -) -> impl Future> { - async { Ok(()) } -} - -fn on_initialized( - _state: &mut LspState, - _params: InitializedParams, -) -> ControlFlow> { - ControlFlow::Continue(()) -} - -fn on_did_change_configuration( - _state: &mut LspState, - _params: DidChangeConfigurationParams, -) -> ControlFlow> { - ControlFlow::Continue(()) -} - -fn on_did_open_text_document( - _state: &mut LspState, - _params: DidOpenTextDocumentParams, -) -> ControlFlow> { - ControlFlow::Continue(()) -} - -fn on_did_change_text_document( - _state: &mut LspState, - _params: DidChangeTextDocumentParams, -) -> ControlFlow> { - ControlFlow::Continue(()) -} - -fn on_did_close_text_document( - _state: &mut LspState, - _params: DidCloseTextDocumentParams, -) -> ControlFlow> { - ControlFlow::Continue(()) -} - -fn on_did_save_text_document( - state: &mut LspState, - params: DidSaveTextDocumentParams, -) -> ControlFlow> { - let file_path = match params.text_document.uri.to_file_path() { - Ok(file_path) => file_path, - Err(()) => { - return ControlFlow::Break(Err(ResponseError::new( - ErrorCode::REQUEST_FAILED, - "URI is not a valid file path", - ) - .into())) - } - }; - - let root_path = match &state.root_path { - Some(root) => root, - None => { - return ControlFlow::Break(Err(ResponseError::new( - ErrorCode::REQUEST_FAILED, - "Could not find project root", - ) - .into())); - } - }; - - let toml_path = match find_package_manifest(root_path, &file_path) { - Ok(toml_path) => toml_path, - Err(err) => { - // If we cannot find a manifest, we log a warning but return no diagnostics - // We can reconsider this when we can build a file without the need for a Nargo.toml file to resolve deps - let _ = state.client.log_message(LogMessageParams { - typ: MessageType::WARNING, - message: format!("{err}"), - }); - return ControlFlow::Continue(()); - } - }; - let workspace = match resolve_workspace_from_toml(&toml_path, PackageSelection::All) { - Ok(workspace) => workspace, - Err(err) => { - // If we found a manifest, but the workspace is invalid, we raise an error about it - return ControlFlow::Break(Err(ResponseError::new( - ErrorCode::REQUEST_FAILED, - format!("{err}"), - ) - .into())); - } - }; - - let mut diagnostics = Vec::new(); - - for package in &workspace { - let (mut context, crate_id) = - prepare_package(package, Box::new(|path| std::fs::read_to_string(path))); - - let file_diagnostics = match check_crate(&mut context, crate_id, false) { - Ok(((), warnings)) => warnings, - Err(errors_and_warnings) => errors_and_warnings, - }; - - // We don't add test headings for a package if it contains no `#[test]` functions - if let Some(tests) = get_package_tests_in_crate(&context, &crate_id, &package.name) { - let _ = state.client.notify::(NargoPackageTests { - package: package.name.to_string(), - tests, - }); - } - - if !file_diagnostics.is_empty() { - let fm = &context.file_manager; - let files = fm.as_file_map(); - - for FileDiagnostic { file_id, diagnostic, call_stack: _ } in file_diagnostics { - // Ignore diagnostics for any file that wasn't the file we saved - // TODO: In the future, we could create "related" diagnostics for these files - // TODO: This currently just appends the `.nr` file extension that we store as a constant, - // but that won't work if we accept other extensions - if fm.path(file_id).with_extension(FILE_EXTENSION) != file_path { - continue; - } - - let mut range = Range::default(); - - // TODO: Should this be the first item in secondaries? Should we bail when we find a range? - for sec in diagnostic.secondaries { - // Not using `unwrap_or_default` here because we don't want to overwrite a valid range with a default range - if let Some(r) = byte_span_to_range(files, file_id, sec.span.into()) { - range = r; - } - } - let severity = match diagnostic.kind { - DiagnosticKind::Error => Some(DiagnosticSeverity::ERROR), - DiagnosticKind::Warning => Some(DiagnosticSeverity::WARNING), - }; - diagnostics.push(Diagnostic { - range, - severity, - message: diagnostic.message, - ..Default::default() - }); - } - } - } - - // We need to refresh lenses when we compile since that's the only time they can be accurately reflected - std::mem::drop(state.client.code_lens_refresh(())); - - let _ = state.client.publish_diagnostics(PublishDiagnosticsParams { - uri: params.text_document.uri, - version: None, - diagnostics, - }); - - ControlFlow::Continue(()) -} - -fn on_exit(_state: &mut LspState, _params: ()) -> ControlFlow> { - ControlFlow::Continue(()) -} - fn get_package_tests_in_crate( context: &Context, crate_id: &CrateId, @@ -355,23 +116,25 @@ fn get_package_tests_in_crate( let tests = context.get_all_test_functions_in_crate_matching(crate_id, FunctionNameMatch::Anything); - let mut package_tests = Vec::new(); - - for (func_name, test_function) in tests { - let location = context.function_meta(&test_function.get_id()).name.location; - let file_id = location.file; - - let file_path = fm.path(file_id).with_extension(FILE_EXTENSION); - let range = byte_span_to_range(files, file_id, location.span.into()).unwrap_or_default(); - - package_tests.push(NargoTest { - id: NargoTestId::new(crate_name.clone(), func_name.clone()), - label: func_name, - uri: Url::from_file_path(file_path) - .expect("Expected a valid file path that can be converted into a URI"), - range, - }); - } + let package_tests: Vec<_> = tests + .into_iter() + .map(|(func_name, test_function)| { + let location = context.function_meta(&test_function.get_id()).name.location; + let file_id = location.file; + + let file_path = fm.path(file_id).with_extension(FILE_EXTENSION); + let range = + byte_span_to_range(files, file_id, location.span.into()).unwrap_or_default(); + + NargoTest { + id: NargoTestId::new(crate_name.clone(), func_name.clone()), + label: func_name, + uri: Url::from_file_path(file_path) + .expect("Expected a valid file path that can be converted into a URI"), + range, + } + }) + .collect(); if package_tests.is_empty() { None @@ -404,65 +167,6 @@ fn byte_span_to_range<'a, F: files::Files<'a> + ?Sized>( } } -#[cfg(test)] -mod lsp_tests { - use lsp_types::TextDocumentSyncCapability; - use tokio::test; - - use super::*; - - #[test] - async fn test_on_initialize() { - struct MockBackend; - impl BlackBoxFunctionSolver for MockBackend { - fn schnorr_verify( - &self, - _public_key_x: &acvm::FieldElement, - _public_key_y: &acvm::FieldElement, - _signature: &[u8], - _message: &[u8], - ) -> Result { - unimplemented!() - } - - fn pedersen( - &self, - _inputs: &[acvm::FieldElement], - _domain_separator: u32, - ) -> Result<(acvm::FieldElement, acvm::FieldElement), acvm::BlackBoxResolutionError> - { - unimplemented!() - } - - fn fixed_base_scalar_mul( - &self, - _low: &acvm::FieldElement, - _high: &acvm::FieldElement, - ) -> Result<(acvm::FieldElement, acvm::FieldElement), acvm::BlackBoxResolutionError> - { - unimplemented!() - } - } - - let client = ClientSocket::new_closed(); - let solver = MockBackend; - let mut state = LspState::new(&client, solver); - let params = InitializeParams::default(); - let response = on_initialize(&mut state, params).await.unwrap(); - assert!(matches!( - response.capabilities, - ServerCapabilities { - text_document_sync: Some(TextDocumentSyncCapability::Options( - TextDocumentSyncOptions { save: Some(_), .. } - )), - code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(false) }), - .. - } - )); - assert!(response.server_info.is_none()); - } -} - cfg_if::cfg_if! { if #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] { use wasm_bindgen::{prelude::*, JsValue}; diff --git a/tooling/lsp/src/notifications/mod.rs b/tooling/lsp/src/notifications/mod.rs new file mode 100644 index 00000000000..d86f81fed37 --- /dev/null +++ b/tooling/lsp/src/notifications/mod.rs @@ -0,0 +1,176 @@ +use std::ops::ControlFlow; + +use async_lsp::{ErrorCode, LanguageClient, ResponseError}; +use fm::FILE_EXTENSION; +use nargo::prepare_package; +use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection}; +use noirc_driver::check_crate; +use noirc_errors::{DiagnosticKind, FileDiagnostic}; + +use crate::types::{ + notification, Diagnostic, DiagnosticSeverity, DidChangeConfigurationParams, + DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, + DidSaveTextDocumentParams, InitializedParams, LogMessageParams, MessageType, NargoPackageTests, + PublishDiagnosticsParams, +}; + +use crate::{byte_span_to_range, get_non_stdlib_asset, get_package_tests_in_crate, LspState}; + +pub(super) fn on_initialized( + _state: &mut LspState, + _params: InitializedParams, +) -> ControlFlow> { + ControlFlow::Continue(()) +} + +pub(super) fn on_did_change_configuration( + _state: &mut LspState, + _params: DidChangeConfigurationParams, +) -> ControlFlow> { + ControlFlow::Continue(()) +} + +pub(super) fn on_did_open_text_document( + _state: &mut LspState, + _params: DidOpenTextDocumentParams, +) -> ControlFlow> { + ControlFlow::Continue(()) +} + +pub(super) fn on_did_change_text_document( + _state: &mut LspState, + _params: DidChangeTextDocumentParams, +) -> ControlFlow> { + ControlFlow::Continue(()) +} + +pub(super) fn on_did_close_text_document( + _state: &mut LspState, + _params: DidCloseTextDocumentParams, +) -> ControlFlow> { + ControlFlow::Continue(()) +} + +pub(super) fn on_did_save_text_document( + state: &mut LspState, + params: DidSaveTextDocumentParams, +) -> ControlFlow> { + let file_path = match params.text_document.uri.to_file_path() { + Ok(file_path) => file_path, + Err(()) => { + return ControlFlow::Break(Err(ResponseError::new( + ErrorCode::REQUEST_FAILED, + "URI is not a valid file path", + ) + .into())) + } + }; + + let root_path = match &state.root_path { + Some(root) => root, + None => { + return ControlFlow::Break(Err(ResponseError::new( + ErrorCode::REQUEST_FAILED, + "Could not find project root", + ) + .into())); + } + }; + + let toml_path = match find_package_manifest(root_path, &file_path) { + Ok(toml_path) => toml_path, + Err(err) => { + // If we cannot find a manifest, we log a warning but return no diagnostics + // We can reconsider this when we can build a file without the need for a Nargo.toml file to resolve deps + let _ = state.client.log_message(LogMessageParams { + typ: MessageType::WARNING, + message: format!("{err}"), + }); + return ControlFlow::Continue(()); + } + }; + let workspace = match resolve_workspace_from_toml(&toml_path, PackageSelection::All) { + Ok(workspace) => workspace, + Err(err) => { + // If we found a manifest, but the workspace is invalid, we raise an error about it + return ControlFlow::Break(Err(ResponseError::new( + ErrorCode::REQUEST_FAILED, + format!("{err}"), + ) + .into())); + } + }; + + let diagnostics: Vec<_> = workspace + .into_iter() + .flat_map(|package| -> Vec { + let (mut context, crate_id) = prepare_package(package, Box::new(get_non_stdlib_asset)); + + let file_diagnostics = match check_crate(&mut context, crate_id, false) { + Ok(((), warnings)) => warnings, + Err(errors_and_warnings) => errors_and_warnings, + }; + + // We don't add test headings for a package if it contains no `#[test]` functions + if let Some(tests) = get_package_tests_in_crate(&context, &crate_id, &package.name) { + let _ = state.client.notify::(NargoPackageTests { + package: package.name.to_string(), + tests, + }); + } + + let fm = &context.file_manager; + let files = fm.as_file_map(); + + file_diagnostics + .into_iter() + .filter_map(|FileDiagnostic { file_id, diagnostic, call_stack: _ }| { + // Ignore diagnostics for any file that wasn't the file we saved + // TODO: In the future, we could create "related" diagnostics for these files + // TODO: This currently just appends the `.nr` file extension that we store as a constant, + // but that won't work if we accept other extensions + if fm.path(file_id).with_extension(FILE_EXTENSION) != file_path { + return None; + } + + // TODO: Should this be the first item in secondaries? Should we bail when we find a range? + let range = diagnostic + .secondaries + .into_iter() + .filter_map(|sec| byte_span_to_range(files, file_id, sec.span.into())) + .last() + .unwrap_or_default(); + + let severity = match diagnostic.kind { + DiagnosticKind::Error => DiagnosticSeverity::ERROR, + DiagnosticKind::Warning => DiagnosticSeverity::WARNING, + }; + Some(Diagnostic { + range, + severity: Some(severity), + message: diagnostic.message, + ..Default::default() + }) + }) + .collect() + }) + .collect(); + + // We need to refresh lenses when we compile since that's the only time they can be accurately reflected + std::mem::drop(state.client.code_lens_refresh(())); + + let _ = state.client.publish_diagnostics(PublishDiagnosticsParams { + uri: params.text_document.uri, + version: None, + diagnostics, + }); + + ControlFlow::Continue(()) +} + +pub(super) fn on_exit( + _state: &mut LspState, + _params: (), +) -> ControlFlow> { + ControlFlow::Continue(()) +} diff --git a/tooling/lsp/src/codelens/mod.rs b/tooling/lsp/src/requests/code_lens_request.rs similarity index 98% rename from tooling/lsp/src/codelens/mod.rs rename to tooling/lsp/src/requests/code_lens_request.rs index db6ab4300f2..e18b14fdb28 100644 --- a/tooling/lsp/src/codelens/mod.rs +++ b/tooling/lsp/src/requests/code_lens_request.rs @@ -14,11 +14,6 @@ use crate::{ LspState, }; -mod test_run; -mod tests; - -pub(crate) use {test_run::on_test_run_request, tests::on_tests_request}; - const ARROW: &str = "▶\u{fe0e}"; const TEST_COMMAND: &str = "nargo.test"; const TEST_CODELENS_TITLE: &str = "Run Test"; @@ -42,7 +37,7 @@ fn package_selection_args(workspace: &Workspace, package: &Package) -> Vec impl Future> { diff --git a/tooling/lsp/src/requests/mod.rs b/tooling/lsp/src/requests/mod.rs new file mode 100644 index 00000000000..166adb10b5a --- /dev/null +++ b/tooling/lsp/src/requests/mod.rs @@ -0,0 +1,98 @@ +use std::future::Future; + +use crate::types::{CodeLensOptions, InitializeParams, TextDocumentSyncOptions}; +use async_lsp::ResponseError; + +use crate::{ + types::{InitializeResult, NargoCapability, NargoTestsOptions, ServerCapabilities}, + LspState, +}; + +// Handlers +// The handlers for `request` are not `async` because it compiles down to lifetimes that can't be added to +// the router. To return a future that fits the trait, it is easiest wrap your implementations in an `async {}` +// block but you can also use `std::future::ready`. +// +// Additionally, the handlers for `notification` aren't async at all. +// +// They are not attached to the `NargoLspService` struct so they can be unit tested with only `LspState` +// and params passed in. + +mod code_lens_request; +mod test_run; +mod tests; + +pub(crate) use { + code_lens_request::on_code_lens_request, test_run::on_test_run_request, tests::on_tests_request, +}; + +pub(crate) fn on_initialize( + state: &mut LspState, + params: InitializeParams, +) -> impl Future> { + state.root_path = params.root_uri.and_then(|root_uri| root_uri.to_file_path().ok()); + + async { + let text_document_sync = + TextDocumentSyncOptions { save: Some(true.into()), ..Default::default() }; + + let code_lens = CodeLensOptions { resolve_provider: Some(false) }; + + let nargo = NargoCapability { + tests: Some(NargoTestsOptions { + fetch: Some(true), + run: Some(true), + update: Some(true), + }), + }; + + Ok(InitializeResult { + capabilities: ServerCapabilities { + text_document_sync: Some(text_document_sync.into()), + code_lens_provider: Some(code_lens), + nargo: Some(nargo), + }, + server_info: None, + }) + } +} + +pub(crate) fn on_shutdown( + _state: &mut LspState, + _params: (), +) -> impl Future> { + async { Ok(()) } +} + +#[cfg(test)] +mod initialization { + use async_lsp::ClientSocket; + use lsp_types::{ + CodeLensOptions, InitializeParams, TextDocumentSyncCapability, TextDocumentSyncOptions, + }; + use tokio::test; + + use crate::{ + requests::on_initialize, solver::MockBackend, types::ServerCapabilities, LspState, + }; + + #[test] + async fn test_on_initialize() { + let client = ClientSocket::new_closed(); + let solver = MockBackend; + let mut state = LspState::new(&client, solver); + let params = InitializeParams::default(); + let response = on_initialize(&mut state, params).await.unwrap(); + assert!(matches!( + response.capabilities, + ServerCapabilities { + text_document_sync: Some(TextDocumentSyncCapability::Options( + TextDocumentSyncOptions { save: Some(_), .. } + )), + code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(false) }), + .. + } + )); + assert!(response.server_info.is_none()); + } +} diff --git a/tooling/lsp/src/codelens/test_run.rs b/tooling/lsp/src/requests/test_run.rs similarity index 100% rename from tooling/lsp/src/codelens/test_run.rs rename to tooling/lsp/src/requests/test_run.rs diff --git a/tooling/lsp/src/codelens/tests.rs b/tooling/lsp/src/requests/tests.rs similarity index 72% rename from tooling/lsp/src/codelens/tests.rs rename to tooling/lsp/src/requests/tests.rs index 91098fceebe..a5988b54f06 100644 --- a/tooling/lsp/src/codelens/tests.rs +++ b/tooling/lsp/src/requests/tests.rs @@ -46,19 +46,19 @@ fn on_tests_request_inner( ResponseError::new(ErrorCode::REQUEST_FAILED, err) })?; - let mut package_tests = Vec::new(); + let package_tests: Vec<_> = workspace + .into_iter() + .filter_map(|package| { + let (mut context, crate_id) = prepare_package(package, Box::new(get_non_stdlib_asset)); + // We ignore the warnings and errors produced by compilation for producing tests + // because we can still get the test functions even if compilation fails + let _ = check_crate(&mut context, crate_id, false); - for package in &workspace { - let (mut context, crate_id) = prepare_package(package, Box::new(get_non_stdlib_asset)); - // We ignore the warnings and errors produced by compilation for producing tests - // because we can still get the test functions even if compilation fails - let _ = check_crate(&mut context, crate_id, false); - - // We don't add test headings for a package if it contains no `#[test]` functions - if let Some(tests) = get_package_tests_in_crate(&context, &crate_id, &package.name) { - package_tests.push(NargoPackageTests { package: package.name.to_string(), tests }); - } - } + // We don't add test headings for a package if it contains no `#[test]` functions + get_package_tests_in_crate(&context, &crate_id, &package.name) + .map(|tests| NargoPackageTests { package: package.name.to_string(), tests }) + }) + .collect(); if package_tests.is_empty() { Ok(None) diff --git a/tooling/lsp/src/solver.rs b/tooling/lsp/src/solver.rs new file mode 100644 index 00000000000..2e8bf6ddc0a --- /dev/null +++ b/tooling/lsp/src/solver.rs @@ -0,0 +1,68 @@ +use acvm::BlackBoxFunctionSolver; + +// This is a struct that wraps a dynamically dispatched `BlackBoxFunctionSolver` +// where we proxy the unimplemented stuff to the wrapped backend, but it +// allows us to avoid changing function signatures to include the `Box` +pub(super) struct WrapperSolver(pub(super) Box); + +impl BlackBoxFunctionSolver for WrapperSolver { + fn schnorr_verify( + &self, + public_key_x: &acvm::FieldElement, + public_key_y: &acvm::FieldElement, + signature: &[u8], + message: &[u8], + ) -> Result { + self.0.schnorr_verify(public_key_x, public_key_y, signature, message) + } + + fn pedersen( + &self, + inputs: &[acvm::FieldElement], + domain_separator: u32, + ) -> Result<(acvm::FieldElement, acvm::FieldElement), acvm::BlackBoxResolutionError> { + self.0.pedersen(inputs, domain_separator) + } + + fn fixed_base_scalar_mul( + &self, + low: &acvm::FieldElement, + high: &acvm::FieldElement, + ) -> Result<(acvm::FieldElement, acvm::FieldElement), acvm::BlackBoxResolutionError> { + self.0.fixed_base_scalar_mul(low, high) + } +} + +// We also have a mocked implementation of the `BlackBoxFunctionSolver` trait for use in tests + +#[cfg(test)] +pub(crate) struct MockBackend; + +#[cfg(test)] +impl BlackBoxFunctionSolver for MockBackend { + fn schnorr_verify( + &self, + _public_key_x: &acvm::FieldElement, + _public_key_y: &acvm::FieldElement, + _signature: &[u8], + _message: &[u8], + ) -> Result { + unimplemented!() + } + + fn pedersen( + &self, + _inputs: &[acvm::FieldElement], + _domain_separator: u32, + ) -> Result<(acvm::FieldElement, acvm::FieldElement), acvm::BlackBoxResolutionError> { + unimplemented!() + } + + fn fixed_base_scalar_mul( + &self, + _low: &acvm::FieldElement, + _high: &acvm::FieldElement, + ) -> Result<(acvm::FieldElement, acvm::FieldElement), acvm::BlackBoxResolutionError> { + unimplemented!() + } +} diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_short_circuit/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_short_circuit/target/acir.gz index 34340161cd7..75f2bcfdb0b 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_short_circuit/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_short_circuit/target/acir.gz differ