Skip to content
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

Defining off-chain EIP-712 messages with no contract #164

Open
h4l opened this issue Feb 16, 2024 · 2 comments
Open

Defining off-chain EIP-712 messages with no contract #164

h4l opened this issue Feb 16, 2024 · 2 comments

Comments

@h4l
Copy link

h4l commented Feb 16, 2024

I'd like to be able to sign an off-chain EIP-712 message with Ledger devices. (Currently, Ledger Live immediately rejects signing requests for such a message, without any user interaction. I can sign using a Ledger device connected via MetaMask, but with only the message hash visible though.)

I understand that I need to add a definition to this repo for the EIP-712 message. However the eip712.json files in this repo expect to have a contract address, which the off-chain message I'm dealing with does not have.

This is an example of an EIP -712 message I want to sign:

{
    "domain": {
        "chainId": "1",
        "name": "reddit",
        "salt": "reddit-sIvILoedIcisHANTEmpE",
        "version": "1"
    },
    "message": {
        "address": "0xd2a2b709af3b6d0bba1ccbd1edd65f353aa42c66",
        "expiresAt": "2024-02-16T05:59:21Z",
        "nonce": "afe92dbe85ec240bd4eeb3c68cb2ed35739ada4919d7320e56ed22bfbd154cdb",
        "redditUserName": "Sure-Complaint4987"
    },
    "primaryType": "Challenge",
    "types": {
        "Challenge": [
            {
                "name": "address",
                "type": "address"
            },
            {
                "name": "nonce",
                "type": "string"
            },
            {
                "name": "expiresAt",
                "type": "string"
            },
            {
                "name": "redditUserName",
                "type": "string"
            }
        ],
        "EIP712Domain": [
            {
                "name": "name",
                "type": "string"
            },
            {
                "name": "chainId",
                "type": "uint256"
            },
            {
                "name": "version",
                "type": "string"
            },
            {
                "name": "salt",
                "type": "string"
            }
        ]
    }
}

The domainSeparator hash of this message is 0x7ba5661dd0ce2767a99b4ab20a3ff32713b280c5097ca1311fc475ed993f9a0c.

Would it be possible to allow messages to be defined in this repository using a domainSeparator hash instead of an address, in cases where a message does not specify a verifyingContract in its domain? Perhaps the domain's un-hashed values should be provided in the definition file too for better transparency.


Note, this message format is used by Reddit to approve user requests to associate a Wallet address with a Reddit account, to create what they call a Vault. I'm not affiliated with Reddit, I'm just working on an app that handles and needs to sign these messages.

@h4l
Copy link
Author

h4l commented Feb 16, 2024

Maybe this would be too much change to consider, but just throwing it out as an idea. It feels to me like this repo's current eip712.json metadata structure with the chain at the root isn't ideal for the EIP-712 messages in the general case. EIP-712 messages don't need to have a chainId in their domain if they're off-chain (although my example does). And with this structure, contracts deployed on multiple chains need to have their definitions duplicated for each chain's deployment.

An alternative could be to structure the EIP-712 definitions something like the following, without treating chainId and address in a special way. Instead, the domainSeparator hashes and primaryType from EIP-712 are used to uniquely identify a message, and map descriptive labels to the fields:

{
    "name": "Reddit",
    "descriptions": [
        {
            "name": "Reddit Vault Pairing Challenge",
            "domainSeparators": [
                {
                    # The domain hash is the identifier. The domain values are
                    # for documentation and transparency. With validation we can
                    # enforce that the domainHash matches the domain values with
                    # the EIP712Domain type below.
                    "domainHash": "0x7ba5661dd0ce2767a99b4ab20a3ff32713b280c5097ca1311fc475ed993f9a0c",
                    "domain": {
                        "chainId": "1",
                        "name": "reddit",
                        "salt": "reddit-sIvILoedIcisHANTEmpE",
                        "version": "1"
                    }
                }
            ],
            "descriptions": [
                {
                    "primaryType": "Challenge",
                    "label": "Pair Wallet as Reddit User's Vault",
                    "fields": [
                        {
                            "label": "Wallet address to pair",
                            "path": "address"
                        },
                        {
                            "label": "Reddit username",
                            "path": "redditUserName"
                        },
                        {
                            "label": "Signature expires at",
                            "path": "expiresAt"
                        }
                    ]
                }
            ]
        }
    ],
    # Regular EIP-712 JSON type definitions referenced by primaryType and domain above
    "types": {
        "Challenge": [
            {
                "name": "address",
                "type": "address"
            },
            {
                "name": "nonce",
                "type": "string"
            },
            {
                "name": "expiresAt",
                "type": "string"
            },
            {
                "name": "redditUserName",
                "type": "string"
            }
        ],
        "EIP712Domain": [
            {
                "name": "name",
                "type": "string"
            },
            {
                "name": "chainId",
                "type": "uint256"
            },
            {
                "name": "version",
                "type": "string"
            },
            {
                "name": "salt",
                "type": "string"
            }
        ]
    }
}

Here's an example of how the ParaSwap definitions from this repo might look (not all chains included):

{
    "name": "ParaSwap",
    "descriptions": [
        {
            "name": "ERC20 Order",
            "domainSeparators": [
                {
                    "name": "AugustusRFQ Ethereum",
                    # These are example values
                    "domainHash": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
                    "domain": {
                        "name": "AugustusRFQ",
                        "version": "1",
                        "chainId": "1",
                        "verifyingContract": "0xe92b586627cca7a83dc919cc7127196d70f55a06"
                    }
                },
                {
                    "name": "AugustusRFQ Polygon",
                    "domainHash": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
                    "domain": {
                        "name": "AugustusRFQ",
                        "version": "1",
                        "chainId": "137",
                        "verifyingContract": "0xf3cd476c3c4d3ac5ca2724767f269070ca09a043"
                    }
                },
                # More domain values for other supported chains ...
                # ...
            ],
            "descriptions": [
                {
                    "primaryType": "Order",
                    "label": "AugustusRFQ ERC20 order",
                    "fields": [
                        {
                            "label": "Nonce and metadata",
                            "path": "nonceAndMeta"
                        },
                        {
                            "label": "Expiration time",
                            "path": "expiry"
                        },
                        {
                            "label": "Maker asset address",
                            "path": "makerAsset"
                        },
                        {
                            "label": "Taker asset address",
                            "path": "takerAsset"
                        },
                        {
                            "label": "Maker address",
                            "path": "maker"
                        },
                        {
                            "label": "Taker address",
                            "path": "taker"
                        },
                        {
                            "label": "Maker amount",
                            "path": "makerAmount"
                        },
                        {
                            "label": "Taker amount",
                            "path": "takerAmount"
                        }
                    ]
                },
                {
                    "primaryType": "OrderNFT",
                    "label": "AugustusRFQ NFT order",
                    "fields": [
                        {
                            "label": "Nonce and metadata",
                            "path": "nonceAndMeta"
                        },
                        {
                            "label": "Expiration time",
                            "path": "expiry"
                        },
                        {
                            "label": "Maker asset encoded",
                            "path": "makerAsset"
                        },
                        {
                            "label": "Maker asset NFT ID",
                            "path": "makerAssetId"
                        },
                        {
                            "label": "Taker asset encoded",
                            "path": "takerAsset"
                        },
                        {
                            "label": "Taker asset NFT ID",
                            "path": "takerAssetId"
                        },
                        {
                            "label": "Maker address",
                            "path": "maker"
                        },
                        {
                            "label": "Taker address",
                            "path": "taker"
                        },
                        {
                            "label": "Maker amount",
                            "path": "makerAmount"
                        },
                        {
                            "label": "Taker amount",
                            "path": "takerAmount"
                        }
                    ]
                }
            ]
        }
    ],
    # Regular EIP-712 JSON type definitions referenced by primaryType and domain above
    "types": {
        "EIP712Domain": [
            {
                "name": "name",
                "type": "string"
            },
            {
                "name": "version",
                "type": "string"
            },
            {
                "name": "chainId",
                "type": "uint256"
            },
            {
                "name": "verifyingContract",
                "type": "address"
            }
        ],
        "Order": [
            {
                "name": "nonceAndMeta",
                "type": "uint256"
            },
            {
                "name": "expiry",
                "type": "uint128"
            },
            {
                "name": "makerAsset",
                "type": "address"
            },
            {
                "name": "takerAsset",
                "type": "address"
            },
            {
                "name": "maker",
                "type": "address"
            },
            {
                "name": "taker",
                "type": "address"
            },
            {
                "name": "makerAmount",
                "type": "uint256"
            },
            {
                "name": "takerAmount",
                "type": "uint256"
            }
        ],
        "OrderNFT": [
            {
                "name": "nonceAndMeta",
                "type": "uint256"
            },
            {
                "name": "expiry",
                "type": "uint128"
            },
            {
                "name": "makerAsset",
                "type": "uint256"
            },
            {
                "name": "makerAssetId",
                "type": "uint256"
            },
            {
                "name": "takerAsset",
                "type": "uint256"
            },
            {
                "name": "takerAssetId",
                "type": "uint256"
            },
            {
                "name": "maker",
                "type": "address"
            },
            {
                "name": "taker",
                "type": "address"
            },
            {
                "name": "makerAmount",
                "type": "uint256"
            },
            {
                "name": "takerAmount",
                "type": "uint256"
            }
        ]
    }
}

@katspaugh
Copy link

katspaugh commented Mar 18, 2024

@adrienlacombe @loicttn what's your take on this? (Ex-Gnosis) Safe would also benefit from this, as each contract is a proxy, so it would be impossible to add each Safe address to the allowlist.

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

No branches or pull requests

2 participants