Skip to content

Commit 24279bf

Browse files
committed
Limited ecsign v return value to Buffer on extended input types, additional input value checks
1 parent 5c80ce8 commit 24279bf

File tree

2 files changed

+84
-65
lines changed

2 files changed

+84
-65
lines changed

src/signature.ts

Lines changed: 38 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +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, PrefixedHexString } from './types'
6+
import { BNLike } from './types'
77
import { isHexString } from '.'
88

99
export interface ECDSASignature {
@@ -12,86 +12,53 @@ export interface ECDSASignature {
1212
s: Buffer
1313
}
1414

15-
export interface ECDSASignatureBN {
16-
v: BN
17-
r: Buffer
18-
s: Buffer
19-
}
20-
2115
export interface ECDSASignatureBuffer {
2216
v: Buffer
2317
r: Buffer
2418
s: Buffer
2519
}
2620

27-
export interface ECDSASignatureHexString {
28-
v: PrefixedHexString
29-
r: Buffer
30-
s: Buffer
31-
}
32-
3321
/**
3422
* Returns the ECDSA signature of a message hash.
3523
*/
3624
export function ecsign(msgHash: Buffer, privateKey: Buffer, chainId?: number): ECDSASignature
37-
export function ecsign(msgHash: Buffer, privateKey: Buffer, chainId: BN): ECDSASignatureBN
38-
export function ecsign(msgHash: Buffer, privateKey: Buffer, chainId: Buffer): ECDSASignatureBuffer
3925
export function ecsign(
4026
msgHash: Buffer,
4127
privateKey: Buffer,
42-
chainId: PrefixedHexString
43-
): ECDSASignatureHexString
28+
chainId: BN | string | Buffer
29+
): ECDSASignatureBuffer
4430
export function ecsign(msgHash: Buffer, privateKey: Buffer, chainId: any): any {
4531
const sig = ecdsaSign(msgHash, privateKey)
4632
const recovery: number = sig.recid
4733

4834
let ret
49-
if (typeof chainId === 'number') {
50-
return {
51-
r: Buffer.from(sig.signature.slice(0, 32)),
52-
s: Buffer.from(sig.signature.slice(32, 64)),
53-
v: recovery + (chainId * 2 + 35)
54-
}
55-
} else if (BN.isBN(chainId)) {
56-
ret = {
57-
r: Buffer.from(sig.signature.slice(0, 32)),
58-
s: Buffer.from(sig.signature.slice(32, 64)),
59-
v: (chainId as BN)
60-
.muln(2)
61-
.addn(35)
62-
.addn(recovery)
63-
}
64-
} else if (Buffer.isBuffer(chainId)) {
65-
ret = {
66-
r: Buffer.from(sig.signature.slice(0, 32)),
67-
s: Buffer.from(sig.signature.slice(32, 64)),
68-
v: toBuffer(
69-
new BN(chainId)
70-
.muln(2)
71-
.addn(35)
72-
.addn(recovery)
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)'
7341
)
7442
}
75-
} else if (typeof chainId === 'string') {
76-
if (!isHexString(chainId)) {
43+
return {
44+
r,
45+
s,
46+
v: chainId ? recovery + (chainId * 2 + 35) : recovery + 27
47+
}
48+
} else {
49+
// BN, string, Buffer
50+
if (typeof chainId === 'string' && !isHexString(chainId)) {
7751
throw new Error(`A chainId string must be provided with a 0x-prefix, given: ${chainId}`)
7852
}
7953
ret = {
80-
r: Buffer.from(sig.signature.slice(0, 32)),
81-
s: Buffer.from(sig.signature.slice(32, 64)),
82-
v:
83-
'0x' +
54+
r,
55+
s,
56+
v: toBuffer(
8457
new BN(toBuffer(chainId))
8558
.muln(2)
8659
.addn(35)
8760
.addn(recovery)
88-
.toString('hex')
89-
}
90-
} else {
91-
ret = {
92-
r: Buffer.from(sig.signature.slice(0, 32)),
93-
s: Buffer.from(sig.signature.slice(32, 64)),
94-
v: recovery + 27
61+
)
9562
}
9663
}
9764

@@ -120,6 +87,23 @@ export const ecrecover = function(
12087
s: Buffer,
12188
chainId?: BNLike
12289
): Buffer {
90+
if (typeof v === 'string' && !isHexString(v)) {
91+
throw new Error(`A v value string must be provided with a 0x-prefix, given: ${v}`)
92+
}
93+
if (typeof chainId === 'string' && !isHexString(chainId)) {
94+
throw new Error(`A chainId string must be provided with a 0x-prefix, given: ${chainId}`)
95+
}
96+
if (typeof v === 'number' && !Number.isSafeInteger(v)) {
97+
throw new Error(
98+
'The provided v is greater than MAX_SAFE_INTEGER (please use an alternative input type)'
99+
)
100+
}
101+
if (typeof chainId === 'number' && !Number.isSafeInteger(chainId)) {
102+
throw new Error(
103+
'The provided chainId is greater than MAX_SAFE_INTEGER (please use an alternative input type)'
104+
)
105+
}
106+
123107
const signature = Buffer.concat([setLengthLeft(r, 32), setLengthLeft(s, 32)], 64)
124108
const recovery = calculateSigRecovery(v, chainId)
125109
if (!isValidSigRecovery(recovery)) {

test/signature.spec.ts

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,33 +56,61 @@ describe('ecsign', function() {
5656
'129ff05af364204442bdb53ab6f18a99ab48acc9326fa689f228040429e3ca66',
5757
'hex'
5858
)
59+
const expectedSigV = Buffer.from('014f', 'hex')
5960

6061
const sig = ecsign(echash, ecprivkey, 150)
6162
assert.deepEqual(sig.r, expectedSigR)
6263
assert.deepEqual(sig.s, expectedSigS)
6364
assert.equal(sig.v, 150 * 2 + 35)
6465

65-
const sigBN = ecsign(echash, ecprivkey, new BN(150))
66-
assert.deepEqual(sigBN.r, expectedSigR)
67-
assert.deepEqual(sigBN.s, expectedSigS)
68-
assert.deepEqual(sigBN.v, new BN(150).muln(2).addn(35))
69-
70-
const sigBuffer = ecsign(echash, ecprivkey, Buffer.from([150]))
66+
let sigBuffer = ecsign(echash, ecprivkey, new BN(150))
7167
assert.deepEqual(sigBuffer.r, expectedSigR)
7268
assert.deepEqual(sigBuffer.s, expectedSigS)
73-
assert.deepEqual(sigBuffer.v, Buffer.from('014f', 'hex'))
69+
assert.deepEqual(sigBuffer.v, expectedSigV)
70+
71+
sigBuffer = ecsign(echash, ecprivkey, Buffer.from([150]))
72+
assert.deepEqual(sigBuffer.v, expectedSigV)
7473

75-
const sigHexString = ecsign(echash, ecprivkey, '0x96')
76-
assert.deepEqual(sigHexString.r, expectedSigR)
77-
assert.deepEqual(sigHexString.s, expectedSigS)
78-
assert.equal(sigHexString.v, '0x14f')
74+
sigBuffer = ecsign(echash, ecprivkey, '0x96')
75+
assert.deepEqual(sigBuffer.v, expectedSigV)
7976

8077
assert.throws(function() {
8178
ecsign(echash, ecprivkey, '96')
8279
})
8380
})
8481
})
8582

83+
it('should produce a signature for a high number chainId greater than MAX_SAFE_INTEGER', function() {
84+
const chainIDBuffer = Buffer.from('796f6c6f763378', 'hex')
85+
const expectedSigR = Buffer.from(
86+
'99e71a99cb2270b8cac5254f9e99b6210c6c10224a1579cf389ef88b20a1abe9',
87+
'hex'
88+
)
89+
const expectedSigS = Buffer.from(
90+
'129ff05af364204442bdb53ab6f18a99ab48acc9326fa689f228040429e3ca66',
91+
'hex'
92+
)
93+
const expectedSigV = Buffer.from('f2ded8deec6713', 'hex')
94+
95+
let sigBuffer = ecsign(echash, ecprivkey, new BN(chainIDBuffer))
96+
assert.deepEqual(sigBuffer.r, expectedSigR)
97+
assert.deepEqual(sigBuffer.s, expectedSigS)
98+
assert.deepEqual(sigBuffer.v, expectedSigV)
99+
100+
sigBuffer = ecsign(echash, ecprivkey, chainIDBuffer)
101+
assert.deepEqual(sigBuffer.v, expectedSigV)
102+
103+
sigBuffer = ecsign(echash, ecprivkey, '0x' + chainIDBuffer.toString('hex'))
104+
assert.deepEqual(sigBuffer.v, expectedSigV)
105+
106+
const chainIDNumber = parseInt(chainIDBuffer.toString('hex'), 16)
107+
assert.throws(() => {
108+
// If we would use a number for the `chainId` parameter then it should throw.
109+
// (The numbers are too high to perform arithmetic on)
110+
ecsign(echash, ecprivkey, chainIDNumber)
111+
})
112+
})
113+
86114
describe('ecrecover', function() {
87115
it('should recover a public key', function() {
88116
const r = Buffer.from('99e71a99cb2270b8cac5254f9e99b6210c6c10224a1579cf389ef88b20a1abe9', 'hex')
@@ -169,6 +197,13 @@ describe('ecrecover', function() {
169197
sender = ecrecover(msgHash, vHexString, r, s, chainIDHexString)
170198
assert.ok(sender.equals(senderPubKey), 'sender pubkey correct (HexString)')
171199

200+
assert.throws(function() {
201+
ecrecover(msgHash, 'f2ded8deec6714', r, s, chainIDHexString)
202+
})
203+
assert.throws(function() {
204+
ecrecover(msgHash, vHexString, r, s, '796f6c6f763378')
205+
})
206+
172207
const chainIDNumber = parseInt(chainIDBuffer.toString('hex'), 16)
173208
const vNumber = parseInt(vBuffer.toString('hex'), 16)
174209
assert.throws(() => {

0 commit comments

Comments
 (0)