1
1
import { keccak256 } from 'ethereum-cryptography/keccak'
2
2
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'
11
4
import { SECP256K1_ORDER , SECP256K1_ORDER_DIV_2 } from './constants'
12
5
import { assertIsBuffer } from './helpers'
13
- import { BigIntLike , toType , TypeOutput } from './types'
14
6
15
7
export interface ECDSASignature {
16
- v : number
17
- r : Buffer
18
- s : Buffer
19
- }
20
-
21
- export interface ECDSASignatureBuffer {
22
- v : Buffer
8
+ v : bigint
23
9
r : Buffer
24
10
s : Buffer
25
11
}
26
12
27
13
/**
28
14
* 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
29
18
*/
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 {
37
20
const [ signature , recovery ] = signSync ( msgHash , privateKey , { recovered : true , der : false } )
38
21
39
22
const r = Buffer . from ( signature . slice ( 0 , 32 ) )
40
23
const s = Buffer . from ( signature . slice ( 32 , 64 ) )
41
24
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 )
52
29
53
- const chainIdBigInt = toType ( chainId as BigIntLike , TypeOutput . BigInt )
54
- const v = bigIntToBuffer ( chainIdBigInt * BigInt ( 2 ) + BigInt ( 35 ) + BigInt ( recovery ) )
55
30
return { r, s, v }
56
31
}
57
32
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
61
35
62
- if ( ! chainId ) {
63
- return vBigInt - BigInt ( 27 )
36
+ if ( chainId === undefined ) {
37
+ return v - BigInt ( 27 )
64
38
}
65
- const chainIdBigInt = bufferToBigInt ( toBuffer ( chainId ) )
66
- return vBigInt - ( chainIdBigInt * BigInt ( 2 ) + BigInt ( 35 ) )
39
+ return v - ( chainId * BigInt ( 2 ) + BigInt ( 35 ) )
67
40
}
68
41
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 )
72
44
}
73
45
74
46
/**
@@ -78,10 +50,10 @@ function isValidSigRecovery(recovery: number | bigint): boolean {
78
50
*/
79
51
export const ecrecover = function (
80
52
msgHash : Buffer ,
81
- v : BigIntLike ,
53
+ v : bigint ,
82
54
r : Buffer ,
83
55
s : Buffer ,
84
- chainId ?: BigIntLike
56
+ chainId ?: bigint
85
57
) : Buffer {
86
58
const signature = Buffer . concat ( [ setLengthLeft ( r , 32 ) , setLengthLeft ( s , 32 ) ] , 64 )
87
59
const recovery = calculateSigRecovery ( v , chainId )
@@ -98,12 +70,7 @@ export const ecrecover = function (
98
70
* NOTE: Accepts `v == 0 | v == 1` for EIP1559 transactions
99
71
* @returns Signature
100
72
*/
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 {
107
74
const recovery = calculateSigRecovery ( v , chainId )
108
75
if ( ! isValidSigRecovery ( recovery ) ) {
109
76
throw new Error ( 'Invalid signature v value' )
@@ -118,20 +85,14 @@ export const toRpcSig = function (
118
85
* NOTE: Accepts `v == 0 | v == 1` for EIP1559 transactions
119
86
* @returns Signature
120
87
*/
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 {
127
89
const recovery = calculateSigRecovery ( v , chainId )
128
90
if ( ! isValidSigRecovery ( recovery ) ) {
129
91
throw new Error ( 'Invalid signature v value' )
130
92
}
131
93
132
- const vn = toType ( v , TypeOutput . Number )
133
94
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 ) ) {
135
96
ss = Buffer . from ( s )
136
97
ss [ 0 ] |= 0x80
137
98
}
@@ -141,7 +102,9 @@ export const toCompactSig = function (
141
102
142
103
/**
143
104
* 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
145
108
* NOTE: After EIP1559, `v` could be `0` or `1` but this function assumes
146
109
* it's a signed message (EIP-191 or EIP-712) adding `27` at the end. Remove if needed.
147
110
*/
@@ -150,24 +113,24 @@ export const fromRpcSig = function (sig: string): ECDSASignature {
150
113
151
114
let r : Buffer
152
115
let s : Buffer
153
- let v : number
116
+ let v : bigint
154
117
if ( buf . length >= 65 ) {
155
118
r = buf . slice ( 0 , 32 )
156
119
s = buf . slice ( 32 , 64 )
157
- v = bufferToInt ( buf . slice ( 64 ) )
120
+ v = bufferToBigInt ( buf . slice ( 64 ) )
158
121
} else if ( buf . length === 64 ) {
159
122
// Compact Signature Representation (https://eips.ethereum.org/EIPS/eip-2098)
160
123
r = buf . slice ( 0 , 32 )
161
124
s = buf . slice ( 32 , 64 )
162
- v = bufferToInt ( buf . slice ( 32 , 33 ) ) >> 7
125
+ v = BigInt ( bufferToInt ( buf . slice ( 32 , 33 ) ) >> 7 )
163
126
s [ 0 ] &= 0x7f
164
127
} else {
165
128
throw new Error ( 'Invalid signature length' )
166
129
}
167
130
168
131
// support both versions of `eth_sign` responses
169
132
if ( v < 27 ) {
170
- v += 27
133
+ v = v + BigInt ( 27 )
171
134
}
172
135
173
136
return {
@@ -183,11 +146,11 @@ export const fromRpcSig = function (sig: string): ECDSASignature {
183
146
* @param homesteadOrLater Indicates whether this is being used on either the homestead hardfork or a later one
184
147
*/
185
148
export const isValidSignature = function (
186
- v : BigIntLike ,
149
+ v : bigint ,
187
150
r : Buffer ,
188
151
s : Buffer ,
189
152
homesteadOrLater : boolean = true ,
190
- chainId ?: BigIntLike
153
+ chainId ?: bigint
191
154
) : boolean {
192
155
if ( r . length !== 32 || s . length !== 32 ) {
193
156
return false
0 commit comments