From 9377a42099d36552a87abc50272f81458f0febd2 Mon Sep 17 00:00:00 2001 From: Sosthene Date: Mon, 30 Jun 2025 22:32:46 +0200 Subject: [PATCH] Add MerkleProofResult and validate_merkle_proof() --- src/api.rs | 79 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 17 deletions(-) diff --git a/src/api.rs b/src/api.rs index 485edd9..4ce15ba 100644 --- a/src/api.rs +++ b/src/api.rs @@ -21,10 +21,10 @@ use anyhow::Error as AnyhowError; use anyhow::Result as AnyhowResult; use sdk_common::aes_gcm::aead::{Aead, Payload}; use sdk_common::crypto::{ - decrypt_with_key, encrypt_with_key, generate_key, AeadCore, Aes256Gcm, AnkSharedSecretHash, KeyInit, AAD + decrypt_with_key, encrypt_with_key, generate_key, verify_merkle_proof, AeadCore, Aes256Gcm, AnkSharedSecretHash, KeyInit, AAD }; use sdk_common::process::{check_tx_for_process_updates, lock_processes, Process, ProcessState}; -use sdk_common::serialization::{OutPointMemberMap, OutPointProcessMap, ciborium_deserialize as common_deserialize}; +use sdk_common::serialization::{OutPointMemberMap, OutPointProcessMap}; use sdk_common::signature::{AnkHash, AnkMessageHash, AnkValidationNoHash, AnkValidationYesHash, Proof}; use sdk_common::sp_client::bitcoin::blockdata::fee_rate; use sdk_common::sp_client::bitcoin::consensus::{deserialize, serialize}; @@ -1739,6 +1739,18 @@ pub fn hash_value(value: JsValue, commited_in: String, label: String) -> ApiResu let hash = AnkPcdHash::from_pcd_value(encoded_value.as_slice(), label.as_bytes(), &outpoint); Ok(hash.as_byte_array().to_lower_hex_string()) } + +#[derive(Tsify, Serialize, Deserialize)] +#[tsify(into_wasm_abi, from_wasm_abi)] +#[allow(non_camel_case_types)] +pub struct MerkleProofResult { + pub proof: String, + pub root: String, + pub attribute: String, + pub attribute_index: usize, + pub total_leaves_count: usize, +} + #[wasm_bindgen] /// Generate a merkle proof for a specific attribute in a process state. /// @@ -1751,16 +1763,17 @@ pub fn hash_value(value: JsValue, commited_in: String, label: String) -> ApiResu /// * `attribute_name` - The name of the attribute to generate a proof for /// /// # Returns -/// A JavaScript object containing: +/// A MerkleProofResult object containing: /// * `proof` - The merkle proof as a hex string /// * `root` - The merkle root (state_id) as a hex string /// * `attribute` - The attribute name that was proven /// * `attribute_index` - The index of the attribute in the merkle tree +/// * `total_leaves_count` - The total number of leaves in the merkle tree /// /// # Errors /// * "Failed to deserialize process state" - If the process state cannot be deserialized from JsValue /// * "Attribute not found in state" - If the attribute doesn't exist in the state -pub fn get_merkle_proof(process_state: JsValue, attribute_name: String) -> ApiResult { +pub fn get_merkle_proof(process_state: JsValue, attribute_name: String) -> ApiResult { // Deserialize the process state from JsValue let state: ProcessState = serde_wasm_bindgen::from_value(process_state) .map_err(|_| ApiError::new("Failed to deserialize process state".to_owned()))?; @@ -1771,7 +1784,7 @@ pub fn get_merkle_proof(process_state: JsValue, attribute_name: String) -> ApiRe // Find the index of the attribute in the commitments let attribute_index = state.pcd_commitment.find_index_of(&attribute_name) .ok_or(ApiError::new("Attribute not found in state".to_owned()))?; - + // Generate the merkle proof for the attribute let proof = merkle_tree.proof(&[attribute_index]); @@ -1779,16 +1792,48 @@ pub fn get_merkle_proof(process_state: JsValue, attribute_name: String) -> ApiRe let proof_bytes = proof.to_bytes(); let proof_hex = proof_bytes.to_lower_hex_string(); - // Create a result object with the proof and additional metadata - let result = Object::new(); - Reflect::set(&result, &JsValue::from_str("proof"), &JsValue::from_str(&proof_hex)) - .map_err(|_| ApiError::new("Failed to set proof field".to_owned()))?; - Reflect::set(&result, &JsValue::from_str("root"), &JsValue::from_str(&state.state_id.to_lower_hex_string())) - .map_err(|_| ApiError::new("Failed to set root field".to_owned()))?; - Reflect::set(&result, &JsValue::from_str("attribute"), &JsValue::from_str(&attribute_name)) - .map_err(|_| ApiError::new("Failed to set attribute field".to_owned()))?; - Reflect::set(&result, &JsValue::from_str("attribute_index"), &JsValue::from_f64(attribute_index as f64)) - .map_err(|_| ApiError::new("Failed to set attribute_index field".to_owned()))?; - - Ok(JsValue::from(result)) + Ok(MerkleProofResult { + proof: proof_hex, + root: state.state_id.to_lower_hex_string(), + attribute: attribute_name, + attribute_index: attribute_index, + total_leaves_count: merkle_tree.leaves_len(), + }) +} + +#[wasm_bindgen] +/// Validate a merkle proof for a specific attribute. +/// +/// This function verifies that a merkle proof is valid and proves the existence +/// of a specific attribute in a given state. It checks that the proof correctly +/// leads to the claimed root when combined with the attribute hash. +/// +/// # Arguments +/// * `proof_result` - The MerkleProofResult containing the proof and metadata +/// * `hash` - The hash of the attribute data as a hex string (the leaf value) +/// +/// # Returns +/// A boolean indicating whether the proof is valid +/// +/// # Errors +/// * "Invalid proof format" - If the proof cannot be parsed +/// * "Invalid hash format" - If the hash is not a valid 32-byte hex string +/// * "Invalid root format" - If the root is not a valid 32-byte hex string +pub fn validate_merkle_proof(proof_result: MerkleProofResult, hash: String) -> ApiResult { + let root_bytes: [u8; 32] = Vec::from_hex(&proof_result.root)? + .try_into() + .map_err(|_| ApiError::new("Invalid root format".to_owned()))?; + + let proof_bytes = Vec::from_hex(&proof_result.proof) + .map_err(|_| ApiError::new("Invalid proof format".to_owned()))?; + + let index = proof_result.attribute_index; + let total_leaves_count = proof_result.total_leaves_count; + + let hash_bytes: [u8; 32] = Vec::from_hex(&hash)?.try_into() + .map_err(|_| ApiError::new("Invalid hash format".to_owned()))?; + + let res = verify_merkle_proof(&proof_bytes, &root_bytes, index, &hash_bytes, total_leaves_count)?; + + Ok(res) }