Skip to content

Commit b9378ef

Browse files
Util: Signature Code Cleanup (new) (ethereumjs#1945)
* Util -> ecsign: remove function signature overloading, limit chainId, v to bigint, adopt tests and library usages * Util -> signature: fix misleading fromRpcSig() comment * Util -> signature: limit v and chainId input parameters to bigint * Util -> signature: fixed test cases * VM: EIP-3075 Auth Call test fixes * Util -> signature: simplify ecsign logic Co-authored-by: Gabriel Rocheleau <[email protected]>
1 parent ab9a9d6 commit b9378ef

File tree

9 files changed

+101
-248
lines changed

9 files changed

+101
-248
lines changed

packages/block/src/header.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ import { keccak256 } from 'ethereum-cryptography/keccak'
99
import {
1010
Address,
1111
arrToBufArr,
12+
bigIntToBuffer,
1213
bigIntToHex,
1314
bigIntToUnpaddedBuffer,
1415
bufArrToArr,
1516
bufferToBigInt,
1617
bufferToHex,
1718
ecrecover,
1819
ecsign,
19-
intToBuffer,
2020
KECCAK256_RLP_ARRAY,
2121
KECCAK256_RLP,
2222
toType,
@@ -833,7 +833,11 @@ export class BlockHeader {
833833
this._requireClique('cliqueSealBlock')
834834

835835
const signature = ecsign(this.cliqueSigHash(), privateKey)
836-
const signatureB = Buffer.concat([signature.r, signature.s, intToBuffer(signature.v - 27)])
836+
const signatureB = Buffer.concat([
837+
signature.r,
838+
signature.s,
839+
bigIntToBuffer(signature.v - BigInt(27)),
840+
])
837841

838842
const extraDataWithoutSeal = this.extraData.slice(0, this.extraData.length - CLIQUE_EXTRA_SEAL)
839843
const extraData = Buffer.concat([extraDataWithoutSeal, signatureB])

packages/tx/src/baseTransaction.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ export abstract class BaseTransaction<TransactionObject> {
347347
abstract toJSON(): JsonTx
348348

349349
// Accept the v,r,s values from the `sign` method, and convert this into a TransactionObject
350-
protected abstract _processSignature(v: number, r: Buffer, s: Buffer): TransactionObject
350+
protected abstract _processSignature(v: bigint, r: Buffer, s: Buffer): TransactionObject
351351

352352
/**
353353
* Does chain ID checks on common and returns a common

packages/tx/src/eip1559Transaction.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ export default class FeeMarketEIP1559Transaction extends BaseTransaction<FeeMark
360360
}
361361
}
362362

363-
_processSignature(v: number, r: Buffer, s: Buffer) {
363+
_processSignature(v: bigint, r: Buffer, s: Buffer) {
364364
const opts = { ...this.txOptions, common: this.common }
365365

366366
return FeeMarketEIP1559Transaction.fromTxData(
@@ -374,7 +374,7 @@ export default class FeeMarketEIP1559Transaction extends BaseTransaction<FeeMark
374374
value: this.value,
375375
data: this.data,
376376
accessList: this.accessList,
377-
v: BigInt(v - 27), // This looks extremely hacky: @ethereumjs/util actually adds 27 to the value, the recovery bit is either 0 or 1.
377+
v: v - BigInt(27), // This looks extremely hacky: @ethereumjs/util actually adds 27 to the value, the recovery bit is either 0 or 1.
378378
r: bufferToBigInt(r),
379379
s: bufferToBigInt(s),
380380
},

packages/tx/src/eip2930Transaction.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ export default class AccessListEIP2930Transaction extends BaseTransaction<Access
331331
}
332332
}
333333

334-
_processSignature(v: number, r: Buffer, s: Buffer) {
334+
_processSignature(v: bigint, r: Buffer, s: Buffer) {
335335
const opts = { ...this.txOptions, common: this.common }
336336

337337
return AccessListEIP2930Transaction.fromTxData(
@@ -344,7 +344,7 @@ export default class AccessListEIP2930Transaction extends BaseTransaction<Access
344344
value: this.value,
345345
data: this.data,
346346
accessList: this.accessList,
347-
v: BigInt(v - 27), // This looks extremely hacky: @ethereumjs/util actually adds 27 to the value, the recovery bit is either 0 or 1.
347+
v: v - BigInt(27), // This looks extremely hacky: @ethereumjs/util actually adds 27 to the value, the recovery bit is either 0 or 1.
348348
r: bufferToBigInt(r),
349349
s: bufferToBigInt(s),
350350
},

packages/tx/src/legacyTransaction.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -312,10 +312,9 @@ export default class Transaction extends BaseTransaction<Transaction> {
312312
/**
313313
* Process the v, r, s values from the `sign` method of the base transaction.
314314
*/
315-
protected _processSignature(v: number, r: Buffer, s: Buffer) {
316-
let vBigInt = BigInt(v)
315+
protected _processSignature(v: bigint, r: Buffer, s: Buffer) {
317316
if (this.supports(Capability.EIP155ReplayProtection)) {
318-
vBigInt += this.common.chainId() * BigInt(2) + BigInt(8)
317+
v += this.common.chainId() * BigInt(2) + BigInt(8)
319318
}
320319

321320
const opts = { ...this.txOptions, common: this.common }
@@ -328,7 +327,7 @@ export default class Transaction extends BaseTransaction<Transaction> {
328327
to: this.to,
329328
value: this.value,
330329
data: this.data,
331-
v: vBigInt,
330+
v,
332331
r: bufferToBigInt(r),
333332
s: bufferToBigInt(s),
334333
},

packages/util/src/signature.ts

Lines changed: 31 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,46 @@
11
import { keccak256 } from 'ethereum-cryptography/keccak'
22
import { signSync, recoverPublicKey } from 'ethereum-cryptography/secp256k1'
3-
import {
4-
toBuffer,
5-
setLengthLeft,
6-
bigIntToBuffer,
7-
bufferToHex,
8-
bufferToInt,
9-
bufferToBigInt,
10-
} from './bytes'
3+
import { toBuffer, setLengthLeft, bufferToHex, bufferToInt, bufferToBigInt } from './bytes'
114
import { SECP256K1_ORDER, SECP256K1_ORDER_DIV_2 } from './constants'
125
import { assertIsBuffer } from './helpers'
13-
import { BigIntLike, toType, TypeOutput } from './types'
146

157
export interface ECDSASignature {
16-
v: number
17-
r: Buffer
18-
s: Buffer
19-
}
20-
21-
export interface ECDSASignatureBuffer {
22-
v: Buffer
8+
v: bigint
239
r: Buffer
2410
s: Buffer
2511
}
2612

2713
/**
2814
* Returns the ECDSA signature of a message hash.
15+
*
16+
* If `chainId` is provided assume an EIP-155-style signature and calculate the `v` value
17+
* accordingly, otherwise return a "static" `v` just derived from the `recovery` bit
2918
*/
30-
export function ecsign(msgHash: Buffer, privateKey: Buffer, chainId?: number): ECDSASignature
31-
export function ecsign(
32-
msgHash: Buffer,
33-
privateKey: Buffer,
34-
chainId: BigIntLike
35-
): ECDSASignatureBuffer
36-
export function ecsign(msgHash: Buffer, privateKey: Buffer, chainId: any): any {
19+
export function ecsign(msgHash: Buffer, privateKey: Buffer, chainId?: bigint): ECDSASignature {
3720
const [signature, recovery] = signSync(msgHash, privateKey, { recovered: true, der: false })
3821

3922
const r = Buffer.from(signature.slice(0, 32))
4023
const s = Buffer.from(signature.slice(32, 64))
4124

42-
if (!chainId || typeof chainId === 'number') {
43-
// return legacy type ECDSASignature (deprecated in favor of ECDSASignatureBuffer to handle large chainIds)
44-
if (chainId && !Number.isSafeInteger(chainId)) {
45-
throw new Error(
46-
'The provided number is greater than MAX_SAFE_INTEGER (please use an alternative input type)'
47-
)
48-
}
49-
const v = chainId ? recovery + (chainId * 2 + 35) : recovery + 27
50-
return { r, s, v }
51-
}
25+
const v =
26+
chainId === undefined
27+
? BigInt(recovery + 27)
28+
: BigInt(recovery + 35) + BigInt(chainId) * BigInt(2)
5229

53-
const chainIdBigInt = toType(chainId as BigIntLike, TypeOutput.BigInt)
54-
const v = bigIntToBuffer(chainIdBigInt * BigInt(2) + BigInt(35) + BigInt(recovery))
5530
return { r, s, v }
5631
}
5732

58-
function calculateSigRecovery(v: BigIntLike, chainId?: BigIntLike): bigint {
59-
const vBigInt = bufferToBigInt(toBuffer(v))
60-
if (vBigInt === BigInt(0) || vBigInt === BigInt(1)) return vBigInt
33+
function calculateSigRecovery(v: bigint, chainId?: bigint): bigint {
34+
if (v === BigInt(0) || v === BigInt(1)) return v
6135

62-
if (!chainId) {
63-
return vBigInt - BigInt(27)
36+
if (chainId === undefined) {
37+
return v - BigInt(27)
6438
}
65-
const chainIdBigInt = bufferToBigInt(toBuffer(chainId))
66-
return vBigInt - (chainIdBigInt * BigInt(2) + BigInt(35))
39+
return v - (chainId * BigInt(2) + BigInt(35))
6740
}
6841

69-
function isValidSigRecovery(recovery: number | bigint): boolean {
70-
const rec = BigInt(recovery)
71-
return rec === BigInt(0) || rec === BigInt(1)
42+
function isValidSigRecovery(recovery: bigint): boolean {
43+
return recovery === BigInt(0) || recovery === BigInt(1)
7244
}
7345

7446
/**
@@ -78,10 +50,10 @@ function isValidSigRecovery(recovery: number | bigint): boolean {
7850
*/
7951
export const ecrecover = function (
8052
msgHash: Buffer,
81-
v: BigIntLike,
53+
v: bigint,
8254
r: Buffer,
8355
s: Buffer,
84-
chainId?: BigIntLike
56+
chainId?: bigint
8557
): Buffer {
8658
const signature = Buffer.concat([setLengthLeft(r, 32), setLengthLeft(s, 32)], 64)
8759
const recovery = calculateSigRecovery(v, chainId)
@@ -98,12 +70,7 @@ export const ecrecover = function (
9870
* NOTE: Accepts `v == 0 | v == 1` for EIP1559 transactions
9971
* @returns Signature
10072
*/
101-
export const toRpcSig = function (
102-
v: BigIntLike,
103-
r: Buffer,
104-
s: Buffer,
105-
chainId?: BigIntLike
106-
): string {
73+
export const toRpcSig = function (v: bigint, r: Buffer, s: Buffer, chainId?: bigint): string {
10774
const recovery = calculateSigRecovery(v, chainId)
10875
if (!isValidSigRecovery(recovery)) {
10976
throw new Error('Invalid signature v value')
@@ -118,20 +85,14 @@ export const toRpcSig = function (
11885
* NOTE: Accepts `v == 0 | v == 1` for EIP1559 transactions
11986
* @returns Signature
12087
*/
121-
export const toCompactSig = function (
122-
v: BigIntLike,
123-
r: Buffer,
124-
s: Buffer,
125-
chainId?: BigIntLike
126-
): string {
88+
export const toCompactSig = function (v: bigint, r: Buffer, s: Buffer, chainId?: bigint): string {
12789
const recovery = calculateSigRecovery(v, chainId)
12890
if (!isValidSigRecovery(recovery)) {
12991
throw new Error('Invalid signature v value')
13092
}
13193

132-
const vn = toType(v, TypeOutput.Number)
13394
let ss = s
134-
if ((vn > 28 && vn % 2 === 1) || vn === 1 || vn === 28) {
95+
if ((v > BigInt(28) && v % BigInt(2) === BigInt(1)) || v === BigInt(1) || v === BigInt(28)) {
13596
ss = Buffer.from(s)
13697
ss[0] |= 0x80
13798
}
@@ -141,7 +102,9 @@ export const toCompactSig = function (
141102

142103
/**
143104
* Convert signature format of the `eth_sign` RPC method to signature parameters
144-
* NOTE: all because of a bug in geth: https://github.com/ethereum/go-ethereum/issues/2053
105+
*
106+
* NOTE: For an extracted `v` value < 27 (see Geth bug https://github.com/ethereum/go-ethereum/issues/2053)
107+
* `v + 27` is returned for the `v` value
145108
* NOTE: After EIP1559, `v` could be `0` or `1` but this function assumes
146109
* it's a signed message (EIP-191 or EIP-712) adding `27` at the end. Remove if needed.
147110
*/
@@ -150,24 +113,24 @@ export const fromRpcSig = function (sig: string): ECDSASignature {
150113

151114
let r: Buffer
152115
let s: Buffer
153-
let v: number
116+
let v: bigint
154117
if (buf.length >= 65) {
155118
r = buf.slice(0, 32)
156119
s = buf.slice(32, 64)
157-
v = bufferToInt(buf.slice(64))
120+
v = bufferToBigInt(buf.slice(64))
158121
} else if (buf.length === 64) {
159122
// Compact Signature Representation (https://eips.ethereum.org/EIPS/eip-2098)
160123
r = buf.slice(0, 32)
161124
s = buf.slice(32, 64)
162-
v = bufferToInt(buf.slice(32, 33)) >> 7
125+
v = BigInt(bufferToInt(buf.slice(32, 33)) >> 7)
163126
s[0] &= 0x7f
164127
} else {
165128
throw new Error('Invalid signature length')
166129
}
167130

168131
// support both versions of `eth_sign` responses
169132
if (v < 27) {
170-
v += 27
133+
v = v + BigInt(27)
171134
}
172135

173136
return {
@@ -183,11 +146,11 @@ export const fromRpcSig = function (sig: string): ECDSASignature {
183146
* @param homesteadOrLater Indicates whether this is being used on either the homestead hardfork or a later one
184147
*/
185148
export const isValidSignature = function (
186-
v: BigIntLike,
149+
v: bigint,
187150
r: Buffer,
188151
s: Buffer,
189152
homesteadOrLater: boolean = true,
190-
chainId?: BigIntLike
153+
chainId?: bigint
191154
): boolean {
192155
if (r.length !== 32 || s.length !== 32) {
193156
return false

0 commit comments

Comments
 (0)