Return SignedOrder from signing utils.
Create a helper back in EIP712Utils for code cleanup. Moved constants in order-utils into the constants object
This commit is contained in:
parent
6e462b7dba
commit
75d274f330
@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"note":
|
"note":
|
||||||
"Removed `SignerType` (including `SignerType.Metamask`). Please use the `MetamaskSubprovider` to wrap web3.currentProvider.",
|
"Removed `SignerType` (including `SignerType.Metamask`). Please use the `MetamaskSubprovider` to wrap `web3.currentProvider`.",
|
||||||
"pr": 1102
|
"pr": 1102
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { schemas } from '@0xproject/json-schemas';
|
import { schemas } from '@0xproject/json-schemas';
|
||||||
import { EIP712_DOMAIN_NAME, EIP712_DOMAIN_SCHEMA, EIP712_DOMAIN_VERSION } from '@0xproject/order-utils';
|
import { eip712Utils } from '@0xproject/order-utils';
|
||||||
import { Order, SignedOrder } from '@0xproject/types';
|
import { Order, SignedOrder } from '@0xproject/types';
|
||||||
import { BigNumber, signTypedDataUtils } from '@0xproject/utils';
|
import { BigNumber, signTypedDataUtils } from '@0xproject/utils';
|
||||||
import _ = require('lodash');
|
import _ = require('lodash');
|
||||||
@ -8,15 +8,6 @@ import { ExchangeContract } from '../contract_wrappers/generated/exchange';
|
|||||||
|
|
||||||
import { assert } from './assert';
|
import { assert } from './assert';
|
||||||
|
|
||||||
const EIP712_ZEROEX_TRANSACTION_SCHEMA = {
|
|
||||||
name: 'ZeroExTransaction',
|
|
||||||
parameters: [
|
|
||||||
{ name: 'salt', type: 'uint256' },
|
|
||||||
{ name: 'signerAddress', type: 'address' },
|
|
||||||
{ name: 'data', type: 'bytes' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transaction Encoder. Transaction messages exist for the purpose of calling methods on the Exchange contract
|
* Transaction Encoder. Transaction messages exist for the purpose of calling methods on the Exchange contract
|
||||||
* in the context of another address. For example, UserA can encode and sign a fillOrder transaction and UserB
|
* in the context of another address. For example, UserA can encode and sign a fillOrder transaction and UserB
|
||||||
@ -37,23 +28,11 @@ export class TransactionEncoder {
|
|||||||
public getTransactionHex(data: string, salt: BigNumber, signerAddress: string): string {
|
public getTransactionHex(data: string, salt: BigNumber, signerAddress: string): string {
|
||||||
const exchangeAddress = this._getExchangeContract().address;
|
const exchangeAddress = this._getExchangeContract().address;
|
||||||
const executeTransactionData = {
|
const executeTransactionData = {
|
||||||
salt: salt.toString(),
|
salt,
|
||||||
signerAddress,
|
signerAddress,
|
||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
const typedData = {
|
const typedData = eip712Utils.createZeroExTransactionTypedData(executeTransactionData, exchangeAddress);
|
||||||
types: {
|
|
||||||
EIP712Domain: EIP712_DOMAIN_SCHEMA.parameters,
|
|
||||||
ZeroExTransaction: EIP712_ZEROEX_TRANSACTION_SCHEMA.parameters,
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
name: EIP712_DOMAIN_NAME,
|
|
||||||
version: EIP712_DOMAIN_VERSION,
|
|
||||||
verifyingContract: exchangeAddress,
|
|
||||||
},
|
|
||||||
message: executeTransactionData,
|
|
||||||
primaryType: EIP712_ZEROEX_TRANSACTION_SCHEMA.name,
|
|
||||||
};
|
|
||||||
const eip712MessageBuffer = signTypedDataUtils.signTypedDataHash(typedData);
|
const eip712MessageBuffer = signTypedDataUtils.signTypedDataHash(typedData);
|
||||||
const messageHex = `0x${eip712MessageBuffer.toString('hex')}`;
|
const messageHex = `0x${eip712MessageBuffer.toString('hex')}`;
|
||||||
return messageHex;
|
return messageHex;
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
import {
|
import { eip712Utils, generatePseudoRandomSalt } from '@0xproject/order-utils';
|
||||||
EIP712_DOMAIN_NAME,
|
|
||||||
EIP712_DOMAIN_SCHEMA,
|
|
||||||
EIP712_DOMAIN_VERSION,
|
|
||||||
generatePseudoRandomSalt,
|
|
||||||
} from '@0xproject/order-utils';
|
|
||||||
import { SignatureType } from '@0xproject/types';
|
import { SignatureType } from '@0xproject/types';
|
||||||
import { signTypedDataUtils } from '@0xproject/utils';
|
import { signTypedDataUtils } from '@0xproject/utils';
|
||||||
import * as ethUtil from 'ethereumjs-util';
|
import * as ethUtil from 'ethereumjs-util';
|
||||||
@ -11,15 +6,6 @@ import * as ethUtil from 'ethereumjs-util';
|
|||||||
import { signingUtils } from './signing_utils';
|
import { signingUtils } from './signing_utils';
|
||||||
import { SignedTransaction } from './types';
|
import { SignedTransaction } from './types';
|
||||||
|
|
||||||
const EIP712_ZEROEX_TRANSACTION_SCHEMA = {
|
|
||||||
name: 'ZeroExTransaction',
|
|
||||||
parameters: [
|
|
||||||
{ name: 'salt', type: 'uint256' },
|
|
||||||
{ name: 'signerAddress', type: 'address' },
|
|
||||||
{ name: 'data', type: 'bytes' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export class TransactionFactory {
|
export class TransactionFactory {
|
||||||
private readonly _signerBuff: Buffer;
|
private readonly _signerBuff: Buffer;
|
||||||
private readonly _exchangeAddress: string;
|
private readonly _exchangeAddress: string;
|
||||||
@ -33,30 +19,18 @@ export class TransactionFactory {
|
|||||||
const salt = generatePseudoRandomSalt();
|
const salt = generatePseudoRandomSalt();
|
||||||
const signerAddress = `0x${this._signerBuff.toString('hex')}`;
|
const signerAddress = `0x${this._signerBuff.toString('hex')}`;
|
||||||
const executeTransactionData = {
|
const executeTransactionData = {
|
||||||
salt: salt.toString(),
|
salt,
|
||||||
signerAddress,
|
signerAddress,
|
||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
const typedData = {
|
|
||||||
types: {
|
const typedData = eip712Utils.createZeroExTransactionTypedData(executeTransactionData, this._exchangeAddress);
|
||||||
EIP712Domain: EIP712_DOMAIN_SCHEMA.parameters,
|
|
||||||
ZeroExTransaction: EIP712_ZEROEX_TRANSACTION_SCHEMA.parameters,
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
name: EIP712_DOMAIN_NAME,
|
|
||||||
version: EIP712_DOMAIN_VERSION,
|
|
||||||
verifyingContract: this._exchangeAddress,
|
|
||||||
},
|
|
||||||
message: executeTransactionData,
|
|
||||||
primaryType: EIP712_ZEROEX_TRANSACTION_SCHEMA.name,
|
|
||||||
};
|
|
||||||
const eip712MessageBuffer = signTypedDataUtils.signTypedDataHash(typedData);
|
const eip712MessageBuffer = signTypedDataUtils.signTypedDataHash(typedData);
|
||||||
const signature = signingUtils.signMessage(eip712MessageBuffer, this._privateKey, signatureType);
|
const signature = signingUtils.signMessage(eip712MessageBuffer, this._privateKey, signatureType);
|
||||||
const signedTx = {
|
const signedTx = {
|
||||||
exchangeAddress: this._exchangeAddress,
|
exchangeAddress: this._exchangeAddress,
|
||||||
signature: `0x${signature.toString('hex')}`,
|
signature: `0x${signature.toString('hex')}`,
|
||||||
...executeTransactionData,
|
...executeTransactionData,
|
||||||
salt,
|
|
||||||
};
|
};
|
||||||
return signedTx;
|
return signedTx;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export const eip712TypedData = {
|
export const eip712TypedData = {
|
||||||
id: 'eip712TypedData',
|
id: '/eip712TypedData',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
types: {
|
types: {
|
||||||
|
@ -3,15 +3,15 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"changes": [
|
"changes": [
|
||||||
{
|
{
|
||||||
"note": "Added ecSignOrderAsync to first sign an order as EIP712 and fallback to EthSign",
|
"note": "Added `ecSignOrderAsync` to first sign an order as EIP712 and fallback to EthSign",
|
||||||
"pr": 1102
|
"pr": 1102
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"note": "Added ecSignTypedDataOrderAsync to sign an order exclusively as EIP712",
|
"note": "Added `ecSignTypedDataOrderAsync` to sign an order exclusively as EIP712",
|
||||||
"pr": 1102
|
"pr": 1102
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"note": "Rename ecSignOrderHashAsync to ecSignHashAsync removing SignerType parameter",
|
"note": "Rename `ecSignOrderHashAsync` to `ecSignHashAsync` removing `SignerType` parameter",
|
||||||
"pr": 1102
|
"pr": 1102
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -13,16 +13,39 @@ export const constants = {
|
|||||||
BASE_16: 16,
|
BASE_16: 16,
|
||||||
INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite
|
INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite
|
||||||
ZERO_AMOUNT: new BigNumber(0),
|
ZERO_AMOUNT: new BigNumber(0),
|
||||||
};
|
EIP712_DOMAIN_NAME: '0x Protocol',
|
||||||
|
EIP712_DOMAIN_VERSION: '2',
|
||||||
export const EIP712_DOMAIN_NAME = '0x Protocol';
|
EIP712_DOMAIN_SCHEMA: {
|
||||||
export const EIP712_DOMAIN_VERSION = '2';
|
name: 'EIP712Domain',
|
||||||
|
parameters: [
|
||||||
export const EIP712_DOMAIN_SCHEMA = {
|
{ name: 'name', type: 'string' },
|
||||||
name: 'EIP712Domain',
|
{ name: 'version', type: 'string' },
|
||||||
parameters: [
|
{ name: 'verifyingContract', type: 'address' },
|
||||||
{ name: 'name', type: 'string' },
|
],
|
||||||
{ name: 'version', type: 'string' },
|
},
|
||||||
{ name: 'verifyingContract', type: 'address' },
|
EIP712_ORDER_SCHEMA: {
|
||||||
],
|
name: 'Order',
|
||||||
|
parameters: [
|
||||||
|
{ name: 'makerAddress', type: 'address' },
|
||||||
|
{ name: 'takerAddress', type: 'address' },
|
||||||
|
{ name: 'feeRecipientAddress', type: 'address' },
|
||||||
|
{ name: 'senderAddress', type: 'address' },
|
||||||
|
{ name: 'makerAssetAmount', type: 'uint256' },
|
||||||
|
{ name: 'takerAssetAmount', type: 'uint256' },
|
||||||
|
{ name: 'makerFee', type: 'uint256' },
|
||||||
|
{ name: 'takerFee', type: 'uint256' },
|
||||||
|
{ name: 'expirationTimeSeconds', type: 'uint256' },
|
||||||
|
{ name: 'salt', type: 'uint256' },
|
||||||
|
{ name: 'makerAssetData', type: 'bytes' },
|
||||||
|
{ name: 'takerAssetData', type: 'bytes' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
EIP712_ZEROEX_TRANSACTION_SCHEMA: {
|
||||||
|
name: 'ZeroExTransaction',
|
||||||
|
parameters: [
|
||||||
|
{ name: 'salt', type: 'uint256' },
|
||||||
|
{ name: 'signerAddress', type: 'address' },
|
||||||
|
{ name: 'data', type: 'bytes' },
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
75
packages/order-utils/src/eip712_utils.ts
Normal file
75
packages/order-utils/src/eip712_utils.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { EIP712Object, EIP712TypedData, EIP712Types, Order, ZeroExTransaction } from '@0xproject/types';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { constants } from './constants';
|
||||||
|
|
||||||
|
export const eip712Utils = {
|
||||||
|
/**
|
||||||
|
* Creates a EIP712TypedData object specific to the 0x protocol for use with signTypedData.
|
||||||
|
* @param primaryType The primary type found in message
|
||||||
|
* @param types The additional types for the data in message
|
||||||
|
* @param message The contents of the message
|
||||||
|
* @param exchangeAddress The address of the exchange contract
|
||||||
|
* @return A typed data object
|
||||||
|
*/
|
||||||
|
createTypedData: (
|
||||||
|
primaryType: string,
|
||||||
|
types: EIP712Types,
|
||||||
|
message: EIP712Object,
|
||||||
|
exchangeAddress: string,
|
||||||
|
): EIP712TypedData => {
|
||||||
|
const typedData = {
|
||||||
|
types: {
|
||||||
|
EIP712Domain: constants.EIP712_DOMAIN_SCHEMA.parameters,
|
||||||
|
...types,
|
||||||
|
},
|
||||||
|
domain: {
|
||||||
|
name: constants.EIP712_DOMAIN_NAME,
|
||||||
|
version: constants.EIP712_DOMAIN_VERSION,
|
||||||
|
verifyingContract: exchangeAddress,
|
||||||
|
},
|
||||||
|
message,
|
||||||
|
primaryType,
|
||||||
|
};
|
||||||
|
return typedData;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Creates an Order EIP712TypedData object for use with signTypedData.
|
||||||
|
* @param Order the order
|
||||||
|
* @return A typed data object
|
||||||
|
*/
|
||||||
|
createOrderTypedData: (order: Order): EIP712TypedData => {
|
||||||
|
const normalizedOrder = _.mapValues(order, value => {
|
||||||
|
return !_.isString(value) ? value.toString() : value;
|
||||||
|
});
|
||||||
|
const typedData = eip712Utils.createTypedData(
|
||||||
|
constants.EIP712_ORDER_SCHEMA.name,
|
||||||
|
{ Order: constants.EIP712_ORDER_SCHEMA.parameters },
|
||||||
|
normalizedOrder,
|
||||||
|
order.exchangeAddress,
|
||||||
|
);
|
||||||
|
return typedData;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Creates an ExecuteTransaction EIP712TypedData object for use with signTypedData and
|
||||||
|
* 0x Exchange executeTransaction.
|
||||||
|
* @param ZeroExTransaction the 0x transaction
|
||||||
|
* @param exchangeAddress The address of the exchange contract
|
||||||
|
* @return A typed data object
|
||||||
|
*/
|
||||||
|
createZeroExTransactionTypedData: (
|
||||||
|
zeroExTransaction: ZeroExTransaction,
|
||||||
|
exchangeAddress: string,
|
||||||
|
): EIP712TypedData => {
|
||||||
|
const normalizedTransaction = _.mapValues(zeroExTransaction, value => {
|
||||||
|
return !_.isString(value) ? value.toString() : value;
|
||||||
|
});
|
||||||
|
const typedData = eip712Utils.createTypedData(
|
||||||
|
constants.EIP712_ZEROEX_TRANSACTION_SCHEMA.name,
|
||||||
|
{ ZeroExTransaction: constants.EIP712_ZEROEX_TRANSACTION_SCHEMA.parameters },
|
||||||
|
normalizedTransaction,
|
||||||
|
exchangeAddress,
|
||||||
|
);
|
||||||
|
return typedData;
|
||||||
|
},
|
||||||
|
};
|
@ -18,7 +18,8 @@ export { ExchangeTransferSimulator } from './exchange_transfer_simulator';
|
|||||||
export { BalanceAndProxyAllowanceLazyStore } from './store/balance_and_proxy_allowance_lazy_store';
|
export { BalanceAndProxyAllowanceLazyStore } from './store/balance_and_proxy_allowance_lazy_store';
|
||||||
export { OrderFilledCancelledLazyStore } from './store/order_filled_cancelled_lazy_store';
|
export { OrderFilledCancelledLazyStore } from './store/order_filled_cancelled_lazy_store';
|
||||||
|
|
||||||
export { EIP712_DOMAIN_NAME, EIP712_DOMAIN_SCHEMA, EIP712_DOMAIN_VERSION } from './constants';
|
export { constants } from './constants';
|
||||||
|
export { eip712Utils } from './eip712_utils';
|
||||||
|
|
||||||
export { Provider, JSONRPCRequestPayload, JSONRPCErrorCallback, JSONRPCResponsePayload } from 'ethereum-types';
|
export { Provider, JSONRPCRequestPayload, JSONRPCErrorCallback, JSONRPCResponsePayload } from 'ethereum-types';
|
||||||
export {
|
export {
|
||||||
@ -34,6 +35,12 @@ export {
|
|||||||
OrderStateValid,
|
OrderStateValid,
|
||||||
OrderStateInvalid,
|
OrderStateInvalid,
|
||||||
ExchangeContractErrs,
|
ExchangeContractErrs,
|
||||||
|
EIP712Parameter,
|
||||||
|
EIP712TypedData,
|
||||||
|
EIP712Types,
|
||||||
|
EIP712Object,
|
||||||
|
EIP712ObjectValue,
|
||||||
|
ZeroExTransaction,
|
||||||
} from '@0xproject/types';
|
} from '@0xproject/types';
|
||||||
export {
|
export {
|
||||||
OrderError,
|
OrderError,
|
||||||
|
@ -4,28 +4,10 @@ import { signTypedDataUtils } from '@0xproject/utils';
|
|||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { assert } from './assert';
|
import { assert } from './assert';
|
||||||
import { EIP712_DOMAIN_NAME, EIP712_DOMAIN_SCHEMA, EIP712_DOMAIN_VERSION } from './constants';
|
import { eip712Utils } from './eip712_utils';
|
||||||
|
|
||||||
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';
|
||||||
|
|
||||||
export const EIP712_ORDER_SCHEMA = {
|
|
||||||
name: 'Order',
|
|
||||||
parameters: [
|
|
||||||
{ name: 'makerAddress', type: 'address' },
|
|
||||||
{ name: 'takerAddress', type: 'address' },
|
|
||||||
{ name: 'feeRecipientAddress', type: 'address' },
|
|
||||||
{ name: 'senderAddress', type: 'address' },
|
|
||||||
{ name: 'makerAssetAmount', type: 'uint256' },
|
|
||||||
{ name: 'takerAssetAmount', type: 'uint256' },
|
|
||||||
{ name: 'makerFee', type: 'uint256' },
|
|
||||||
{ name: 'takerFee', type: 'uint256' },
|
|
||||||
{ name: 'expirationTimeSeconds', type: 'uint256' },
|
|
||||||
{ name: 'salt', type: 'uint256' },
|
|
||||||
{ name: 'makerAssetData', type: 'bytes' },
|
|
||||||
{ name: 'takerAssetData', type: 'bytes' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const orderHashUtils = {
|
export const orderHashUtils = {
|
||||||
/**
|
/**
|
||||||
* Checks if the supplied hex encoded order hash is valid.
|
* Checks if the supplied hex encoded order hash is valid.
|
||||||
@ -45,7 +27,7 @@ export const orderHashUtils = {
|
|||||||
/**
|
/**
|
||||||
* Computes the orderHash for a supplied order.
|
* Computes the orderHash for a supplied order.
|
||||||
* @param order An object that conforms to the Order or SignedOrder interface definitions.
|
* @param order An object that conforms to the Order or SignedOrder interface definitions.
|
||||||
* @return The resulting orderHash from hashing the supplied order.
|
* @return Hex encoded string orderHash from hashing the supplied order.
|
||||||
*/
|
*/
|
||||||
getOrderHashHex(order: SignedOrder | Order): string {
|
getOrderHashHex(order: SignedOrder | Order): string {
|
||||||
try {
|
try {
|
||||||
@ -64,27 +46,12 @@ export const orderHashUtils = {
|
|||||||
return orderHashHex;
|
return orderHashHex;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Computes the orderHash for a supplied order and returns it as a Buffer
|
* Computes the orderHash for a supplied order
|
||||||
* @param order An object that conforms to the Order or SignedOrder interface definitions.
|
* @param order An object that conforms to the Order or SignedOrder interface definitions.
|
||||||
* @return The resulting orderHash from hashing the supplied order as a Buffer
|
* @return A Buffer containing the resulting orderHash from hashing the supplied order
|
||||||
*/
|
*/
|
||||||
getOrderHashBuffer(order: SignedOrder | Order): Buffer {
|
getOrderHashBuffer(order: SignedOrder | Order): Buffer {
|
||||||
const normalizedOrder = _.mapValues(order, value => {
|
const typedData = eip712Utils.createOrderTypedData(order);
|
||||||
return _.isObject(value) ? value.toString() : value;
|
|
||||||
});
|
|
||||||
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: normalizedOrder,
|
|
||||||
primaryType: EIP712_ORDER_SCHEMA.name,
|
|
||||||
};
|
|
||||||
const orderHashBuff = signTypedDataUtils.signTypedDataHash(typedData);
|
const orderHashBuff = signTypedDataUtils.signTypedDataHash(typedData);
|
||||||
return orderHashBuff;
|
return orderHashBuff;
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { schemas } from '@0xproject/json-schemas';
|
import { schemas } from '@0xproject/json-schemas';
|
||||||
import { ECSignature, Order, SignatureType, ValidatorSignature } from '@0xproject/types';
|
import { ECSignature, Order, SignatureType, SignedOrder, 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,11 +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 './constants';
|
import { eip712Utils } 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, orderHashUtils } from './order_hash';
|
import { orderHashUtils } from './order_hash';
|
||||||
import { OrderError } from './types';
|
import { OrderError } from './types';
|
||||||
import { utils } from './utils';
|
import { utils } from './utils';
|
||||||
|
|
||||||
@ -195,53 +195,45 @@ export const signatureUtils = {
|
|||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Signs an order and returns its elliptic curve signature and signature type. First `eth_signTypedData` is requested
|
* Signs an order and returns its elliptic curve signature and signature type. First `eth_signTypedData` is requested
|
||||||
* then a fallback to `eth_sign` if not available on this provider.
|
* then a fallback to `eth_sign` if not available on the supplied provider.
|
||||||
* @param order The Order to sign.
|
* @param order The Order to sign.
|
||||||
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
|
* @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.
|
* must be available via the supplied Provider.
|
||||||
* @return A hex encoded string containing the Elliptic curve signature generated by signing the orderHash and the Signature Type.
|
* @return A SignedOrder containing the order and Elliptic curve signature with Signature Type.
|
||||||
*/
|
*/
|
||||||
async ecSignOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<string> {
|
async ecSignOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<SignedOrder> {
|
||||||
try {
|
try {
|
||||||
const signatureHex = signatureUtils.ecSignTypedDataOrderAsync(provider, order, signerAddress);
|
const signedOrder = signatureUtils.ecSignTypedDataOrderAsync(provider, order, signerAddress);
|
||||||
return signatureHex;
|
return signedOrder;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Fallback to using EthSign when ethSignTypedData is not supported
|
// Fallback to using EthSign when ethSignTypedData is not supported
|
||||||
const orderHash = orderHashUtils.getOrderHashHex(order);
|
const orderHash = orderHashUtils.getOrderHashHex(order);
|
||||||
const signatureHex = await signatureUtils.ecSignHashAsync(provider, orderHash, signerAddress);
|
const signatureHex = await signatureUtils.ecSignHashAsync(provider, orderHash, signerAddress);
|
||||||
return signatureHex;
|
const signedOrder = {
|
||||||
|
...order,
|
||||||
|
signature: signatureHex,
|
||||||
|
};
|
||||||
|
return signedOrder;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Signs an order using `eth_signTypedData` and returns its elliptic curve signature and signature type.
|
* Signs an order using `eth_signTypedData` and returns its elliptic curve signature and signature type.
|
||||||
* @param order The Order to sign.
|
* @param order The Order to sign.
|
||||||
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
|
* @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.
|
* must be available via the supplied Provider.
|
||||||
* @return A hex encoded string containing the Elliptic curve signature generated by signing the order with `eth_signTypedData`
|
* @return A SignedOrder containing the order and Elliptic curve signature with Signature Type.
|
||||||
* and the Signature Type.
|
|
||||||
*/
|
*/
|
||||||
async ecSignTypedDataOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<string> {
|
async ecSignTypedDataOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<SignedOrder> {
|
||||||
assert.isWeb3Provider('provider', provider);
|
assert.isWeb3Provider('provider', provider);
|
||||||
assert.isETHAddressHex('signerAddress', signerAddress);
|
assert.isETHAddressHex('signerAddress', signerAddress);
|
||||||
const web3Wrapper = new Web3Wrapper(provider);
|
const web3Wrapper = new Web3Wrapper(provider);
|
||||||
await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper);
|
await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper);
|
||||||
|
// Detect if Metamask to transition users to the MetamaskSubprovider
|
||||||
|
if ((provider as any).isMetaMask) {
|
||||||
|
throw new Error('Unsupported Provider, please use MetamaskSubprovider.');
|
||||||
|
}
|
||||||
const normalizedSignerAddress = signerAddress.toLowerCase();
|
const normalizedSignerAddress = signerAddress.toLowerCase();
|
||||||
const normalizedOrder = _.mapValues(order, value => {
|
const typedData = eip712Utils.createOrderTypedData(order);
|
||||||
return _.isObject(value) ? value.toString() : value;
|
|
||||||
});
|
|
||||||
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: normalizedOrder,
|
|
||||||
primaryType: 'Order',
|
|
||||||
};
|
|
||||||
const signature = await web3Wrapper.signTypedDataAsync(normalizedSignerAddress, typedData);
|
const signature = await web3Wrapper.signTypedDataAsync(normalizedSignerAddress, typedData);
|
||||||
const ecSignatureRSV = parseSignatureHexAsRSV(signature);
|
const ecSignatureRSV = parseSignatureHexAsRSV(signature);
|
||||||
const signatureBuffer = Buffer.concat([
|
const signatureBuffer = Buffer.concat([
|
||||||
@ -251,14 +243,16 @@ export const signatureUtils = {
|
|||||||
ethUtil.toBuffer(SignatureType.EIP712),
|
ethUtil.toBuffer(SignatureType.EIP712),
|
||||||
]);
|
]);
|
||||||
const signatureHex = `0x${signatureBuffer.toString('hex')}`;
|
const signatureHex = `0x${signatureBuffer.toString('hex')}`;
|
||||||
return signatureHex;
|
return {
|
||||||
|
...order,
|
||||||
|
signature: signatureHex,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Signs a hash and returns its elliptic curve signature and signature type.
|
* Signs a hash and returns its elliptic curve signature and signature type.
|
||||||
* This method currently supports TestRPC, Geth and Parity above and below V1.6.6
|
|
||||||
* @param msgHash Hex encoded message to sign.
|
* @param msgHash Hex encoded message to sign.
|
||||||
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
|
* @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.
|
* must be available via the supplied Provider.
|
||||||
* @return A hex encoded string containing the Elliptic curve signature generated by signing the msgHash and the Signature Type.
|
* @return A hex encoded string containing the Elliptic curve signature generated by signing the msgHash and the Signature Type.
|
||||||
*/
|
*/
|
||||||
async ecSignHashAsync(provider: Provider, msgHash: string, signerAddress: string): Promise<string> {
|
async ecSignHashAsync(provider: Provider, msgHash: string, signerAddress: string): Promise<string> {
|
||||||
@ -268,6 +262,10 @@ export const signatureUtils = {
|
|||||||
const web3Wrapper = new Web3Wrapper(provider);
|
const web3Wrapper = new Web3Wrapper(provider);
|
||||||
await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper);
|
await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper);
|
||||||
const normalizedSignerAddress = signerAddress.toLowerCase();
|
const normalizedSignerAddress = signerAddress.toLowerCase();
|
||||||
|
// Detect if Metamask to transition users to the MetamaskSubprovider
|
||||||
|
if ((provider as any).isMetaMask) {
|
||||||
|
throw new Error('Unsupported Provider, please use MetamaskSubprovider.');
|
||||||
|
}
|
||||||
|
|
||||||
const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHash);
|
const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHash);
|
||||||
const prefixedMsgHashHex = signatureUtils.addSignedMessagePrefix(msgHash);
|
const prefixedMsgHashHex = signatureUtils.addSignedMessagePrefix(msgHash);
|
||||||
|
44
packages/order-utils/test/eip712_utils_test.ts
Normal file
44
packages/order-utils/test/eip712_utils_test.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { BigNumber } from '@0xproject/utils';
|
||||||
|
import * as chai from 'chai';
|
||||||
|
import 'mocha';
|
||||||
|
|
||||||
|
import { constants } from '../src/constants';
|
||||||
|
import { eip712Utils } from '../src/eip712_utils';
|
||||||
|
|
||||||
|
import { chaiSetup } from './utils/chai_setup';
|
||||||
|
|
||||||
|
chaiSetup.configure();
|
||||||
|
const expect = chai.expect;
|
||||||
|
|
||||||
|
describe('EIP712 Utils', () => {
|
||||||
|
describe('createTypedData', () => {
|
||||||
|
it('adds in the EIP712DomainSeparator', () => {
|
||||||
|
const primaryType = 'Test';
|
||||||
|
const typedData = eip712Utils.createTypedData(
|
||||||
|
primaryType,
|
||||||
|
{ Test: [{ name: 'testValue', type: 'uint256' }] },
|
||||||
|
{ testValue: '1' },
|
||||||
|
constants.NULL_ADDRESS,
|
||||||
|
);
|
||||||
|
expect(typedData.domain).to.not.be.undefined();
|
||||||
|
expect(typedData.types.EIP712Domain).to.not.be.undefined();
|
||||||
|
const domainObject = typedData.domain;
|
||||||
|
expect(domainObject.name).to.eq(constants.EIP712_DOMAIN_NAME);
|
||||||
|
expect(typedData.primaryType).to.eq(primaryType);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('createTypedData', () => {
|
||||||
|
it('adds in the EIP712DomainSeparator', () => {
|
||||||
|
const typedData = eip712Utils.createZeroExTransactionTypedData(
|
||||||
|
{
|
||||||
|
salt: new BigNumber('0'),
|
||||||
|
data: constants.NULL_BYTES,
|
||||||
|
signerAddress: constants.NULL_BYTES,
|
||||||
|
},
|
||||||
|
constants.NULL_ADDRESS,
|
||||||
|
);
|
||||||
|
expect(typedData.primaryType).to.eq(constants.EIP712_ZEROEX_TRANSACTION_SCHEMA.name);
|
||||||
|
expect(typedData.types.EIP712Domain).to.not.be.undefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -35,6 +35,20 @@ describe('Order hashing', () => {
|
|||||||
const orderHash = orderHashUtils.getOrderHashHex(order);
|
const orderHash = orderHashUtils.getOrderHashHex(order);
|
||||||
expect(orderHash).to.be.equal(expectedOrderHash);
|
expect(orderHash).to.be.equal(expectedOrderHash);
|
||||||
});
|
});
|
||||||
|
it('calculates the order hash if amounts are strings', async () => {
|
||||||
|
// It's common for developers using javascript to provide the amounts
|
||||||
|
// as strings. Since we eventually toString() the BigNumber
|
||||||
|
// before encoding we should result in the same orderHash in this scenario
|
||||||
|
// tslint:disable-next-line:no-unnecessary-type-assertion
|
||||||
|
const orderHash = orderHashUtils.getOrderHashHex({
|
||||||
|
...order,
|
||||||
|
makerAssetAmount: '0',
|
||||||
|
takerAssetAmount: '0',
|
||||||
|
makerFee: '0',
|
||||||
|
takerFee: '0',
|
||||||
|
} as any);
|
||||||
|
expect(orderHash).to.be.equal(expectedOrderHash);
|
||||||
|
});
|
||||||
it('throws a readable error message if taker format is invalid', async () => {
|
it('throws a readable error message if taker format is invalid', async () => {
|
||||||
const orderWithInvalidtakerFormat = {
|
const orderWithInvalidtakerFormat = {
|
||||||
...order,
|
...order,
|
||||||
|
@ -152,14 +152,14 @@ describe('Signature utils', () => {
|
|||||||
ethUtil.toBuffer(SignatureType.EIP712),
|
ethUtil.toBuffer(SignatureType.EIP712),
|
||||||
]);
|
]);
|
||||||
const signatureHex = `0x${signatureBuffer.toString('hex')}`;
|
const signatureHex = `0x${signatureBuffer.toString('hex')}`;
|
||||||
const eip712Signature = await signatureUtils.ecSignOrderAsync(provider, order, makerAddress);
|
const signedOrder = await signatureUtils.ecSignOrderAsync(provider, order, makerAddress);
|
||||||
const isValidSignature = await signatureUtils.isValidSignatureAsync(
|
const isValidSignature = await signatureUtils.isValidSignatureAsync(
|
||||||
provider,
|
provider,
|
||||||
orderHashHex,
|
orderHashHex,
|
||||||
eip712Signature,
|
signedOrder.signature,
|
||||||
makerAddress,
|
makerAddress,
|
||||||
);
|
);
|
||||||
expect(signatureHex).to.eq(eip712Signature);
|
expect(signatureHex).to.eq(signedOrder.signature);
|
||||||
expect(isValidSignature).to.eq(true);
|
expect(isValidSignature).to.eq(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -238,6 +238,51 @@ describe('Signature utils', () => {
|
|||||||
expect(isValidSignature).to.be.true();
|
expect(isValidSignature).to.be.true();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('#ecSignTypedDataOrderAsync', () => {
|
||||||
|
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 return the correct Signature for signatureHex concatenated as R + S + V', async () => {
|
||||||
|
const expectedSignature =
|
||||||
|
'0x1cd472c439833774b55d248c31b6585f21aea1b9363ebb4ec58549e46b62eb5a6f696f5781f62de008ee7f77650ef940d99c97ec1dee67b3f5cea1bbfdfeb2eba602';
|
||||||
|
const fakeProvider = {
|
||||||
|
async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise<void> {
|
||||||
|
if (payload.method === 'eth_signTypedData') {
|
||||||
|
const [address, typedData] = payload.params;
|
||||||
|
const signature = await web3Wrapper.signTypedDataAsync(address, typedData);
|
||||||
|
callback(null, {
|
||||||
|
id: 42,
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
result: signature,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const signedOrder = await signatureUtils.ecSignTypedDataOrderAsync(fakeProvider, order, makerAddress);
|
||||||
|
expect(signedOrder.signature).to.equal(expectedSignature);
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('#convertECSignatureToSignatureHex', () => {
|
describe('#convertECSignatureToSignatureHex', () => {
|
||||||
const ecSignature: ECSignature = {
|
const ecSignature: ECSignature = {
|
||||||
v: 27,
|
v: 27,
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"changes": [
|
"changes": [
|
||||||
{
|
{
|
||||||
"note": "Add Metamask Subprovider to handle inconsistent JSON RPC behaviour",
|
"note": "Add `MetamaskSubprovider` to handle inconsistent JSON RPC behaviour",
|
||||||
"pr": 1102
|
"pr": 1102
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"note": "Add support for eth_signTypedData in Mnemonic, Private and EthLightWallet",
|
"note": "Add support for `eth_signTypedData` in wallets Mnemonic, Private and EthLightWallet",
|
||||||
"pr": 1102
|
"pr": 1102
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -48,6 +48,13 @@ export {
|
|||||||
LedgerGetAddressResult,
|
LedgerGetAddressResult,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
export { ECSignature } from '@0xproject/types';
|
export {
|
||||||
|
ECSignature,
|
||||||
|
EIP712Object,
|
||||||
|
EIP712ObjectValue,
|
||||||
|
EIP712TypedData,
|
||||||
|
EIP712Types,
|
||||||
|
EIP712Parameter,
|
||||||
|
} from '@0xproject/types';
|
||||||
|
|
||||||
export { JSONRPCRequestPayload, Provider, JSONRPCResponsePayload, JSONRPCErrorCallback } from 'ethereum-types';
|
export { JSONRPCRequestPayload, Provider, JSONRPCResponsePayload, JSONRPCErrorCallback } from 'ethereum-types';
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { EIP712TypedData } from '@0xproject/types';
|
||||||
import * as lightwallet from 'eth-lightwallet';
|
import * as lightwallet from 'eth-lightwallet';
|
||||||
|
|
||||||
import { PartialTxParams } from '../types';
|
import { PartialTxParams } from '../types';
|
||||||
@ -48,16 +49,16 @@ export class EthLightwalletSubprovider extends BaseWalletSubprovider {
|
|||||||
// Lightwallet loses the chain id information when hex encoding the transaction
|
// Lightwallet loses the chain id information when hex encoding the transaction
|
||||||
// this results in a different signature on certain networks. PrivateKeyWallet
|
// this results in a different signature on certain networks. PrivateKeyWallet
|
||||||
// respects this as it uses the parameters passed in
|
// respects this as it uses the parameters passed in
|
||||||
let privKey = this._keystore.exportPrivateKey(txParams.from, this._pwDerivedKey);
|
let privateKey = this._keystore.exportPrivateKey(txParams.from, this._pwDerivedKey);
|
||||||
const privKeyWallet = new PrivateKeyWalletSubprovider(privKey);
|
const privateKeyWallet = new PrivateKeyWalletSubprovider(privateKey);
|
||||||
privKey = '';
|
privateKey = '';
|
||||||
const privKeySignature = await privKeyWallet.signTransactionAsync(txParams);
|
const privateKeySignature = await privateKeyWallet.signTransactionAsync(txParams);
|
||||||
return privKeySignature;
|
return privateKeySignature;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Sign a personal Ethereum signed message. The signing account will be the account
|
* Sign a personal Ethereum signed message. The signing account will be the account
|
||||||
* associated with the provided address.
|
* associated with the provided address.
|
||||||
* If you've added the this Subprovider to your app's provider, you can simply send an `eth_sign`
|
* If you've added this Subprovider to your app's provider, you can simply send an `eth_sign`
|
||||||
* or `personal_sign` JSON RPC request, and this method will be called auto-magically.
|
* or `personal_sign` JSON RPC request, and this method will be called auto-magically.
|
||||||
* If you are not using this via a ProviderEngine instance, you can call it directly.
|
* If you are not using this via a ProviderEngine instance, you can call it directly.
|
||||||
* @param data Hex string message to sign
|
* @param data Hex string message to sign
|
||||||
@ -65,10 +66,10 @@ export class EthLightwalletSubprovider extends BaseWalletSubprovider {
|
|||||||
* @return Signature hex string (order: rsv)
|
* @return Signature hex string (order: rsv)
|
||||||
*/
|
*/
|
||||||
public async signPersonalMessageAsync(data: string, address: string): Promise<string> {
|
public async signPersonalMessageAsync(data: string, address: string): Promise<string> {
|
||||||
let privKey = this._keystore.exportPrivateKey(address, this._pwDerivedKey);
|
let privateKey = this._keystore.exportPrivateKey(address, this._pwDerivedKey);
|
||||||
const privKeyWallet = new PrivateKeyWalletSubprovider(privKey);
|
const privateKeyWallet = new PrivateKeyWalletSubprovider(privateKey);
|
||||||
privKey = '';
|
privateKey = '';
|
||||||
const result = privKeyWallet.signPersonalMessageAsync(data, address);
|
const result = privateKeyWallet.signPersonalMessageAsync(data, address);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -80,11 +81,11 @@ export class EthLightwalletSubprovider extends BaseWalletSubprovider {
|
|||||||
* @param data the typed data object
|
* @param data the typed data object
|
||||||
* @return Signature hex string (order: rsv)
|
* @return Signature hex string (order: rsv)
|
||||||
*/
|
*/
|
||||||
public async signTypedDataAsync(address: string, typedData: any): Promise<string> {
|
public async signTypedDataAsync(address: string, typedData: EIP712TypedData): Promise<string> {
|
||||||
let privKey = this._keystore.exportPrivateKey(address, this._pwDerivedKey);
|
let privateKey = this._keystore.exportPrivateKey(address, this._pwDerivedKey);
|
||||||
const privKeyWallet = new PrivateKeyWalletSubprovider(privKey);
|
const privateKeyWallet = new PrivateKeyWalletSubprovider(privateKey);
|
||||||
privKey = '';
|
privateKey = '';
|
||||||
const result = privKeyWallet.signTypedDataAsync(address, typedData);
|
const result = privateKeyWallet.signTypedDataAsync(address, typedData);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ export class MetamaskSubprovider extends Subprovider {
|
|||||||
private readonly _web3Wrapper: Web3Wrapper;
|
private readonly _web3Wrapper: Web3Wrapper;
|
||||||
private readonly _provider: Provider;
|
private readonly _provider: Provider;
|
||||||
/**
|
/**
|
||||||
* Instantiates a new SignerSubprovider
|
* Instantiates a new MetamaskSubprovider
|
||||||
* @param provider Web3 provider that should handle all user account related requests
|
* @param provider Web3 provider that should handle all user account related requests
|
||||||
*/
|
*/
|
||||||
constructor(provider: Provider) {
|
constructor(provider: Provider) {
|
||||||
@ -83,7 +83,9 @@ export class MetamaskSubprovider extends Subprovider {
|
|||||||
case 'eth_signTypedData_v3':
|
case 'eth_signTypedData_v3':
|
||||||
[address, message] = payload.params;
|
[address, message] = payload.params;
|
||||||
try {
|
try {
|
||||||
// Metamask has namespaced signTypedData to v3 for an indeterminate period of time.
|
// Metamask supports multiple versions and has namespaced signTypedData to v3 for an indeterminate period of time.
|
||||||
|
// eth_signTypedData is mapped to an older implementation before the spec was finalized.
|
||||||
|
// Source: https://github.com/MetaMask/metamask-extension/blob/c49d854b55b3efd34c7fd0414b76f7feaa2eec7c/app/scripts/metamask-controller.js#L1262
|
||||||
// and expects message to be serialised as JSON
|
// and expects message to be serialised as JSON
|
||||||
const messageJSON = JSON.stringify(message);
|
const messageJSON = JSON.stringify(message);
|
||||||
const signature = await this._web3Wrapper.sendRawPayloadAsync<string>({
|
const signature = await this._web3Wrapper.sendRawPayloadAsync<string>({
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { assert } from '@0xproject/assert';
|
import { assert } from '@0xproject/assert';
|
||||||
|
import { EIP712TypedData } from '@0xproject/types';
|
||||||
import { addressUtils } from '@0xproject/utils';
|
import { addressUtils } from '@0xproject/utils';
|
||||||
import * as bip39 from 'bip39';
|
import * as bip39 from 'bip39';
|
||||||
import HDNode = require('hdkey');
|
import HDNode = require('hdkey');
|
||||||
@ -90,10 +91,10 @@ export class MnemonicWalletSubprovider extends BaseWalletSubprovider {
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Sign a personal Ethereum signed message. The signing account will be the account
|
* Sign a personal Ethereum signed message. The signing account will be the account
|
||||||
* associated with the provided address.
|
* associated with the provided address. If you've added the MnemonicWalletSubprovider to
|
||||||
* If you've added the MnemonicWalletSubprovider to your app's provider, you can simply send an `eth_sign`
|
* your app's provider, you can simply send an `eth_sign` or `personal_sign` JSON RPC request,
|
||||||
* or `personal_sign` JSON RPC request, and this method will be called auto-magically.
|
* and this method will be called auto-magically. If you are not using this via a ProviderEngine
|
||||||
* If you are not using this via a ProviderEngine instance, you can call it directly.
|
* instance, you can call it directly.
|
||||||
* @param data Hex string message to sign
|
* @param data Hex string message to sign
|
||||||
* @param address Address of the account to sign with
|
* @param address Address of the account to sign with
|
||||||
* @return Signature hex string (order: rsv)
|
* @return Signature hex string (order: rsv)
|
||||||
@ -109,16 +110,16 @@ export class MnemonicWalletSubprovider extends BaseWalletSubprovider {
|
|||||||
return sig;
|
return sig;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Sign an EIP712 Typed Data message. The signing account will be the account
|
* Sign an EIP712 Typed Data message. The signing account will be the account
|
||||||
* associated with the provided address.
|
* associated with the provided address. If you've added this MnemonicWalletSubprovider to
|
||||||
* If you've added this MnemonicWalletSubprovider to your app's provider, you can simply send an `eth_signTypedData`
|
* your app's provider, you can simply send an `eth_signTypedData` JSON RPC request, and
|
||||||
* JSON RPC request, and this method will be called auto-magically.
|
* this method will be called auto-magically. If you are not using this via a ProviderEngine
|
||||||
* If you are not using this via a ProviderEngine instance, you can call it directly.
|
* instance, you can call it directly.
|
||||||
* @param address Address of the account to sign with
|
* @param address Address of the account to sign with
|
||||||
* @param data the typed data object
|
* @param data the typed data object
|
||||||
* @return Signature hex string (order: rsv)
|
* @return Signature hex string (order: rsv)
|
||||||
*/
|
*/
|
||||||
public async signTypedDataAsync(address: string, typedData: any): Promise<string> {
|
public async signTypedDataAsync(address: string, typedData: EIP712TypedData): Promise<string> {
|
||||||
if (_.isUndefined(typedData)) {
|
if (_.isUndefined(typedData)) {
|
||||||
throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage);
|
throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { assert } from '@0xproject/assert';
|
import { assert } from '@0xproject/assert';
|
||||||
|
import { EIP712TypedData } from '@0xproject/types';
|
||||||
import { signTypedDataUtils } from '@0xproject/utils';
|
import { signTypedDataUtils } from '@0xproject/utils';
|
||||||
import EthereumTx = require('ethereumjs-tx');
|
import EthereumTx = require('ethereumjs-tx');
|
||||||
import * as ethUtil from 'ethereumjs-util';
|
import * as ethUtil from 'ethereumjs-util';
|
||||||
@ -95,7 +96,7 @@ export class PrivateKeyWalletSubprovider extends BaseWalletSubprovider {
|
|||||||
* @param data the typed data object
|
* @param data the typed data object
|
||||||
* @return Signature hex string (order: rsv)
|
* @return Signature hex string (order: rsv)
|
||||||
*/
|
*/
|
||||||
public async signTypedDataAsync(address: string, typedData: any): Promise<string> {
|
public async signTypedDataAsync(address: string, typedData: EIP712TypedData): Promise<string> {
|
||||||
if (_.isUndefined(typedData)) {
|
if (_.isUndefined(typedData)) {
|
||||||
throw new Error(WalletSubproviderErrors.DataMissingForSignTypedData);
|
throw new Error(WalletSubproviderErrors.DataMissingForSignTypedData);
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ import { Subprovider } from './subprovider';
|
|||||||
export class SignerSubprovider extends Subprovider {
|
export class SignerSubprovider extends Subprovider {
|
||||||
private readonly _web3Wrapper: Web3Wrapper;
|
private readonly _web3Wrapper: Web3Wrapper;
|
||||||
/**
|
/**
|
||||||
* Instantiates a new SignerSubprovider
|
* Instantiates a new SignerSubprovider.
|
||||||
* @param provider Web3 provider that should handle all user account related requests
|
* @param provider Web3 provider that should handle all user account related requests
|
||||||
*/
|
*/
|
||||||
constructor(provider: Provider) {
|
constructor(provider: Provider) {
|
||||||
|
@ -73,6 +73,13 @@ describe('EthLightwalletSubprovider', () => {
|
|||||||
const txHex = await ethLightwalletSubprovider.signTransactionAsync(fixtureData.TX_DATA);
|
const txHex = await ethLightwalletSubprovider.signTransactionAsync(fixtureData.TX_DATA);
|
||||||
expect(txHex).to.be.equal(fixtureData.TX_DATA_SIGNED_RESULT);
|
expect(txHex).to.be.equal(fixtureData.TX_DATA_SIGNED_RESULT);
|
||||||
});
|
});
|
||||||
|
it('signs an EIP712 sign typed data message', async () => {
|
||||||
|
const signature = await ethLightwalletSubprovider.signTypedDataAsync(
|
||||||
|
fixtureData.TEST_RPC_ACCOUNT_0,
|
||||||
|
fixtureData.EIP712_TEST_TYPED_DATA,
|
||||||
|
);
|
||||||
|
expect(signature).to.be.equal(fixtureData.EIP712_TEST_TYPED_DATA_SIGNED_RESULT);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('calls through a provider', () => {
|
describe('calls through a provider', () => {
|
||||||
@ -129,6 +136,20 @@ describe('EthLightwalletSubprovider', () => {
|
|||||||
});
|
});
|
||||||
provider.sendAsync(payload, callback);
|
provider.sendAsync(payload, callback);
|
||||||
});
|
});
|
||||||
|
it('signs an EIP712 sign typed data message with eth_signTypedData', (done: DoneCallback) => {
|
||||||
|
const payload = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'eth_signTypedData',
|
||||||
|
params: [fixtureData.TEST_RPC_ACCOUNT_0, fixtureData.EIP712_TEST_TYPED_DATA],
|
||||||
|
id: 1,
|
||||||
|
};
|
||||||
|
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||||
|
expect(err).to.be.a('null');
|
||||||
|
expect(response.result).to.be.equal(fixtureData.EIP712_TEST_TYPED_DATA_SIGNED_RESULT);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
provider.sendAsync(payload, callback);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('failure cases', () => {
|
describe('failure cases', () => {
|
||||||
it('should throw if `data` param not hex when calling eth_sign', (done: DoneCallback) => {
|
it('should throw if `data` param not hex when calling eth_sign', (done: DoneCallback) => {
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
{
|
{
|
||||||
"note": "Added `EIP712Parameter` `EIP712Types` `EIP712TypedData` for EIP712 signing",
|
"note": "Added `EIP712Parameter` `EIP712Types` `EIP712TypedData` for EIP712 signing",
|
||||||
"pr": 1102
|
"pr": 1102
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Added `ZeroExTransaction` type for Exchange executeTransaction",
|
||||||
|
"pr": 1102
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -41,6 +41,15 @@ export interface SignedOrder extends Order {
|
|||||||
signature: string;
|
signature: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ZeroExTransaction for use with 0x Exchange executeTransaction
|
||||||
|
*/
|
||||||
|
export interface ZeroExTransaction {
|
||||||
|
salt: BigNumber;
|
||||||
|
signerAddress: string;
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Elliptic Curve signature
|
* Elliptic Curve signature
|
||||||
*/
|
*/
|
||||||
@ -598,9 +607,16 @@ export interface EIP712Parameter {
|
|||||||
export interface EIP712Types {
|
export interface EIP712Types {
|
||||||
[key: string]: EIP712Parameter[];
|
[key: string]: EIP712Parameter[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type EIP712ObjectValue = string | number | EIP712Object;
|
||||||
|
|
||||||
|
export interface EIP712Object {
|
||||||
|
[key: string]: EIP712ObjectValue;
|
||||||
|
}
|
||||||
|
|
||||||
export interface EIP712TypedData {
|
export interface EIP712TypedData {
|
||||||
types: EIP712Types;
|
types: EIP712Types;
|
||||||
domain: any;
|
domain: EIP712Object;
|
||||||
message: any;
|
message: EIP712Object;
|
||||||
primaryType: string;
|
primaryType: string;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import * as ethUtil from 'ethereumjs-util';
|
import * as ethUtil from 'ethereumjs-util';
|
||||||
import * as ethers from 'ethers';
|
import * as ethers from 'ethers';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { EIP712TypedData, EIP712Types } from '@0xproject/types';
|
import { EIP712Object, EIP712ObjectValue, EIP712TypedData, EIP712Types } from '@0xproject/types';
|
||||||
|
|
||||||
export const signTypedDataUtils = {
|
export const signTypedDataUtils = {
|
||||||
/**
|
/**
|
||||||
@ -42,32 +43,40 @@ export const signTypedDataUtils = {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
_encodeData(primaryType: string, data: any, types: EIP712Types): string {
|
_encodeData(primaryType: string, data: EIP712Object, types: EIP712Types): string {
|
||||||
const encodedTypes = ['bytes32'];
|
const encodedTypes = ['bytes32'];
|
||||||
const encodedValues = [signTypedDataUtils._typeHash(primaryType, types)];
|
const encodedValues: Array<Buffer | EIP712ObjectValue> = [signTypedDataUtils._typeHash(primaryType, types)];
|
||||||
for (const field of types[primaryType]) {
|
for (const field of types[primaryType]) {
|
||||||
let value = data[field.name];
|
const value = data[field.name];
|
||||||
if (field.type === 'string' || field.type === 'bytes') {
|
if (field.type === 'string' || field.type === 'bytes') {
|
||||||
value = ethUtil.sha3(value);
|
const hashValue = ethUtil.sha3(value as string);
|
||||||
encodedTypes.push('bytes32');
|
encodedTypes.push('bytes32');
|
||||||
encodedValues.push(value);
|
encodedValues.push(hashValue);
|
||||||
} else if (types[field.type] !== undefined) {
|
} else if (types[field.type] !== undefined) {
|
||||||
encodedTypes.push('bytes32');
|
encodedTypes.push('bytes32');
|
||||||
value = ethUtil.sha3(signTypedDataUtils._encodeData(field.type, value, types));
|
const hashValue = ethUtil.sha3(
|
||||||
encodedValues.push(value);
|
// tslint:disable-next-line:no-unnecessary-type-assertion
|
||||||
|
signTypedDataUtils._encodeData(field.type, value as EIP712Object, types),
|
||||||
|
);
|
||||||
|
encodedValues.push(hashValue);
|
||||||
} else if (field.type.lastIndexOf(']') === field.type.length - 1) {
|
} else if (field.type.lastIndexOf(']') === field.type.length - 1) {
|
||||||
throw new Error('Arrays currently unimplemented in encodeData');
|
throw new Error('Arrays currently unimplemented in encodeData');
|
||||||
} else {
|
} else {
|
||||||
encodedTypes.push(field.type);
|
encodedTypes.push(field.type);
|
||||||
encodedValues.push(value);
|
const normalizedValue = signTypedDataUtils._normalizeValue(field.type, value);
|
||||||
|
encodedValues.push(normalizedValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ethers.utils.defaultAbiCoder.encode(encodedTypes, encodedValues);
|
return ethers.utils.defaultAbiCoder.encode(encodedTypes, encodedValues);
|
||||||
},
|
},
|
||||||
|
_normalizeValue(type: string, value: any): EIP712ObjectValue {
|
||||||
|
const normalizedValue = type === 'uint256' && _.isObject(value) && value.isBigNumber ? value.toString() : value;
|
||||||
|
return normalizedValue;
|
||||||
|
},
|
||||||
_typeHash(primaryType: string, types: EIP712Types): Buffer {
|
_typeHash(primaryType: string, types: EIP712Types): Buffer {
|
||||||
return ethUtil.sha3(signTypedDataUtils._encodeType(primaryType, types));
|
return ethUtil.sha3(signTypedDataUtils._encodeType(primaryType, types));
|
||||||
},
|
},
|
||||||
_structHash(primaryType: string, data: any, types: EIP712Types): Buffer {
|
_structHash(primaryType: string, data: EIP712Object, types: EIP712Types): Buffer {
|
||||||
return ethUtil.sha3(signTypedDataUtils._encodeData(primaryType, data, types));
|
return ethUtil.sha3(signTypedDataUtils._encodeData(primaryType, data, types));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -318,9 +318,9 @@ export class Web3Wrapper {
|
|||||||
* Sign an EIP712 typed data message with a specific address's private key (`eth_signTypedData`)
|
* Sign an EIP712 typed data message with a specific address's private key (`eth_signTypedData`)
|
||||||
* @param address Address of signer
|
* @param address Address of signer
|
||||||
* @param typedData Typed data message to sign
|
* @param typedData Typed data message to sign
|
||||||
* @returns Signature string (might be VRS or RSV depending on the Signer)
|
* @returns Signature string (as RSV)
|
||||||
*/
|
*/
|
||||||
public async signTypedDataAsync(address: string, typedData: object): Promise<string> {
|
public async signTypedDataAsync(address: string, typedData: any): Promise<string> {
|
||||||
assert.isETHAddressHex('address', address);
|
assert.isETHAddressHex('address', address);
|
||||||
assert.doesConformToSchema('typedData', typedData, schemas.eip712TypedData);
|
assert.doesConformToSchema('typedData', typedData, schemas.eip712TypedData);
|
||||||
const signData = await this.sendRawPayloadAsync<string>({
|
const signData = await this.sendRawPayloadAsync<string>({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user