Signing schemes
As CoW Protocol handles user's orders that were provided off-chain, the authenticity of a user's order needs to be asserted. Authenticity is asserted by the user signing their order. A user may be an EOA or a smart contract.
Order digest
The order digest is a bytes32
that uniquely describes the parameters of an order. It is used as the input to the signing schemes. The digest is computed as described in the EIP-712
.
Replay protection
Replay protection for the signing schemes is achieved by using an EIP-712
domain separator. This domain separator is unique to each deployment of the CoW Protocol settlement contract on a particular chain.
When computing the domain separator, the following parameters are used:
{
name: "Gnosis Protocol",
version: "v2",
chainId: /* chain ID for the current network: eg. 1 for mainnet */,
verifyingContract: "0x9008D19f58AAbD9eD0D60971565AA8510560ab41"
}
CoW Protocol was formerly known as Gnosis Protocol v2. The domain separator is still named Gnosis Protocol
as the change would have required a new deployment of the settlement contract.
The actual domain separator is the result of hashing the previous EIP712Domain
struct with the EIP-712
defined hashStruct
function.
For convenience, the domain separator is exposed as a public view function in the settlement contract:
Encoding
To encode the order into the order digest, the EIP-712
specification is used:
orderDigest = keccak256("\x19\x01" ‖ domainSeparator ‖ hashStruct(orderStruct))
The components are:
keccak256
, the standard unpadded Ethereum hashing function"\x19\x01"
, two bytes‖
, the byte-concatenation functiondomainSeparator
, the domain separator as mentioned in the previous sectionhashStruct
, the identically named function in the EIP-712 standard
hashStruct pseudocode
hashStruct(typeHash, data) = keccak256(typeHash ‖ encodeData(data))
The components are:
keccak256
, the standard unpadded Ethereum hashing function‖
, the byte-concatenation functiontypeHash
, the hash of the struct typeencodeData
, the identically named function in the EIP-712 standard
The resulting order digest is a bytes32
value that is used as the input to the signing schemes.
JavaScript example
Phew, that was a lot of hashing! Feeling dizzy? Don't worry, we got you covered.
The @cowprotocol/contracts
package provides a function to compute the order digest for you:
import {
Order,
OrderBalance,
OrderKind,
domain,
hashOrder
} from "@cowprotocol/contracts"
// Define an order
const order: Order = {
sellToken: "0x6b175474e89094c44da98b954eedeac495271d0f", // dai
buyToken: "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", // cow
sellAmount: "10000000000000000000000000", // 10m dai
buyAmount: "10000", // 10k cow
validTo: 1704067200,
appData: "0xc85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4", // keccak256("cow")
feeAmount: "0",
kind: OrderKind.SELL,
partiallyFillable: false,
sellTokenBalance: OrderBalance.ERC20,
buyTokenBalance: OrderBalance.ERC20,
};
// hash the order to generate the order digest
const digest = hashOrder(domain(1, "0x9008D19f58AAbD9eD0D60971565AA8510560ab41"), order);
// output the digest
console.log(`Order digest: ${digest}`)
Etherscan Example
For convenience, we also deployed a small helper contract that makes it easy to compute order uids as well as their full encoding for each chain using the Etherscan UI:
Supported schemes
CoW Protocol supports four different signing schemes. These signing schemes are summarized in the following table:
Signing Scheme | Gas-less | EOA | Smart Contract |
---|---|---|---|
eth_sign | ✅ | ✅ | ❌ |
EIP-712 | ✅ | ✅ | ❌ |
ERC-1271 | ✅ | ❌ | ✅ |
PreSign | ❌ | ✅ | ✅ |
Except for the PreSign
scheme, all signing schemes involve signing an order digest that is based on the message structure of EIP-712
.
In the expanded detail sections below, signature
refers to the value passed to signature
in the OrderBook API.
eth_sign
This signature type is the most commonly supported signing mechanism for EOAs.
The signature is computed as:
signature = ethSign(orderDigest)
The components are:
ethSign
, using the user's private key to ECDSA-sign a message prefixed with"\x19Ethereum signed message:\n"
and its lengthorderDigest
, the order digest
Most Ethereum libraries support ethSign
signatures (ethers-js, web3py).
EIP-712
This signing method, also known as typed structured data signing, is the recommended signing method for EOAs, since the user will be able to see the full order data that is being signed in most wallet implementations.
The signature is computed as:
signature = ecdsaSign(orderDigest)
The components are:
ecdsaSign
, using the user's private key to ECDSA-sign the messageorderDigest
, the order digest
Many Ethereum libraries have some degree of support for signing typed data without building the order digest by hand (for example ethers-js, and web3py).
In any case, you may want to read about the domain separator and encoding the order struct.
ERC-1271
This signing mechanism is the only option that can be used by smart contracts to provide off-chain signatures for orders.
In order to support smart-contract orders, the trading smart contract (the user) must implement the EIP-1271 interface.
signature = eip1271Signature
When submitting an ERC-1271
order to the API, the from
field MUST be set to the address of the smart contract that will be signing the order. This is because the ERC-1271
signature is not an Ethereum signature, and thus the from
field cannot be inferred from the signature.
The components are:
eip1271Signature
, anybytes
that is a valid signature for the contract for the order. This signature is contract specific.
For an order to be accepted, the eip1271Signature
must be valid for the orderDigest
message, that is in Solidity:
isValidSignature(orderDigest, eip1271Signature) == MAGICVALUE
PreSign
This is the only signing method that supports both EOA and smart-contract traders.
Together with submitting a PreSign
order, the user must pre-approve the order on-chain. This is done by calling the settlement contract setPreSignature
function:
setPreSignature(orderUid, true);
The components are:
orderUid
, the unique identifier of the ordertrue
, whether the order is pre-signed or not (allows for cancelling pre-signed orders)
The signature is an empty bytes
string:
signature = 0x
When submitting a PreSign
order to the API, the from
field MUST be set to the address of the order owner. This is because the PreSign
signature is not an Ethereum signature, and thus the from
field cannot be inferred from the signature.
If an order was already filled, then pre-signing it does not make it tradable again.