diff --git a/.gitignore b/.gitignore index 5c17c04b..f873ba96 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ Cargo.lock # IDE directories .vscode/ .idea/ +.zed/ # cspell cspell.json diff --git a/contracts/resources/ContractsArchitecture.png b/contracts/resources/ContractsArchitecture.png index fd528ecd..7b7ca50f 100644 Binary files a/contracts/resources/ContractsArchitecture.png and b/contracts/resources/ContractsArchitecture.png differ diff --git a/contracts/resources/MessageProofs.png b/contracts/resources/MessageProofs.png new file mode 100644 index 00000000..b961ec49 Binary files /dev/null and b/contracts/resources/MessageProofs.png differ diff --git a/contracts/src/interfaces/IBeefyMessageVerifier.sol b/contracts/src/interfaces/IBeefyMessageVerifier.sol new file mode 100644 index 00000000..05205428 --- /dev/null +++ b/contracts/src/interfaces/IBeefyMessageVerifier.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +interface IBeefyMessageVerifierErrors {} + +interface IBeefyMessageVerifierEvents {} + +/// @dev An BEEFY MMRLeaf without the `leaf_extra field. +/// Reference: https://github.com/paritytech/polkadot-sdk/blob/1abdb7f39cada4840d795b9b721631a3cfe53174/substrate/primitives/consensus/beefy/src/mmr.rs#L53 +struct BeefyMMRLeafPartial { + uint8 version; + uint32 parentNumber; + bytes32 parentHash; + uint64 nextAuthoritySetID; + uint32 nextAuthoritySetLen; + bytes32 nextAuthoritySetRoot; +} + +/// @dev Parameters used to verify a proof generated by https://github.com/paritytech/polkadot-sdk/blob/1abdb7f39cada4840d795b9b721631a3cfe53174/substrate/utils/binary-merkle-tree/src/lib.rs#L144 +struct SubstrateBinaryMerkleProof { + uint256 position; + uint256 width; + bytes32[] proof; +} + +/// @dev Parameters used to verify a that a BEEFY MMRLeaf is part of the latest BEEFY MMR Root. +/// Reference: https://github.com/Snowfork/snowbridge/blob/069177d6c6d44c5a1478ae2f14051c0b3bd7a69a/contracts/src/BeefyClient.sol#L399 +struct BeefyMMRProof { + bytes32[] proof; + uint256 leafProofOrder; +} + +interface IBeefyMessageVerifier is IBeefyMessageVerifierErrors, IBeefyMessageVerifierEvents { + /** + * @notice Verifies a message passed through the "Extra Leaf Data" field of a BEEFY MMRLeaf. + * @dev The message has to be a part of the committed messages in a BEEFY MMRLeaf, that + * is part of the latest BEEFY MMR Root that the validators have signed and finalised. + * @dev To prove that the message is part of the committed messages, three proofs are required: + * 1. A proof that the message is part of a message commitment in the BEEFY MMRLeaf. + * 2. A proof that the message commitment is part of the BEEFY MMRLeaf. + * 3. A proof that the BEEFY MMRLeaf is part of the latest BEEFY MMR Root. + * @dev For more a diagram on these three layers of proofs, see: + * https://github.com/Moonsong-Labs/datahaven/blob/main/contracts/resources/MessageProofs.png + * @dev For more details on BEEFY, see: https://wiki.polkadot.network/docs/learn-consensus#bridging-beefy + * @dev For more details on the MMRLeaf specification, see: https://spec.polkadot.network/sect-finality#defn-beefy-payload + * @dev For more details on the verification of the MMRLeaf, see Snowbridge's docs: https://docs.snowbridge.network/architecture/verification/polkadot + * @param message The message to verify. + * @param messageProof The proof that the message is part of a message commitment in the BEEFY MMRLeaf. + * @param messageId The ID that identifies the kind of message being passed through the "Extra Leaf Data" field of the BEEFY MMRLeaf. + * @param messageCommitmentProof The proof that the message commitment is part of the BEEFY MMRLeaf. + * @param partialBeefyLeaf The partial BEEFY leaf (without the `leaf_extra` field which contains the root of the message commitments). + * @param beefyLeafProof The proof that the BEEFY MMRLeaf is part of the latest BEEFY MMR Root. + * @return isValid Whether the message is valid. + */ + function verifyBeefyMessage( + bytes calldata message, + bytes calldata messageProof, + bytes32 messageId, + SubstrateBinaryMerkleProof calldata messageCommitmentProof, + BeefyMMRLeafPartial calldata partialBeefyLeaf, + BeefyMMRProof calldata beefyLeafProof + ) external view returns (bool); +} diff --git a/contracts/src/middleware/ServiceMessageDispatcher.sol b/contracts/src/middleware/ServiceMessageDispatcher.sol new file mode 100644 index 00000000..37b4127d --- /dev/null +++ b/contracts/src/middleware/ServiceMessageDispatcher.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; +import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; + +import "../interfaces/IBeefyMessageVerifier.sol"; + +contract ServiceMessageDispatcher is IBeefyMessageVerifier, Initializable, OwnableUpgradeable { + bytes32 public constant VALIDATOR_POINTS_MESSAGE_ID = keccak256("VALIDATOR_POINTS"); + bytes32 public constant SLASH_MESSAGE_ID = keccak256("SLASH"); + + /// @notice A mapping of message IDs to external verifiers. + /// @dev The external verifier is a contract that implements the `IBeefyMessageVerifier` interface. + /// @dev This provides a way to delegate the verification of unknown message IDs to an external contract, + /// thus extending the functionality of this contract without the need to modify the contract. + mapping(bytes32 => address) public messageIdToExternalVerifier; + + function initialize() public initializer { + __Ownable_init(); + } + + function verifyValidatorPointsMessage( + bytes calldata, // message, + bytes calldata, // messageProof, + SubstrateBinaryMerkleProof calldata, // messageCommitmentProof, + BeefyMMRLeafPartial calldata, // partialBeefyLeaf, + BeefyMMRProof calldata // beefyLeafProof + ) public view returns (bool) { + // TODO: Implement the logic to verify the validator points message + return true; + } + + function verifySlashMessage( + bytes calldata, // message, + bytes calldata, // messageProof, + SubstrateBinaryMerkleProof calldata, // messageCommitmentProof, + BeefyMMRLeafPartial calldata, // partialBeefyLeaf, + BeefyMMRProof calldata // beefyLeafProof + ) public view returns (bool) { + // TODO: Implement the logic to verify the slash message + return true; + } + + function verifyBeefyMessage( + bytes calldata message, + bytes calldata messageProof, + bytes32 messageId, + SubstrateBinaryMerkleProof calldata messageCommitmentProof, + BeefyMMRLeafPartial calldata partialBeefyLeaf, + BeefyMMRProof calldata beefyLeafProof + ) external view override returns (bool) { + // For known message IDs, we can verify it here. + if (messageId == VALIDATOR_POINTS_MESSAGE_ID) { + return verifyValidatorPointsMessage( + message, messageProof, messageCommitmentProof, partialBeefyLeaf, beefyLeafProof + ); + } else if (messageId == SLASH_MESSAGE_ID) { + return verifySlashMessage( + message, messageProof, messageCommitmentProof, partialBeefyLeaf, beefyLeafProof + ); + } else if (messageIdToExternalVerifier[messageId] != address(0)) { + // For unknown message IDs, we delegate the verification to an external verifier, if there is one registered. + return IBeefyMessageVerifier(messageIdToExternalVerifier[messageId]).verifyBeefyMessage( + message, + messageProof, + messageId, + messageCommitmentProof, + partialBeefyLeaf, + beefyLeafProof + ); + } + // Unknown message IDs are not supported. + return false; + } +}