lazarus-receipts 1.0.2

Lazarus Receipts SDK - Cryptographic receipt verification
Documentation
//! Merkle proof verification for Lazarus Receipts.

use crate::hash::{hash256, hex_to_bytes};

/// A node in a directional Merkle proof.
#[derive(Debug, Clone)]
pub struct ProofNode {
    /// 'L' or 'R' indicating sibling position
    pub side: char,
    /// 32-byte sibling hash
    pub hash: [u8; 32],
}

impl ProofNode {
    /// Create a new proof node from side and hex hash.
    pub fn from_hex(side: char, hex: &str) -> Result<Self, &'static str> {
        Ok(ProofNode {
            side,
            hash: hex_to_bytes(hex)?,
        })
    }
}

/// Verify a Merkle proof using positional v1.0.1 rule.
///
/// Directional proof nodes specify side:
/// - 'L': sibling is on left, cur = H(sibling || cur)
/// - 'R': sibling is on right, cur = H(cur || sibling)
///
/// # Arguments
/// * `leaf` - 32-byte leaf hash
/// * `root` - 32-byte expected root hash
/// * `proof` - Vector of proof nodes with side and hash
///
/// # Returns
/// `true` if proof verifies, `false` otherwise
pub fn verify_merkle_proof_directional(
    leaf: [u8; 32],
    root: [u8; 32],
    proof: &[ProofNode],
) -> bool {
    if proof.is_empty() {
        return false;
    }

    // Validate all nodes have valid side
    for node in proof {
        if node.side != 'L' && node.side != 'R' {
            return false;
        }
    }

    let mut current = leaf;

    for node in proof {
        match node.side {
            'L' => {
                // Sibling on left: H(sibling || current)
                let mut combined = Vec::with_capacity(64);
                combined.extend_from_slice(&node.hash);
                combined.extend_from_slice(&current);
                current = hash256(&combined);
            }
            'R' => {
                // Sibling on right: H(current || sibling)
                let mut combined = Vec::with_capacity(64);
                combined.extend_from_slice(&current);
                combined.extend_from_slice(&node.hash);
                current = hash256(&combined);
            }
            _ => return false,
        }
    }

    current == root
}

/// Legacy sorted-pair Merkle verification.
///
/// **WARNING**: NOT auditor-grade. Use `verify_merkle_proof_directional` instead.
///
/// This uses sorted concatenation which loses positional information.
/// Included only for backward compatibility with legacy systems.
#[deprecated(
    since = "1.0.0",
    note = "Use verify_merkle_proof_directional (positional_v1_0_1) instead"
)]
pub fn verify_merkle_proof_legacy_sorted(
    leaf: [u8; 32],
    root: [u8; 32],
    siblings: &[[u8; 32]],
) -> bool {
    if siblings.is_empty() {
        return false;
    }

    let mut current = leaf;

    for sibling in siblings {
        // Sorted concatenation - loses positional information
        let combined = if current < *sibling {
            let mut v = Vec::with_capacity(64);
            v.extend_from_slice(&current);
            v.extend_from_slice(sibling);
            v
        } else {
            let mut v = Vec::with_capacity(64);
            v.extend_from_slice(sibling);
            v.extend_from_slice(&current);
            v
        };
        current = hash256(&combined);
    }

    current == root
}