-
-
Save itzmeanjan/5f2f0b5ed5e245bc63477ca78f681d88 to your computer and use it in GitHub Desktop.
// 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) | |
}) | |
} |
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.
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.
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.