aboutsummaryrefslogtreecommitdiffstats
path: root/packages/order-utils/src/signature_utils.ts
blob: ebd636b20dd6ee4a330a68d8a94878c97892ad49 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import { schemas } from '@0xproject/json-schemas';
import { ECSignature, Provider } from '@0xproject/types';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';

import { assert } from './assert';
import { OrderError } from './types';

/**
 * Verifies that the elliptic curve signature `signature` was generated
 * by signing `data` with the private key corresponding to the `signerAddress` address.
 * @param   data          The hex encoded data signed by the supplied signature.
 * @param   signature     An object containing the elliptic curve signature parameters.
 * @param   signerAddress The hex encoded address that signed the data, producing the supplied signature.
 * @return  Whether the signature is valid for the supplied signerAddress and data.
 */
export function isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean {
    assert.isHexString('data', data);
    assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema);
    assert.isETHAddressHex('signerAddress', signerAddress);
    const normalizedSignerAddress = signerAddress.toLowerCase();

    const dataBuff = ethUtil.toBuffer(data);
    const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff);
    try {
        const pubKey = ethUtil.ecrecover(
            msgHashBuff,
            signature.v,
            ethUtil.toBuffer(signature.r),
            ethUtil.toBuffer(signature.s),
        );
        const retrievedAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey));
        return retrievedAddress === signerAddress;
    } catch (err) {
        return false;
    }
}
/**
 * Signs an orderHash and returns it's elliptic curve signature.
 * This method currently supports TestRPC, Geth and Parity above and below V1.6.6
 * @param   orderHash       Hex encoded orderHash 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.
 * @param   shouldAddPersonalMessagePrefix  Some signers add the personal message prefix `\x19Ethereum Signed Message`
 *          themselves (e.g Parity Signer, Ledger, TestRPC) and others expect it to already be done by the client
 *          (e.g Metamask). Depending on which signer this request is going to, decide on whether to add the prefix
 *          before sending the request.
 * @return  An object containing the Elliptic curve signature parameters generated by signing the orderHash.
 */
export async function signOrderHashAsync(
    provider: Provider,
    orderHash: string,
    signerAddress: string,
    shouldAddPersonalMessagePrefix: boolean,
): Promise<ECSignature> {
    assert.isHexString('orderHash', orderHash);
    const web3Wrapper = new Web3Wrapper(provider);
    await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper);
    const normalizedSignerAddress = signerAddress.toLowerCase();

    let msgHashHex = orderHash;
    if (shouldAddPersonalMessagePrefix) {
        const orderHashBuff = ethUtil.toBuffer(orderHash);
        const msgHashBuff = ethUtil.hashPersonalMessage(orderHashBuff);
        msgHashHex = ethUtil.bufferToHex(msgHashBuff);
    }

    const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHashHex);

    // HACK: There is no consensus on whether the signatureHex string should be formatted as
    // v + r + s OR r + s + v, and different clients (even different versions of the same client)
    // return the signature params in different orders. In order to support all client implementations,
    // we parse the signature in both ways, and evaluate if either one is a valid signature.
    // tslint:disable-next-line:custom-no-magic-numbers
    const validVParamValues = [27, 28];
    const ecSignatureVRS = parseSignatureHexAsVRS(signature);
    if (_.includes(validVParamValues, ecSignatureVRS.v)) {
        const isValidVRSSignature = isValidSignature(orderHash, ecSignatureVRS, normalizedSignerAddress);
        if (isValidVRSSignature) {
            return ecSignatureVRS;
        }
    }

    const ecSignatureRSV = parseSignatureHexAsRSV(signature);
    if (_.includes(validVParamValues, ecSignatureRSV.v)) {
        const isValidRSVSignature = isValidSignature(orderHash, ecSignatureRSV, normalizedSignerAddress);
        if (isValidRSVSignature) {
            return ecSignatureRSV;
        }
    }

    throw new Error(OrderError.InvalidSignature);
}

function parseSignatureHexAsVRS(signatureHex: string): ECSignature {
    const signatureBuffer = ethUtil.toBuffer(signatureHex);
    let v = signatureBuffer[0];
    // HACK: Sometimes v is returned as [0, 1] and sometimes as [27, 28]
    // If it is returned as [0, 1], add 27 to both so it becomes [27, 28]
    const lowestValidV = 27;
    const isProperlyFormattedV = v >= lowestValidV;
    if (!isProperlyFormattedV) {
        v += lowestValidV;
    }
    // signatureBuffer contains vrs
    const vEndIndex = 1;
    const rsIndex = 33;
    const r = signatureBuffer.slice(vEndIndex, rsIndex);
    const sEndIndex = 65;
    const s = signatureBuffer.slice(rsIndex, sEndIndex);
    const ecSignature: ECSignature = {
        v,
        r: ethUtil.bufferToHex(r),
        s: ethUtil.bufferToHex(s),
    };
    return ecSignature;
}

function parseSignatureHexAsRSV(signatureHex: string): ECSignature {
    const { v, r, s } = ethUtil.fromRpcSig(signatureHex);
    const ecSignature: ECSignature = {
        v,
        r: ethUtil.bufferToHex(r),
        s: ethUtil.bufferToHex(s),
    };
    return ecSignature;
}