Expose eth_signTypedData functionality for order signing
This commit is contained in:
parent
119f8c9449
commit
adcfaa2e80
@ -56,7 +56,7 @@
|
|||||||
"async-child-process": "^1.1.1",
|
"async-child-process": "^1.1.1",
|
||||||
"bundlesize": "^0.17.0",
|
"bundlesize": "^0.17.0",
|
||||||
"coveralls": "^3.0.0",
|
"coveralls": "^3.0.0",
|
||||||
"ganache-cli": "6.1.3",
|
"ganache-cli": "6.1.8",
|
||||||
"lcov-result-merger": "^3.0.0",
|
"lcov-result-merger": "^3.0.0",
|
||||||
"npm-cli-login": "^0.0.10",
|
"npm-cli-login": "^0.0.10",
|
||||||
"npm-run-all": "^4.1.2",
|
"npm-run-all": "^4.1.2",
|
||||||
|
28
packages/json-schemas/schemas/eip712_typed_data.ts
Normal file
28
packages/json-schemas/schemas/eip712_typed_data.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
export const eip712TypedData = {
|
||||||
|
id: 'eip712TypedData',
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
types: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
EIP712Domain: { type: 'array' },
|
||||||
|
},
|
||||||
|
additionalProperties: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: { type: 'string' },
|
||||||
|
type: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['name', 'type'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['EIP712Domain'],
|
||||||
|
},
|
||||||
|
primaryType: { type: 'string' },
|
||||||
|
domain: { type: 'object' },
|
||||||
|
message: { type: 'object' },
|
||||||
|
},
|
||||||
|
required: ['types', 'primaryType', 'domain', 'message'],
|
||||||
|
};
|
@ -2,6 +2,7 @@ import { addressSchema, hexSchema, numberSchema } from '../schemas/basic_type_sc
|
|||||||
import { blockParamSchema, blockRangeSchema } from '../schemas/block_range_schema';
|
import { blockParamSchema, blockRangeSchema } from '../schemas/block_range_schema';
|
||||||
import { callDataSchema } from '../schemas/call_data_schema';
|
import { callDataSchema } from '../schemas/call_data_schema';
|
||||||
import { ecSignatureParameterSchema, ecSignatureSchema } from '../schemas/ec_signature_schema';
|
import { ecSignatureParameterSchema, ecSignatureSchema } from '../schemas/ec_signature_schema';
|
||||||
|
import { eip712TypedData } from '../schemas/eip712_typed_data';
|
||||||
import { indexFilterValuesSchema } from '../schemas/index_filter_values_schema';
|
import { indexFilterValuesSchema } from '../schemas/index_filter_values_schema';
|
||||||
import { orderCancellationRequestsSchema } from '../schemas/order_cancel_schema';
|
import { orderCancellationRequestsSchema } from '../schemas/order_cancel_schema';
|
||||||
import { orderFillOrKillRequestsSchema } from '../schemas/order_fill_or_kill_requests_schema';
|
import { orderFillOrKillRequestsSchema } from '../schemas/order_fill_or_kill_requests_schema';
|
||||||
@ -39,6 +40,7 @@ export const schemas = {
|
|||||||
hexSchema,
|
hexSchema,
|
||||||
ecSignatureParameterSchema,
|
ecSignatureParameterSchema,
|
||||||
ecSignatureSchema,
|
ecSignatureSchema,
|
||||||
|
eip712TypedData,
|
||||||
indexFilterValuesSchema,
|
indexFilterValuesSchema,
|
||||||
orderCancellationRequestsSchema,
|
orderCancellationRequestsSchema,
|
||||||
orderFillOrKillRequestsSchema,
|
orderFillOrKillRequestsSchema,
|
||||||
|
@ -5,11 +5,11 @@ import { crypto } from './crypto';
|
|||||||
import { EIP712Schema, EIP712Types } from './types';
|
import { EIP712Schema, EIP712Types } from './types';
|
||||||
|
|
||||||
const EIP191_PREFIX = '\x19\x01';
|
const EIP191_PREFIX = '\x19\x01';
|
||||||
const EIP712_DOMAIN_NAME = '0x Protocol';
|
|
||||||
const EIP712_DOMAIN_VERSION = '2';
|
|
||||||
const EIP712_VALUE_LENGTH = 32;
|
const EIP712_VALUE_LENGTH = 32;
|
||||||
|
export const EIP712_DOMAIN_NAME = '0x Protocol';
|
||||||
|
export const EIP712_DOMAIN_VERSION = '2';
|
||||||
|
|
||||||
const EIP712_DOMAIN_SCHEMA: EIP712Schema = {
|
export const EIP712_DOMAIN_SCHEMA: EIP712Schema = {
|
||||||
name: 'EIP712Domain',
|
name: 'EIP712Domain',
|
||||||
parameters: [
|
parameters: [
|
||||||
{ name: 'name', type: EIP712Types.String },
|
{ name: 'name', type: EIP712Types.String },
|
||||||
|
@ -8,7 +8,7 @@ import { EIP712Schema, EIP712Types } from './types';
|
|||||||
|
|
||||||
const INVALID_TAKER_FORMAT = 'instance.takerAddress is not of a type(s) string';
|
const INVALID_TAKER_FORMAT = 'instance.takerAddress is not of a type(s) string';
|
||||||
|
|
||||||
const EIP712_ORDER_SCHEMA: EIP712Schema = {
|
export const EIP712_ORDER_SCHEMA: EIP712Schema = {
|
||||||
name: 'Order',
|
name: 'Order',
|
||||||
parameters: [
|
parameters: [
|
||||||
{ name: 'makerAddress', type: EIP712Types.Address },
|
{ name: 'makerAddress', type: EIP712Types.Address },
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { schemas } from '@0xproject/json-schemas';
|
import { schemas } from '@0xproject/json-schemas';
|
||||||
import { ECSignature, SignatureType, SignerType, ValidatorSignature } from '@0xproject/types';
|
import { ECSignature, Order, SignatureType, SignerType, ValidatorSignature } from '@0xproject/types';
|
||||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||||
import { Provider } from 'ethereum-types';
|
import { Provider } from 'ethereum-types';
|
||||||
import * as ethUtil from 'ethereumjs-util';
|
import * as ethUtil from 'ethereumjs-util';
|
||||||
@ -7,9 +7,11 @@ import * as _ from 'lodash';
|
|||||||
|
|
||||||
import { artifacts } from './artifacts';
|
import { artifacts } from './artifacts';
|
||||||
import { assert } from './assert';
|
import { assert } from './assert';
|
||||||
|
import { EIP712_DOMAIN_NAME, EIP712_DOMAIN_SCHEMA, EIP712_DOMAIN_VERSION } from './eip712_utils';
|
||||||
import { ExchangeContract } from './generated_contract_wrappers/exchange';
|
import { ExchangeContract } from './generated_contract_wrappers/exchange';
|
||||||
import { IValidatorContract } from './generated_contract_wrappers/i_validator';
|
import { IValidatorContract } from './generated_contract_wrappers/i_validator';
|
||||||
import { IWalletContract } from './generated_contract_wrappers/i_wallet';
|
import { IWalletContract } from './generated_contract_wrappers/i_wallet';
|
||||||
|
import { EIP712_ORDER_SCHEMA } from './order_hash';
|
||||||
import { OrderError } from './types';
|
import { OrderError } from './types';
|
||||||
import { utils } from './utils';
|
import { utils } from './utils';
|
||||||
|
|
||||||
@ -191,6 +193,52 @@ export const signatureUtils = {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Signs an order using `eth_signTypedData` and returns it's elliptic curve signature and signature type.
|
||||||
|
* This method currently supports Ganache.
|
||||||
|
* @param order The Order to sign.
|
||||||
|
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
|
||||||
|
* must be available via the Provider supplied to 0x.js.
|
||||||
|
* @return A hex encoded string containing the Elliptic curve signature generated by signing the orderHash and the Signature Type.
|
||||||
|
*/
|
||||||
|
async ecSignOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<string> {
|
||||||
|
assert.isWeb3Provider('provider', provider);
|
||||||
|
assert.isETHAddressHex('signerAddress', signerAddress);
|
||||||
|
const web3Wrapper = new Web3Wrapper(provider);
|
||||||
|
await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper);
|
||||||
|
const normalizedSignerAddress = signerAddress.toLowerCase();
|
||||||
|
const typedData = {
|
||||||
|
types: {
|
||||||
|
EIP712Domain: EIP712_DOMAIN_SCHEMA.parameters,
|
||||||
|
Order: EIP712_ORDER_SCHEMA.parameters,
|
||||||
|
},
|
||||||
|
domain: {
|
||||||
|
name: EIP712_DOMAIN_NAME,
|
||||||
|
version: EIP712_DOMAIN_VERSION,
|
||||||
|
verifyingContract: order.exchangeAddress,
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
...order,
|
||||||
|
salt: order.salt.toString(),
|
||||||
|
makerFee: order.makerFee.toString(),
|
||||||
|
takerFee: order.takerFee.toString(),
|
||||||
|
makerAssetAmount: order.makerAssetAmount.toString(),
|
||||||
|
takerAssetAmount: order.takerAssetAmount.toString(),
|
||||||
|
expirationTimeSeconds: order.expirationTimeSeconds.toString(),
|
||||||
|
},
|
||||||
|
primaryType: 'Order',
|
||||||
|
};
|
||||||
|
const signature = await web3Wrapper.signTypedDataAsync(normalizedSignerAddress, typedData);
|
||||||
|
const ecSignatureRSV = parseSignatureHexAsRSV(signature);
|
||||||
|
const signatureBuffer = Buffer.concat([
|
||||||
|
ethUtil.toBuffer(ecSignatureRSV.v),
|
||||||
|
ethUtil.toBuffer(ecSignatureRSV.r),
|
||||||
|
ethUtil.toBuffer(ecSignatureRSV.s),
|
||||||
|
ethUtil.toBuffer(SignatureType.EIP712),
|
||||||
|
]);
|
||||||
|
const signatureHex = `0x${signatureBuffer.toString('hex')}`;
|
||||||
|
return signatureHex;
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Signs an orderHash and returns it's elliptic curve signature and signature type.
|
* Signs an orderHash and returns it's elliptic curve signature and signature type.
|
||||||
* This method currently supports TestRPC, Geth and Parity above and below V1.6.6
|
* This method currently supports TestRPC, Geth and Parity above and below V1.6.6
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { SignerType } from '@0xproject/types';
|
import { Order, SignatureType, SignerType } from '@0xproject/types';
|
||||||
import { BigNumber } from '@0xproject/utils';
|
import { BigNumber } from '@0xproject/utils';
|
||||||
import * as chai from 'chai';
|
import * as chai from 'chai';
|
||||||
import { JSONRPCErrorCallback, JSONRPCRequestPayload } from 'ethereum-types';
|
import { JSONRPCErrorCallback, JSONRPCRequestPayload } from 'ethereum-types';
|
||||||
|
import * as ethUtil from 'ethereumjs-util';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import * as Sinon from 'sinon';
|
|
||||||
|
|
||||||
import { generatePseudoRandomSalt } from '../src';
|
import { generatePseudoRandomSalt, orderHashUtils } from '../src';
|
||||||
|
import { constants } from '../src/constants';
|
||||||
import { signatureUtils } from '../src/signature_utils';
|
import { signatureUtils } from '../src/signature_utils';
|
||||||
|
|
||||||
import { chaiSetup } from './utils/chai_setup';
|
import { chaiSetup } from './utils/chai_setup';
|
||||||
@ -115,19 +116,53 @@ describe('Signature utils', () => {
|
|||||||
expect(salt.lessThan(twoPow256)).to.be.true();
|
expect(salt.lessThan(twoPow256)).to.be.true();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('#ecSignOrderAsync', () => {
|
||||||
|
let makerAddress: string;
|
||||||
|
const fakeExchangeContractAddress = '0x1dc4c1cefef38a777b15aa20260a54e584b16c48';
|
||||||
|
let order: Order;
|
||||||
|
before(async () => {
|
||||||
|
const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
|
||||||
|
makerAddress = availableAddreses[0];
|
||||||
|
order = {
|
||||||
|
makerAddress,
|
||||||
|
takerAddress: constants.NULL_ADDRESS,
|
||||||
|
senderAddress: constants.NULL_ADDRESS,
|
||||||
|
feeRecipientAddress: constants.NULL_ADDRESS,
|
||||||
|
makerAssetData: constants.NULL_ADDRESS,
|
||||||
|
takerAssetData: constants.NULL_ADDRESS,
|
||||||
|
exchangeAddress: fakeExchangeContractAddress,
|
||||||
|
salt: new BigNumber(0),
|
||||||
|
makerFee: new BigNumber(0),
|
||||||
|
takerFee: new BigNumber(0),
|
||||||
|
makerAssetAmount: new BigNumber(0),
|
||||||
|
takerAssetAmount: new BigNumber(0),
|
||||||
|
expirationTimeSeconds: new BigNumber(0),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
it('should result in the same signature as signing order hash without prefix', async () => {
|
||||||
|
const orderHashHex = orderHashUtils.getOrderHashHex(order);
|
||||||
|
const sig = ethUtil.ecsign(
|
||||||
|
ethUtil.toBuffer(orderHashHex),
|
||||||
|
Buffer.from('F2F48EE19680706196E2E339E5DA3491186E0C4C5030670656B0E0164837257D', 'hex'),
|
||||||
|
);
|
||||||
|
const signatureBuffer = Buffer.concat([
|
||||||
|
ethUtil.toBuffer(sig.v),
|
||||||
|
ethUtil.toBuffer(sig.r),
|
||||||
|
ethUtil.toBuffer(sig.s),
|
||||||
|
ethUtil.toBuffer(SignatureType.EIP712),
|
||||||
|
]);
|
||||||
|
const signatureHex = `0x${signatureBuffer.toString('hex')}`;
|
||||||
|
const eip712Signature = await signatureUtils.ecSignOrderAsync(provider, order, makerAddress);
|
||||||
|
expect(signatureHex).to.eq(eip712Signature);
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('#ecSignOrderHashAsync', () => {
|
describe('#ecSignOrderHashAsync', () => {
|
||||||
let stubs: Sinon.SinonStub[] = [];
|
|
||||||
let makerAddress: string;
|
let makerAddress: string;
|
||||||
before(async () => {
|
before(async () => {
|
||||||
const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
|
const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
|
||||||
makerAddress = availableAddreses[0];
|
makerAddress = availableAddreses[0];
|
||||||
});
|
});
|
||||||
afterEach(() => {
|
it('should return the correct Signature', async () => {
|
||||||
// clean up any stubs after the test has completed
|
|
||||||
_.each(stubs, s => s.restore());
|
|
||||||
stubs = [];
|
|
||||||
});
|
|
||||||
it('Should return the correct Signature', async () => {
|
|
||||||
const orderHash = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
|
const orderHash = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
|
||||||
const expectedSignature =
|
const expectedSignature =
|
||||||
'0x1b61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403';
|
'0x1b61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403';
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
"ethereum-types": "^1.0.11",
|
"ethereum-types": "^1.0.11",
|
||||||
"ethereumjs-tx": "^1.3.5",
|
"ethereumjs-tx": "^1.3.5",
|
||||||
"ethereumjs-util": "^5.1.1",
|
"ethereumjs-util": "^5.1.1",
|
||||||
"ganache-core": "0xProject/ganache-core#monorepo-dep",
|
"ganache-core": "^2.2.1",
|
||||||
"hdkey": "^0.7.1",
|
"hdkey": "^0.7.1",
|
||||||
"json-rpc-error": "2.0.0",
|
"json-rpc-error": "2.0.0",
|
||||||
"lodash": "^4.17.5",
|
"lodash": "^4.17.5",
|
||||||
|
@ -314,6 +314,21 @@ export class Web3Wrapper {
|
|||||||
});
|
});
|
||||||
return signData;
|
return signData;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Sign an EIP712 typed data message with a specific address's private key (`eth_signTypedData`)
|
||||||
|
* @param address Address of signer
|
||||||
|
* @param typedData Typed data message to sign
|
||||||
|
* @returns Signature string (might be VRS or RSV depending on the Signer)
|
||||||
|
*/
|
||||||
|
public async signTypedDataAsync(address: string, typedData: object): Promise<string> {
|
||||||
|
assert.isETHAddressHex('address', address);
|
||||||
|
assert.doesConformToSchema('typedData', typedData, schemas.eip712TypedData);
|
||||||
|
const signData = await this.sendRawPayloadAsync<string>({
|
||||||
|
method: 'eth_signTypedData',
|
||||||
|
params: [address, typedData],
|
||||||
|
});
|
||||||
|
return signData;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Fetches the latest block number
|
* Fetches the latest block number
|
||||||
* @returns Block number
|
* @returns Block number
|
||||||
|
Loading…
x
Reference in New Issue
Block a user