// 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); } }