Skip to content
Merged
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
51 changes: 48 additions & 3 deletions docs/utils/eip712.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,27 @@ bytes32 internal constant _DOMAIN_TYPEHASH_SANS_CHAIN_ID =
`keccak256("EIP712Domain(string name,string version,address verifyingContract)")`.
This is only used in `_hashTypedDataSansChainId`.

### _DOMAIN_TYPEHASH_SANS_CHAIN_ID_AND_VERIFYING_CONTRACT

```solidity
bytes32 internal constant
_DOMAIN_TYPEHASH_SANS_CHAIN_ID_AND_VERIFYING_CONTRACT =
0xb03948446334eb9b2196d5eb166f69b9d49403eb4a12f36de8d3f9f3cb8e15c3
```

`keccak256("EIP712Domain(string name,string version)")`.
This is only used in `_hashTypedDataSansChainIdAndVerifyingContract`.

### _DOMAIN_TYPEHASH_SANS_VERIFYING_CONTRACT

```solidity
bytes32 internal constant _DOMAIN_TYPEHASH_SANS_VERIFYING_CONTRACT =
0xc2f8787176b8ac6bf7215b4adcc1e069bf4ab82d9ab1df05a57a91d425935b6e
```

`keccak256("EIP712Domain(string name,string version,uint256 chainId)")`.
This is only used in `_hashTypedDataSansVerifyingContract`.

## Hashing Operations

### _domainSeparator()
Expand Down Expand Up @@ -84,9 +105,33 @@ function _hashTypedDataSansChainId(bytes32 structHash)
```

Variant of `_hashTypedData` that excludes the chain ID.
We expect that most contracts will use `_hashTypedData` as the main hash,
and `_hashTypedDataSansChainId` only occasionally for cross-chain workflows.
Thus this is optimized for smaller bytecode size over runtime gas.
Included for the niche use case of cross-chain workflows.

### _hashTypedDataSansChainIdAndVerifyingContract(bytes32)

```solidity
function _hashTypedDataSansChainIdAndVerifyingContract(bytes32 structHash)
internal
view
virtual
returns (bytes32 digest)
```

Variant of `_hashTypedData` that excludes the chain ID and verifying contract.
Included for the niche use case of cross-chain and multi-verifier workflows.

### _hashTypedDataSansVerifyingContract(bytes32)

```solidity
function _hashTypedDataSansVerifyingContract(bytes32 structHash)
internal
view
virtual
returns (bytes32 digest)
```

Variant of `_hashTypedData` that excludes the chain ID and verifying contract.
Included for the niche use case of multi-verifier workflows.

## EIP-5267 Operations

Expand Down
65 changes: 62 additions & 3 deletions src/utils/EIP712.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ abstract contract EIP712 {
bytes32 internal constant _DOMAIN_TYPEHASH_SANS_CHAIN_ID =
0x91ab3d17e3a50a9d89e63fd30b92be7f5336b03b287bb946787a83a9d62a2766;

/// @dev `keccak256("EIP712Domain(string name,string version)")`.
/// This is only used in `_hashTypedDataSansChainIdAndVerifyingContract`.
bytes32 internal constant _DOMAIN_TYPEHASH_SANS_CHAIN_ID_AND_VERIFYING_CONTRACT =
0xb03948446334eb9b2196d5eb166f69b9d49403eb4a12f36de8d3f9f3cb8e15c3;

/// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId)")`.
/// This is only used in `_hashTypedDataSansVerifyingContract`.
bytes32 internal constant _DOMAIN_TYPEHASH_SANS_VERIFYING_CONTRACT =
0xc2f8787176b8ac6bf7215b4adcc1e069bf4ab82d9ab1df05a57a91d425935b6e;

uint256 private immutable _cachedThis;
uint256 private immutable _cachedChainId;
bytes32 private immutable _cachedNameHash;
Expand Down Expand Up @@ -147,9 +157,7 @@ abstract contract EIP712 {
}

/// @dev Variant of `_hashTypedData` that excludes the chain ID.
/// We expect that most contracts will use `_hashTypedData` as the main hash,
/// and `_hashTypedDataSansChainId` only occasionally for cross-chain workflows.
/// Thus this is optimized for smaller bytecode size over runtime gas.
/// Included for the niche use case of cross-chain workflows.
function _hashTypedDataSansChainId(bytes32 structHash)
internal
view
Expand All @@ -174,6 +182,57 @@ abstract contract EIP712 {
}
}

/// @dev Variant of `_hashTypedData` that excludes the chain ID and verifying contract.
/// Included for the niche use case of cross-chain and multi-verifier workflows.
function _hashTypedDataSansChainIdAndVerifyingContract(bytes32 structHash)
internal
view
virtual
returns (bytes32 digest)
{
(string memory name, string memory version) = _domainNameAndVersion();
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Load the free memory pointer.
mstore(0x00, _DOMAIN_TYPEHASH_SANS_CHAIN_ID_AND_VERIFYING_CONTRACT)
mstore(0x20, keccak256(add(name, 0x20), mload(name)))
mstore(0x40, keccak256(add(version, 0x20), mload(version)))
// Compute the digest.
mstore(0x20, keccak256(0x00, 0x60)) // Store the domain separator.
mstore(0x00, 0x1901) // Store "\x19\x01".
mstore(0x40, structHash) // Store the struct hash.
digest := keccak256(0x1e, 0x42)
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero pointer.
}
}

/// @dev Variant of `_hashTypedData` that excludes the chain ID and verifying contract.
/// Included for the niche use case of multi-verifier workflows.
function _hashTypedDataSansVerifyingContract(bytes32 structHash)
internal
view
virtual
returns (bytes32 digest)
{
(string memory name, string memory version) = _domainNameAndVersion();
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Load the free memory pointer.
mstore(0x00, _DOMAIN_TYPEHASH_SANS_VERIFYING_CONTRACT)
mstore(0x20, keccak256(add(name, 0x20), mload(name)))
mstore(0x40, keccak256(add(version, 0x20), mload(version)))
mstore(0x60, chainid())
// Compute the digest.
mstore(0x20, keccak256(0x00, 0x80)) // Store the domain separator.
mstore(0x00, 0x1901) // Store "\x19\x01".
mstore(0x40, structHash) // Store the struct hash.
digest := keccak256(0x1e, 0x42)
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero pointer.
}
}

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EIP-5267 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
Expand Down
103 changes: 103 additions & 0 deletions test/EIP712.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -238,4 +238,107 @@ contract EIP712Test is SoladyTest {
function testHashTypedDataSansChainIdOnDynamicClone() public {
_testHashTypedDataSansChainId(MockEIP712(address(mockDynamicClone)));
}

function _testHashTypedDataSansChainIdAndVerifyingContract(MockEIP712 mockToTest) public {
_TestTemps memory t;
(, t.name, t.version,,,,) = mockToTest.eip712Domain();

(t.signer, t.privateKey) = _randomSigner();

(t.to,) = _randomSigner();

t.message = "Hello Milady!";

t.structHash = keccak256(abi.encode("Message(address to,string message)", t.to, t.message));
t.expectedDigest = keccak256(
abi.encodePacked(
"\x19\x01",
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version)"),
keccak256(bytes(t.name)),
keccak256(bytes(t.version))
)
),
t.structHash
)
);

assertEq(
mockToTest.hashTypedDataSansChainIdAndVerifyingContract(t.structHash), t.expectedDigest
);

(t.v, t.r, t.s) = vm.sign(t.privateKey, t.expectedDigest);

t.recoveredAddress = ecrecover(t.expectedDigest, t.v, t.r, t.s);

assertEq(t.recoveredAddress, t.signer);
}

function testHashTypedDataSansChainIdAndVerifyingContract() public {
_testHashTypedDataSansChainIdAndVerifyingContract(mock);
}

function testHashTypedDataSansChainIdAndVerifyingContractOnClone() public {
_testHashTypedDataSansChainIdAndVerifyingContract(mockClone);
}

function testHashTypedDataSansChainIdAndVerifyingContractOnDynamic() public {
_testHashTypedDataSansChainIdAndVerifyingContract(MockEIP712(address(mockDynamic)));
}

function testHashTypedDataSansChainIdAndVerifyingContractOnDynamicClone() public {
_testHashTypedDataSansChainIdAndVerifyingContract(MockEIP712(address(mockDynamicClone)));
}

function _testHashTypedDataSansVerifyingContract(MockEIP712 mockToTest) public {
_TestTemps memory t;
(, t.name, t.version,,,,) = mockToTest.eip712Domain();

(t.signer, t.privateKey) = _randomSigner();

(t.to,) = _randomSigner();

t.message = "Hello Milady!";

t.structHash = keccak256(abi.encode("Message(address to,string message)", t.to, t.message));
t.expectedDigest = keccak256(
abi.encodePacked(
"\x19\x01",
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId)"),
keccak256(bytes(t.name)),
keccak256(bytes(t.version)),
block.chainid
)
),
t.structHash
)
);

assertEq(mockToTest.hashTypedDataSansVerifyingContract(t.structHash), t.expectedDigest);

(t.v, t.r, t.s) = vm.sign(t.privateKey, t.expectedDigest);

t.recoveredAddress = ecrecover(t.expectedDigest, t.v, t.r, t.s);

assertEq(t.recoveredAddress, t.signer);
}

function testHashTypedDataSansVerifyingContract() public {
_testHashTypedDataSansVerifyingContract(mock);
}

function testHashTypedDataSansVerifyingContractOnClone() public {
_testHashTypedDataSansVerifyingContract(mockClone);
}

function testHashTypedDataSansVerifyingContractOnDynamic() public {
_testHashTypedDataSansVerifyingContract(MockEIP712(address(mockDynamic)));
}

function testHashTypedDataSansVerifyingContractOnDynamicClone() public {
_testHashTypedDataSansVerifyingContract(MockEIP712(address(mockDynamicClone)));
}
}
16 changes: 16 additions & 0 deletions test/utils/mocks/MockEIP712.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,20 @@ contract MockEIP712 is EIP712 {
function hashTypedDataSansChainId(bytes32 structHash) external view returns (bytes32) {
return _hashTypedDataSansChainId(structHash);
}

function hashTypedDataSansChainIdAndVerifyingContract(bytes32 structHash)
external
view
returns (bytes32)
{
return _hashTypedDataSansChainIdAndVerifyingContract(structHash);
}

function hashTypedDataSansVerifyingContract(bytes32 structHash)
external
view
returns (bytes32)
{
return _hashTypedDataSansVerifyingContract(structHash);
}
}
16 changes: 16 additions & 0 deletions test/utils/mocks/MockEIP712Dynamic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,20 @@ contract MockEIP712Dynamic is EIP712 {
function hashTypedDataSansChainId(bytes32 structHash) external view returns (bytes32) {
return _hashTypedDataSansChainId(structHash);
}

function hashTypedDataSansChainIdAndVerifyingContract(bytes32 structHash)
external
view
returns (bytes32)
{
return _hashTypedDataSansChainIdAndVerifyingContract(structHash);
}

function hashTypedDataSansVerifyingContract(bytes32 structHash)
external
view
returns (bytes32)
{
return _hashTypedDataSansVerifyingContract(structHash);
}
}