|
// SPDX-License-Identifier: AGPL-3.0-only |
|
pragma solidity >=0.8.0; |
|
|
|
/// @notice Gas optimized ECDSA wrapper. |
|
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/ECDSA.sol) |
|
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol) |
|
library ECDSA { |
|
function recover(bytes32 hash, bytes calldata signature) internal view returns (address result) { |
|
assembly { |
|
// Copy the free memory pointer so that we can restore it later. |
|
let m := mload(0x40) |
|
|
|
// Directly load the fields from the calldata. |
|
let s := calldataload(add(signature.offset, 0x20)) |
|
// If `signature.length == 65`, but just do it anyway as it costs less gas than a switch. |
|
let v := byte(0, calldataload(add(signature.offset, 0x40))) |
|
|
|
// If `signature.length == 64`. |
|
if iszero(sub(signature.length, 64)) { |
|
// Here, `s` is actually `vs` that needs to be recovered into `v` and `s`. |
|
v := add(shr(255, s), 27) |
|
// prettier-ignore |
|
s := and(s, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) |
|
} |
|
|
|
// If signature is valid and not malleable. |
|
if and( |
|
// `s` in lower half order. |
|
// prettier-ignore |
|
lt(s, 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1), |
|
// `v` is 27 or 28 |
|
byte(v, 0x0101000000) |
|
) { |
|
mstore(0x00, hash) |
|
mstore(0x20, v) |
|
calldatacopy(0x40, signature.offset, 0x20) // Directly copy `r` over. |
|
mstore(0x60, s) |
|
let success := staticcall( |
|
gas(), // Amount of gas left for the transaction. |
|
0x01, // Address of `ecrecover`. |
|
0x00, // Start of input. |
|
0x80, // Size of input. |
|
0x40, // Start of output. |
|
0x20 // Size of output. |
|
) |
|
// Restore the zero slot. |
|
mstore(0x60, 0) |
|
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. |
|
result := mload(sub(0x60, mul(returndatasize(), success))) |
|
} |
|
// Restore the free memory pointer. |
|
mstore(0x40, m) |
|
} |
|
} |
|
|
|
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) { |
|
assembly { |
|
// Store into scratch space for keccak256. |
|
mstore(0x20, hash) |
|
mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") |
|
// 0x40 - 0x04 = 0x3c |
|
result := keccak256(0x04, 0x3c) |
|
} |
|
} |
|
|
|
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) { |
|
assembly { |
|
// We need at most 128 bytes for Ethereum signed message header. |
|
// The max length of the ASCII reprenstation of a uint256 is 78 bytes. |
|
// The length of "\x19Ethereum Signed Message:\n" is 26 bytes. |
|
// The next multiple of 32 above 78 + 26 is 128. |
|
|
|
// Instead of allocating, we temporarily copy the 128 bytes before the |
|
// start of `s` data to some variables. |
|
let m3 := mload(sub(s, 0x60)) |
|
let m2 := mload(sub(s, 0x40)) |
|
let m1 := mload(sub(s, 0x20)) |
|
// The length of `s` is in bytes. |
|
let sLength := mload(s) |
|
|
|
let ptr := add(s, 0x20) |
|
|
|
// `end` marks the end of the memory which we will compute the keccak256 of. |
|
let end := add(ptr, sLength) |
|
|
|
// Convert the length of the bytes to ASCII decimal representation |
|
// and store it into the memory. |
|
for { |
|
let temp := sLength |
|
ptr := sub(ptr, 1) |
|
mstore8(ptr, add(48, mod(temp, 10))) |
|
temp := div(temp, 10) |
|
} temp { |
|
temp := div(temp, 10) |
|
} { |
|
ptr := sub(ptr, 1) |
|
mstore8(ptr, add(48, mod(temp, 10))) |
|
} |
|
|
|
// Move the pointer 32 bytes lower to make room for the string. |
|
// `start` marks the start of the memory which we will compute the keccak256 of. |
|
let start := sub(ptr, 32) |
|
// Copy the header over to the memory. |
|
mstore(start, "\x00\x00\x00\x00\x00\x00\x19Ethereum Signed Message:\n") |
|
start := add(start, 6) |
|
|
|
// Compute the keccak256 of the memory. |
|
result := keccak256(start, sub(end, start)) |
|
|
|
// Restore the previous memory. |
|
mstore(s, sLength) |
|
mstore(sub(s, 0x20), m1) |
|
mstore(sub(s, 0x40), m2) |
|
mstore(sub(s, 0x60), m3) |
|
} |
|
} |
|
} |
|
|
|
/// @notice Gas optimized verification of proof of inclusion for a leaf in a Merkle tree. |
|
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/MerkleProof.sol) |
|
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/MerkleProof.sol) |
|
library MerkleProof { |
|
function verify( |
|
bytes32[] calldata proof, |
|
bytes32 root, |
|
bytes32 leaf |
|
) internal pure returns (bool isValid) { |
|
assembly { |
|
// Left shift by 5 is equivalent to multiplying by 0x20. |
|
let end := add(proof.offset, shl(5, proof.length)) |
|
|
|
// Iterate over proof elements to compute root hash. |
|
for { |
|
// Initialize `data` to the offset of `proof` in the calldata. |
|
let data := proof.offset |
|
} iszero(eq(data, end)) { |
|
data := add(data, 0x20) |
|
} { |
|
// Slot of `leaf` in scratch space. |
|
// If the condition is true: 0x20, otherwise: 0x00. |
|
let scratch := shl(5, gt(leaf, calldataload(data))) |
|
|
|
// Store elements to hash contiguously in scratch space. |
|
// Scratch space is 64 bytes (0x00 - 0x3f) and both elements are 32 bytes. |
|
mstore(scratch, leaf) |
|
mstore(xor(scratch, 0x20), calldataload(data)) |
|
// Reuse `leaf` to store the hash to reduce stack operations. |
|
leaf := keccak256(0x00, 0x40) |
|
} |
|
isValid := eq(leaf, root) |
|
} |
|
} |
|
} |
|
|
|
|
|
contract TestSolidity { |
|
|
|
// Input: 0x2d0828dd7c97cff316356da3c16c68ba2316886a0e05ebafb8291939310d51a3, |
|
// 0x331fe75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e01c |
|
// Gas used: 26869 |
|
function recover(bytes32 hash, bytes calldata signature) public view returns (address result) { |
|
result = ECDSA.recover(hash, signature); |
|
} |
|
|
|
// Input: 0x2d0828dd7c97cff316356da3c16c68ba2316886a0e05ebafb8291939310d51a3 |
|
// Gas used: 21970 |
|
function toEthSignedMessageHash(bytes32 hash) public view returns (bytes32 result) { |
|
result = ECDSA.toEthSignedMessageHash(hash); |
|
} |
|
|
|
// Input: ["0xbc954aa7056a720314aebed21b6994f389984cd457eeb75b766bbe2454fe5074", "0x6b134bd38f9b7cca9c5619690efeed06cc215e0e99ec813d4cdc787d3059c134", "0x0550e4726615ca382b3453a849fab0d68175c7d51d1b4fc0098bc6728272f1a8", "0x88e86d4b2c853a78cad494383812e063579c739ae56172b819f36ac881b21406", "0x0e5981d26305d5fae2eb1c45c8db50d44417870d4e1b66b2b296f2ed3b6cc9d6", "0xbcd65a69edd6ca87dfc5db88ab259577604fa7ad5ec6b5bd7841b1a0fae4a18f", "0xdcbeba93024e673b78504e60eea6a848b1d04970b0e1192f02ea91de1e12e31f", "0xf7fd534621bec145aa8b37c12e4fc58413adaf76d3c38c42ad3d464dfd239cd6", "0xcf20917b390bf32bf4ead5776b5254325bd8b13d9e1308d6b3ad52ab37d9a040", "0xafee348eb2224de248b5d43949d38d5be97fe1b72637f7d9c776561a3f75e17e"], |
|
// 0xef35dac8c7728a6c30dc702829819d9d3349f1435480726d0a865665ef8ace69, |
|
// 0x54a6ae86104cedff370ad1da9bb9f2fc64a6e5fb3f2a8ee17a1d1c0d8ecd2267 |
|
// Gas used: 29481 |
|
function merkleVerify(bytes32[] calldata proof, bytes32 root, bytes32 leaf) external pure returns (bool isValid) { |
|
isValid = MerkleProof.verify(proof, root, leaf); |
|
} |
|
|
|
// Input: 1234 |
|
// Gas used: 22122 |
|
function toString(uint256 value) external pure returns (string memory ptr) { |
|
assembly { |
|
// The maximum value of a uint256 contains 78 digits (1 byte per digit), |
|
// but we allocate 128 bytes to keep the free memory pointer 32-byte word aliged. |
|
// We will need 1 32-byte word to store the length, |
|
// and 3 32-byte words to store a maximum of 78 digits. Total: 32 + 3 * 32 = 128. |
|
ptr := add(mload(0x40), 128) |
|
// Update the free memory pointer to allocate. |
|
mstore(0x40, ptr) |
|
|
|
// Cache the end of the memory to calculate the length later. |
|
let end := ptr |
|
|
|
// We write the string from the rightmost digit to the leftmost digit. |
|
// The following is essentially a do-while loop that also handles the zero case. |
|
// Costs a bit more than early returning for the zero case, |
|
// but cheaper in terms of deployment and overall runtime costs. |
|
for { |
|
// Initialize and perform the first pass without check. |
|
let temp := value |
|
// Move the pointer 1 byte leftwards to point to an empty character slot. |
|
ptr := sub(ptr, 1) |
|
// Write the character to the pointer. 48 is the ASCII index of '0'. |
|
mstore8(ptr, add(48, mod(temp, 10))) |
|
temp := div(temp, 10) |
|
} temp { |
|
// Keep dividing `temp` until zero. |
|
temp := div(temp, 10) |
|
} { // Body of the for loop. |
|
ptr := sub(ptr, 1) |
|
mstore8(ptr, add(48, mod(temp, 10))) |
|
} |
|
|
|
let length := sub(end, ptr) |
|
// Move the pointer 32 bytes leftwards to make room for the length. |
|
ptr := sub(ptr, 32) |
|
// Store the length. |
|
mstore(ptr, length) |
|
} |
|
} |
|
} |