evm: add optimized parseAndVerify endpoint
This commit is contained in:
parent
6294969ecf
commit
8e3c03ce3d
|
@ -53,4 +53,8 @@ contract Getters is State {
|
||||||
function nextSequence(address emitter) public view returns (uint64) {
|
function nextSequence(address emitter) public view returns (uint64) {
|
||||||
return _state.sequences[emitter];
|
return _state.sequences[emitter];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getGuardianSetHash(uint32 index) public view returns (bytes32) {
|
||||||
|
return _state.guardianSetHashes[index];
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -12,11 +12,41 @@ import "./libraries/external/BytesLib.sol";
|
||||||
contract Messages is Getters {
|
contract Messages is Getters {
|
||||||
using BytesLib for bytes;
|
using BytesLib for bytes;
|
||||||
|
|
||||||
|
function parseAndVerifyVMOptimized(bytes calldata encodedVM, bytes calldata guardianSet) public view returns (Structs.VM memory vm, bool valid, string memory reason) {
|
||||||
|
// Verify that the specified guardian set is the current guardian set.
|
||||||
|
require(
|
||||||
|
getGuardianSetHash(getCurrentGuardianSetIndex()) == keccak256(guardianSet),
|
||||||
|
"invalid guardian set"
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: Optimize parsing function.
|
||||||
|
vm = parseVM(encodedVM);
|
||||||
|
(valid, reason) = verifyVMInternal(vm, parseGuardianSetOptimized(guardianSet), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseGuardianSetOptimized(bytes calldata guardianSetData) public pure returns (Structs.GuardianSet memory guardianSet) {
|
||||||
|
// Fetch the guardian set length.
|
||||||
|
uint256 endGuardianKeyIndex = guardianSetData.length - 4;
|
||||||
|
uint256 guardianCount = endGuardianKeyIndex / 20;
|
||||||
|
|
||||||
|
guardianSet = Structs.GuardianSet({
|
||||||
|
keys : new address[](guardianCount),
|
||||||
|
expirationTime : guardianSetData.toUint32(endGuardianKeyIndex)
|
||||||
|
});
|
||||||
|
|
||||||
|
for(uint256 i = 0; i < guardianCount;) {
|
||||||
|
unchecked {
|
||||||
|
guardianSet.keys[i] = guardianSetData.toAddress(i * 20);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// @dev parseAndVerifyVM serves to parse an encodedVM and wholy validate it for consumption
|
/// @dev parseAndVerifyVM serves to parse an encodedVM and wholy validate it for consumption
|
||||||
function parseAndVerifyVM(bytes calldata encodedVM) public view returns (Structs.VM memory vm, bool valid, string memory reason) {
|
function parseAndVerifyVM(bytes calldata encodedVM) public view returns (Structs.VM memory vm, bool valid, string memory reason) {
|
||||||
vm = parseVM(encodedVM);
|
vm = parseVM(encodedVM);
|
||||||
/// setting checkHash to false as we can trust the hash field in this case given that parseVM computes and then sets the hash field above
|
/// setting checkHash to false as we can trust the hash field in this case given that parseVM computes and then sets the hash field above
|
||||||
(valid, reason) = verifyVMInternal(vm, false);
|
(valid, reason) = verifyVMInternal(vm, getGuardianSet(vm.guardianSetIndex), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,7 +58,7 @@ contract Messages is Getters {
|
||||||
* - it aims to verify the hash field provided against the contents of the vm
|
* - it aims to verify the hash field provided against the contents of the vm
|
||||||
*/
|
*/
|
||||||
function verifyVM(Structs.VM memory vm) public view returns (bool valid, string memory reason) {
|
function verifyVM(Structs.VM memory vm) public view returns (bool valid, string memory reason) {
|
||||||
(valid, reason) = verifyVMInternal(vm, true);
|
(valid, reason) = verifyVMInternal(vm, getGuardianSet(vm.guardianSetIndex), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,10 +67,7 @@ contract Messages is Getters {
|
||||||
* in the case that the vm is securely parsed and the hash field can be trusted, checkHash can be set to false
|
* in the case that the vm is securely parsed and the hash field can be trusted, checkHash can be set to false
|
||||||
* as the check would be redundant
|
* as the check would be redundant
|
||||||
*/
|
*/
|
||||||
function verifyVMInternal(Structs.VM memory vm, bool checkHash) internal view returns (bool valid, string memory reason) {
|
function verifyVMInternal(Structs.VM memory vm, Structs.GuardianSet memory guardianSet, bool checkHash) internal view returns (bool valid, string memory reason) {
|
||||||
/// @dev Obtain the current guardianSet for the guardianSetIndex provided
|
|
||||||
Structs.GuardianSet memory guardianSet = getGuardianSet(vm.guardianSetIndex);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify that the hash field in the vm matches with the hash of the contents of the vm if checkHash is set
|
* Verify that the hash field in the vm matches with the hash of the contents of the vm if checkHash is set
|
||||||
* WARNING: This hash check is critical to ensure that the vm.hash provided matches with the hash of the body.
|
* WARNING: This hash check is critical to ensure that the vm.hash provided matches with the hash of the body.
|
||||||
|
@ -65,6 +92,8 @@ contract Messages is Getters {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint256 guardianCount = guardianSet.keys.length;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Checks whether the guardianSet has zero keys
|
* @dev Checks whether the guardianSet has zero keys
|
||||||
* WARNING: This keys check is critical to ensure the guardianSet has keys present AND to ensure
|
* WARNING: This keys check is critical to ensure the guardianSet has keys present AND to ensure
|
||||||
|
@ -72,7 +101,7 @@ contract Messages is Getters {
|
||||||
* key length is 0 and vm.signatures length is 0, this could compromise the integrity of both vm and
|
* key length is 0 and vm.signatures length is 0, this could compromise the integrity of both vm and
|
||||||
* signature verification.
|
* signature verification.
|
||||||
*/
|
*/
|
||||||
if(guardianSet.keys.length == 0){
|
if(guardianCount == 0){
|
||||||
return (false, "invalid guardian set");
|
return (false, "invalid guardian set");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +116,7 @@ contract Messages is Getters {
|
||||||
* if making any changes to this, obtain additional peer review. If guardianSet key length is 0 and
|
* 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.
|
* vm.signatures length is 0, this could compromise the integrity of both vm and signature verification.
|
||||||
*/
|
*/
|
||||||
if (vm.signatures.length < quorum(guardianSet.keys.length)){
|
if (vm.signatures.length < quorum(guardianCount)){
|
||||||
return (false, "no quorum");
|
return (false, "no quorum");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,8 +139,9 @@ contract Messages is Getters {
|
||||||
*/
|
*/
|
||||||
function verifySignatures(bytes32 hash, Structs.Signature[] memory signatures, Structs.GuardianSet memory guardianSet) public pure returns (bool valid, string memory reason) {
|
function verifySignatures(bytes32 hash, Structs.Signature[] memory signatures, Structs.GuardianSet memory guardianSet) public pure returns (bool valid, string memory reason) {
|
||||||
uint8 lastIndex = 0;
|
uint8 lastIndex = 0;
|
||||||
|
uint256 sigCount = signatures.length;
|
||||||
uint256 guardianCount = guardianSet.keys.length;
|
uint256 guardianCount = guardianSet.keys.length;
|
||||||
for (uint i = 0; i < signatures.length; i++) {
|
for (uint i = 0; i < sigCount;) {
|
||||||
Structs.Signature memory sig = signatures[i];
|
Structs.Signature memory sig = signatures[i];
|
||||||
address signatory = ecrecover(hash, sig.v, sig.r, sig.s);
|
address signatory = ecrecover(hash, sig.v, sig.r, sig.s);
|
||||||
// ecrecover returns 0 for invalid signatures. We explicitly require valid signatures to avoid unexpected
|
// ecrecover returns 0 for invalid signatures. We explicitly require valid signatures to avoid unexpected
|
||||||
|
@ -134,6 +164,8 @@ contract Messages is Getters {
|
||||||
if(signatory != guardianSet.keys[sig.guardianIndex]){
|
if(signatory != guardianSet.keys[sig.guardianIndex]){
|
||||||
return (false, "VM signature invalid");
|
return (false, "VM signature invalid");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unchecked { i += 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If we are here, we've validated that the provided signatures are valid for the provided guardianSet
|
/// If we are here, we've validated that the provided signatures are valid for the provided guardianSet
|
||||||
|
|
|
@ -16,10 +16,11 @@ contract Setters is State {
|
||||||
|
|
||||||
function storeGuardianSet(Structs.GuardianSet memory set, uint32 index) internal {
|
function storeGuardianSet(Structs.GuardianSet memory set, uint32 index) internal {
|
||||||
uint setLength = set.keys.length;
|
uint setLength = set.keys.length;
|
||||||
for (uint i = 0; i < setLength; i++) {
|
for (uint i = 0; i < setLength;) {
|
||||||
require(set.keys[i] != address(0), "Invalid key");
|
require(set.keys[i] != address(0), "Invalid key");
|
||||||
|
unchecked { i += 1; }
|
||||||
}
|
}
|
||||||
_state.guardianSets[index] = set;
|
_state.guardianSets[index] = set;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setInitialized(address implementatiom) internal {
|
function setInitialized(address implementatiom) internal {
|
||||||
|
@ -54,4 +55,8 @@ contract Setters is State {
|
||||||
require(evmChainId == block.chainid, "invalid evmChainId");
|
require(evmChainId == block.chainid, "invalid evmChainId");
|
||||||
_state.evmChainId = evmChainId;
|
_state.evmChainId = evmChainId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setGuardianSetHash(uint32 index, bytes32 hash) internal {
|
||||||
|
_state.guardianSetHashes[index] = hash;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -44,6 +44,9 @@ contract Storage {
|
||||||
|
|
||||||
// EIP-155 Chain ID
|
// EIP-155 Chain ID
|
||||||
uint256 evmChainId;
|
uint256 evmChainId;
|
||||||
|
|
||||||
|
// Guardian set hashes.
|
||||||
|
mapping(uint32 => bytes32) guardianSetHashes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,10 +25,9 @@ contract TestMessages is Test {
|
||||||
ExportedMessages messages;
|
ExportedMessages messages;
|
||||||
|
|
||||||
Structs.GuardianSet guardianSet;
|
Structs.GuardianSet guardianSet;
|
||||||
|
Structs.GuardianSet guardianSetOpt;
|
||||||
|
|
||||||
function setUp() public {
|
function setupSingleGuardian() internal {
|
||||||
messages = new ExportedMessages();
|
|
||||||
|
|
||||||
// initialize guardian set with one guardian
|
// initialize guardian set with one guardian
|
||||||
address[] memory keys = new address[](1);
|
address[] memory keys = new address[](1);
|
||||||
keys[0] = vm.addr(testGuardian);
|
keys[0] = vm.addr(testGuardian);
|
||||||
|
@ -36,6 +35,42 @@ contract TestMessages is Test {
|
||||||
require(messages.quorum(guardianSet.keys.length) == 1, "Quorum should be 1");
|
require(messages.quorum(guardianSet.keys.length) == 1, "Quorum should be 1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setupMultiGuardian() internal {
|
||||||
|
// initialize guardian set with 19 guardians
|
||||||
|
address[] memory keys = new address[](19);
|
||||||
|
for (uint256 i = 0; i < 19; ++i) {
|
||||||
|
keys[i] = makeAddr(string(abi.encodePacked("guarian", i)));
|
||||||
|
}
|
||||||
|
guardianSetOpt = Structs.GuardianSet(keys, 0);
|
||||||
|
require(messages.quorum(guardianSetOpt.keys.length) == 13, "Quorum should be 13");
|
||||||
|
}
|
||||||
|
|
||||||
|
function setUp() public {
|
||||||
|
messages = new ExportedMessages();
|
||||||
|
setupSingleGuardian();
|
||||||
|
setupMultiGuardian();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testParseGuardianSetOptimized(uint8 guardianCount) public view {
|
||||||
|
vm.assume(guardianCount > 0 && guardianCount <= 19);
|
||||||
|
|
||||||
|
// Encode the guardian set.
|
||||||
|
bytes memory encodedGuardianSet;
|
||||||
|
for (uint256 i = 0; i < guardianCount; ++i) {
|
||||||
|
encodedGuardianSet = abi.encodePacked(encodedGuardianSet, guardianSetOpt.keys[i]);
|
||||||
|
}
|
||||||
|
encodedGuardianSet = abi.encodePacked(encodedGuardianSet, guardianSetOpt.expirationTime);
|
||||||
|
|
||||||
|
// Parse the guardian set.
|
||||||
|
Structs.GuardianSet memory parsedSet = messages.parseGuardianSetOptimized(encodedGuardianSet);
|
||||||
|
|
||||||
|
// Validate the results by comparing the parsed set to the original set.
|
||||||
|
for (uint256 i = 0; i < guardianCount; ++i) {
|
||||||
|
assert(parsedSet.keys[i] == guardianSetOpt.keys[i]);
|
||||||
|
}
|
||||||
|
assert(parsedSet.expirationTime == guardianSetOpt.expirationTime);
|
||||||
|
}
|
||||||
|
|
||||||
function testQuorum() public {
|
function testQuorum() public {
|
||||||
assertEq(messages.quorum(0), 1);
|
assertEq(messages.quorum(0), 1);
|
||||||
assertEq(messages.quorum(1), 1);
|
assertEq(messages.quorum(1), 1);
|
||||||
|
|
Loading…
Reference in New Issue