Skip to content

Commit b3ba3ee

Browse files
committed
Added BNLike input type for v and chain IDs in toRpcSig() and isValidSignature(), expanded tests
1 parent abe0aff commit b3ba3ee

File tree

2 files changed

+103
-49
lines changed

2 files changed

+103
-49
lines changed

src/signature.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,7 @@ function isValidSigRecovery(recovery: number | BN): boolean {
7676
return rec.eqn(0) || rec.eqn(1)
7777
}
7878

79-
/**
80-
* ECDSA public key recovery from signature.
81-
* @returns Recovered public key
82-
*/
83-
export const ecrecover = function(
84-
msgHash: Buffer,
85-
v: BNLike,
86-
r: Buffer,
87-
s: Buffer,
88-
chainId?: BNLike
89-
): Buffer {
79+
function vAndChainIdTypeChecks(v: BNLike, chainId?: BNLike) {
9080
if (typeof v === 'string' && !isHexString(v)) {
9181
throw new Error(`A v value string must be provided with a 0x-prefix, given: ${v}`)
9282
}
@@ -103,6 +93,20 @@ export const ecrecover = function(
10393
'The provided chainId is greater than MAX_SAFE_INTEGER (please use an alternative input type)'
10494
)
10595
}
96+
}
97+
98+
/**
99+
* ECDSA public key recovery from signature.
100+
* @returns Recovered public key
101+
*/
102+
export const ecrecover = function(
103+
msgHash: Buffer,
104+
v: BNLike,
105+
r: Buffer,
106+
s: Buffer,
107+
chainId?: BNLike
108+
): Buffer {
109+
vAndChainIdTypeChecks(v, chainId)
106110

107111
const signature = Buffer.concat([setLengthLeft(r, 32), setLengthLeft(s, 32)], 64)
108112
const recovery = calculateSigRecovery(v, chainId)
@@ -117,7 +121,8 @@ export const ecrecover = function(
117121
* Convert signature parameters into the format of `eth_sign` RPC method.
118122
* @returns Signature
119123
*/
120-
export const toRpcSig = function(v: number, r: Buffer, s: Buffer, chainId?: number): string {
124+
export const toRpcSig = function(v: BNLike, r: Buffer, s: Buffer, chainId?: BNLike): string {
125+
vAndChainIdTypeChecks(v, chainId)
121126
const recovery = calculateSigRecovery(v, chainId)
122127
if (!isValidSigRecovery(recovery)) {
123128
throw new Error('Invalid signature v value')
@@ -156,12 +161,13 @@ export const fromRpcSig = function(sig: string): ECDSASignature {
156161
* @param homesteadOrLater Indicates whether this is being used on either the homestead hardfork or a later one
157162
*/
158163
export const isValidSignature = function(
159-
v: number,
164+
v: BNLike,
160165
r: Buffer,
161166
s: Buffer,
162167
homesteadOrLater: boolean = true,
163-
chainId?: number
168+
chainId?: BNLike
164169
): boolean {
170+
vAndChainIdTypeChecks(v, chainId)
165171
const SECP256K1_N_DIV_2 = new BN(
166172
'7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0',
167173
16

test/signature.spec.ts

Lines changed: 83 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
hashPersonalMessage,
88
isValidSignature,
99
fromRpcSig,
10-
toRpcSig
10+
toRpcSig,
11+
intToBuffer
1112
} from '../src'
1213

1314
const echash = Buffer.from(
@@ -294,6 +295,45 @@ describe('isValidSignature', function() {
294295
const s = Buffer.from('129ff05af364204442bdb53ab6f18a99ab48acc9326fa689f228040429e3ca66', 'hex')
295296
const v = chainId * 2 + 35
296297
assert.equal(isValidSignature(v, r, s, false, chainId), true)
298+
assert.equal(isValidSignature(intToBuffer(v), r, s, false, intToBuffer(chainId)), true)
299+
assert.equal(isValidSignature(new BN(v), r, s, false, new BN(chainId)), true)
300+
assert.equal(
301+
isValidSignature(
302+
'0x' + intToBuffer(v).toString('hex'),
303+
r,
304+
s,
305+
false,
306+
'0x' + intToBuffer(chainId).toString('hex')
307+
),
308+
true
309+
)
310+
})
311+
it('should work otherwise(chainId larger than MAX_INTEGER)', function() {
312+
const r = Buffer.from('ec212841e0b7aaffc3b3e33a08adf32fa07159e856ef23db85175a4f6d71dc0f', 'hex')
313+
const s = Buffer.from('4b8e02b96b94064a5aa2f8d72bd0040616ba8e482a5dd96422e38c9a4611f8d5', 'hex')
314+
315+
const vBuffer = Buffer.from('f2ded8deec6714', 'hex')
316+
const chainIDBuffer = Buffer.from('796f6c6f763378', 'hex')
317+
assert.equal(isValidSignature(vBuffer, r, s, false, chainIDBuffer), true)
318+
assert.equal(isValidSignature(new BN(vBuffer), r, s, false, new BN(chainIDBuffer)), true)
319+
assert.equal(
320+
isValidSignature(
321+
'0x' + vBuffer.toString('hex'),
322+
r,
323+
s,
324+
false,
325+
'0x' + chainIDBuffer.toString('hex')
326+
),
327+
true
328+
)
329+
330+
const chainIDNumber = parseInt(chainIDBuffer.toString('hex'), 16)
331+
const vNumber = parseInt(vBuffer.toString('hex'), 16)
332+
assert.throws(() => {
333+
// If we would use numbers for the `v` and `chainId` parameters, then it should throw.
334+
// (The numbers are too high to perform arithmetic on)
335+
isValidSignature(vNumber, r, s, false, chainIDNumber)
336+
})
297337
})
298338
// FIXME: add homestead test
299339
})
@@ -303,49 +343,57 @@ describe('message sig', function() {
303343
const s = Buffer.from('129ff05af364204442bdb53ab6f18a99ab48acc9326fa689f228040429e3ca66', 'hex')
304344

305345
it('should return hex strings that the RPC can use', function() {
306-
assert.equal(
307-
toRpcSig(27, r, s),
346+
const sig =
308347
'0x99e71a99cb2270b8cac5254f9e99b6210c6c10224a1579cf389ef88b20a1abe9129ff05af364204442bdb53ab6f18a99ab48acc9326fa689f228040429e3ca661b'
309-
)
310-
assert.deepEqual(
311-
fromRpcSig(
312-
'0x99e71a99cb2270b8cac5254f9e99b6210c6c10224a1579cf389ef88b20a1abe9129ff05af364204442bdb53ab6f18a99ab48acc9326fa689f228040429e3ca661b'
313-
),
314-
{
315-
v: 27,
316-
r: r,
317-
s: s
318-
}
319-
)
320-
assert.deepEqual(
321-
fromRpcSig(
322-
'0x99e71a99cb2270b8cac5254f9e99b6210c6c10224a1579cf389ef88b20a1abe9129ff05af364204442bdb53ab6f18a99ab48acc9326fa689f228040429e3ca6600'
323-
),
324-
{
325-
v: 27,
326-
r: r,
327-
s: s
328-
}
329-
)
348+
assert.equal(toRpcSig(27, r, s), sig)
349+
assert.deepEqual(fromRpcSig(sig), {
350+
v: 27,
351+
r: r,
352+
s: s
353+
})
330354
})
331355

332356
it('should return hex strings that the RPC can use (chainId=150)', function() {
357+
const sig =
358+
'0x99e71a99cb2270b8cac5254f9e99b6210c6c10224a1579cf389ef88b20a1abe9129ff05af364204442bdb53ab6f18a99ab48acc9326fa689f228040429e3ca66014f'
333359
const chainId = 150
334360
const v = chainId * 2 + 35
361+
assert.equal(toRpcSig(v, r, s, chainId), sig)
362+
assert.equal(toRpcSig(intToBuffer(v), r, s, intToBuffer(chainId)), sig)
363+
assert.equal(toRpcSig(new BN(v), r, s, new BN(chainId)), sig)
335364
assert.equal(
336-
toRpcSig(v, r, s, chainId),
337-
'0x99e71a99cb2270b8cac5254f9e99b6210c6c10224a1579cf389ef88b20a1abe9129ff05af364204442bdb53ab6f18a99ab48acc9326fa689f228040429e3ca66014f'
338-
)
339-
assert.deepEqual(
340-
fromRpcSig(
341-
'0x99e71a99cb2270b8cac5254f9e99b6210c6c10224a1579cf389ef88b20a1abe9129ff05af364204442bdb53ab6f18a99ab48acc9326fa689f228040429e3ca66014f'
365+
toRpcSig(
366+
'0x' + intToBuffer(v).toString('hex'),
367+
r,
368+
s,
369+
'0x' + intToBuffer(chainId).toString('hex')
342370
),
343-
{
344-
v,
345-
r: r,
346-
s: s
347-
}
371+
sig
348372
)
373+
assert.deepEqual(fromRpcSig(sig), {
374+
v,
375+
r: r,
376+
s: s
377+
})
378+
})
379+
380+
it('should return hex strings that the RPC can use (chainId larger than MAX_SAFE_INTEGER)', function() {
381+
const sig =
382+
'0x99e71a99cb2270b8cac5254f9e99b6210c6c10224a1579cf389ef88b20a1abe9129ff05af364204442bdb53ab6f18a99ab48acc9326fa689f228040429e3ca66f2ded8deec6714'
383+
const chainIDBuffer = Buffer.from('796f6c6f763378', 'hex')
384+
const vBuffer = Buffer.from('f2ded8deec6714', 'hex')
385+
assert.equal(toRpcSig(vBuffer, r, s, chainIDBuffer), sig)
386+
assert.equal(toRpcSig(new BN(vBuffer), r, s, new BN(chainIDBuffer)), sig)
387+
assert.equal(
388+
toRpcSig('0x' + vBuffer.toString('hex'), r, s, '0x' + chainIDBuffer.toString('hex')),
389+
sig
390+
)
391+
392+
const chainIDNumber = parseInt(chainIDBuffer.toString('hex'), 16)
393+
const vNumber = parseInt(vBuffer.toString('hex'), 16)
394+
assert.throws(function() {
395+
toRpcSig(vNumber, r, s, chainIDNumber)
396+
})
349397
})
350398

351399
it('should throw on shorter length', function() {

0 commit comments

Comments
 (0)