Skip to content

Implement 7739 #140

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open

Implement 7739 #140

wants to merge 33 commits into from

Conversation

zhongeric
Copy link
Collaborator

@zhongeric zhongeric commented Apr 11, 2025

Implement ERC7739 per the spec: https://eips.ethereum.org/EIPS/eip-7739 , which defines dynamic nested 712 encoding.

Files:

  • ERC7739.sol

Inspired by https://github.com/OpenZeppelin/openzeppelin-community-contracts/blob/master/contracts/utils/cryptography/ERC7739.sol

Base contract implementing logic for verifying nested typed data sign signatures and personal signatures. Handles decoding of wrapped signatures and generating the correct hashTypedDataV4 based on the current account's domainSeparator.

  • libraries/ERC7739Utils.sol

Modified from https://github.com/OpenZeppelin/openzeppelin-community-contracts/blob/master/contracts/utils/cryptography/ERC7739Utils.sol

Util contract implementing decodeContentsDescr which iterates over a string in memory to find the contentsName, handling both implicit and explicit modes. For more info, see https://eips.ethereum.org/EIPS/eip-7739#contentsdescription-with-implicit-and-explicit-modes. The main change I made here was to do string operations in memory instead of calldata. I'm open to calldata just wanted to do it in memory to get it working 😂

  • libraries/TypedDataSignLib.sol

712 hashing logic for typed data sign, generates dynamic type hash and name.

  • libraries/PersonalSignLib.sol

Applies simple 712 hash over a hashed eth personal sign digest.

@zhongeric zhongeric marked this pull request as ready for review April 13, 2025 23:10
/// @notice An abstract contract that implements the ERC-7739 standard
/// @notice This contract assumes that all data verified through ERC-1271 `isValidSignature` implements the defensive nested hashing scheme defined in EIP-7739
/// @dev See https://eips.ethereum.org/EIPS/eip-7739
abstract contract ERC7739 is EIP712 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High level comment.. but why is this a contract? It seems like it might just be better as a re-useable library? All of the functions defined here are private and I feel like we could re-architect it in a way where we wouldnt have to inherit from 712 and rather pass in any domain separator info

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I moved some funcs to the library so this base contract is mostly just _isValidTypedDataSig and _isValidNestedPersonalSignature. I think it might be a bit tricky to not inherit from 712 because we need to use the 712 domain in two different ways (domain separator for personal sign) and domainBytes for nested 712

Comment on lines +103 to +106
// signature is first 65 bytes of wrappedSignature + 0x
const signatureLength = 130 // 65 * 2
const start = 2; // skip the first 0x
const signature = '0x' + wrappedSignature.slice(start, start + signatureLength);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flagging this FYI:

  • Viem returns a wrapped signature so we have to slice off the first 0x and 65 bytes for the actual signature to compare against key.verify in ffi test


Key memory key = getKey(keyHash);
bool isValid = key.verify(digest, signature);

// Must be branched because we do abi decoding in memory which will throw since the ecnoding schemes are different
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flag here also that decoding in memory makes the implementation a little awkaward. Comment here says more

/// @notice We don't care how the hash was computed for personal sign, and it does not match the typestring above
/// i.e. keccak256("\x19Ethereum Signed Message:\n" || len(someMessage) || someMessage)
function hash(bytes32 message) internal pure returns (bytes32) {
return keccak256(abi.encode(PERSONAL_SIGN_TYPEHASH, message));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm this seems wrong? the personal sign type defines the input as a bytes param and here it is already a bytes32?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep I asked viem to see what the reasoning is

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants