-
Notifications
You must be signed in to change notification settings - Fork 6.3k
Open
Labels
Description
Abstract
It would be nice to be able to write ECDSA as pure function.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
/**
* @dev Library for low-level memory interaction.
*/
library Memory {
/**
* @dev Allocates chunk of memory of unbounded size.
* @dev Does not update free memory pointer.
* @return memPtr Pointer to allocated memory.
*/
function allocateUnbounded() internal pure returns (uint256 memPtr) {
// https://github.com/ethereum/solidity/blob/v0.8.30/libsolidity/codegen/YulUtilFunctions.cpp#L3211
assembly ("memory-safe") {
memPtr := mload(0x40)
}
}
/**
* @dev Writes word to memory at given offset.
* @param memPtr Pointer to memory.
* @param offset Offset in memory.
* @param value Word to write.
*/
function writeWord(uint256 memPtr, uint256 offset, uint256 value) internal pure {
// https://evm.codes/#52
assembly ("memory-safe") {
mstore(add(memPtr, offset), value)
}
}
}
/**
* @dev Library for low-level ECDSA recovery.
*/
library ECDSA {
/**
* @dev Recovers Ethereum address from ECDSA signature.
* `ecrecover(e, v, r, s)` works according to formula $Q = r^{-1} \( sR - eG \)$
* from https://secg.org/sec1-v2.pdf#subsubsection.4.1.6.
* @param memPtr Memory pointer for writing 128 bytes of input data.
* @param e Message hash, can be any 256-bit number,
* will be reduced to valid scalar (can be zero scalar).
* @param v Recovery ID, can be 27 or 28.
* Point `R(x, y)` has `yParity = v - 27`, `y` is calculated from `yParity`.
* @param r Non-zero scalar r, must be in `[1, Secp256k1.N)` and `x = r` must be on curve.
* Point `R(x, y)` has coordinate `x = r`.
* @param s Non-zero scalar s, must be in `[1, Secp256k1.N)`.
* @return recovered 160-bit Ethereum address of `Q` point.
* @dev If `v, r, s` do not satisfy above conditions, then `recovered = 0`
*/
function recover(uint256 memPtr, uint256 e, uint256 v, uint256 r, uint256 s)
internal
view
returns (uint256 recovered)
{
// https://github.com/ethereum/solidity/blob/v0.8.30/libsolidity/codegen/ir/IRGeneratorForStatements.cpp#L1653
Memory.writeWord(memPtr, 0x00, e);
Memory.writeWord(memPtr, 0x20, v);
Memory.writeWord(memPtr, 0x40, r);
Memory.writeWord(memPtr, 0x60, s);
Memory.writeWord(0x00, 0x00, 0x00);
// https://evm.codes/precompiled#0x01
assembly ("memory-safe") {
pop(staticcall(gas(), 0x01, memPtr, 0x80, 0x00, 0x20))
recovered := mload(0x00)
}
}
}
contract Demo {
constructor() payable {}
// pure
function recover1(bytes32 e, uint8 v, bytes32 r, bytes32 s) public pure returns(address) {
return ecrecover(e, v, r, s);
}
// view
function recover2(bytes32 e, uint8 v, bytes32 r, bytes32 s) public view returns(address) {
uint256 memPtr = Memory.allocateUnbounded();
return address(uint160(ECDSA.recover(memPtr, uint256(e), v, uint256(r), uint256(s))));
}
}Motivation
This can be useful for optimization. For example, suppose you're building a cryptographic library in Solidity. There's no point in using view if it's actually pure.