Skip to content

Commit 243fd47

Browse files
committed
add toType helper with 0x-prefix and MAX_SAFE_INTEGER checks
1 parent b3ba3ee commit 243fd47

File tree

6 files changed

+104
-99
lines changed

6 files changed

+104
-99
lines changed

src/account.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import assert from 'assert'
22
import BN from 'bn.js'
33
import * as rlp from 'rlp'
4-
import { isHexString, stripHexPrefix } from 'ethjs-util'
4+
import { stripHexPrefix } from 'ethjs-util'
55
import { KECCAK256_RLP, KECCAK256_NULL } from './constants'
66
import { zeros, bufferToHex, toBuffer } from './bytes'
77
import { keccak, keccak256, keccakFromString, rlphash } from './hash'
88
import { assertIsHexString, assertIsBuffer } from './helpers'
9-
import { BNLike, BufferLike, bnToRlp } from './types'
9+
import { BNLike, BufferLike, bnToRlp, toType, TypeOutput } from './types'
1010

1111
const {
1212
privateKeyVerify,
@@ -139,20 +139,12 @@ export const isValidAddress = function(hexAddress: string): boolean {
139139
*/
140140
export const toChecksumAddress = function(hexAddress: string, eip1191ChainId?: BNLike): string {
141141
assertIsHexString(hexAddress)
142-
if (typeof eip1191ChainId === 'string' && !isHexString(eip1191ChainId)) {
143-
throw new Error(`A chainId string must be provided with a 0x-prefix, given: ${eip1191ChainId}`)
144-
}
145142
const address = stripHexPrefix(hexAddress).toLowerCase()
146143

147144
let prefix = ''
148145
if (eip1191ChainId) {
149-
// Performance optimization
150-
if (typeof eip1191ChainId === 'number' && Number.isSafeInteger(eip1191ChainId)) {
151-
prefix = eip1191ChainId.toString()
152-
} else {
153-
prefix = new BN(toBuffer(eip1191ChainId)).toString()
154-
}
155-
prefix += '0x'
146+
const chainId = toType(eip1191ChainId, TypeOutput.BN)
147+
prefix = chainId.toString() + '0x'
156148
}
157149

158150
const hash = keccakFromString(prefix + address).toString('hex')

src/bytes.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -105,24 +105,24 @@ export const unpadHexString = function(a: string): string {
105105
return stripZeros(a) as string
106106
}
107107

108+
export type ToBufferInputTypes =
109+
| PrefixedHexString
110+
| number
111+
| BN
112+
| Buffer
113+
| Uint8Array
114+
| number[]
115+
| TransformableToArray
116+
| TransformableToBuffer
117+
| null
118+
| undefined
119+
108120
/**
109121
* Attempts to turn a value into a `Buffer`.
110122
* Inputs supported: `Buffer`, `String`, `Number`, null/undefined, `BN` and other objects with a `toArray()` or `toBuffer()` method.
111123
* @param v the value
112124
*/
113-
export const toBuffer = function(
114-
v:
115-
| PrefixedHexString
116-
| number
117-
| BN
118-
| Buffer
119-
| Uint8Array
120-
| number[]
121-
| TransformableToArray
122-
| TransformableToBuffer
123-
| null
124-
| undefined
125-
): Buffer {
125+
export const toBuffer = function(v: ToBufferInputTypes): Buffer {
126126
if (v === null || v === undefined) {
127127
return Buffer.allocUnsafe(0)
128128
}

src/signature.ts

Lines changed: 24 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import BN from 'bn.js'
33
import { toBuffer, setLengthLeft, bufferToHex, bufferToInt } from './bytes'
44
import { keccak } from './hash'
55
import { assertIsBuffer } from './helpers'
6-
import { BNLike } from './types'
7-
import { isHexString } from '.'
6+
import { BNLike, toType, TypeOutput } from './types'
87

98
export interface ECDSASignature {
109
v: number
@@ -22,79 +21,40 @@ export interface ECDSASignatureBuffer {
2221
* Returns the ECDSA signature of a message hash.
2322
*/
2423
export function ecsign(msgHash: Buffer, privateKey: Buffer, chainId?: number): ECDSASignature
25-
export function ecsign(
26-
msgHash: Buffer,
27-
privateKey: Buffer,
28-
chainId: BN | string | Buffer
29-
): ECDSASignatureBuffer
24+
export function ecsign(msgHash: Buffer, privateKey: Buffer, chainId: BNLike): ECDSASignatureBuffer
3025
export function ecsign(msgHash: Buffer, privateKey: Buffer, chainId: any): any {
31-
const sig = ecdsaSign(msgHash, privateKey)
32-
const recovery: number = sig.recid
33-
34-
let ret
35-
const r = Buffer.from(sig.signature.slice(0, 32))
36-
const s = Buffer.from(sig.signature.slice(32, 64))
37-
if (!chainId || typeof chainId === 'number') {
38-
if (chainId && !Number.isSafeInteger(chainId)) {
39-
throw new Error(
40-
'The provided chainId is greater than MAX_SAFE_INTEGER (please use an alternative input type)'
41-
)
42-
}
43-
return {
44-
r,
45-
s,
46-
v: chainId ? recovery + (chainId * 2 + 35) : recovery + 27
47-
}
26+
const { signature, recid: recovery } = ecdsaSign(msgHash, privateKey)
27+
28+
const r = Buffer.from(signature.slice(0, 32))
29+
const s = Buffer.from(signature.slice(32, 64))
30+
31+
if (!chainId) {
32+
return { r, s, v: recovery + 27 }
4833
} else {
49-
// BN, string, Buffer
50-
if (typeof chainId === 'string' && !isHexString(chainId)) {
51-
throw new Error(`A chainId string must be provided with a 0x-prefix, given: ${chainId}`)
52-
}
53-
ret = {
54-
r,
55-
s,
56-
v: toBuffer(
57-
new BN(toBuffer(chainId))
58-
.muln(2)
59-
.addn(35)
60-
.addn(recovery)
61-
)
62-
}
34+
const chainIdBN = toType(chainId, TypeOutput.BN)
35+
const vValue = chainIdBN
36+
.muln(2)
37+
.addn(35)
38+
.addn(recovery)
39+
const v = typeof chainId === 'number' ? vValue.toNumber() : vValue.toArrayLike(Buffer)
40+
return { r, s, v }
6341
}
64-
65-
return ret
6642
}
6743

6844
function calculateSigRecovery(v: BNLike, chainId?: BNLike): BN {
69-
const vBN = new BN(toBuffer(v))
70-
const chainIdBN = chainId ? new BN(toBuffer(chainId)) : undefined
71-
return chainIdBN ? vBN.sub(chainIdBN.muln(2).addn(35)) : vBN.subn(27)
45+
const vBN = toType(v, TypeOutput.BN)
46+
if (!chainId) {
47+
return vBN.subn(27)
48+
}
49+
const chainIdBN = toType(chainId, TypeOutput.BN)
50+
return vBN.sub(chainIdBN.muln(2).addn(35))
7251
}
7352

7453
function isValidSigRecovery(recovery: number | BN): boolean {
7554
const rec = new BN(recovery)
7655
return rec.eqn(0) || rec.eqn(1)
7756
}
7857

79-
function vAndChainIdTypeChecks(v: BNLike, chainId?: BNLike) {
80-
if (typeof v === 'string' && !isHexString(v)) {
81-
throw new Error(`A v value string must be provided with a 0x-prefix, given: ${v}`)
82-
}
83-
if (typeof chainId === 'string' && !isHexString(chainId)) {
84-
throw new Error(`A chainId string must be provided with a 0x-prefix, given: ${chainId}`)
85-
}
86-
if (typeof v === 'number' && !Number.isSafeInteger(v)) {
87-
throw new Error(
88-
'The provided v is greater than MAX_SAFE_INTEGER (please use an alternative input type)'
89-
)
90-
}
91-
if (typeof chainId === 'number' && !Number.isSafeInteger(chainId)) {
92-
throw new Error(
93-
'The provided chainId is greater than MAX_SAFE_INTEGER (please use an alternative input type)'
94-
)
95-
}
96-
}
97-
9858
/**
9959
* ECDSA public key recovery from signature.
10060
* @returns Recovered public key
@@ -106,8 +66,6 @@ export const ecrecover = function(
10666
s: Buffer,
10767
chainId?: BNLike
10868
): Buffer {
109-
vAndChainIdTypeChecks(v, chainId)
110-
11169
const signature = Buffer.concat([setLengthLeft(r, 32), setLengthLeft(s, 32)], 64)
11270
const recovery = calculateSigRecovery(v, chainId)
11371
if (!isValidSigRecovery(recovery)) {
@@ -122,7 +80,6 @@ export const ecrecover = function(
12280
* @returns Signature
12381
*/
12482
export const toRpcSig = function(v: BNLike, r: Buffer, s: Buffer, chainId?: BNLike): string {
125-
vAndChainIdTypeChecks(v, chainId)
12683
const recovery = calculateSigRecovery(v, chainId)
12784
if (!isValidSigRecovery(recovery)) {
12885
throw new Error('Invalid signature v value')
@@ -167,7 +124,6 @@ export const isValidSignature = function(
167124
homesteadOrLater: boolean = true,
168125
chainId?: BNLike
169126
): boolean {
170-
vAndChainIdTypeChecks(v, chainId)
171127
const SECP256K1_N_DIV_2 = new BN(
172128
'7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0',
173129
16
@@ -182,8 +138,8 @@ export const isValidSignature = function(
182138
return false
183139
}
184140

185-
const rBN: BN = new BN(r)
186-
const sBN: BN = new BN(s)
141+
const rBN = new BN(r)
142+
const sBN = new BN(s)
187143

188144
if (rBN.isZero() || rBN.gt(SECP256K1_N) || sBN.isZero() || sBN.gt(SECP256K1_N)) {
189145
return false

src/types.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import BN from 'bn.js'
2+
import { isHexString } from 'ethjs-util'
23
import { Address } from './address'
3-
import { unpadBuffer } from './bytes'
4+
import { unpadBuffer, toBuffer, ToBufferInputTypes } from './bytes'
45

56
/*
67
* A type that represents a BNLike input that can be converted to a BN.
@@ -62,3 +63,51 @@ export function bnToRlp(value: BN): Buffer {
6263
// for compatibility with browserify and similar tools
6364
return unpadBuffer(value.toArrayLike(Buffer))
6465
}
66+
67+
/**
68+
* Type output options
69+
*/
70+
export enum TypeOutput {
71+
Number,
72+
BN,
73+
Buffer,
74+
PrefixedHexString
75+
}
76+
77+
export type TypeOutputReturnType = {
78+
[TypeOutput.Number]: Number
79+
[TypeOutput.BN]: BN
80+
[TypeOutput.Buffer]: Buffer
81+
[TypeOutput.PrefixedHexString]: PrefixedHexString
82+
}
83+
84+
/**
85+
* Convert an input to a specified type
86+
* @param input value to convert
87+
* @param outputType type to output
88+
*/
89+
export function toType<T extends TypeOutput>(
90+
input: ToBufferInputTypes,
91+
outputType: T
92+
): TypeOutputReturnType[T] {
93+
if (typeof input === 'string' && !isHexString(input)) {
94+
throw new Error(`A string must be provided with a 0x-prefix, given: ${input}`)
95+
} else if (typeof input === 'number' && !Number.isSafeInteger(input)) {
96+
throw new Error(
97+
'The provided number is greater than MAX_SAFE_INTEGER (please use an alternative input type)'
98+
)
99+
}
100+
101+
input = toBuffer(input)
102+
103+
if (outputType === TypeOutput.Buffer) {
104+
return input as any
105+
} else if (outputType === TypeOutput.BN) {
106+
return new BN(input) as any
107+
} else if (outputType === TypeOutput.Number) {
108+
return new BN(input).toNumber() as any
109+
} else {
110+
// outputType === TypeOutput.PrefixedHexString
111+
return `0x${input.toString('hex')}` as any
112+
}
113+
}

test/account.spec.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,15 @@ describe('.toChecksumAddress()', function() {
621621
addr
622622
)
623623
const chainIDNumber = parseInt(chainIDBuffer.toString('hex'), 16)
624-
assert.equal(toChecksumAddress(addr.toLowerCase(), chainIDNumber), addr)
624+
assert.throws(
625+
() => {
626+
toChecksumAddress(addr.toLowerCase(), chainIDNumber)
627+
},
628+
{
629+
message:
630+
'The provided number is greater than MAX_SAFE_INTEGER (please use an alternative input type)'
631+
}
632+
)
625633
})
626634
})
627635

test/signature.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,8 +348,8 @@ describe('message sig', function() {
348348
assert.equal(toRpcSig(27, r, s), sig)
349349
assert.deepEqual(fromRpcSig(sig), {
350350
v: 27,
351-
r: r,
352-
s: s
351+
r,
352+
s
353353
})
354354
})
355355

@@ -372,8 +372,8 @@ describe('message sig', function() {
372372
)
373373
assert.deepEqual(fromRpcSig(sig), {
374374
v,
375-
r: r,
376-
s: s
375+
r,
376+
s
377377
})
378378
})
379379

0 commit comments

Comments
 (0)