From 36156314f54eaa680c5c315eeb7cc4364715eab3 Mon Sep 17 00:00:00 2001 From: j-berman Date: Tue, 14 Jan 2025 20:00:29 -0800 Subject: [PATCH] Multi-input FCMP++ proofs over FFI working --- src/fcmp_pp/fcmp_pp_rust/fcmp++.h | 28 ++-- src/fcmp_pp/fcmp_pp_rust/src/lib.rs | 238 ++++++++++++++++++---------- src/fcmp_pp/tower_cycle.cpp | 60 ++++--- src/fcmp_pp/tower_cycle.h | 30 ++-- tests/unit_tests/fcmp_pp.cpp | 39 +++-- 5 files changed, 255 insertions(+), 140 deletions(-) diff --git a/src/fcmp_pp/fcmp_pp_rust/fcmp++.h b/src/fcmp_pp/fcmp_pp_rust/fcmp++.h index 7f285ad0bf7..3d299c9d0ca 100644 --- a/src/fcmp_pp/fcmp_pp_rust/fcmp++.h +++ b/src/fcmp_pp/fcmp_pp_rust/fcmp++.h @@ -117,8 +117,14 @@ using BranchBlindSlice = Slice; using Ed25519ScalarBytes = uint8_t *; +using Path = uint8_t *; + using TreeRoot = uint8_t *; +using FcmpProveInput = uint8_t *; + +using FcmpProveInputSlice = Slice; + extern "C" { HeliosPoint helios_hash_init_point(); @@ -182,24 +188,28 @@ CResult blind_i_blind(Blind i_blind); CResult blind_i_blind_blind(Blind i_blind_blind); CResult blind_c_blind(Blind c_blind); +CResult path_new(OutputSlice leaves, + uintptr_t output_idx, + HeliosScalarChunks helios_layer_chunks, + SeleneScalarChunks selene_layer_chunks); + CResult output_blinds_new(Blind o_blind, Blind i_blind, Blind i_blind_blind, Blind c_blind); -CResult helios_branch_blind(); -CResult selene_branch_blind(); - -void prove(Ed25519ScalarBytes x, +CResult fcmp_prove_input_new(Ed25519ScalarBytes x, Ed25519ScalarBytes y, - uintptr_t leaf_idx, - OutputSlice leaves, - HeliosScalarChunks helios_layer_chunks, - SeleneScalarChunks selene_layer_chunks, RerandomizedOutput rerandomized_output, + Path path, OutputBlinds output_blinds, BranchBlindSlice helios_branch_blinds, - BranchBlindSlice selene_branch_blinds, + BranchBlindSlice selene_branch_blinds); + +CResult helios_branch_blind(); +CResult selene_branch_blind(); + +void prove(FcmpProveInputSlice fcmp_prove_inputs, TreeRoot tree_root); } // extern "C" diff --git a/src/fcmp_pp/fcmp_pp_rust/src/lib.rs b/src/fcmp_pp/fcmp_pp_rust/src/lib.rs index 368d9574f19..1aea97f2587 100644 --- a/src/fcmp_pp/fcmp_pp_rust/src/lib.rs +++ b/src/fcmp_pp/fcmp_pp_rust/src/lib.rs @@ -18,7 +18,7 @@ use full_chain_membership_proofs::tree::{hash_grow, hash_trim}; use monero_fcmp_plus_plus::{ HELIOS_HASH_INIT, SELENE_HASH_INIT, HELIOS_GENERATORS, SELENE_GENERATORS, FCMP_PARAMS, Curves, - fcmps::{TreeRoot, Path, Branches, BranchBlind, BranchesWithBlinds, OBlind, IBlind, IBlindBlind, CBlind, OutputBlinds, Fcmp}, + fcmps::{TreeRoot, Path, Branches, BranchBlind, OBlind, IBlind, IBlindBlind, CBlind, OutputBlinds, Fcmp}, FcmpPlusPlus, Output, sal::{RerandomizedOutput, OpenedInputTuple, SpendAuthAndLinkability}, }; @@ -29,6 +29,62 @@ use monero_generators::{T, FCMP_U, FCMP_V}; //-------------------------------------------------------------------------------------- Path +#[no_mangle] +pub unsafe extern "C" fn path_new( + leaves: OutputSlice, + output_idx: usize, + helios_layer_chunks: HeliosScalarChunks, + selene_layer_chunks: SeleneScalarChunks, +) -> CResult, ()>{ + // Collect decompressed leaves + let leaves: &[OutputBytes] = leaves.into(); + let leaves: Vec = leaves + .iter() + .map(|x| { + // TODO: don't unwrap, error + Output::new( + ed25519_point_from_bytes(x.O), + ed25519_point_from_bytes(x.I), + ed25519_point_from_bytes(x.C), + ) + .unwrap() + }) + .collect(); + + // Output + if output_idx >= leaves.len() { + // TODO: return error instead of panic + panic!("output_idx is too high"); + } + let output = leaves[output_idx].clone(); + + // Collect helios layer chunks + let helios_layers: &[HeliosScalarSlice] = helios_layer_chunks.into(); + let helios_layers: Vec::F>> = helios_layers + .iter() + .map(|x| { + let inner_slice: &[::F] = x.into(); + inner_slice.iter().map(|y| y.to_owned()).collect() + }) + .collect(); + + // Collect selene layer chunks + let selene_layers: &[SeleneScalarSlice] = selene_layer_chunks.into(); + let selene_layers: Vec::F>> = selene_layers + .iter() + .map(|x| { + let inner_slice: &[::F] = x.into(); + inner_slice.iter().map(|y| y.to_owned()).collect() + }) + .collect(); + + let curve_2_layers = helios_layers; + let curve_1_layers = selene_layers; + + let path: Path = Path { output, leaves, curve_2_layers, curve_1_layers }; + CResult::ok(path) +} + //-------------------------------------------------------------------------------------- Fcmp /// # Safety @@ -541,25 +597,25 @@ pub extern "C" fn hash_trim_selene( #[no_mangle] pub extern "C" fn o_blind(output: *const RerandomizedOutput) -> CResult { - let rerandomized_output = unsafe { (*output).clone() }; + let rerandomized_output = unsafe { output.read() }; CResult::ok(rerandomized_output.o_blind()) } #[no_mangle] pub extern "C" fn i_blind(output: *const RerandomizedOutput) -> CResult { - let rerandomized_output = unsafe { (*output).clone() }; + let rerandomized_output = unsafe { output.read() }; CResult::ok(rerandomized_output.i_blind()) } #[no_mangle] pub extern "C" fn i_blind_blind(output: *const RerandomizedOutput) -> CResult { - let rerandomized_output = unsafe { (*output).clone() }; + let rerandomized_output = unsafe { output.read() }; CResult::ok(rerandomized_output.i_blind_blind()) } #[no_mangle] pub extern "C" fn c_blind(output: *const RerandomizedOutput) -> CResult { - let rerandomized_output = unsafe { (*output).clone() }; + let rerandomized_output = unsafe { output.read() }; CResult::ok(rerandomized_output.c_blind()) } @@ -578,126 +634,132 @@ pub extern "C" fn rerandomize_output(output: OutputBytes) -> CResult, + output_blinds: OutputBlinds, + c1_branch_blinds: Vec::G>>, + c2_branch_blinds: Vec::G>>, +} + +pub type FcmpProveInputSlice = Slice<*const FcmpProveInput>; + #[no_mangle] -pub extern "C" fn prove(// TODO: signable_tx_hash, - x: *const u8, +pub extern "C" fn fcmp_prove_input_new(x: *const u8, y: *const u8, - output_idx: usize, - leaves: OutputSlice, - helios_layer_chunks: HeliosScalarChunks, - selene_layer_chunks: SeleneScalarChunks, - // TODO: make sure these aren't getting consumed rerandomized_output: *const RerandomizedOutput, + path: *const Path, output_blinds: *const OutputBlinds, helios_branch_blinds: HeliosBranchBlindSlice, selene_branch_blinds: SeleneBranchBlindSlice, +) -> CResult { + // SAL + let x = ed25519_scalar_from_bytes(x); + let y = ed25519_scalar_from_bytes(y); + let rerandomized_output = unsafe { rerandomized_output.read() }; + let sal_input = SalInput { x, y, rerandomized_output }; + + // Path and output blinds + let path = unsafe { path.read() }; + let output_blinds = unsafe { output_blinds.read() }; + + // Collect branch blinds + let c1_branch_blinds: &[*const BranchBlind::<::G>] = selene_branch_blinds.into(); + let c1_branch_blinds: Vec::G>> = c1_branch_blinds + .iter() + .map(|x| unsafe { (*x.to_owned()).clone() }) + .collect(); + + let c2_branch_blinds: &[*const BranchBlind::<::G>] = helios_branch_blinds.into(); + let c2_branch_blinds: Vec::G>> = c2_branch_blinds + .iter() + .map(|x| unsafe { (*x.to_owned()).clone() }) + .collect(); + + let fcmp_prove_input = FcmpProveInput { sal_input, path, output_blinds, c1_branch_blinds, c2_branch_blinds }; + CResult::ok(fcmp_prove_input) +} + +#[no_mangle] +pub extern "C" fn prove(// TODO: signable_tx_hash, + inputs: FcmpProveInputSlice, // TODO: tree_root is only used for verify, remove it tree_root: *const TreeRoot, ) { // TODO: pass signable_tx_hash as param let signable_tx_hash = [0; 32]; - // Leaves - let leaves: &[OutputBytes] = leaves.into(); - let leaves: Vec = leaves + // Collect inputs into a vec + let inputs: &[*const FcmpProveInput] = inputs.into(); + let inputs: Vec = inputs .iter() - .map(|x| { - // TODO: don't unwrap, error - Output::new( - ed25519_point_from_bytes(x.O), - ed25519_point_from_bytes(x.I), - ed25519_point_from_bytes(x.C), - ) - .unwrap() - }) + .map(|x| unsafe { x.read() }) .collect(); - // Output - if output_idx >= leaves.len() { - // TODO: return error instead of panic - panic!("output_idx is too high"); - } - let output = leaves[output_idx].clone(); + // SAL proofs + let mut key_images: Vec = vec![]; + let sal_proofs = inputs.iter().map(|prove_input| { + let sal_input = &prove_input.sal_input; + let rerandomized_output = &sal_input.rerandomized_output; + let x = sal_input.x; + let y = sal_input.y; - // SAL proof - let (input, spend_auth_and_linkability, L) = { - let x = ed25519_scalar_from_bytes(x); - let y = ed25519_scalar_from_bytes(y); - let rerandomized_output = unsafe { (*rerandomized_output).clone() }; let input = rerandomized_output.input(); - let opening = OpenedInputTuple::open(rerandomized_output, &x, &y).unwrap(); + let opening = OpenedInputTuple::open(rerandomized_output.clone(), &x, &y).unwrap(); + + #[allow(non_snake_case)] let (L, spend_auth_and_linkability) = SpendAuthAndLinkability::prove(&mut OsRng, signable_tx_hash.clone(), opening); - assert_eq!(output.I() * x, L); - (input, spend_auth_and_linkability, L) - }; - - // Helios layer chunks - let helios_layers: &[HeliosScalarSlice] = helios_layer_chunks.into(); - let helios_layers: Vec::F>> = helios_layers - .iter() - .map(|x| { - let inner_slice: &[::F] = x.into(); - inner_slice.iter().map(|y| y.to_owned()).collect() - }) - .collect(); - // Selene layer chunks - let selene_layers: &[SeleneScalarSlice] = selene_layer_chunks.into(); - let selene_layers: Vec::F>> = selene_layers - .iter() - .map(|x| { - let inner_slice: &[::F] = x.into(); - inner_slice.iter().map(|y| y.to_owned()).collect() - }) - .collect(); + // assert_eq!(prove_input.path.output.O(), EdwardsPoint((*x * *EdwardsPoint::generator()) + (*y * *EdwardsPoint(T())))); + assert_eq!(prove_input.path.output.I() * x, L); - let curve_2_layers = helios_layers; - let curve_1_layers = selene_layers; + key_images.push(L); + (input, spend_auth_and_linkability) + }).collect(); - let path: Path = Path { output, leaves, curve_2_layers, curve_1_layers }; - let branches = Branches::new(vec![path]).unwrap(); - - let output_blinds = unsafe { (*output_blinds).clone() }; + let paths = inputs.iter().map(|x| x.path.clone()).collect(); + let output_blinds = inputs.iter().map(|x| x.output_blinds.clone()).collect(); - // Collect branch blinds - let c1_branch_blinds: &[*const BranchBlind::<::G>] = selene_branch_blinds.into(); - let c1_branch_blinds: Vec::G>> = c1_branch_blinds - .iter() - .map(|x| unsafe { (*x.to_owned()).clone() }) - .collect(); + let c1_branch_blinds: Vec<_> = inputs.iter().map(|x| x.c1_branch_blinds.clone()).collect::>().into_iter().flatten().collect(); + let c2_branch_blinds: Vec<_> = inputs.iter().map(|x| x.c2_branch_blinds.clone()).collect::>().into_iter().flatten().collect(); - let c2_branch_blinds: &[*const BranchBlind::<::G>] = helios_branch_blinds.into(); - let c2_branch_blinds: Vec::G>> = c2_branch_blinds - .iter() - .map(|x| unsafe { (*x.to_owned()).clone() }) - .collect(); + let branches = Branches::new(paths).unwrap(); - // Blind branches with branch blinds assert_eq!(branches.necessary_c1_blinds(), c1_branch_blinds.len()); assert_eq!(branches.necessary_c2_blinds(), c2_branch_blinds.len()); - let n_layers = c1_branch_blinds.len() + c2_branch_blinds.len(); - let blinded_branches = branches.blind(vec![output_blinds], c1_branch_blinds, c2_branch_blinds).unwrap(); + let n_branch_blinds = (c1_branch_blinds.len() + c2_branch_blinds.len()) / inputs.len(); + + let blinded_branches = branches.blind(output_blinds, c1_branch_blinds, c2_branch_blinds).unwrap(); // Membership proof let fcmp = Fcmp::prove( - &mut OsRng, - FCMP_PARAMS(), - blinded_branches, - ).unwrap(); + &mut OsRng, + FCMP_PARAMS(), + blinded_branches, + ).unwrap(); - // Combine SAL and membership proof - let fcmp_plus_plus = FcmpPlusPlus::new(vec![(input, spend_auth_and_linkability)], fcmp); + // Combine SAL proofs and membership proof + let fcmp_plus_plus = FcmpPlusPlus::new(sal_proofs, fcmp); // let mut buf = vec![]; // fcmp_plus_plus.write(&mut buf).unwrap(); - // TODO: remove everything below let mut ed_verifier = multiexp::BatchVerifier::new(1); let mut c1_verifier = generalized_bulletproofs::Generators::batch_verifier(); let mut c2_verifier = generalized_bulletproofs::Generators::batch_verifier(); - let tree_root: TreeRoot = unsafe { (*tree_root).clone() }; + let tree_root: TreeRoot = unsafe { tree_root.read() }; + + let n_layers = 1 + n_branch_blinds; // TODO: remove verify fcmp_plus_plus @@ -707,9 +769,9 @@ pub extern "C" fn prove(// TODO: signable_tx_hash, &mut c1_verifier, &mut c2_verifier, tree_root, - 1 + n_layers, + n_layers, signable_tx_hash, - vec![L], + key_images, ) .unwrap(); diff --git a/src/fcmp_pp/tower_cycle.cpp b/src/fcmp_pp/tower_cycle.cpp index 355f38ff399..7c72282db58 100644 --- a/src/fcmp_pp/tower_cycle.cpp +++ b/src/fcmp_pp/tower_cycle.cpp @@ -360,10 +360,24 @@ BlindedPoint blind_c_blind(const Blind c_blind) return handle_blinded_res(__func__, res); } -OutputBlinds output_blinds_new(const Blind blinded_o_blind, - const Blind blinded_i_blind, - const Blind blinded_i_blind_blind, - const Blind blinded_c_blind) +Path path_new(const OutputChunk &leaves, + std::size_t output_idx, + const Helios::ScalarChunks &helios_layer_chunks, + const Selene::ScalarChunks &selene_layer_chunks) +{ + auto res = fcmp_pp_rust::path_new(leaves, output_idx, helios_layer_chunks, selene_layer_chunks); + if (res.err != nullptr) + { + free(res.err); + throw std::runtime_error("failed to get new path"); + } + return (Path) res.value; +} + +OutputBlinds output_blinds_new(const BlindedPoint blinded_o_blind, + const BlindedPoint blinded_i_blind, + const BlindedPoint blinded_i_blind_blind, + const BlindedPoint blinded_c_blind) { auto res = fcmp_pp_rust::output_blinds_new(blinded_o_blind, blinded_i_blind, blinded_i_blind_blind, blinded_c_blind); if (res.err != nullptr) @@ -396,29 +410,35 @@ BranchBlind selene_branch_blind() return handle_branch_blind(__func__, res); } -void prove(const Ed25519Scalar x, +FcmpProveInput fcmp_prove_input_new(const Ed25519Scalar x, const Ed25519Scalar y, - std::size_t output_idx, - const OutputChunk &leaves, - const Helios::ScalarChunks &c1_layer_chunks, - const Selene::ScalarChunks &c2_layer_chunks, const RerandomizedOutput rerandomized_output, + const Path path, const OutputBlinds output_blinds, - const BranchBlinds &c1_branch_blinds, - const BranchBlinds &c2_branch_blinds, - const TreeRoot tree_root) + const BranchBlinds &helios_branch_blinds, + const BranchBlinds &selene_branch_blinds) { - fcmp_pp_rust::prove(x, + auto res = fcmp_pp_rust::fcmp_prove_input_new(x, y, - output_idx, - leaves, - c1_layer_chunks, - c2_layer_chunks, rerandomized_output, + path, output_blinds, - c1_branch_blinds, - c2_branch_blinds, - tree_root); + helios_branch_blinds, + selene_branch_blinds); + + if (res.err != nullptr) + { + free(res.err); + throw std::runtime_error("failed to get new FCMP++ prove input"); + } + + return (FcmpProveInput) res.value; +} + +void prove(const FcmpProveInputs fcmp_prove_inputs, + const TreeRoot tree_root) +{ + fcmp_pp_rust::prove(fcmp_prove_inputs, tree_root); } //---------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------- diff --git a/src/fcmp_pp/tower_cycle.h b/src/fcmp_pp/tower_cycle.h index aa7b554376a..2e97b49ab8a 100644 --- a/src/fcmp_pp/tower_cycle.h +++ b/src/fcmp_pp/tower_cycle.h @@ -51,7 +51,11 @@ using OutputBlinds = fcmp_pp_rust::OutputBlinds; using BranchBlind = fcmp_pp_rust::BranchBlind; using BranchBlinds = fcmp_pp_rust::BranchBlindSlice; using Ed25519Scalar = fcmp_pp_rust::Ed25519ScalarBytes; +using Path = fcmp_pp_rust::Path; using TreeRoot = fcmp_pp_rust::TreeRoot; + +using FcmpProveInput = fcmp_pp_rust::FcmpProveInput; +using FcmpProveInputs = fcmp_pp_rust::FcmpProveInputSlice; //---------------------------------------------------------------------------------------------------------------------- // Need to forward declare Scalar types for point_to_cycle_scalar below using SeleneScalar = fcmp_pp_rust::SeleneScalar; @@ -217,24 +221,28 @@ BlindedPoint blind_i_blind(const Blind i_blind); BlindedPoint blind_i_blind_blind(const Blind i_blind_blind); BlindedPoint blind_c_blind(const Blind c_blind); -OutputBlinds output_blinds_new(const Blind blinded_o_blind, - const Blind blinded_i_blind, - const Blind blinded_i_blind_blind, - const Blind blinded_c_blind); +Path path_new(const OutputChunk &leaves, + std::size_t output_idx, + const Helios::ScalarChunks &helios_layer_chunks, + const Selene::ScalarChunks &selene_layer_chunks); + +OutputBlinds output_blinds_new(const BlindedPoint blinded_o_blind, + const BlindedPoint blinded_i_blind, + const BlindedPoint blinded_i_blind_blind, + const BlindedPoint blinded_c_blind); BranchBlind helios_branch_blind(); BranchBlind selene_branch_blind(); -void prove(const Ed25519Scalar x, +FcmpProveInput fcmp_prove_input_new(const Ed25519Scalar x, const Ed25519Scalar y, - std::size_t output_idx, - const OutputChunk &leaves, - const Helios::ScalarChunks &c1_layer_chunks, - const Selene::ScalarChunks &c2_layer_chunks, const RerandomizedOutput rerandomized_output, + const Path path, const OutputBlinds output_blinds, - const BranchBlinds &c1_branch_blinds, - const BranchBlinds &c2_branch_blinds, + const BranchBlinds &helios_branch_blinds, + const BranchBlinds &selene_branch_blinds); + +void prove(const FcmpProveInputs fcmp_prove_inputs, const TreeRoot tree_root); //---------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------- diff --git a/tests/unit_tests/fcmp_pp.cpp b/tests/unit_tests/fcmp_pp.cpp index 264c3d2c4a4..ee81e61c047 100644 --- a/tests/unit_tests/fcmp_pp.cpp +++ b/tests/unit_tests/fcmp_pp.cpp @@ -90,6 +90,8 @@ const OutputContextsAndKeys generate_random_outputs(const CurveTreesV1 &curve_tr //---------------------------------------------------------------------------------------------------------------------- TEST(fcmp_pp, prove) { + static const std::size_t N_INPUTS = 2; + // TODO: fix C++ to match layer widths with rust side. These are mixed because I switched it for C++ static const std::size_t helios_chunk_width = fcmp_pp::curve_trees::SELENE_CHUNK_WIDTH; static const std::size_t selene_chunk_width = fcmp_pp::curve_trees::HELIOS_CHUNK_WIDTH; @@ -120,10 +122,12 @@ TEST(fcmp_pp, prove) std::vector helios_branch_blinds; std::vector selene_branch_blinds; + std::vector fcmp_prove_inputs; + // Create proof for every leaf in the tree for (std::size_t leaf_idx = 0; leaf_idx < global_tree.get_n_leaf_tuples(); ++leaf_idx) { - LOG_PRINT_L1("Constructing proof for leaf idx " << leaf_idx); + LOG_PRINT_L1("Constructing proof inputs for leaf idx " << leaf_idx); const auto path = global_tree.get_path_at_leaf_idx(leaf_idx); const std::size_t output_idx = leaf_idx % curve_trees->m_c2_width; @@ -148,6 +152,8 @@ TEST(fcmp_pp, prove) } const fcmp_pp::tower_cycle::OutputChunk leaves{output_bytes.data(), output_bytes.size()}; + const auto rerandomized_output = fcmp_pp::tower_cycle::rerandomize_output(output_bytes[output_idx]); + // helios scalars from selene points std::vector> helios_scalars; std::vector helios_chunks; @@ -188,7 +194,10 @@ TEST(fcmp_pp, prove) } const Selene::ScalarChunks selene_scalar_chunks{selene_chunks.data(), selene_chunks.size()}; - const auto rerandomized_output = fcmp_pp::tower_cycle::rerandomize_output(output_bytes[output_idx]); + const auto path_rust = fcmp_pp::tower_cycle::path_new(leaves, + output_idx, + helios_scalar_chunks, + selene_scalar_chunks); // Collect blinds for rerandomized output const auto o_blind = fcmp_pp::tower_cycle::o_blind(rerandomized_output); @@ -215,18 +224,24 @@ TEST(fcmp_pp, prove) for (std::size_t i = 0; i < helios_scalars.size(); ++i) selene_branch_blinds.emplace_back(fcmp_pp::tower_cycle::selene_branch_blind()); - fcmp_pp::tower_cycle::prove(x, - y, - output_idx, - leaves, - helios_scalar_chunks, - selene_scalar_chunks, - rerandomized_output, - output_blinds, - {helios_branch_blinds.data(), helios_branch_blinds.size()}, - {selene_branch_blinds.data(), selene_branch_blinds.size()}, + auto fcmp_prove_input = fcmp_pp::tower_cycle::fcmp_prove_input_new(x, + y, + rerandomized_output, + path_rust, + output_blinds, + {helios_branch_blinds.data(), helios_branch_blinds.size()}, + {selene_branch_blinds.data(), selene_branch_blinds.size()}); + + fcmp_prove_inputs.emplace_back(std::move(fcmp_prove_input)); + if (fcmp_prove_inputs.size() < N_INPUTS) + continue; + + LOG_PRINT_L1("Constructing proof"); + fcmp_pp::tower_cycle::prove( + {fcmp_prove_inputs.data(), fcmp_prove_inputs.size()}, tree_root ); + fcmp_prove_inputs.clear(); } } //----------------------------------------------------------------------------------------------------------------------