Add MerkleProofResult and validate_merkle_proof()

This commit is contained in:
Sosthene 2025-06-30 22:32:46 +02:00
parent 1e1dbdf8df
commit f3e504689e

View File

@ -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<JsValue> {
pub fn get_merkle_proof(process_state: JsValue, attribute_name: String) -> ApiResult<MerkleProofResult> {
// 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<bool> {
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)
}