Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
212 lines (177 sloc) 7.78 KB

Ethereum uses ECDSA signatures. To generate a signature for an AirSwap order, the order must first be hashed according to EIP-712, which can be seen in the Solidity and TypeScript implementations.

Once hashed, one of two signing methods may be used, either personalSign or signTypedData. AirSwap signatures include a byte version to indicate personalSign (0x45) or signTypedData (0x01). The primary distinction is that in the former, Ethereum wallets prefix the hash with byte \x19 to stay out of range of valid RLP so that a signature cannot be executed as a transaction.

An AirSwap signature has the following properties:

Param Type Description
signatory address Wallet used to generate the signature.
validator address Swap contract address to be used for settlement.
version bytes1 EIP-191 signature version 0x01 or 0x45.
v uint8 v value of the ECDSA signature.
r bytes32 r value of the ECDSA signature.
s bytes32 s value of the ECDSA signature.

Each signature should be included on its order, accessible as order.signature, to be settled by invoking swap(order) on the Swap contract.

JavaScript

Using personalSign

You can use personalSign with the hashing function in the @airswap/order-utils package.

const ethUtil = require('ethereumjs-util')
const { hashes } = require('@airswap/order-utils')
const orderHashHex = hashes.getOrderHash(order)
const sig = await web3.eth.sign(orderHashHex, signer)
const { v, r, s } = ethUtil.fromRpcSig(sig)
return {
  version: '0x45',
  v,
  r,
  s,
}

Using signTypedData

You can use signTypedData by calling it directly.

const ethUtil = require('ethereumjs-util')
const sigUtil = require('eth-sig-util')
const DOMAIN_NAME = 'SWAP'
const DOMAIN_VERSION = '2'
const verifyingContract = '0x0...' // Address of the Swap Contract
const sig = sigUtil.signTypedData(privateKey, {
  data: {
    types, // See: @airswap/order-utils/src/constants.js
    domain: {
      name: DOMAIN_NAME,
      version: DOMAIN_VERSION,
      verifyingContract,
    },
    primaryType: 'Order',
    message: order, // See: @airswap/order-utils/src/orders.js
  },
})
const { r, s, v } = ethUtil.fromRpcSig(sig)
return {
  version: '0x01', // Version 0x01: signTypedData
  r,
  s,
  v,
}

Python

Using ecdsa_raw_sign

The python example uses the eth-abi and eth-utils packages.

To sign the order, you'll need to create hashes of the encoded types, as well as the domain separator (used at the final step). SWAP_DOMAIN and SWAP_VERSION should be set to SWAP and 2, respectively.

from eth_utils import keccak
from eth_abi import encode_abi
from bitcoin import ecdsa_raw_sign

SWAP_VERSION = "2"
SWAP_DOMAIN = "SWAP
ERC_20_INTERFACE_ID = bytes.fromhex("36372b07")

SWAP_TYPES = {
    "party": b"Party(bytes4 kind,address wallet,address token,uint256 amount,uint256 id)",
    "order": b"Order(uint256 nonce,uint256 expiry,Party signer,Party sender,Party affiliate)",
    "eip712": b"EIP712Domain(string name,string version,address verifyingContract)",
}

SWAP_TYPE_HASHES = {
    "party": keccak(SWAP_TYPES["party"]),
    "order": keccak(SWAP_TYPES["order"] + SWAP_TYPES["party"]),
    "eip712": keccak(SWAP_TYPES["eip712"]),
}

DOMAIN_SEPARATOR = keccak(
    encode_abi(
        ["bytes32", "bytes32", "bytes32", "address"],
        [
            SWAP_TYPE_HASHES["eip712"],
            keccak(SWAP_DOMAIN.encode()),
            keccak(SWAP_VERSION.encode()),
            SWAP_CONTRACT_ADDRESS,
        ],
    )
)

You can then create a hash of the order itself with the type information included, assuming that order represents your order in a dict.

hashed_signer = keccak(
    encode_abi(
        ["bytes32", "bytes4", "address", "address", "uint256", "uint256"],
        [
            SWAP_TYPE_HASHES["party"],
            ERC_20_INTERFACE_ID,
            order["signerWallet"],
            order["signerToken"],
            int(order["signerAmount"]),
            int(order["signerId"]),
        ],
    )
)

hashed_sender = keccak(
    encode_abi(
        ["bytes32", "bytes4", "address", "address", "uint256", "uint256"],
        [
            SWAP_TYPE_HASHES["party"],
            ERC_20_INTERFACE_ID,
            order["senderWallet"],
            order["senderToken"],
            int(order["senderAmount"]),
            int(order["senderId"]),
        ],
    )
)

hashed_affiliate = keccak(
    encode_abi(
        ["bytes32",  "bytes4", "address", "address", "uint256", "uint256"],
        [
            SWAP_TYPE_HASHES["party"],
            ERC_20_INTERFACE_ID,
            "0x0000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000",
            0,
            0,
        ],
    )
)

hashed_order = keccak(
    encode_abi(
        ["bytes32", "uint256", "uint256", "bytes32", "bytes32", "bytes32"],
        [
            SWAP_TYPE_HASHES["order"],
            int(order["nonce"]),
            int(order["expiry"]),
            hashed_signer,
            hashed_sender,
            hashed_affiliate,
        ],
    )
)

Finally, package the hashed order with the EIP-712 domain separator and prefixes, then sign it with your private key PRIVATE_KEY.

encoded_order = keccak(b"\x19Ethereum Signed Message:\n32" + keccak(b"\x19\x01" + DOMAIN_SEPARATOR + hashed_order))

V, R, S = ecdsa_raw_sign(encoded_order, PRIVATE_KEY)

v = V
r = Web3.toHex(R)
s = Web3.toHex(S)

# The bitcoin.ecdsa_raw_sign method we are using may return r & s values that are under 66 bytes, so check for
# that and pad with '0' if necessary to align with bytes32 types
if len(s) < 66:
  diff = 66 - len(s)
  s = "0x" + "0" * diff + s[2:]

if len(r) < 66:
  diff = 66 - len(r)
  r = "0x" + "0" * diff + r[2:]

# version is 0x45 for personalSign
signed_order = {
  "version": "0x45",
  "signatory": WALLET_ADDRESS,
  "validator": SWAP_CONTRACT_ADDRESS,
  "v": v,
  "r": r,
  "s": s
}

EIP712Domain

If you plan to use signTypedData or do the EIP-712 hashing manually, use the following values for the EIP712Domain. The typehash, name, and version must be hashed to bytes32 using keccak256.

Param Type Value
Typehash bytes32 EIP712Domain(string name,string version,address verifyingContract)
Name bytes32 SWAP
Version bytes32 2
verifyingContract address Swap contract address to be used for settlement.
You can’t perform that action at this time.