Skip to content

Instantly share code, notes, and snippets.

Last active March 2, 2021 09:16
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
What would you like to do?
Smart contract for decoding checkpoint submission data from Matic Network ( L2 ) to Ethereum Network ( L1 )
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Here's v1 of this contract :
// ๐Ÿ‘† 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;
// 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 => {
// `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)
Copy link

Given txInput of new submitHeaderBlock(bytes data, uint256[3][] sigs) call, ๐Ÿ‘† contract will attempt to extract out signer addresses.

Here's previous version of this contract : v1

From now on v2 is supposed to be used as checkpoint submission transaction's payload has changed.

Deployment details to be added soon ๐Ÿ˜‰.

Copy link

Newly added RecoverCheckpointSignerList.js is nothing but JS implementation of prewritten Smart Contract. Use it in your project for decoding Matic check point signer list from check point submission data.

Block number after which this should be used, it yet to be provided.

Copy link

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 tx

$ node
Welcome to Node.js v12.19.0.
Type ".help" for more information.
> const Web3 = require('web3')
> const web3 = new Web3(new Web3.providers.HttpProvider('<put-your-api-key>'))
> web3.eth.getTransaction('0x0009dc0fad318e76ad84f89b3b5fbb4556f5f83c006f7160cb26bcbd5d79edaa').then(console.log)
Promise { <pending> }
> {
  blockHash: '0xa65fa10c9eeb87a7890db49a2b07a9d026bce32e6519cda2ef6994be75608b81',
  blockNumber: 4291825,
  from: '0xC26880A0AF2EA0c7E8130e6EC47Af756465452E8',
  gas: 301745,
  gasPrice: '25000000000',
  hash: '0x0009dc0fad318e76ad84f89b3b5fbb4556f5f83c006f7160cb26bcbd5d79edaa',
  input: '0x4e43e4950000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000c26880a0af2ea0c7e8130e6ec47af756465452e80000000000000000000000000000000000000000000000000000000000a14d020000000000000000000000000000000000000000000000000000000000a14e0117ff23d54bb91b36ee8d83097ddedddde97b702a298f211655f9b5207118394a1a2c704cdd05b1da028e06f6e0bfe0d914798e671b436237c2c05fadd363fe8f00000000000000000000000000000000000000000000000000000000000138810000000000000000000000000000000000000000000000000000000000000007ba8e827f4af702ff5565b68653e4d0e0fd2c94c0b7b02f78f1b9ce0b2963236237025e57622dd13997a6980eb434149af9ed096a2f22039d1177baf5dde41973000000000000000000000000000000000000000000000000000000000000001cc53f3536f61867f54ebf25a192cf489391b0540d21fcc666a311b7e2c15907683168e40ee8b43da021671e5302c02f50afaed9ed6d0d52c567e923f6f8e8daf9000000000000000000000000000000000000000000000000000000000000001bd1cecbd4ebe1efb2fcf83fd64d990c0b5e9d0b6f7988ead07575c48c450d6c782f32fb59dea59363babe5a22cff91d76d687f396400e4dfc1fac52fbe2c71ab6000000000000000000000000000000000000000000000000000000000000001ca9c25d984871ba4a46140ac3823dc4c814bde0655b72fa00cd2a680f57544e234523a9d5a79dbddea126715c727568122e19345dea9f387589171efe46f74bdd000000000000000000000000000000000000000000000000000000000000001cca91e17f7d5ebef9fef564f082f1d0991991f545a6fd27fcd383bc5030187b541d8a0ff533bce7ae067e754ccbf8b5601493415efdf69722ccddd9b607aee700000000000000000000000000000000000000000000000000000000000000001c96038bfac17eee2fc2fe1781d918687f607107a32a06434ce2db6b9b618b9100349188fd5420ce8a230165ad23e18e56351099c1a022d3aacb9579cd2e3f3ad3000000000000000000000000000000000000000000000000000000000000001c23158274a86c50fb0099cdae0f87292bc77277d479900a99fd1fba69642b98235e7d7d46ddb9dba6be91c450d22140fda983cc8ecfd54772ffeec0bcf01e9fff000000000000000000000000000000000000000000000000000000000000001b',
  nonce: 5474,
  r: '0xb01c5744acc8953cbdea46890d9008b96681c467d3ffbc617058be3f8163477f',
  s: '0x24c17e4ba30e9d98cb7097f9c55c70f6bc377bbfb2a8d514c66eb478bb5061c2',
  to: '0x2890bA17EfE978480615e330ecB65333b880928e',
  transactionIndex: 4,
  v: '0x1c',
  value: '0'

We took txInput data & invoked ๐Ÿ‘† contract's decode method & we got back check point signer list


Now either of contract method or JS implementation can be used for recovering checkpoint signer list from checkpoint submission data with updated RootChain contract.

Copy link

Meshugah commented Mar 2, 2021

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.

Copy link

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