Skip to content

Instantly share code, notes, and snippets.

@itzmeanjan
Last active March 2, 2021 09:16
Show Gist options
  • 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

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