Skip to content

Instantly share code, notes, and snippets.

Last active February 8, 2021 12:25
Show Gist options
  • Save itzmeanjan/5b7c6e973bc0785018cce100ee68fee7 to your computer and use it in GitHub Desktop.
Save itzmeanjan/5b7c6e973bc0785018cce100ee68fee7 to your computer and use it in GitHub Desktop.
Smart contract for decoding Matic Network's check point signer addresses
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract DecodeCheckpointSignerList {
// 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, bytes 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, bytes memory) {
return abi.decode(slice(payload, 4, payload.length - 4), (bytes, bytes));
// 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));
// Attempt to recover signer address, given original message & signed message
function ecrecovery(bytes32 voteHash, bytes memory sig) internal pure returns (bytes memory) {
bytes32 r;
bytes32 s;
uint8 v;
assembly {
r := mload(add(sig, 32))
s := mload(add(sig, 64))
v := and(mload(add(sig, 65)), 255)
if (v < 27) v += 27;
return addresstoBytes(ecrecover(voteHash, v, r, s));
// Converts Ethereum address to bytes data type
function addresstoBytes(address a) internal pure returns (bytes memory) {
return abi.encodePacked(a);
// Passing transaction input data of `submitHeaderBlock(bytes data, bytes sigs)` function
// call, it attempts to figure out what are those signers who signer this checkpoint
// Note: Sending checkpoint from Matic Network ( L2 ) to Ethereum Network ( L1 )
// is nothing but calling `submitHeaderBlock(bytes data, bytes sigs)`, defined
// in RootChain contract, deployed on Ethereum Network, with proper arguments, by some validator.
// RootChain :
// 0x2890bA17EfE978480615e330ecB65333b880928e [ Goerli ]
// 0x86E4Dc95c7FBdBf52e33D563BbDB00823894C287 [ Ethereum Mainnet ]
function decode(bytes calldata payload) external pure returns (bytes[] memory) {
(bytes memory data, bytes memory sigs) = decodeIntoDataAndSignature(payload);
bytes32 voteHash = computeVoteHash(data);
bytes[] memory signers = new bytes[](sigs.length / 65);
uint256 count = 0;
for(uint256 i = 0; i < sigs.length; i += 65) {
signers[count++] = ecrecovery(voteHash, slice(sigs, i, 65));
return signers;
Copy link

Given check point submission transaction hash, input data for invocation of RootChain.submitHeaderBlock(bytes data, bytes sigs), can be obtained very easily using web3js like πŸ‘‡

$ 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('<api-key>'))
> web3.eth.getTransaction('0x6eaa68da9a4fc0df104165581bff6fd340ee64f765c9153fcf70ab314a4ff7b9').then(console.log)
Promise { <pending> }
> {
  blockHash: '0xc226b396d58c96198e04d98b97669f0c173ee6fa9ad224298dfff003558489a6',
  blockNumber: 4220027,
  from: '0xC26880A0AF2EA0c7E8130e6EC47Af756465452E8',
  gas: 476806,
  gasPrice: '2000000000',
  hash: '0x6eaa68da9a4fc0df104165581bff6fd340ee64f765c9153fcf70ab314a4ff7b9',
  input: '0x6a791f110000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000c26880a0af2ea0c7e8130e6ec47af756465452e800000000000000000000000000000000000000000000000000000000009a370200000000000000000000000000000000000000000000000000000000009a3801c04286729eaa4a9fee6698a7a78c90009f2dd76e71df4458f04fbcc7eee109401a2c704cdd05b1da028e06f6e0bfe0d914798e671b436237c2c05fadd363fe8f000000000000000000000000000000000000000000000000000000000001388100000000000000000000000000000000000000000000000000000000000001c794e2784333fea7efdcc5e1874ba4734387a846aad22ec54b2c8730f0a1ce86e27f483f8032c229c6fcd993b00c2e10c43edab912e51f8eef01d9bcf8ba1f0af200fcf30d47a10473c21ce852170d7a29f8cbc4f36fa3b0d0931d688a7e9caf3c3b30ecb8b1d810400a4f425468114dfbcc6ab9a476e4a56551dfe20430322ce5d800bb0514711a104715846b941bb3b4db45337f15ba71521210511d7048dc9688692d1cbb9b6d11360bd9d0c7c1cfa1c343f5fb199a9db6a5eca7e145c883f0244901aaa04ceb818d30de0520faebd86ab0ff5caf5076919f09f861762247a3cee5e2037d3f6532aac542b48b7088410dd01e84eae4d537289e494a48194dba5e9e4a0156002d004cdd2d6ae3a7fb9067ba48f37e9f21a4da10236c213ef1716e5b438351aa321e6a9ffbfa1764e57fab6bb736d66cd810ded7218323bba5d89c43bf45001a779826e0bf3e47c42ab1fa835cb2635d865bbc26b16279522dc703c95f8878600e8dc768e988d7b87df4e402ef20be9f74a24b8c54d3bd0147b06d46da250f011fe7991b6c36d55b31435aba926f182f678c8a68a125b005b681d505e856b34605dad8ac512252e33d29e14d1e7bde9dbbcae986e3ee9078bde498dd4aa8b9d70000000000000000000000000000000000000000000000000000',
  nonce: 5001,
  r: '0x3e04b6b7feb0fc9c85955ea302676f0aa7e8af6ee70f4fce8b71489029631dc6',
  s: '0x6e0e9820fbad475d7ab4908d405e5ebfb04db2df4e2b13683989d6729236ccc0',
  to: '0x2890bA17EfE978480615e330ecB65333b880928e',
  transactionIndex: 0,
  v: '0x1b',
  value: '0'

Now using πŸ‘† contract's decode method, all check point signer addresses can be determined very easily. decode accepts single parameter which is input field of transaction object πŸ‘†.

Deployment details to be put πŸ‘‡, in some time.

Copy link

DecodeCheckpointSignerList is deployed on

Copy link

New revision to return checkpoint signer addresses in bytes[] form instead of address[].

πŸ‘† change is being made to address issue faced in Matic Network's The Graph deployment, where it requires addresses in Array<Bytes> form & conversion from Array<Address> to Array<Bytes> fails in runtime.

An issue was created in graph-ts repo, reporting this.

But this change probably solves aforementioned problem πŸ˜‰.

Copy link

itzmeanjan commented Feb 8, 2021

Updated contract's deployment details:

Please use πŸ‘† contracts if you need to get signer addresses back in bytes[] form

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment