Skip to content

Instantly share code, notes, and snippets.

@itzmeanjan
Last active March 2, 2021 09:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save itzmeanjan/5f2f0b5ed5e245bc63477ca78f681d88 to your computer and use it in GitHub Desktop.
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 )
// 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;
}
}
// 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)
})
}
@itzmeanjan
Copy link
Author

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('https://goerli.infura.io/v3/<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

[]address{
0x928Ed6A3e94437bbd316cCAD78479f1d163A6A8C,
0x92Da9f8F3Ee16A276896fC7b2550b2151AAE0332,
0xb26c22237816d898cB9992D767444105BFDc03b6,
0xbe188D6641E8b680743A4815dFA0f6208038960F,
0xC26880A0AF2EA0c7E8130e6EC47Af756465452E8,
0xc275DC8bE39f50D12F66B6a63629C39dA5BAe5bd,
0xF903ba9E006193c1527BfBe65fe2123704EA3F99
}

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

@Meshugah
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.

@itzmeanjan
Copy link
Author

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