Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ DSTestPlusTest:testBrutalizeMemory() (gas: 823)
DSTestPlusTest:testFailBoundMinBiggerThanMax() (gas: 287)
DSTestPlusTest:testMeasuringGas() (gas: 24600)
DSTestPlusTest:testRelApproxEqBothZeroesPasses() (gas: 391)
ECDSATest:testBytes32ToEthSignedMessageHash() (gas: 342)
ECDSATest:testBytesToEthSignedMessageHashEmpty() (gas: 610)
ECDSATest:testBytesToEthSignedMessageHashEmptyLong() (gas: 771)
ECDSATest:testBytesToEthSignedMessageHashShort() (gas: 629)
ECDSATest:testRecoverWithInvalidLongSignature() (gas: 5041)
ECDSATest:testRecoverWithInvalidShortSignature() (gas: 4915)
ECDSATest:testRecoverWithInvalidSignature() (gas: 5175)
ECDSATest:testRecoverWithV0SignatureWithShortEIP2098Format() (gas: 4962)
ECDSATest:testRecoverWithV0SignatureWithVersion00() (gas: 5077)
ECDSATest:testRecoverWithV0SignatureWithVersion27() (gas: 5074)
ECDSATest:testRecoverWithV0SignatureWithWrongVersion() (gas: 5076)
ECDSATest:testRecoverWithV1SignatureWithShortEIP2098Format() (gas: 4984)
ECDSATest:testRecoverWithV1SignatureWithVersion01() (gas: 5075)
ECDSATest:testRecoverWithV1SignatureWithVersion28() (gas: 5054)
ECDSATest:testRecoverWithV1SignatureWithWrongVersion() (gas: 5053)
ECDSATest:testRecoverWithValidSignature() (gas: 5150)
ECDSATest:testRecoverWithWrongSigner() (gas: 5130)
ERC1155Test:testApproveAll() (gas: 31053)
ERC1155Test:testBatchBalanceOf() (gas: 157552)
ERC1155Test:testBatchBurn() (gas: 151044)
Expand Down
126 changes: 126 additions & 0 deletions src/test/ECDSA.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

import {DSTestPlus} from "./utils/DSTestPlus.sol";

import {ECDSA} from "../utils/ECDSA.sol";

contract ECDSATest is DSTestPlus {
using ECDSA for bytes32;
using ECDSA for bytes;

bytes32 constant TEST_MESSAGE = 0x7dbaf558b0a1a5dc7a67202117ab143c1d8605a983e4a743bc06fcc03162dc0d;

bytes32 constant WRONG_MESSAGE = 0x2d0828dd7c97cff316356da3c16c68ba2316886a0e05ebafb8291939310d51a3;

address constant SIGNER = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8;

address constant V0_SIGNER = 0x2cc1166f6212628A0deEf2B33BEFB2187D35b86c;

address constant V1_SIGNER = 0x1E318623aB09Fe6de3C9b8672098464Aeda9100E;

function testRecoverWithInvalidShortSignature() public {
bytes memory signature = hex"1234";
assertTrue(this.recover(TEST_MESSAGE, signature) == address(0));
}

function testRecoverWithInvalidLongSignature() public {
// prettier-ignore
bytes memory signature = hex"01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
assertTrue(this.recover(TEST_MESSAGE, signature) == address(0));
}

function testRecoverWithValidSignature() public {
// prettier-ignore
bytes memory signature = hex"8688e590483917863a35ef230c0f839be8418aa4ee765228eddfcea7fe2652815db01c2c84b0ec746e1b74d97475c599b3d3419fa7181b4e01de62c02b721aea1b";
assertTrue(this.recover(TEST_MESSAGE.toEthSignedMessageHash(), signature) == SIGNER);
}

function testRecoverWithWrongSigner() public {
// prettier-ignore
bytes memory signature = hex"8688e590483917863a35ef230c0f839be8418aa4ee765228eddfcea7fe2652815db01c2c84b0ec746e1b74d97475c599b3d3419fa7181b4e01de62c02b721aea1b";
assertTrue(this.recover(WRONG_MESSAGE.toEthSignedMessageHash(), signature) != SIGNER);
}

function testRecoverWithInvalidSignature() public {
// prettier-ignore
bytes memory signature = hex"332ce75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e01c";
assertTrue(this.recover(TEST_MESSAGE.toEthSignedMessageHash(), signature) != SIGNER);
}

function testRecoverWithV0SignatureWithVersion00() public {
// prettier-ignore
bytes memory signature = hex"5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be89200";
assertTrue(this.recover(TEST_MESSAGE, signature) == address(0));
}

function testRecoverWithV0SignatureWithVersion27() public {
// prettier-ignore
bytes memory signature = hex"5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be8921b";
assertTrue(this.recover(TEST_MESSAGE, signature) == V0_SIGNER);
}

function testRecoverWithV0SignatureWithWrongVersion() public {
// prettier-ignore
bytes memory signature = hex"5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be89202";
assertTrue(this.recover(TEST_MESSAGE, signature) == address(0));
}

function testRecoverWithV0SignatureWithShortEIP2098Format() public {
// prettier-ignore
bytes memory signature = hex"5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be892";
assertTrue(this.recover(TEST_MESSAGE, signature) == V0_SIGNER);
}

function testRecoverWithV1SignatureWithVersion01() public {
// prettier-ignore
bytes memory signature = hex"331fe75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e001";
assertTrue(this.recover(TEST_MESSAGE, signature) == address(0));
}

function testRecoverWithV1SignatureWithVersion28() public {
// prettier-ignore
bytes memory signature = hex"331fe75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e01c";
assertTrue(this.recover(TEST_MESSAGE, signature) == V1_SIGNER);
}

function testRecoverWithV1SignatureWithWrongVersion() public {
// prettier-ignore
bytes memory signature = hex"331fe75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e002";
assertTrue(this.recover(TEST_MESSAGE, signature) == address(0));
}

function testRecoverWithV1SignatureWithShortEIP2098Format() public {
// prettier-ignore
bytes memory signature = hex"331fe75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feffc8e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e0";
assertTrue(this.recover(TEST_MESSAGE, signature) == V1_SIGNER);
}

function testBytes32ToEthSignedMessageHash() public {
// prettier-ignore
assertTrue(TEST_MESSAGE.toEthSignedMessageHash() == bytes32(0x7d768af957ef8cbf6219a37e743d5546d911dae3e46449d8a5810522db2ef65e));
}

function testBytesToEthSignedMessageHashShort() public {
bytes memory message = hex"61626364";
// prettier-ignore
assertTrue(message.toEthSignedMessageHash() == bytes32(0xefd0b51a9c4e5f3449f4eeacb195bf48659fbc00d2f4001bf4c088ba0779fb33));
}

function testBytesToEthSignedMessageHashEmpty() public {
bytes memory message = hex"";
// prettier-ignore
assertTrue(message.toEthSignedMessageHash() == bytes32(0x5f35dce98ba4fba25530a026ed80b2cecdaa31091ba4958b99b52ea1d068adad));
}

function testBytesToEthSignedMessageHashEmptyLong() public {
// prettier-ignore
bytes memory message = hex"4142434445464748494a4b4c4d4e4f505152535455565758595a6162636465666768696a6b6c6d6e6f707172737475767778797a3031323334353637383921402324255e262a28292d3d5b5d7b7d";
// prettier-ignore
assertTrue(message.toEthSignedMessageHash() == bytes32(0xa46dbedd405cff161b6e80c17c8567597621d9f4c087204201097cb34448e71b));
}

function recover(bytes32 hash, bytes calldata signature) external view returns (address) {
return ECDSA.recover(hash, signature);
}
}
115 changes: 115 additions & 0 deletions src/utils/ECDSA.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: MIT
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 `s` from the calldata.
let s := calldataload(add(signature.offset, 0x20))

switch signature.length
case 64 {
// Here, `s` is actually `vs` that needs to be recovered into `v` and `s`.
// Compute `v` and store it in the scratch space.
mstore(0x20, add(shr(255, s), 27))
// prettier-ignore
s := and(s, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
}
case 65 {
// Compute `v` and store it in the scratch space.
mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40))))
}

// If `s` in lower half order, such that the signature is not malleable.
// prettier-ignore
if iszero(gt(s, 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0)) {
mstore(0x00, hash)
calldatacopy(0x40, signature.offset, 0x20) // Directly copy `r` over.
mstore(0x60, s)
pop(
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, returndatasize()))
}
// 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)
}
}
}