-
Notifications
You must be signed in to change notification settings - Fork 390
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement checkpoint fraud proofs (#4277)
### Description Implements 4 categories of checkpoint fraud proofs for use in future validator slashing protocol: 1. **premature**: if a checkpoint index is greater than the corresponding mailbox count, it is fraudulent 2. **non local**: if a checkpoint origin does not match the checkpoint's mailbox domain, it is fraudulent 3. **message id**: if a checkpoint message ID differs from the actual message ID (verified by merkle proof) at the checkpoint index, it is fraudulent 4. **root**: if a checkpoint root differs from the actual root (verified by merkle proof + root reconstruction) at the checkpoint index, it is fraudulent Notably this is implemented independently from signature verification to allow for multiple checkpoint signing schemes to reuse the same checkpoint logic. ### Related issues - Touches #3799 - See #2431 for previous discussions ### Backward compatibility Yes ### Testing Unit testing with fixtures in `vectors/merkle.json` that are generated by the rust merkle tree and proof code
- Loading branch information
Showing
7 changed files
with
519 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
--- | ||
"@hyperlane-xyz/cli": patch | ||
'@hyperlane-xyz/cli': patch | ||
--- | ||
|
||
Require at least 1 chain selection in warp init |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@hyperlane-xyz/core': minor | ||
--- | ||
|
||
Implement checkpoint fraud proofs for use in slashing |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
// 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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.