pyth-crosschain/ethereum/contracts/wormhole-receiver/ReceiverMessages.sol

180 lines
6.6 KiB
Solidity

// contracts/Messages.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;
import "./ReceiverGetters.sol";
import "./ReceiverStructs.sol";
import "../libraries/external/BytesLib.sol";
contract ReceiverMessages is ReceiverGetters {
using BytesLib for bytes;
/// @dev parseAndVerifyVM serves to parse an encodedVM and wholy validate it for consumption
function parseAndVerifyVM(
bytes calldata encodedVM
)
public
view
returns (ReceiverStructs.VM memory vm, bool valid, string memory reason)
{
vm = parseVM(encodedVM);
(valid, reason) = verifyVM(vm);
}
/**
* @dev `verifyVM` serves to validate an arbitrary vm against a valid Guardian set
* - it aims to make sure the VM is for a known guardianSet
* - it aims to ensure the guardianSet is not expired
* - it aims to ensure the VM has reached quorum
* - it aims to verify the signatures provided against the guardianSet
*/
function verifyVM(
ReceiverStructs.VM memory vm
) public view returns (bool valid, string memory reason) {
/// @dev Obtain the current guardianSet for the guardianSetIndex provided
ReceiverStructs.GuardianSet memory guardianSet = getGuardianSet(
vm.guardianSetIndex
);
/**
* @dev Checks whether the guardianSet has zero keys
* WARNING: This keys check is critical to ensure the guardianSet has keys present AND to ensure
* that guardianSet key size doesn't fall to zero and negatively impact quorum assessment. If guardianSet
* key length is 0 and vm.signatures length is 0, this could compromise the integrity of both vm and
* signature verification.
*/
if (guardianSet.keys.length == 0) {
return (false, "invalid guardian set");
}
/// @dev Checks if VM guardian set index matches the current index (unless the current set is expired).
if (
vm.guardianSetIndex != getCurrentGuardianSetIndex() &&
guardianSet.expirationTime < block.timestamp
) {
return (false, "guardian set has expired");
}
/**
* @dev We're using a fixed point number transformation with 1 decimal to deal with rounding.
* WARNING: This quorum check is critical to assessing whether we have enough Guardian signatures to validate a VM
* if making any changes to this, obtain additional peer review. If guardianSet key length is 0 and
* vm.signatures length is 0, this could compromise the integrity of both vm and signature verification.
*/
if (
(((guardianSet.keys.length * 10) / 3) * 2) / 10 + 1 >
vm.signatures.length
) {
return (false, "no quorum");
}
/// @dev Verify the proposed vm.signatures against the guardianSet
(bool signaturesValid, string memory invalidReason) = verifySignatures(
vm.hash,
vm.signatures,
guardianSet
);
if (!signaturesValid) {
return (false, invalidReason);
}
/// If we are here, we've validated the VM is a valid multi-sig that matches the guardianSet.
return (true, "");
}
/**
* @dev verifySignatures serves to validate arbitrary sigatures against an arbitrary guardianSet
* - it intentionally does not solve for expectations within guardianSet (you should use verifyVM if you need these protections)
* - it intentioanlly does not solve for quorum (you should use verifyVM if you need these protections)
* - it intentionally returns true when signatures is an empty set (you should use verifyVM if you need these protections)
*/
function verifySignatures(
bytes32 hash,
ReceiverStructs.Signature[] memory signatures,
ReceiverStructs.GuardianSet memory guardianSet
) public pure returns (bool valid, string memory reason) {
uint8 lastIndex = 0;
for (uint i = 0; i < signatures.length; i++) {
ReceiverStructs.Signature memory sig = signatures[i];
/// Ensure that provided signature indices are ascending only
require(
i == 0 || sig.guardianIndex > lastIndex,
"signature indices must be ascending"
);
lastIndex = sig.guardianIndex;
/// Check to see if the signer of the signature does not match a specific Guardian key at the provided index
if (
ecrecover(hash, sig.v, sig.r, sig.s) !=
guardianSet.keys[sig.guardianIndex]
) {
return (false, "VM signature invalid");
}
}
/// If we are here, we've validated that the provided signatures are valid for the provided guardianSet
return (true, "");
}
/**
* @dev parseVM serves to parse an encodedVM into a vm struct
* - it intentionally performs no validation functions, it simply parses raw into a struct
*/
function parseVM(
bytes memory encodedVM
) public pure virtual returns (ReceiverStructs.VM memory vm) {
uint index = 0;
vm.version = encodedVM.toUint8(index);
index += 1;
require(vm.version == 1, "VM version incompatible");
vm.guardianSetIndex = encodedVM.toUint32(index);
index += 4;
// Parse Signatures
uint256 signersLen = encodedVM.toUint8(index);
index += 1;
vm.signatures = new ReceiverStructs.Signature[](signersLen);
for (uint i = 0; i < signersLen; i++) {
vm.signatures[i].guardianIndex = encodedVM.toUint8(index);
index += 1;
vm.signatures[i].r = encodedVM.toBytes32(index);
index += 32;
vm.signatures[i].s = encodedVM.toBytes32(index);
index += 32;
vm.signatures[i].v = encodedVM.toUint8(index) + 27;
index += 1;
}
// Hash the body
bytes memory body = encodedVM.slice(index, encodedVM.length - index);
vm.hash = keccak256(abi.encodePacked(keccak256(body)));
// Parse the body
vm.timestamp = encodedVM.toUint32(index);
index += 4;
vm.nonce = encodedVM.toUint32(index);
index += 4;
vm.emitterChainId = encodedVM.toUint16(index);
index += 2;
vm.emitterAddress = encodedVM.toBytes32(index);
index += 32;
vm.sequence = encodedVM.toUint64(index);
index += 8;
vm.consistencyLevel = encodedVM.toUint8(index);
index += 1;
vm.payload = encodedVM.slice(index, encodedVM.length - index);
}
}