Last active
March 2, 2021 09:16
-
-
Save itzmeanjan/5f2f0b5ed5e245bc63477ca78f681d88 to your computer and use it in GitHub Desktop.
Smart contract for decoding checkpoint submission data from Matic Network ( L2 ) to Ethereum Network ( L1 )
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.0; | |
// Here's v1 of this contract : https://gist.github.com/itzmeanjan/5b7c6e973bc0785018cce100ee68fee7 | |
// | |
// 👆 used for decoding checkpoint submission data previously, after recent change in the way | |
// checkpoints are to be submitted to Ethereum, 👇 is supposed to be used | |
// | |
// Call `decode` pure function with `txInput` of `submitHeaderBlock(bytes data, uint256[3][] sigs)` tx | |
// as argument, it'll return checkpoint signer addresses | |
contract DecodeCheckpointSignerListV2 { | |
// Slice specified number of bytes from arbitrary length byte array, starting from certain index | |
function slice(bytes memory payload, uint256 start, uint256 length) internal pure returns (bytes memory) { | |
require(length + 31 >= length, "slice_overflow"); | |
require(start + length >= start, "slice_overflow"); | |
require(payload.length >= start + length, "slice_outOfBounds"); | |
bytes memory tempBytes; | |
assembly { | |
switch iszero(length) | |
case 0 { | |
tempBytes := mload(0x40) | |
let lengthmod := and(length, 31) | |
let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) | |
let end := add(mc, length) | |
for { | |
let cc := add(add(add(payload, lengthmod), mul(0x20, iszero(lengthmod))), start) | |
} lt(mc, end) { | |
mc := add(mc, 0x20) | |
cc := add(cc, 0x20) | |
} { | |
mstore(mc, mload(cc)) | |
} | |
mstore(tempBytes, length) | |
mstore(0x40, and(add(mc, 31), not(31))) | |
} | |
default { | |
tempBytes := mload(0x40) | |
mstore(tempBytes, 0) | |
mstore(0x40, add(tempBytes, 0x20)) | |
} | |
} | |
return tempBytes; | |
} | |
// Given input data for transaction invoking `submitHeaderBlock(bytes data, uint256[3][] sigs)` | |
// attempts to extract out data & signature fields | |
// | |
// Note: Function signature is also included in `payload` i.e. first 4 bytes, which will be | |
// stripped out 👇 | |
function decodeIntoDataAndSignature(bytes calldata payload) internal pure returns (bytes memory, uint256[3][] memory) { | |
return abi.decode(slice(payload, 4, payload.length - 4), (bytes, uint256[3][])); | |
} | |
// Given 👆 function call for extracting `data` from transaction input data | |
// has succeeded, votehash can be computed, which was signed by these check point signers | |
function computeVoteHash(bytes memory payload) internal pure returns (bytes32) { | |
return keccak256(abi.encodePacked(hex"01", payload)); | |
} | |
// Given original message & signed message i.e. signature components in form of `r, s, v` attempts to find out | |
// signer address | |
function ecrecovery(bytes32 votehash, uint256[3] memory sig) internal pure returns(address) { | |
bytes32 r; | |
bytes32 s; | |
uint8 v; | |
assembly { | |
r := mload(sig) | |
s := mload(add(sig, 32)) | |
v := byte(31, mload(add(sig, 64))) | |
} | |
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) return address(0x0); | |
if (v < 27) v += 27; | |
if (v != 27 && v != 28) return address(0x0); | |
address result = ecrecover(votehash, v, r, s); | |
require(result != address(0x0), "Failed to extract signer"); | |
return result; | |
} | |
// Passing transaction input data of `submitHeaderBlock(bytes data, uint256[3][] sigs)` function | |
// call, it attempts to figure out who are those checkpoint signers | |
// | |
// Note: Sending checkpoint from Matic Network ( L2 ) to Ethereum Network ( L1 ) | |
// is nothing but calling `submitHeaderBlock(bytes data, uint256[3][] sigs)`, defined | |
// in RootChain contract, deployed on Ethereum Network, with proper arguments, by some validator. | |
function decode(bytes calldata payload) external pure returns (address[] memory) { | |
(bytes memory data, uint256[3][] memory sigs) = decodeIntoDataAndSignature(payload); | |
bytes32 voteHash = computeVoteHash(data); | |
address[] memory signers = new address[](sigs.length); | |
for(uint256 i = 0; i < sigs.length; i++) { | |
signers[i] = ecrecovery(voteHash, sigs[i]); | |
} | |
return signers; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Before using this script in your project, make sure you install 👇 | |
// two dependencies | |
const ethUtil = require('ethereumjs-util') | |
const ethABI = require('ethereumjs-abi') | |
// Remove function signature from txInput data & attempt to | |
// extract two arguments to that function | |
const decodeIntoDataAndSignature = payload => { | |
let _data = `0x${payload.slice(10)}` | |
return ethABI.rawDecode(['bytes', 'uint256[3][]'], ethUtil.toBuffer(_data)) | |
} | |
// After two arguments are decoded, calculate vote hash, which validators | |
// signed using their private key | |
// | |
// Using this message & signature components, we'll attempt to find out | |
// who was the signer | |
const computeVoteHash = data => { | |
return ethABI.soliditySHA3(['bytes', 'bytes'], [ethUtil.toBuffer('0x01'), data]) | |
} | |
// This function is supposed to be called with txInput data of check point submission tx | |
const decode = payload => { | |
const [data, sigs] = decodeIntoDataAndSignature(payload) | |
const voteHash = computeVoteHash(data) | |
return sigs.map(sig => { | |
// `sig` is an array with signature components in order `[v, r, s]` | |
const pubKey = ethUtil.ecrecover(voteHash, sig[2], sig[0], sig[1]) | |
const addr = ethUtil.pubToAddress(pubKey) | |
return ethUtil.bufferToHex(addr) | |
}) | |
} |
Maybe let's change the name of the iterator from v to something else? @itzmeanjan I'll change it to sig
on mine. And create a pull request for the same.
Maybe let's change the name of the iterator from v to something else? @itzmeanjan I'll change it to
sig
on mine. And create a pull request for the same.
Hm, that'll make it more easily understandable.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
At tx new check point submission contracts were deployed on Goerli Network.
To check whether our
decode
function works as expected with new checkpoint submission method or not, we ran following test with new checkpoint submission txWe took
txInput
data & invoked 👆 contract'sdecode
method & we got back check point signer listNow either of contract method or JS implementation can be used for recovering checkpoint signer list from checkpoint submission data with updated
RootChain
contract.