diff --git a/.gitignore b/.gitignore index 5499980..d22c54f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ /target Cargo.lock # Circom generated files -/src/frontend/circom/test_folder/test_circuit.r1cs -/src/frontend/circom/test_folder/test_circuit_js/ +folding-schemes/src/frontend/circom/test_folder/cubic_circuit.r1cs +folding-schemes/src/frontend/circom/test_folder/cubic_circuit_js/ # generated contracts at test time -solidity-verifiers/generated \ No newline at end of file +solidity-verifiers/generated diff --git a/folding-schemes/Cargo.toml b/folding-schemes/Cargo.toml index 2734256..9ccd8a1 100644 --- a/folding-schemes/Cargo.toml +++ b/folding-schemes/Cargo.toml @@ -14,7 +14,7 @@ ark-relations = { version = "^0.4.0", default-features = false } ark-r1cs-std = { version = "0.4.0", default-features = false } # this is patched at the workspace level ark-snark = { version = "^0.4.0"} ark-serialize = "^0.4.0" -ark-circom = { git = "https://github.com/gakonst/ark-circom.git" } +ark-circom = { git = "https://github.com/arnaucube/circom-compat.git" } thiserror = "1.0" rayon = "1.7.0" num-bigint = "0.4" diff --git a/folding-schemes/src/frontend/circom/mod.rs b/folding-schemes/src/frontend/circom/mod.rs index 1a8ad41..6315480 100644 --- a/folding-schemes/src/frontend/circom/mod.rs +++ b/folding-schemes/src/frontend/circom/mod.rs @@ -1,226 +1,177 @@ -use std::{error::Error, fs::File, io::BufReader, marker::PhantomData, path::PathBuf}; - -use color_eyre::Result; +use ark_circom::circom::CircomCircuit; +use ark_ff::PrimeField; +use ark_r1cs_std::alloc::AllocVar; +use ark_r1cs_std::fields::fp::FpVar; +use ark_r1cs_std::R1CSVar; +use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; +use ark_std::fmt::Debug; use num_bigint::BigInt; +use std::path::PathBuf; -use ark_circom::{circom::r1cs_reader, WitnessCalculator}; -use ark_ec::pairing::Pairing; -use ark_ff::PrimeField; +use crate::frontend::FCircuit; +use crate::Error; -use crate::ccs::r1cs::R1CS; -use crate::utils::vec::SparseMatrix; - -// Define the sparse matrices on PrimeFiled. -pub type Constraints = (ConstraintVec, ConstraintVec, ConstraintVec); -pub type ConstraintVec = Vec<(usize, F)>; -type ExtractedConstraints = (Vec>, usize, usize); -pub type ExtractedConstraintsResult = Result, Box>; -pub type R1CSandZ = (R1CS, Vec); - -// A struct that wraps Circom functionalities, allowing for extraction of R1CS and witnesses -// based on file paths to Circom's .r1cs and .wasm. -pub struct CircomWrapper { - r1cs_filepath: PathBuf, - wasm_filepath: PathBuf, - _marker: PhantomData, +pub mod utils; +use utils::CircomWrapper; + +/// Define CircomFCircuit +#[derive(Clone, Debug)] +pub struct CircomFCircuit { + circom_wrapper: CircomWrapper, } -impl CircomWrapper { - // Creates a new instance of the CircomWrapper with the file paths. - pub fn new(r1cs_filepath: PathBuf, wasm_filepath: PathBuf) -> Self { - CircomWrapper { - r1cs_filepath, - wasm_filepath, - _marker: PhantomData, - } +impl FCircuit for CircomFCircuit { + type Params = (PathBuf, PathBuf); + + fn new(params: Self::Params) -> Self { + let (r1cs_path, wasm_path) = params; + let circom_wrapper = CircomWrapper::new(r1cs_path, wasm_path); + Self { circom_wrapper } } - // Aggregates multiple functions to obtain R1CS and Z as defined in folding-schemes from Circom. - pub fn extract_r1cs_and_z( - &self, - inputs: &[(String, Vec)], - ) -> Result, Box> { - let (constraints, pub_io_len, num_variables) = self.extract_constraints_from_r1cs()?; - let witness = self.calculate_witness(inputs)?; - self.circom_to_folding_schemes_r1cs_and_z(constraints, &witness, pub_io_len, num_variables) + fn state_len(&self) -> usize { + 1 } - // Extracts constraints from the r1cs file. - pub fn extract_constraints_from_r1cs(&self) -> ExtractedConstraintsResult - where - E: Pairing, - { - // Opens the .r1cs file and create a reader. - let file = File::open(&self.r1cs_filepath)?; - let reader = BufReader::new(file); - - // Reads the R1CS file and extract the constraints directly. - let r1cs_file = r1cs_reader::R1CSFile::::new(reader)?; - let pub_io_len = (r1cs_file.header.n_pub_in + r1cs_file.header.n_pub_out) as usize; - let r1cs = r1cs_reader::R1CS::::from(r1cs_file); - let num_variables = r1cs.num_variables; - let constraints: Vec> = r1cs.constraints; - - Ok((constraints, pub_io_len, num_variables)) + fn step_native(&self, _i: usize, z_i: Vec) -> Result, Error> { + // Converts PrimeField values to BigInt for computing witness. + let input_num_bigint = z_i + .iter() + .map(|val| self.circom_wrapper.ark_primefield_to_num_bigint(*val)) + .collect::>(); + + // Computes witness + let witness = self + .circom_wrapper + .extract_witness(&[("ivc_input".to_string(), input_num_bigint)]) + .map_err(|e| { + Error::WitnessCalculationError(format!("Failed to calculate witness: {}", e)) + })?; + + // Extracts the z_i1(next state) from the witness vector. + let z_i1 = witness[1..1 + self.state_len()].to_vec(); + Ok(z_i1) } - // Converts a set of constraints from ark-circom into R1CS format of folding-schemes. - pub fn convert_to_folding_schemes_r1cs( + fn generate_step_constraints( &self, - constraints: Vec>, - pub_io_len: usize, - num_variables: usize, - ) -> R1CS - where - F: PrimeField, - { - let mut a_matrix: Vec> = Vec::new(); - let mut b_matrix: Vec> = Vec::new(); - let mut c_matrix: Vec> = Vec::new(); - - let n_rows = constraints.len(); - - for (ai, bi, ci) in constraints { - a_matrix.push( - ai.into_iter() - .map(|(index, scalar)| (scalar, index)) - .collect(), - ); - b_matrix.push( - bi.into_iter() - .map(|(index, scalar)| (scalar, index)) - .collect(), - ); - c_matrix.push( - ci.into_iter() - .map(|(index, scalar)| (scalar, index)) - .collect(), - ); + cs: ConstraintSystemRef, + _i: usize, + z_i: Vec>, + ) -> Result>, SynthesisError> { + let mut input_values = Vec::new(); + // Converts each FpVar to PrimeField value, then to num_bigint::BigInt. + for fp_var in z_i.iter() { + // Extracts the PrimeField value from FpVar. + let primefield_value = fp_var.value()?; + // Converts the PrimeField value to num_bigint::BigInt. + let num_bigint_value = self + .circom_wrapper + .ark_primefield_to_num_bigint(primefield_value); + input_values.push(num_bigint_value); } - let l = pub_io_len; - let n_cols = num_variables; + let num_bigint_inputs = vec![("ivc_input".to_string(), input_values)]; - let A = SparseMatrix { - n_rows, - n_cols, - coeffs: a_matrix, - }; - let B = SparseMatrix { - n_rows, - n_cols, - coeffs: b_matrix, - }; - let C = SparseMatrix { - n_rows, - n_cols, - coeffs: c_matrix, + // Extracts R1CS and witness. + let (r1cs, witness) = self + .circom_wrapper + .extract_r1cs_and_witness(&num_bigint_inputs) + .map_err(|_| SynthesisError::AssignmentMissing)?; + + // Initializes the CircomCircuit. + let circom_circuit = CircomCircuit { + r1cs, + witness: witness.clone(), + inputs_already_computed: true, }; - R1CS:: { l, A, B, C } - } + // Generates the constraints for the circom_circuit. + circom_circuit + .generate_constraints(cs.clone()) + .map_err(|_| SynthesisError::AssignmentMissing)?; - // Calculates the witness given the Wasm filepath and inputs. - pub fn calculate_witness(&self, inputs: &[(String, Vec)]) -> Result> { - let mut calculator = WitnessCalculator::new(&self.wasm_filepath)?; - calculator.calculate_witness(inputs.iter().cloned(), true) - } + // Checks for constraint satisfaction. + if !cs.is_satisfied().unwrap() { + return Err(SynthesisError::Unsatisfiable); + } - // Converts a num_bigint input to `PrimeField`'s BigInt. - pub fn num_bigint_to_ark_bigint( - &self, - value: &BigInt, - ) -> Result> { - let big_uint = value - .to_biguint() - .ok_or_else(|| "BigInt is negative".to_string())?; - F::BigInt::try_from(big_uint).map_err(|_| "BigInt conversion failed".to_string().into()) - } + let w = witness.ok_or(SynthesisError::Unsatisfiable)?; - // Converts R1CS constraints and witness from ark-circom format - // into folding-schemes R1CS and z format. - pub fn circom_to_folding_schemes_r1cs_and_z( - &self, - constraints: Vec>, - witness: &[BigInt], - pub_io_len: usize, - num_variables: usize, - ) -> Result<(R1CS, Vec), Box> - where - F: PrimeField, - { - let folding_schemes_r1cs = - self.convert_to_folding_schemes_r1cs(constraints, pub_io_len, num_variables); - - let z: Result, _> = witness - .iter() - .map(|big_int| { - let ark_big_int = self.num_bigint_to_ark_bigint::(big_int)?; - F::from_bigint(ark_big_int).ok_or_else(|| { - "Failed to convert bigint to field element" - .to_string() - .into() - }) - }) - .collect(); - - z.map(|z| (folding_schemes_r1cs, z)) + // Extracts the z_i1(next state) from the witness vector. + let z_i1: Vec> = + Vec::>::new_witness(cs.clone(), || Ok(w[1..1 + self.state_len()].to_vec()))?; + + Ok(z_i1) } } #[cfg(test)] -mod tests { - use crate::frontend::circom::CircomWrapper; - use ark_bn254::Bn254; - use num_bigint::BigInt; - use std::env; - - /* - test_circuit represents 35 = x^3 + x + 5 in test_folder/test_circuit.circom. - In the test_circom_conversion_success function, x is assigned the value 3, which satisfies the R1CS correctly. - In the test_circom_conversion_failure function, x is assigned the value 6, which doesn't satisfy the R1CS. - */ - - /* - To generate .r1cs and .wasm files, run the below command in the terminal. - bash ./src/frontend/circom/test_folder/compile.sh - */ - - fn test_circom_conversion_logic(expect_success: bool, inputs: Vec<(String, Vec)>) { - let current_dir = env::current_dir().unwrap(); - let base_path = current_dir.join("src/frontend/circom/test_folder"); - - let r1cs_filepath = base_path.join("test_circuit.r1cs"); - let wasm_filepath = base_path.join("test_circuit_js/test_circuit.wasm"); - - assert!(r1cs_filepath.exists()); - assert!(wasm_filepath.exists()); - - let circom_wrapper = CircomWrapper::::new(r1cs_filepath, wasm_filepath); - - let (r1cs, z) = circom_wrapper - .extract_r1cs_and_z(&inputs) - .expect("Error processing input"); - - // Checks the relationship of R1CS. - let check_result = std::panic::catch_unwind(|| { - r1cs.check_relation(&z).unwrap(); - }); - - match (expect_success, check_result) { - (true, Ok(_)) => {} - (false, Err(_)) => {} - (true, Err(_)) => panic!("Expected success but got a failure."), - (false, Ok(_)) => panic!("Expected a failure but got success."), - } +pub mod tests { + use super::*; + use ark_pallas::Fr; + use ark_r1cs_std::alloc::AllocVar; + use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; + + // Tests the step_native function of CircomFCircuit. + #[test] + fn test_circom_step_native() { + let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs"); + let wasm_path = + PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm"); + + let circom_fcircuit = CircomFCircuit::::new((r1cs_path, wasm_path)); + + let z_i = vec![Fr::from(3u32)]; + let z_i1 = circom_fcircuit.step_native(1, z_i).unwrap(); + assert_eq!(z_i1, vec![Fr::from(35u32)]); } + // Tests the generate_step_constraints function of CircomFCircuit. #[test] - fn test_circom_conversion() { - // expect it to pass for correct inputs. - test_circom_conversion_logic(true, vec![("step_in".to_string(), vec![BigInt::from(3)])]); + fn test_circom_step_constraints() { + let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs"); + let wasm_path = + PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm"); + + let circom_fcircuit = CircomFCircuit::::new((r1cs_path, wasm_path)); + + let cs = ConstraintSystem::::new_ref(); + + let z_i = vec![Fr::from(3u32)]; + + let z_i_var = Vec::>::new_witness(cs.clone(), || Ok(z_i)).unwrap(); + + let cs = ConstraintSystem::::new_ref(); + let z_i1_var = circom_fcircuit + .generate_step_constraints(cs.clone(), 1, z_i_var) + .unwrap(); + assert_eq!(z_i1_var.value().unwrap(), vec![Fr::from(35u32)]); + } + + // Tests the WrapperCircuit with CircomFCircuit. + #[test] + fn test_wrapper_circomtofcircuit() { + let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs"); + let wasm_path = + PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm"); + + let circom_fcircuit = CircomFCircuit::::new((r1cs_path, wasm_path)); + + // Allocates z_i1 by using step_native function. + let z_i = vec![Fr::from(3_u32)]; + let wrapper_circuit = crate::frontend::tests::WrapperCircuit { + FC: circom_fcircuit.clone(), + z_i: Some(z_i.clone()), + z_i1: Some(circom_fcircuit.step_native(0, z_i.clone()).unwrap()), + }; + + let cs = ConstraintSystem::::new_ref(); - // expect it to fail for incorrect inputs. - test_circom_conversion_logic(false, vec![("step_in".to_string(), vec![BigInt::from(7)])]); + wrapper_circuit.generate_constraints(cs.clone()).unwrap(); + assert!( + cs.is_satisfied().unwrap(), + "Constraint system is not satisfied" + ); } } diff --git a/folding-schemes/src/frontend/circom/test_folder/compile.sh b/folding-schemes/src/frontend/circom/test_folder/compile.sh old mode 100644 new mode 100755 index 2db5715..a714797 --- a/folding-schemes/src/frontend/circom/test_folder/compile.sh +++ b/folding-schemes/src/frontend/circom/test_folder/compile.sh @@ -1,2 +1,2 @@ #!/bin/bash -circom ./folding-schemes/src/frontend/circom/test_folder/test_circuit.circom --r1cs --wasm --prime bn128 --output ./folding-schemes/src/frontend/circom/test_folder/ \ No newline at end of file +circom ./folding-schemes/src/frontend/circom/test_folder/cubic_circuit.circom --r1cs --wasm --prime bn128 --output ./folding-schemes/src/frontend/circom/test_folder/ \ No newline at end of file diff --git a/folding-schemes/src/frontend/circom/test_folder/cubic_circuit.circom b/folding-schemes/src/frontend/circom/test_folder/cubic_circuit.circom new file mode 100644 index 0000000..247cdb3 --- /dev/null +++ b/folding-schemes/src/frontend/circom/test_folder/cubic_circuit.circom @@ -0,0 +1,13 @@ +pragma circom 2.0.3; + +template Example () { + signal input ivc_input[1]; + signal output ivc_output[1]; + signal temp; + + temp <== ivc_input[0] * ivc_input[0]; + ivc_output[0] <== temp * ivc_input[0] + ivc_input[0] + 5; +} + +component main {public [ivc_input]} = Example(); + diff --git a/folding-schemes/src/frontend/circom/test_folder/test_circuit.circom b/folding-schemes/src/frontend/circom/test_folder/test_circuit.circom deleted file mode 100644 index aa17932..0000000 --- a/folding-schemes/src/frontend/circom/test_folder/test_circuit.circom +++ /dev/null @@ -1,13 +0,0 @@ -pragma circom 2.0.3; - -template Example () { - signal input step_in; - signal output step_out; - signal temp; - - temp <== step_in * step_in; - step_out <== temp * step_in + step_in + 5; - step_out === 35; -} - -component main = Example(); diff --git a/folding-schemes/src/frontend/circom/utils.rs b/folding-schemes/src/frontend/circom/utils.rs new file mode 100644 index 0000000..7aba643 --- /dev/null +++ b/folding-schemes/src/frontend/circom/utils.rs @@ -0,0 +1,149 @@ +use ark_circom::{ + circom::{r1cs_reader, R1CS}, + WitnessCalculator, +}; +use ark_ff::{BigInteger, PrimeField}; +use color_eyre::Result; +use num_bigint::{BigInt, Sign}; +use std::{fs::File, io::BufReader, marker::PhantomData, path::PathBuf}; + +use crate::Error; + +// A struct that wraps Circom functionalities, allowing for extraction of R1CS and witnesses +// based on file paths to Circom's .r1cs and .wasm. +#[derive(Clone, Debug)] +pub struct CircomWrapper { + r1cs_filepath: PathBuf, + wasm_filepath: PathBuf, + _marker: PhantomData, +} + +impl CircomWrapper { + // Creates a new instance of the CircomWrapper with the file paths. + pub fn new(r1cs_filepath: PathBuf, wasm_filepath: PathBuf) -> Self { + CircomWrapper { + r1cs_filepath, + wasm_filepath, + _marker: PhantomData, + } + } + + // Aggregated function to obtain R1CS and witness from Circom. + pub fn extract_r1cs_and_witness( + &self, + inputs: &[(String, Vec)], + ) -> Result<(R1CS, Option>), Error> { + // Extracts the R1CS + let file = File::open(&self.r1cs_filepath)?; + let reader = BufReader::new(file); + let r1cs_file = r1cs_reader::R1CSFile::::new(reader)?; + let r1cs = r1cs_reader::R1CS::::from(r1cs_file); + + // Extracts the witness vector + let witness_vec = self.extract_witness(inputs)?; + + Ok((r1cs, Some(witness_vec))) + } + + // Extracts the witness vector as a vector of PrimeField elements. + pub fn extract_witness(&self, inputs: &[(String, Vec)]) -> Result, Error> { + let witness_bigint = self.calculate_witness(inputs)?; + + witness_bigint + .iter() + .map(|big_int| { + self.num_bigint_to_ark_bigint(big_int) + .and_then(|ark_big_int| { + F::from_bigint(ark_big_int) + .ok_or_else(|| Error::Other("could not get F from bigint".to_string())) + }) + }) + .collect() + } + + // Calculates the witness given the Wasm filepath and inputs. + pub fn calculate_witness( + &self, + inputs: &[(String, Vec)], + ) -> Result, Error> { + let mut calculator = WitnessCalculator::new(&self.wasm_filepath).map_err(|e| { + Error::WitnessCalculationError(format!("Failed to create WitnessCalculator: {}", e)) + })?; + calculator + .calculate_witness(inputs.iter().cloned(), true) + .map_err(|e| { + Error::WitnessCalculationError(format!("Failed to calculate witness: {}", e)) + }) + } + + // Converts a num_bigint::BigInt to a PrimeField::BigInt. + pub fn num_bigint_to_ark_bigint(&self, value: &BigInt) -> Result { + let big_uint = value + .to_biguint() + .ok_or_else(|| Error::BigIntConversionError("BigInt is negative".to_string()))?; + F::BigInt::try_from(big_uint).map_err(|_| { + Error::BigIntConversionError("Failed to convert to PrimeField::BigInt".to_string()) + }) + } + + // Converts a PrimeField element to a num_bigint::BigInt representation. + pub fn ark_primefield_to_num_bigint(&self, value: F) -> BigInt { + let primefield_bigint: F::BigInt = value.into_bigint(); + let bytes = primefield_bigint.to_bytes_be(); + BigInt::from_bytes_be(Sign::Plus, &bytes) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_bn254::Fr; + use ark_circom::circom::{CircomBuilder, CircomConfig}; + use ark_circom::CircomCircuit; + use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; + use std::path::PathBuf; + + //To generate .r1cs and .wasm files, run the below command in the terminal. + //bash ./folding-schemes/src/frontend/circom/test_folder/compile.sh + + // Test the satisfication by using the CircomBuilder of circom-compat + #[test] + fn test_circombuilder_satisfied() { + let cfg = CircomConfig::::new( + "./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm", + "./src/frontend/circom/test_folder/cubic_circuit.r1cs", + ) + .unwrap(); + let mut builder = CircomBuilder::new(cfg); + builder.push_input("ivc_input", 3); + + let circom = builder.build().unwrap(); + let cs = ConstraintSystem::::new_ref(); + circom.generate_constraints(cs.clone()).unwrap(); + assert!(cs.is_satisfied().unwrap()); + } + + // Test the satisfication by using the CircomWrapper + #[test] + fn test_extract_r1cs_and_witness() { + let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs"); + let wasm_path = + PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm"); + + let inputs = vec![("ivc_input".to_string(), vec![BigInt::from(3)])]; + let wrapper = CircomWrapper::::new(r1cs_path, wasm_path); + + let (r1cs, witness) = wrapper.extract_r1cs_and_witness(&inputs).unwrap(); + + let cs = ConstraintSystem::::new_ref(); + + let circom_circuit = CircomCircuit { + r1cs, + witness, + inputs_already_computed: false, + }; + + circom_circuit.generate_constraints(cs.clone()).unwrap(); + assert!(cs.is_satisfied().unwrap()); + } +} diff --git a/folding-schemes/src/frontend/mod.rs b/folding-schemes/src/frontend/mod.rs index 3990f46..96f4e38 100644 --- a/folding-schemes/src/frontend/mod.rs +++ b/folding-schemes/src/frontend/mod.rs @@ -1,11 +1,11 @@ -pub mod circom; - use crate::Error; use ark_ff::PrimeField; use ark_r1cs_std::fields::fp::FpVar; use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; use ark_std::fmt::Debug; +pub mod circom; + /// FCircuit defines the trait of the circuit of the F function, which is the one being folded (ie. /// inside the agmented F' function). /// The parameter z_i denotes the current state, and z_{i+1} denotes the next state after applying @@ -44,7 +44,7 @@ pub trait FCircuit: Clone + Debug { #[cfg(test)] pub mod tests { use super::*; - use ark_pallas::Fr; + use ark_bn254::Fr; use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget}; use ark_relations::r1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, diff --git a/folding-schemes/src/lib.rs b/folding-schemes/src/lib.rs index 7a06a38..91d120a 100644 --- a/folding-schemes/src/lib.rs +++ b/folding-schemes/src/lib.rs @@ -32,6 +32,8 @@ pub enum Error { ArithError(#[from] utils::espresso::virtual_polynomial::ArithErrors), #[error(transparent)] ProtoGalaxy(folding::protogalaxy::ProtoGalaxyError), + #[error("std::io::Error")] + IOError(#[from] std::io::Error), #[error("{0}")] Other(String), @@ -86,6 +88,10 @@ pub enum Error { NotSupported(String), #[error("max i-th step reached (usize limit reached)")] MaxStep, + #[error("Circom Witness calculation error: {0}")] + WitnessCalculationError(String), + #[error("BigInt to PrimeField conversion error: {0}")] + BigIntConversionError(String), } /// FoldingScheme defines trait that is implemented by the diverse folding schemes. It is defined