// contracts/Messages.sol // SPDX-License-Identifier: Apache 2 pragma solidity ^0.8.0; pragma experimental ABIEncoderV2; import "./Getters.sol"; import "./Structs.sol"; import "./libraries/external/BytesLib.sol"; contract Messages is Getters { using BytesLib for bytes; function parseAndVerifyVM(bytes calldata encodedVM) public view returns (Structs.VM memory vm, bool valid, string memory reason) { vm = parseVM(encodedVM); (valid, reason) = verifyVM(vm); } function verifyVM(Structs.VM memory vm) public view returns (bool valid, string memory reason) { Structs.GuardianSet memory guardianSet = getGuardianSet(vm.guardianSetIndex); if(guardianSet.keys.length == 0){ return (false, "invalid guardian set"); } if(vm.guardianSetIndex != getCurrentGuardianSetIndex() && guardianSet.expirationTime < block.timestamp){ return (false, "guardian set has expired"); } // We're using a fixed point number transformation with 1 decimal to deal with rounding. if(((guardianSet.keys.length * 10 / 3) * 2) / 10 + 1 > vm.signatures.length){ return (false, "no quorum"); } // Verify signatures (bool signaturesValid, string memory invalidReason) = verifySignatures(vm.hash, vm.signatures, guardianSet); if(!signaturesValid){ return (false, invalidReason); } return (true, ""); } function verifySignatures(bytes32 hash, Structs.Signature[] memory signatures, Structs.GuardianSet memory guardianSet) public pure returns (bool valid, string memory reason) { uint8 lastIndex = 0; for (uint i = 0; i < signatures.length; i++) { Structs.Signature memory sig = signatures[i]; require(i == 0 || sig.guardianIndex > lastIndex, "signature indices must be ascending"); lastIndex = sig.guardianIndex; if(ecrecover(hash, sig.v, sig.r, sig.s) != guardianSet.keys[sig.guardianIndex]){ return (false, "VM signature invalid"); } } return (true, ""); } function parseVM(bytes memory encodedVM) public pure virtual returns (Structs.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 Structs.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); } }