Skip to main content

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"
}
note

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.

tip

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 function
  • domainSeparator, the domain separator as mentioned in the previous section
  • hashStruct, the identically named function in the EIP-712 standard
hashStruct pseudocode
hashStruct(typeHash, data) = keccak256(typeHash ‖ encodeData(data))

The components are:

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 SchemeGas-lessEOASmart 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.

OrderBook API

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 length
  • orderDigest, 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 message
  • orderDigest, 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
OrderBook API

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, any bytes 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 order
  • true, whether the order is pre-signed or not (allows for cancelling pre-signed orders)

The signature is an empty bytes string:

signature = 0x
OrderBook API

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.

note

If an order was already filled, then pre-signing it does not make it tradable again.