use csv_adapter_core::Hash;
use sha2::{Digest, Sha256};
use crate::mpt;
use crate::seal_contract::CsvSealAbi;
use crate::types::EthereumInclusionProof;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DecodedLog {
pub address: [u8; 20],
pub topics: Vec<[u8; 32]>,
pub data: Vec<u8>,
pub log_index: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ReceiptProofResult {
pub is_valid: bool,
pub receipt_hash: [u8; 32],
pub block_number: u64,
pub logs: Vec<DecodedLog>,
pub has_seal_used_event: bool,
}
pub fn verify_receipt_inclusion(_tx_hash: &[u8; 32], proof: &EthereumInclusionProof) -> bool {
!proof.receipt_rlp.is_empty() || !proof.merkle_proof.is_empty()
}
pub fn verify_receipt_proof(
receipt_root: [u8; 32],
receipt_index: u64,
receipt_rlp: &[u8],
proof_nodes: &[Vec<u8>],
expected_seal_id: Option<[u8; 32]>,
csv_seal_address: [u8; 20],
) -> ReceiptProofResult {
let digest = Sha256::digest(receipt_rlp);
let receipt_hash: [u8; 32] = digest.into();
let proof_nodes_bytes: Vec<alloy_primitives::Bytes> = proof_nodes
.iter()
.map(|node| alloy_primitives::Bytes::from(node.clone()))
.collect();
let receipt_root = alloy_primitives::B256::from(receipt_root);
let proof_valid = mpt::verify_receipt_proof(receipt_root, &proof_nodes_bytes, receipt_index);
if !proof_valid {
return ReceiptProofResult {
is_valid: false,
receipt_hash,
block_number: 0,
logs: Vec::new(),
has_seal_used_event: false,
};
}
let logs = match decode_receipt_logs(receipt_rlp) {
Ok(l) => l,
Err(_) => {
return ReceiptProofResult {
is_valid: false,
receipt_hash,
block_number: 0,
logs: Vec::new(),
has_seal_used_event: false,
};
}
};
let seal_used_signature = CsvSealAbi::seal_used_event_signature();
let has_seal_used_event = check_for_seal_used_event(
&logs,
csv_seal_address,
seal_used_signature,
expected_seal_id,
);
ReceiptProofResult {
is_valid: true,
receipt_hash,
block_number: 0,
logs,
has_seal_used_event,
}
}
fn receipt_index_to_path_key(index: u64) -> [u8; 32] {
let mut key = [0u8; 32];
let index_bytes = index.to_be_bytes();
for i in 0..8 {
key[32 - 8 + i] = index_bytes[i];
}
key
}
fn decode_receipt_logs(receipt_rlp: &[u8]) -> Result<Vec<DecodedLog>, ()> {
if receipt_rlp.is_empty() {
return Ok(Vec::new());
}
let (is_typed, data) = if receipt_rlp[0] <= 0x7f {
(true, &receipt_rlp[1..])
} else {
(false, receipt_rlp)
};
if is_typed {
decode_logs_from_rlp(data)
} else {
decode_logs_from_rlp(data)
}
}
fn decode_logs_from_rlp(rlp_data: &[u8]) -> Result<Vec<DecodedLog>, ()> {
if rlp_data.len() < 2 {
return Err(());
}
let (list_items, _consumed) = rlp_decode_list(rlp_data)?;
if list_items.len() < 4 {
return Err(());
}
let logs_rlp = &list_items[3];
let (logs_items, _) = rlp_decode_list(logs_rlp)?;
let mut logs = Vec::new();
for (log_index, log_rlp) in logs_items.iter().enumerate() {
if let Ok(log) = decode_single_log(log_rlp, log_index as u64) {
logs.push(log);
}
}
Ok(logs)
}
fn decode_single_log(log_rlp: &[u8], log_index: u64) -> Result<DecodedLog, ()> {
let (items, _) = rlp_decode_list(log_rlp)?;
if items.len() < 3 {
return Err(());
}
let address = rlp_decode_bytes(items[0])?;
if address.len() != 20 {
return Err(());
}
let mut addr = [0u8; 20];
addr.copy_from_slice(&address);
let (topics_items, _) = rlp_decode_list(items[1])?;
let mut topics = Vec::new();
for topic_rlp in &topics_items {
let topic_bytes = rlp_decode_bytes(topic_rlp)?;
if topic_bytes.len() == 32 {
let mut topic = [0u8; 32];
topic.copy_from_slice(&topic_bytes);
topics.push(topic);
}
}
let data = rlp_decode_bytes(items[2])?;
Ok(DecodedLog {
address: addr,
topics,
data,
log_index,
})
}
fn rlp_decode_list(data: &[u8]) -> Result<(Vec<&[u8]>, usize), ()> {
if data.is_empty() {
return Err(());
}
let prefix = data[0];
if (0xc0..=0xf7).contains(&prefix) {
let len = (prefix - 0xc0) as usize;
if data.len() < 1 + len {
return Err(());
}
let items = rlp_parse_items(&data[1..1 + len])?;
Ok((items, 1 + len))
}
else if prefix >= 0xf8 {
let len_of_len = (prefix - 0xf7) as usize;
if data.len() < 1 + len_of_len {
return Err(());
}
let len_bytes = &data[1..1 + len_of_len];
let len = decode_big_endian(len_bytes);
if data.len() < 1 + len_of_len + len {
return Err(());
}
let items = rlp_parse_items(&data[1 + len_of_len..1 + len_of_len + len])?;
Ok((items, 1 + len_of_len + len))
} else {
Err(())
}
}
fn rlp_parse_items(data: &[u8]) -> Result<Vec<&[u8]>, ()> {
let mut items = Vec::new();
let mut offset = 0;
while offset < data.len() {
let (_, consumed) = rlp_decode_item_length(&data[offset..])?;
if offset + consumed > data.len() {
return Err(());
}
items.push(&data[offset..offset + consumed]);
offset += consumed;
}
Ok(items)
}
fn rlp_decode_item_length(data: &[u8]) -> Result<(bool, usize), ()> {
if data.is_empty() {
return Err(());
}
let prefix = data[0];
if prefix <= 0x7f {
Ok((false, 1))
}
else if (0x80..=0xb7).contains(&prefix) {
let len = (prefix - 0x80) as usize;
Ok((false, 1 + len))
}
else if (0xb8..=0xbf).contains(&prefix) {
let len_of_len = (prefix - 0xb7) as usize;
if data.len() < 1 + len_of_len {
return Err(());
}
let len = decode_big_endian(&data[1..1 + len_of_len]);
Ok((false, 1 + len_of_len + len))
}
else if (0xc0..=0xf7).contains(&prefix) {
let len = (prefix - 0xc0) as usize;
Ok((true, 1 + len))
}
else if prefix >= 0xf8 {
let len_of_len = (prefix - 0xf7) as usize;
if data.len() < 1 + len_of_len {
return Err(());
}
let len = decode_big_endian(&data[1..1 + len_of_len]);
Ok((true, 1 + len_of_len + len))
} else {
Err(())
}
}
fn rlp_decode_bytes(data: &[u8]) -> Result<Vec<u8>, ()> {
if data.is_empty() {
return Err(());
}
let prefix = data[0];
if prefix <= 0x7f {
Ok(vec![prefix])
}
else if (0x80..=0xb7).contains(&prefix) {
let len = (prefix - 0x80) as usize;
if data.len() < 1 + len {
return Err(());
}
Ok(data[1..1 + len].to_vec())
}
else if (0xb8..=0xbf).contains(&prefix) {
let len_of_len = (prefix - 0xb7) as usize;
if data.len() < 1 + len_of_len {
return Err(());
}
let len = decode_big_endian(&data[1..1 + len_of_len]);
if data.len() < 1 + len_of_len + len {
return Err(());
}
Ok(data[1 + len_of_len..1 + len_of_len + len].to_vec())
}
else if prefix >= 0xc0 {
Ok(Vec::new())
} else {
Err(())
}
}
fn decode_big_endian(bytes: &[u8]) -> usize {
let mut result: usize = 0;
for &b in bytes {
result = (result << 8) | (b as usize);
}
result
}
fn check_for_seal_used_event(
logs: &[DecodedLog],
csv_seal_address: [u8; 20],
seal_used_signature: [u8; 32],
expected_seal_id: Option<[u8; 32]>,
) -> bool {
for log in logs {
if log.address != csv_seal_address {
continue;
}
if log.topics.is_empty() || log.topics[0] != seal_used_signature {
continue;
}
if let Some(seal_id) = expected_seal_id {
if log.data.len() >= 64 {
let mut event_seal_id = [0u8; 32];
event_seal_id.copy_from_slice(&log.data[..32]);
let mut event_commitment = [0u8; 32];
event_commitment.copy_from_slice(&log.data[32..64]);
if event_seal_id == seal_id {
return true;
}
}
} else {
if log.data.len() >= 64 {
return true;
}
}
}
false
}
pub fn to_core_inclusion_proof(proof: &EthereumInclusionProof) -> csv_adapter_core::InclusionProof {
let mut proof_bytes = Vec::new();
proof_bytes.extend_from_slice(&proof.receipt_rlp);
proof_bytes.extend_from_slice(&proof.merkle_proof);
proof_bytes.extend_from_slice(&proof.block_hash);
proof_bytes.extend_from_slice(&proof.block_number.to_le_bytes());
proof_bytes.extend_from_slice(&proof.log_index.to_le_bytes());
csv_adapter_core::InclusionProof::new(proof_bytes, Hash::new(proof.block_hash), proof.log_index)
.expect("valid inclusion proof")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_verify_receipt_inclusion() {
let tx_hash = [1u8; 32];
let proof =
EthereumInclusionProof::new(vec![0xAB; 100], vec![0xCD; 64], [2u8; 32], 1000, 5);
assert!(verify_receipt_inclusion(&tx_hash, &proof));
}
#[test]
fn test_to_core_inclusion_proof() {
let proof = EthereumInclusionProof::new(vec![0xAB; 50], vec![], [3u8; 32], 1000, 5);
let core_proof = to_core_inclusion_proof(&proof);
assert_eq!(core_proof.position, 5);
assert_eq!(core_proof.block_hash, Hash::new([3u8; 32]));
}
}