You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
144 lines
5.3 KiB
144 lines
5.3 KiB
4 months ago
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||
|
pragma solidity >=0.8.0;
|
||
|
|
||
|
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
|
||
|
|
||
|
import {TypeCasts} from "./libs/TypeCasts.sol";
|
||
|
import {Checkpoint, CheckpointLib} from "./libs/CheckpointLib.sol";
|
||
|
import {MerkleLib, TREE_DEPTH} from "./libs/Merkle.sol";
|
||
|
import {MerkleTreeHook} from "./hooks/MerkleTreeHook.sol";
|
||
|
import {IMailbox} from "./interfaces/IMailbox.sol";
|
||
|
|
||
|
struct StoredIndex {
|
||
|
uint32 index;
|
||
|
bool exists;
|
||
|
}
|
||
|
|
||
|
contract CheckpointFraudProofs {
|
||
|
using CheckpointLib for Checkpoint;
|
||
|
using Address for address;
|
||
|
|
||
|
mapping(address merkleTree => mapping(bytes32 root => StoredIndex index))
|
||
|
public storedCheckpoints;
|
||
|
|
||
|
function storedCheckpointContainsMessage(
|
||
|
address merkleTree,
|
||
|
uint32 index,
|
||
|
bytes32 messageId,
|
||
|
bytes32[TREE_DEPTH] calldata proof
|
||
|
) public view returns (bool) {
|
||
|
bytes32 root = MerkleLib.branchRoot(messageId, proof, index);
|
||
|
StoredIndex storage storedIndex = storedCheckpoints[merkleTree][root];
|
||
|
return storedIndex.exists && storedIndex.index >= index;
|
||
|
}
|
||
|
|
||
|
modifier onlyMessageInStoredCheckpoint(
|
||
|
Checkpoint calldata checkpoint,
|
||
|
bytes32[TREE_DEPTH] calldata proof,
|
||
|
bytes32 messageId
|
||
|
) {
|
||
|
require(
|
||
|
storedCheckpointContainsMessage(
|
||
|
checkpoint.merkleTreeAddress(),
|
||
|
checkpoint.index,
|
||
|
messageId,
|
||
|
proof
|
||
|
),
|
||
|
"message must be member of stored checkpoint"
|
||
|
);
|
||
|
_;
|
||
|
}
|
||
|
|
||
|
function isLocal(
|
||
|
Checkpoint calldata checkpoint
|
||
|
) public view returns (bool) {
|
||
|
address merkleTree = checkpoint.merkleTreeAddress();
|
||
|
return
|
||
|
merkleTree.isContract() &&
|
||
|
MerkleTreeHook(merkleTree).localDomain() == checkpoint.origin;
|
||
|
}
|
||
|
|
||
|
modifier onlyLocal(Checkpoint calldata checkpoint) {
|
||
|
require(isLocal(checkpoint), "must be local checkpoint");
|
||
|
_;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @notice Stores the latest checkpoint of the provided merkle tree hook
|
||
|
* @param merkleTree Address of the merkle tree hook to store the latest checkpoint of.
|
||
|
* @dev Must be called before proving fraud to circumvent race on message insertion and merkle proof construction.
|
||
|
*/
|
||
|
function storeLatestCheckpoint(
|
||
|
address merkleTree
|
||
|
) external returns (bytes32 root, uint32 index) {
|
||
|
(root, index) = MerkleTreeHook(merkleTree).latestCheckpoint();
|
||
|
storedCheckpoints[merkleTree][root] = StoredIndex(index, true);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @notice Checks whether the provided checkpoint is premature (fraud).
|
||
|
* @param checkpoint Checkpoint to check.
|
||
|
* @dev Checks whether checkpoint.index is greater than or equal to mailbox count
|
||
|
* @return Whether the provided checkpoint is premature.
|
||
|
*/
|
||
|
function isPremature(
|
||
|
Checkpoint calldata checkpoint
|
||
|
) public view onlyLocal(checkpoint) returns (bool) {
|
||
|
// count is the number of messages in the mailbox (i.e. the latest index + 1)
|
||
|
uint32 count = MerkleTreeHook(checkpoint.merkleTreeAddress()).count();
|
||
|
|
||
|
// index >= count is equivalent to index > latest index
|
||
|
return checkpoint.index >= count;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @notice Checks whether the provided checkpoint has a fraudulent message ID.
|
||
|
* @param checkpoint Checkpoint to check.
|
||
|
* @param proof Merkle proof of the actual message ID at checkpoint.index on checkpoint.merkleTree
|
||
|
* @param actualMessageId Actual message ID at checkpoint.index on checkpoint.merkleTree
|
||
|
* @dev Must produce proof of inclusion for actualMessageID against some stored checkpoint.
|
||
|
* @return Whether the provided checkpoint has a fraudulent message ID.
|
||
|
*/
|
||
|
function isFraudulentMessageId(
|
||
|
Checkpoint calldata checkpoint,
|
||
|
bytes32[TREE_DEPTH] calldata proof,
|
||
|
bytes32 actualMessageId
|
||
|
)
|
||
|
public
|
||
|
view
|
||
|
onlyLocal(checkpoint)
|
||
|
onlyMessageInStoredCheckpoint(checkpoint, proof, actualMessageId)
|
||
|
returns (bool)
|
||
|
{
|
||
|
return actualMessageId != checkpoint.messageId;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @notice Checks whether the provided checkpoint has a fraudulent root.
|
||
|
* @param checkpoint Checkpoint to check.
|
||
|
* @param proof Merkle proof of the checkpoint.messageId at checkpoint.index on checkpoint.merkleTree
|
||
|
* @dev Must produce proof of inclusion for checkpoint.messageId against some stored checkpoint.
|
||
|
* @return Whether the provided checkpoint has a fraudulent message ID.
|
||
|
*/
|
||
|
function isFraudulentRoot(
|
||
|
Checkpoint calldata checkpoint,
|
||
|
bytes32[TREE_DEPTH] calldata proof
|
||
|
)
|
||
|
public
|
||
|
view
|
||
|
onlyLocal(checkpoint)
|
||
|
onlyMessageInStoredCheckpoint(checkpoint, proof, checkpoint.messageId)
|
||
|
returns (bool)
|
||
|
{
|
||
|
// proof of checkpoint.messageId at checkpoint.index is the list of siblings from the leaf node to some stored root
|
||
|
// once verifying the proof, we can reconstruct the specific root at checkpoint.index by replacing siblings greater
|
||
|
// than the index (right subtrees) with zeroes
|
||
|
bytes32 root = MerkleLib.reconstructRoot(
|
||
|
checkpoint.messageId,
|
||
|
proof,
|
||
|
checkpoint.index
|
||
|
);
|
||
|
return root != checkpoint.root;
|
||
|
}
|
||
|
}
|