Skip to main content

Prover Contract

The Prover contract is essential for verifying transactions that occur on the opposite chain (for example, on the Calimero side, if the Prover contract is on the NEAR side). To validate a transaction, the Prover contract needs the Merkle tree root for a specific block height. The Bridge Service handles this process by making an RPC call (EXPERIMENTAL_light_client_proof) to the Archival node. This call retrieves the proof for a particular receipt, which is then passed as an argument to the Prover contract.

Proof Data Structure

The Prover contract utilizes the following data structures:

FullOutcomeProof

pub struct FullOutcomeProof {
/// Proof of execution outcome
pub outcome_proof: ExecutionOutcomeWithIdAndProof,
/// Proof of shard execution outcome root
pub outcome_root_proof: MerklePath,
/// A lightweight representation of the block that contains the outcome root
pub block_header_lite: BlockHeaderLight,
/// Proof of the existence of the block in the block merkle tree,
/// which consists of blocks up to the light client head
pub block_proof: MerklePath,
}

The FullOutcomeProof structure encapsulates the complete set of proofs required to verify the execution outcome and the existence of the block within the block merkle tree. It includes the following components:

  • outcome_proof: Proof of the execution outcome, including the block hash and merkle proof of the execution outcome within its shard.
  • outcome_root_proof: Proof of the shard execution outcome root.
  • block_header_lite: A lightweight representation of the block that contains the outcome root, including relevant block header information.
  • block_proof: Proof of the existence of the block in the block merkle tree, which includes blocks up to the light client head.

BlockHeaderLight

pub struct BlockHeaderLight {
pub prev_block_hash: Hash,
pub inner_rest_hash: Hash,
pub inner_lite: BlockHeaderInnerLite,
}

The BlockHeaderLight structure represents a lightweight version of the block header. It includes the following fields:

  • prev_block_hash: The hash of the previous block.
  • inner_rest_hash: The hash of the remaining part of the block header.
  • inner_lite: A lightweight representation of the inner part of the block header.

ExecutionOutcomeWithIdAndProof

pub struct ExecutionOutcomeWithIdAndProof {
/// Proof of the execution outcome
pub proof: MerklePath,
/// Block hash of the block that contains the outcome root
pub block_hash: Hash,
pub outcome_with_id: ExecutionOutcomeWithId,
}

The ExecutionOutcomeWithIdAndProof structure represents the proof of the execution outcome. It includes the following components:

  • proof: Proof of the execution outcome, represented as a merkle path.
  • block_hash: The block hash of the block that contains the outcome root.
  • outcome_with_id: The execution outcome along with its ID.

ExecutionOutcomeWithId

pub struct ExecutionOutcomeWithId {
/// The transaction hash or the receipt ID.
pub id: Hash,
/// The actual outcome
pub outcome: ExecutionOutcome,
}

The ExecutionOutcomeWithId structure represents the execution outcome along with its ID. It includes the following fields:

  • id: The hash of the transaction or the receipt ID.
  • outcome: The actual execution outcome.

ExecutionOutcome

pub struct ExecutionOutcome {
/// Logs from this transaction or receipt.
pub logs: Vec<Vec<u8>>,
/// Receipt IDs generated by this transaction or receipt.
pub receipt_ids: Vec<Hash>,
/// The amount of gas burnt by the given transaction or receipt.
pub gas_burnt: u64,
/// The total number of tokens burnt by the given transaction or receipt.
pub tokens_burnt: u128,
/// The transaction or receipt ID that produced this outcome.
pub executor_id: String,
/// Execution status. Contains the result in case of successful execution.
pub status: ExecutionStatus,
}

The ExecutionOutcome structure represents the execution outcome of a transaction or receipt. It includes the following fields:

  • logs: Logs generated by the transaction or receipt.
  • receipt_ids: Receipt IDs generated by the transaction or receipt.
  • gas_burnt: The amount of gas consumed by the transaction or receipt.
  • tokens_burnt: The total number of tokens burnt by the transaction or receipt.
  • executor_id: The ID of the executor that produced this outcome.
  • status: The execution status, which contains the result in case of successful execution.

Verification Steps

To verify the correctness of the proof, the Prover contract follows the following steps:

Execution Outcome Root Verification

If the outcome root of the transaction or receipt is included in block B, the outcome_proof includes the block hash of B and the merkle proof of the execution outcome within its shard. The outcome root in B can be reconstructed using the following formula:

shard_outcome_root = compute_root(sha256(execution_outcome.hash()), outcome_proof.proof);
block_outcome_root = compute_root(sha256(shard_outcome_root.hash()), outcome_root_proof);

The reconstructed outcome root must match the outcome root stored in block_header_lite.inner_lite.

Block Merkle Root Verification

The block hash can be computed from BlockHeaderLight using the following function:

fn compute_block_hash(block_header_lite: &BlockHeaderLight) -> Hash {
sha256(concat(
sha256(concat(
sha256(block_header_lite.inner_lite.hash()),
sha256(block_header_lite.inner_rest_hash)
)),
block_header_lite.prev_hash
))
}

The expected block merkle root can be computed as follows:

block_hash = compute_block_hash(block_header_lite);
block_merkle_root = compute_root(block_hash, block_proof);

The computed block merkle root must match the block merkle root stored in the light client block of the light client head.

Root Computation

The compute_root function is used to calculate the root hash based on a given node and merkle path. It iterates over the merkle path items and combines the hashes to compute the root hash.

fn compute_root(node: &Hash, path: MerklePath) -> Hash {
let mut hash: Hash = *node;
for item in path.items {
hash = match item.direction {
MERKLE_PATH_LEFT => sha256(concat(item.hash(), hash)),
MERKLE_PATH_RIGHT => sha256(concat(hash, item.hash())),
}
.try_into()
.unwrap()
}
return hash;
}

By following these steps and utilizing the provided data structures and functions, the Prover contract ensures the validity and integrity of the proofs for events on the specified chain.