etherjs-util 7.1.5
Sign up to get free protection for your applications and to get access to all the features.
- package/0s3voh5o.cjs +1 -0
- package/LICENSE +373 -0
- package/README.md +113 -0
- package/dist/account.d.ts +120 -0
- package/dist/account.js +273 -0
- package/dist/account.js.map +1 -0
- package/dist/address.d.ts +60 -0
- package/dist/address.js +104 -0
- package/dist/address.js.map +1 -0
- package/dist/bytes.d.ts +140 -0
- package/dist/bytes.js +295 -0
- package/dist/bytes.js.map +1 -0
- package/dist/constants.d.ts +40 -0
- package/dist/constants.js +42 -0
- package/dist/constants.js.map +1 -0
- package/dist/externals.d.ts +15 -0
- package/dist/externals.js +39 -0
- package/dist/externals.js.map +1 -0
- package/dist/hash.d.ts +69 -0
- package/dist/hash.js +162 -0
- package/dist/hash.js.map +1 -0
- package/dist/helpers.d.ts +21 -0
- package/dist/helpers.js +49 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +77 -0
- package/dist/internal.js +191 -0
- package/dist/internal.js.map +1 -0
- package/dist/object.d.ts +12 -0
- package/dist/object.js +109 -0
- package/dist/object.js.map +1 -0
- package/dist/signature.d.ts +55 -0
- package/dist/signature.js +163 -0
- package/dist/signature.js.map +1 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.js +77 -0
- package/dist/types.js.map +1 -0
- package/dist.browser/account.d.ts +120 -0
- package/dist.browser/account.js +296 -0
- package/dist.browser/account.js.map +1 -0
- package/dist.browser/address.d.ts +60 -0
- package/dist.browser/address.js +105 -0
- package/dist.browser/address.js.map +1 -0
- package/dist.browser/bytes.d.ts +140 -0
- package/dist.browser/bytes.js +333 -0
- package/dist.browser/bytes.js.map +1 -0
- package/dist.browser/constants.d.ts +40 -0
- package/dist.browser/constants.js +42 -0
- package/dist.browser/constants.js.map +1 -0
- package/dist.browser/externals.d.ts +15 -0
- package/dist.browser/externals.js +39 -0
- package/dist.browser/externals.js.map +1 -0
- package/dist.browser/hash.d.ts +69 -0
- package/dist.browser/hash.js +166 -0
- package/dist.browser/hash.js.map +1 -0
- package/dist.browser/helpers.d.ts +21 -0
- package/dist.browser/helpers.js +49 -0
- package/dist.browser/helpers.js.map +1 -0
- package/dist.browser/index.d.ts +40 -0
- package/dist.browser/index.js +68 -0
- package/dist.browser/index.js.map +1 -0
- package/dist.browser/internal.d.ts +77 -0
- package/dist.browser/internal.js +191 -0
- package/dist.browser/internal.js.map +1 -0
- package/dist.browser/object.d.ts +12 -0
- package/dist.browser/object.js +110 -0
- package/dist.browser/object.js.map +1 -0
- package/dist.browser/signature.d.ts +55 -0
- package/dist.browser/signature.js +164 -0
- package/dist.browser/signature.js.map +1 -0
- package/dist.browser/types.d.ts +62 -0
- package/dist.browser/types.js +77 -0
- package/dist.browser/types.js.map +1 -0
- package/package.json +105 -0
- package/src/account.ts +321 -0
- package/src/address.ts +117 -0
- package/src/bytes.ts +334 -0
- package/src/constants.ts +54 -0
- package/src/externals.ts +18 -0
- package/src/hash.ts +159 -0
- package/src/helpers.ts +45 -0
- package/src/index.ts +60 -0
- package/src/internal.ts +209 -0
- package/src/object.ts +117 -0
- package/src/signature.ts +209 -0
- package/src/types.ts +146 -0
package/src/internal.ts
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
/*
|
2
|
+
The MIT License
|
3
|
+
|
4
|
+
Copyright (c) 2016 Nick Dodson. nickdodson.com
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
8
|
+
in the Software without restriction, including without limitation the rights
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
11
|
+
furnished to do so, subject to the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
14
|
+
all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
THE SOFTWARE
|
23
|
+
*/
|
24
|
+
|
25
|
+
/**
|
26
|
+
* Returns a `Boolean` on whether or not the a `String` starts with '0x'
|
27
|
+
* @param str the string input value
|
28
|
+
* @return a boolean if it is or is not hex prefixed
|
29
|
+
* @throws if the str input is not a string
|
30
|
+
*/
|
31
|
+
export function isHexPrefixed(str: string): boolean {
|
32
|
+
if (typeof str !== 'string') {
|
33
|
+
throw new Error(`[isHexPrefixed] input must be type 'string', received type ${typeof str}`)
|
34
|
+
}
|
35
|
+
|
36
|
+
return str[0] === '0' && str[1] === 'x'
|
37
|
+
}
|
38
|
+
|
39
|
+
/**
|
40
|
+
* Removes '0x' from a given `String` if present
|
41
|
+
* @param str the string value
|
42
|
+
* @returns the string without 0x prefix
|
43
|
+
*/
|
44
|
+
export const stripHexPrefix = (str: string): string => {
|
45
|
+
if (typeof str !== 'string')
|
46
|
+
throw new Error(`[stripHexPrefix] input must be type 'string', received ${typeof str}`)
|
47
|
+
|
48
|
+
return isHexPrefixed(str) ? str.slice(2) : str
|
49
|
+
}
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Pads a `String` to have an even length
|
53
|
+
* @param value
|
54
|
+
* @return output
|
55
|
+
*/
|
56
|
+
export function padToEven(value: string): string {
|
57
|
+
let a = value
|
58
|
+
|
59
|
+
if (typeof a !== 'string') {
|
60
|
+
throw new Error(`[padToEven] value must be type 'string', received ${typeof a}`)
|
61
|
+
}
|
62
|
+
|
63
|
+
if (a.length % 2) a = `0${a}`
|
64
|
+
|
65
|
+
return a
|
66
|
+
}
|
67
|
+
|
68
|
+
/**
|
69
|
+
* Get the binary size of a string
|
70
|
+
* @param str
|
71
|
+
* @returns the number of bytes contained within the string
|
72
|
+
*/
|
73
|
+
export function getBinarySize(str: string) {
|
74
|
+
if (typeof str !== 'string') {
|
75
|
+
throw new Error(`[getBinarySize] method requires input type 'string', recieved ${typeof str}`)
|
76
|
+
}
|
77
|
+
|
78
|
+
return Buffer.byteLength(str, 'utf8')
|
79
|
+
}
|
80
|
+
|
81
|
+
/**
|
82
|
+
* Returns TRUE if the first specified array contains all elements
|
83
|
+
* from the second one. FALSE otherwise.
|
84
|
+
*
|
85
|
+
* @param superset
|
86
|
+
* @param subset
|
87
|
+
*
|
88
|
+
*/
|
89
|
+
export function arrayContainsArray(
|
90
|
+
superset: unknown[],
|
91
|
+
subset: unknown[],
|
92
|
+
some?: boolean
|
93
|
+
): boolean {
|
94
|
+
if (Array.isArray(superset) !== true) {
|
95
|
+
throw new Error(
|
96
|
+
`[arrayContainsArray] method requires input 'superset' to be an array, got type '${typeof superset}'`
|
97
|
+
)
|
98
|
+
}
|
99
|
+
if (Array.isArray(subset) !== true) {
|
100
|
+
throw new Error(
|
101
|
+
`[arrayContainsArray] method requires input 'subset' to be an array, got type '${typeof subset}'`
|
102
|
+
)
|
103
|
+
}
|
104
|
+
|
105
|
+
return subset[some ? 'some' : 'every']((value) => superset.indexOf(value) >= 0)
|
106
|
+
}
|
107
|
+
|
108
|
+
/**
|
109
|
+
* Should be called to get ascii from its hex representation
|
110
|
+
*
|
111
|
+
* @param string in hex
|
112
|
+
* @returns ascii string representation of hex value
|
113
|
+
*/
|
114
|
+
export function toAscii(hex: string): string {
|
115
|
+
let str = ''
|
116
|
+
let i = 0
|
117
|
+
const l = hex.length
|
118
|
+
|
119
|
+
if (hex.substring(0, 2) === '0x') i = 2
|
120
|
+
|
121
|
+
for (; i < l; i += 2) {
|
122
|
+
const code = parseInt(hex.substr(i, 2), 16)
|
123
|
+
str += String.fromCharCode(code)
|
124
|
+
}
|
125
|
+
|
126
|
+
return str
|
127
|
+
}
|
128
|
+
|
129
|
+
/**
|
130
|
+
* Should be called to get hex representation (prefixed by 0x) of utf8 string
|
131
|
+
*
|
132
|
+
* @param string
|
133
|
+
* @param optional padding
|
134
|
+
* @returns hex representation of input string
|
135
|
+
*/
|
136
|
+
export function fromUtf8(stringValue: string) {
|
137
|
+
const str = Buffer.from(stringValue, 'utf8')
|
138
|
+
|
139
|
+
return `0x${padToEven(str.toString('hex')).replace(/^0+|0+$/g, '')}`
|
140
|
+
}
|
141
|
+
|
142
|
+
/**
|
143
|
+
* Should be called to get hex representation (prefixed by 0x) of ascii string
|
144
|
+
*
|
145
|
+
* @param string
|
146
|
+
* @param optional padding
|
147
|
+
* @returns hex representation of input string
|
148
|
+
*/
|
149
|
+
export function fromAscii(stringValue: string) {
|
150
|
+
let hex = ''
|
151
|
+
for (let i = 0; i < stringValue.length; i++) {
|
152
|
+
const code = stringValue.charCodeAt(i)
|
153
|
+
const n = code.toString(16)
|
154
|
+
hex += n.length < 2 ? `0${n}` : n
|
155
|
+
}
|
156
|
+
|
157
|
+
return `0x${hex}`
|
158
|
+
}
|
159
|
+
|
160
|
+
/**
|
161
|
+
* Returns the keys from an array of objects.
|
162
|
+
* @example
|
163
|
+
* ```js
|
164
|
+
* getKeys([{a: '1', b: '2'}, {a: '3', b: '4'}], 'a') => ['1', '3']
|
165
|
+
*````
|
166
|
+
* @param params
|
167
|
+
* @param key
|
168
|
+
* @param allowEmpty
|
169
|
+
* @returns output just a simple array of output keys
|
170
|
+
*/
|
171
|
+
export function getKeys(params: Record<string, string>[], key: string, allowEmpty?: boolean) {
|
172
|
+
if (!Array.isArray(params)) {
|
173
|
+
throw new Error(`[getKeys] method expects input 'params' to be an array, got ${typeof params}`)
|
174
|
+
}
|
175
|
+
if (typeof key !== 'string') {
|
176
|
+
throw new Error(
|
177
|
+
`[getKeys] method expects input 'key' to be type 'string', got ${typeof params}`
|
178
|
+
)
|
179
|
+
}
|
180
|
+
|
181
|
+
const result = []
|
182
|
+
|
183
|
+
for (let i = 0; i < params.length; i++) {
|
184
|
+
let value = params[i][key]
|
185
|
+
if (allowEmpty && !value) {
|
186
|
+
value = ''
|
187
|
+
} else if (typeof value !== 'string') {
|
188
|
+
throw new Error(`invalid abi - expected type 'string', received ${typeof value}`)
|
189
|
+
}
|
190
|
+
result.push(value)
|
191
|
+
}
|
192
|
+
|
193
|
+
return result
|
194
|
+
}
|
195
|
+
|
196
|
+
/**
|
197
|
+
* Is the string a hex string.
|
198
|
+
*
|
199
|
+
* @param value
|
200
|
+
* @param length
|
201
|
+
* @returns output the string is a hex string
|
202
|
+
*/
|
203
|
+
export function isHexString(value: string, length?: number): boolean {
|
204
|
+
if (typeof value !== 'string' || !value.match(/^0x[0-9A-Fa-f]*$/)) return false
|
205
|
+
|
206
|
+
if (length && value.length !== 2 + 2 * length) return false
|
207
|
+
|
208
|
+
return true
|
209
|
+
}
|
package/src/object.ts
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
import assert from 'assert'
|
2
|
+
import { stripHexPrefix } from './internal'
|
3
|
+
import { rlp } from './externals'
|
4
|
+
import { toBuffer, baToJSON, unpadBuffer } from './bytes'
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Defines properties on a `Object`. It make the assumption that underlying data is binary.
|
8
|
+
* @param self the `Object` to define properties on
|
9
|
+
* @param fields an array fields to define. Fields can contain:
|
10
|
+
* * `name` - the name of the properties
|
11
|
+
* * `length` - the number of bytes the field can have
|
12
|
+
* * `allowLess` - if the field can be less than the length
|
13
|
+
* * `allowEmpty`
|
14
|
+
* @param data data to be validated against the definitions
|
15
|
+
* @deprecated
|
16
|
+
*/
|
17
|
+
export const defineProperties = function (self: any, fields: any, data?: any) {
|
18
|
+
self.raw = []
|
19
|
+
self._fields = []
|
20
|
+
|
21
|
+
// attach the `toJSON`
|
22
|
+
self.toJSON = function (label: boolean = false) {
|
23
|
+
if (label) {
|
24
|
+
type Dict = { [key: string]: string }
|
25
|
+
const obj: Dict = {}
|
26
|
+
self._fields.forEach((field: string) => {
|
27
|
+
obj[field] = `0x${self[field].toString('hex')}`
|
28
|
+
})
|
29
|
+
return obj
|
30
|
+
}
|
31
|
+
return baToJSON(self.raw)
|
32
|
+
}
|
33
|
+
|
34
|
+
self.serialize = function serialize() {
|
35
|
+
return rlp.encode(self.raw)
|
36
|
+
}
|
37
|
+
|
38
|
+
fields.forEach((field: any, i: number) => {
|
39
|
+
self._fields.push(field.name)
|
40
|
+
function getter() {
|
41
|
+
return self.raw[i]
|
42
|
+
}
|
43
|
+
function setter(v: any) {
|
44
|
+
v = toBuffer(v)
|
45
|
+
|
46
|
+
if (v.toString('hex') === '00' && !field.allowZero) {
|
47
|
+
v = Buffer.allocUnsafe(0)
|
48
|
+
}
|
49
|
+
|
50
|
+
if (field.allowLess && field.length) {
|
51
|
+
v = unpadBuffer(v)
|
52
|
+
assert(
|
53
|
+
field.length >= v.length,
|
54
|
+
`The field ${field.name} must not have more ${field.length} bytes`
|
55
|
+
)
|
56
|
+
} else if (!(field.allowZero && v.length === 0) && field.length) {
|
57
|
+
assert(
|
58
|
+
field.length === v.length,
|
59
|
+
`The field ${field.name} must have byte length of ${field.length}`
|
60
|
+
)
|
61
|
+
}
|
62
|
+
|
63
|
+
self.raw[i] = v
|
64
|
+
}
|
65
|
+
|
66
|
+
Object.defineProperty(self, field.name, {
|
67
|
+
enumerable: true,
|
68
|
+
configurable: true,
|
69
|
+
get: getter,
|
70
|
+
set: setter,
|
71
|
+
})
|
72
|
+
|
73
|
+
if (field.default) {
|
74
|
+
self[field.name] = field.default
|
75
|
+
}
|
76
|
+
|
77
|
+
// attach alias
|
78
|
+
if (field.alias) {
|
79
|
+
Object.defineProperty(self, field.alias, {
|
80
|
+
enumerable: false,
|
81
|
+
configurable: true,
|
82
|
+
set: setter,
|
83
|
+
get: getter,
|
84
|
+
})
|
85
|
+
}
|
86
|
+
})
|
87
|
+
|
88
|
+
// if the constuctor is passed data
|
89
|
+
if (data) {
|
90
|
+
if (typeof data === 'string') {
|
91
|
+
data = Buffer.from(stripHexPrefix(data), 'hex')
|
92
|
+
}
|
93
|
+
|
94
|
+
if (Buffer.isBuffer(data)) {
|
95
|
+
data = rlp.decode(data)
|
96
|
+
}
|
97
|
+
|
98
|
+
if (Array.isArray(data)) {
|
99
|
+
if (data.length > self._fields.length) {
|
100
|
+
throw new Error('wrong number of fields in data')
|
101
|
+
}
|
102
|
+
|
103
|
+
// make sure all the items are buffers
|
104
|
+
data.forEach((d, i) => {
|
105
|
+
self[self._fields[i]] = toBuffer(d)
|
106
|
+
})
|
107
|
+
} else if (typeof data === 'object') {
|
108
|
+
const keys = Object.keys(data)
|
109
|
+
fields.forEach((field: any) => {
|
110
|
+
if (keys.indexOf(field.name) !== -1) self[field.name] = data[field.name]
|
111
|
+
if (keys.indexOf(field.alias) !== -1) self[field.alias] = data[field.alias]
|
112
|
+
})
|
113
|
+
} else {
|
114
|
+
throw new Error('invalid data')
|
115
|
+
}
|
116
|
+
}
|
117
|
+
}
|
package/src/signature.ts
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
import { ecdsaSign, ecdsaRecover, publicKeyConvert } from 'ethereum-cryptography/secp256k1'
|
2
|
+
import { BN } from './externals'
|
3
|
+
import { toBuffer, setLengthLeft, bufferToHex, bufferToInt } from './bytes'
|
4
|
+
import { keccak } from './hash'
|
5
|
+
import { assertIsBuffer } from './helpers'
|
6
|
+
import { BNLike, toType, TypeOutput } from './types'
|
7
|
+
|
8
|
+
export interface ECDSASignature {
|
9
|
+
v: number
|
10
|
+
r: Buffer
|
11
|
+
s: Buffer
|
12
|
+
}
|
13
|
+
|
14
|
+
export interface ECDSASignatureBuffer {
|
15
|
+
v: Buffer
|
16
|
+
r: Buffer
|
17
|
+
s: Buffer
|
18
|
+
}
|
19
|
+
|
20
|
+
/**
|
21
|
+
* Returns the ECDSA signature of a message hash.
|
22
|
+
*/
|
23
|
+
export function ecsign(msgHash: Buffer, privateKey: Buffer, chainId?: number): ECDSASignature
|
24
|
+
export function ecsign(msgHash: Buffer, privateKey: Buffer, chainId: BNLike): ECDSASignatureBuffer
|
25
|
+
export function ecsign(msgHash: Buffer, privateKey: Buffer, chainId: any): any {
|
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 || typeof chainId === 'number') {
|
32
|
+
// return legacy type ECDSASignature (deprecated in favor of ECDSASignatureBuffer to handle large chainIds)
|
33
|
+
if (chainId && !Number.isSafeInteger(chainId)) {
|
34
|
+
throw new Error(
|
35
|
+
'The provided number is greater than MAX_SAFE_INTEGER (please use an alternative input type)'
|
36
|
+
)
|
37
|
+
}
|
38
|
+
const v = chainId ? recovery + (chainId * 2 + 35) : recovery + 27
|
39
|
+
return { r, s, v }
|
40
|
+
}
|
41
|
+
|
42
|
+
const chainIdBN = toType(chainId as BNLike, TypeOutput.BN)
|
43
|
+
const v = chainIdBN.muln(2).addn(35).addn(recovery).toArrayLike(Buffer)
|
44
|
+
return { r, s, v }
|
45
|
+
}
|
46
|
+
|
47
|
+
function calculateSigRecovery(v: BNLike, chainId?: BNLike): BN {
|
48
|
+
const vBN = toType(v, TypeOutput.BN)
|
49
|
+
|
50
|
+
if (vBN.eqn(0) || vBN.eqn(1)) return toType(v, TypeOutput.BN)
|
51
|
+
|
52
|
+
if (!chainId) {
|
53
|
+
return vBN.subn(27)
|
54
|
+
}
|
55
|
+
const chainIdBN = toType(chainId, TypeOutput.BN)
|
56
|
+
return vBN.sub(chainIdBN.muln(2).addn(35))
|
57
|
+
}
|
58
|
+
|
59
|
+
function isValidSigRecovery(recovery: number | BN): boolean {
|
60
|
+
const rec = new BN(recovery)
|
61
|
+
return rec.eqn(0) || rec.eqn(1)
|
62
|
+
}
|
63
|
+
|
64
|
+
/**
|
65
|
+
* ECDSA public key recovery from signature.
|
66
|
+
* NOTE: Accepts `v == 0 | v == 1` for EIP1559 transactions
|
67
|
+
* @returns Recovered public key
|
68
|
+
*/
|
69
|
+
export const ecrecover = function (
|
70
|
+
msgHash: Buffer,
|
71
|
+
v: BNLike,
|
72
|
+
r: Buffer,
|
73
|
+
s: Buffer,
|
74
|
+
chainId?: BNLike
|
75
|
+
): Buffer {
|
76
|
+
const signature = Buffer.concat([setLengthLeft(r, 32), setLengthLeft(s, 32)], 64)
|
77
|
+
const recovery = calculateSigRecovery(v, chainId)
|
78
|
+
if (!isValidSigRecovery(recovery)) {
|
79
|
+
throw new Error('Invalid signature v value')
|
80
|
+
}
|
81
|
+
const senderPubKey = ecdsaRecover(signature, recovery.toNumber(), msgHash)
|
82
|
+
return Buffer.from(publicKeyConvert(senderPubKey, false).slice(1))
|
83
|
+
}
|
84
|
+
|
85
|
+
/**
|
86
|
+
* Convert signature parameters into the format of `eth_sign` RPC method.
|
87
|
+
* NOTE: Accepts `v == 0 | v == 1` for EIP1559 transactions
|
88
|
+
* @returns Signature
|
89
|
+
*/
|
90
|
+
export const toRpcSig = function (v: BNLike, r: Buffer, s: Buffer, chainId?: BNLike): string {
|
91
|
+
const recovery = calculateSigRecovery(v, chainId)
|
92
|
+
if (!isValidSigRecovery(recovery)) {
|
93
|
+
throw new Error('Invalid signature v value')
|
94
|
+
}
|
95
|
+
|
96
|
+
// geth (and the RPC eth_sign method) uses the 65 byte format used by Bitcoin
|
97
|
+
return bufferToHex(Buffer.concat([setLengthLeft(r, 32), setLengthLeft(s, 32), toBuffer(v)]))
|
98
|
+
}
|
99
|
+
|
100
|
+
/**
|
101
|
+
* Convert signature parameters into the format of Compact Signature Representation (EIP-2098).
|
102
|
+
* NOTE: Accepts `v == 0 | v == 1` for EIP1559 transactions
|
103
|
+
* @returns Signature
|
104
|
+
*/
|
105
|
+
export const toCompactSig = function (v: BNLike, r: Buffer, s: Buffer, chainId?: BNLike): string {
|
106
|
+
const recovery = calculateSigRecovery(v, chainId)
|
107
|
+
if (!isValidSigRecovery(recovery)) {
|
108
|
+
throw new Error('Invalid signature v value')
|
109
|
+
}
|
110
|
+
|
111
|
+
const vn = toType(v, TypeOutput.Number)
|
112
|
+
let ss = s
|
113
|
+
if ((vn > 28 && vn % 2 === 1) || vn === 1 || vn === 28) {
|
114
|
+
ss = Buffer.from(s)
|
115
|
+
ss[0] |= 0x80
|
116
|
+
}
|
117
|
+
|
118
|
+
return bufferToHex(Buffer.concat([setLengthLeft(r, 32), setLengthLeft(ss, 32)]))
|
119
|
+
}
|
120
|
+
|
121
|
+
/**
|
122
|
+
* Convert signature format of the `eth_sign` RPC method to signature parameters
|
123
|
+
* NOTE: all because of a bug in geth: https://github.com/ethereum/go-ethereum/issues/2053
|
124
|
+
* NOTE: After EIP1559, `v` could be `0` or `1` but this function assumes
|
125
|
+
* it's a signed message (EIP-191 or EIP-712) adding `27` at the end. Remove if needed.
|
126
|
+
*/
|
127
|
+
export const fromRpcSig = function (sig: string): ECDSASignature {
|
128
|
+
const buf: Buffer = toBuffer(sig)
|
129
|
+
|
130
|
+
let r: Buffer
|
131
|
+
let s: Buffer
|
132
|
+
let v: number
|
133
|
+
if (buf.length >= 65) {
|
134
|
+
r = buf.slice(0, 32)
|
135
|
+
s = buf.slice(32, 64)
|
136
|
+
v = bufferToInt(buf.slice(64))
|
137
|
+
} else if (buf.length === 64) {
|
138
|
+
// Compact Signature Representation (https://eips.ethereum.org/EIPS/eip-2098)
|
139
|
+
r = buf.slice(0, 32)
|
140
|
+
s = buf.slice(32, 64)
|
141
|
+
v = bufferToInt(buf.slice(32, 33)) >> 7
|
142
|
+
s[0] &= 0x7f
|
143
|
+
} else {
|
144
|
+
throw new Error('Invalid signature length')
|
145
|
+
}
|
146
|
+
|
147
|
+
// support both versions of `eth_sign` responses
|
148
|
+
if (v < 27) {
|
149
|
+
v += 27
|
150
|
+
}
|
151
|
+
|
152
|
+
return {
|
153
|
+
v,
|
154
|
+
r,
|
155
|
+
s,
|
156
|
+
}
|
157
|
+
}
|
158
|
+
|
159
|
+
/**
|
160
|
+
* Validate a ECDSA signature.
|
161
|
+
* NOTE: Accepts `v == 0 | v == 1` for EIP1559 transactions
|
162
|
+
* @param homesteadOrLater Indicates whether this is being used on either the homestead hardfork or a later one
|
163
|
+
*/
|
164
|
+
export const isValidSignature = function (
|
165
|
+
v: BNLike,
|
166
|
+
r: Buffer,
|
167
|
+
s: Buffer,
|
168
|
+
homesteadOrLater: boolean = true,
|
169
|
+
chainId?: BNLike
|
170
|
+
): boolean {
|
171
|
+
const SECP256K1_N_DIV_2 = new BN(
|
172
|
+
'7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0',
|
173
|
+
16
|
174
|
+
)
|
175
|
+
const SECP256K1_N = new BN('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 16)
|
176
|
+
|
177
|
+
if (r.length !== 32 || s.length !== 32) {
|
178
|
+
return false
|
179
|
+
}
|
180
|
+
|
181
|
+
if (!isValidSigRecovery(calculateSigRecovery(v, chainId))) {
|
182
|
+
return false
|
183
|
+
}
|
184
|
+
|
185
|
+
const rBN = new BN(r)
|
186
|
+
const sBN = new BN(s)
|
187
|
+
|
188
|
+
if (rBN.isZero() || rBN.gt(SECP256K1_N) || sBN.isZero() || sBN.gt(SECP256K1_N)) {
|
189
|
+
return false
|
190
|
+
}
|
191
|
+
|
192
|
+
if (homesteadOrLater && sBN.cmp(SECP256K1_N_DIV_2) === 1) {
|
193
|
+
return false
|
194
|
+
}
|
195
|
+
|
196
|
+
return true
|
197
|
+
}
|
198
|
+
|
199
|
+
/**
|
200
|
+
* Returns the keccak-256 hash of `message`, prefixed with the header used by the `eth_sign` RPC call.
|
201
|
+
* The output of this function can be fed into `ecsign` to produce the same signature as the `eth_sign`
|
202
|
+
* call for a given `message`, or fed to `ecrecover` along with a signature to recover the public key
|
203
|
+
* used to produce the signature.
|
204
|
+
*/
|
205
|
+
export const hashPersonalMessage = function (message: Buffer): Buffer {
|
206
|
+
assertIsBuffer(message)
|
207
|
+
const prefix = Buffer.from(`\u0019Ethereum Signed Message:\n${message.length}`, 'utf-8')
|
208
|
+
return keccak(Buffer.concat([prefix, message]))
|
209
|
+
}
|
package/src/types.ts
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
import { BN } from './externals'
|
2
|
+
import { isHexString } from './internal'
|
3
|
+
import { Address } from './address'
|
4
|
+
import { unpadBuffer, toBuffer, ToBufferInputTypes } from './bytes'
|
5
|
+
|
6
|
+
/*
|
7
|
+
* A type that represents a BNLike input that can be converted to a BN.
|
8
|
+
*/
|
9
|
+
export type BNLike = BN | PrefixedHexString | number | Buffer
|
10
|
+
|
11
|
+
/*
|
12
|
+
* A type that represents a BufferLike input that can be converted to a Buffer.
|
13
|
+
*/
|
14
|
+
export type BufferLike =
|
15
|
+
| Buffer
|
16
|
+
| Uint8Array
|
17
|
+
| number[]
|
18
|
+
| number
|
19
|
+
| BN
|
20
|
+
| TransformableToBuffer
|
21
|
+
| PrefixedHexString
|
22
|
+
|
23
|
+
/*
|
24
|
+
* A type that represents a `0x`-prefixed hex string.
|
25
|
+
*/
|
26
|
+
export type PrefixedHexString = string
|
27
|
+
|
28
|
+
/**
|
29
|
+
* A type that represents an Address-like value.
|
30
|
+
* To convert to address, use `new Address(toBuffer(value))`
|
31
|
+
*/
|
32
|
+
export type AddressLike = Address | Buffer | PrefixedHexString
|
33
|
+
|
34
|
+
/*
|
35
|
+
* A type that represents an object that has a `toArray()` method.
|
36
|
+
*/
|
37
|
+
export interface TransformableToArray {
|
38
|
+
toArray(): Uint8Array
|
39
|
+
toBuffer?(): Buffer
|
40
|
+
}
|
41
|
+
|
42
|
+
/*
|
43
|
+
* A type that represents an object that has a `toBuffer()` method.
|
44
|
+
*/
|
45
|
+
export interface TransformableToBuffer {
|
46
|
+
toBuffer(): Buffer
|
47
|
+
toArray?(): Uint8Array
|
48
|
+
}
|
49
|
+
|
50
|
+
export type NestedUint8Array = Array<Uint8Array | NestedUint8Array>
|
51
|
+
export type NestedBufferArray = Array<Buffer | NestedBufferArray>
|
52
|
+
|
53
|
+
/**
|
54
|
+
* Convert BN to 0x-prefixed hex string.
|
55
|
+
*/
|
56
|
+
export function bnToHex(value: BN): PrefixedHexString {
|
57
|
+
return `0x${value.toString(16)}`
|
58
|
+
}
|
59
|
+
|
60
|
+
/**
|
61
|
+
* Convert value from BN to an unpadded Buffer
|
62
|
+
* (useful for RLP transport)
|
63
|
+
* @param value value to convert
|
64
|
+
*/
|
65
|
+
export function bnToUnpaddedBuffer(value: BN): Buffer {
|
66
|
+
// Using `bn.toArrayLike(Buffer)` instead of `bn.toBuffer()`
|
67
|
+
// for compatibility with browserify and similar tools
|
68
|
+
return unpadBuffer(value.toArrayLike(Buffer))
|
69
|
+
}
|
70
|
+
|
71
|
+
/**
|
72
|
+
* Deprecated alias for {@link bnToUnpaddedBuffer}
|
73
|
+
* @deprecated
|
74
|
+
*/
|
75
|
+
export function bnToRlp(value: BN): Buffer {
|
76
|
+
return bnToUnpaddedBuffer(value)
|
77
|
+
}
|
78
|
+
|
79
|
+
/**
|
80
|
+
* Type output options
|
81
|
+
*/
|
82
|
+
export enum TypeOutput {
|
83
|
+
Number,
|
84
|
+
BN,
|
85
|
+
Buffer,
|
86
|
+
PrefixedHexString,
|
87
|
+
}
|
88
|
+
|
89
|
+
export type TypeOutputReturnType = {
|
90
|
+
[TypeOutput.Number]: number
|
91
|
+
[TypeOutput.BN]: BN
|
92
|
+
[TypeOutput.Buffer]: Buffer
|
93
|
+
[TypeOutput.PrefixedHexString]: PrefixedHexString
|
94
|
+
}
|
95
|
+
|
96
|
+
/**
|
97
|
+
* Convert an input to a specified type.
|
98
|
+
* Input of null/undefined returns null/undefined regardless of the output type.
|
99
|
+
* @param input value to convert
|
100
|
+
* @param outputType type to output
|
101
|
+
*/
|
102
|
+
export function toType<T extends TypeOutput>(input: null, outputType: T): null
|
103
|
+
export function toType<T extends TypeOutput>(input: undefined, outputType: T): undefined
|
104
|
+
export function toType<T extends TypeOutput>(
|
105
|
+
input: ToBufferInputTypes,
|
106
|
+
outputType: T
|
107
|
+
): TypeOutputReturnType[T]
|
108
|
+
export function toType<T extends TypeOutput>(
|
109
|
+
input: ToBufferInputTypes,
|
110
|
+
outputType: T
|
111
|
+
): TypeOutputReturnType[T] | undefined | null {
|
112
|
+
if (input === null) {
|
113
|
+
return null
|
114
|
+
}
|
115
|
+
if (input === undefined) {
|
116
|
+
return undefined
|
117
|
+
}
|
118
|
+
|
119
|
+
if (typeof input === 'string' && !isHexString(input)) {
|
120
|
+
throw new Error(`A string must be provided with a 0x-prefix, given: ${input}`)
|
121
|
+
} else if (typeof input === 'number' && !Number.isSafeInteger(input)) {
|
122
|
+
throw new Error(
|
123
|
+
'The provided number is greater than MAX_SAFE_INTEGER (please use an alternative input type)'
|
124
|
+
)
|
125
|
+
}
|
126
|
+
|
127
|
+
const output = toBuffer(input)
|
128
|
+
|
129
|
+
if (outputType === TypeOutput.Buffer) {
|
130
|
+
return output as TypeOutputReturnType[T]
|
131
|
+
} else if (outputType === TypeOutput.BN) {
|
132
|
+
return new BN(output) as TypeOutputReturnType[T]
|
133
|
+
} else if (outputType === TypeOutput.Number) {
|
134
|
+
const bn = new BN(output)
|
135
|
+
const max = new BN(Number.MAX_SAFE_INTEGER.toString())
|
136
|
+
if (bn.gt(max)) {
|
137
|
+
throw new Error(
|
138
|
+
'The provided number is greater than MAX_SAFE_INTEGER (please use an alternative output type)'
|
139
|
+
)
|
140
|
+
}
|
141
|
+
return bn.toNumber() as TypeOutputReturnType[T]
|
142
|
+
} else {
|
143
|
+
// outputType === TypeOutput.PrefixedHexString
|
144
|
+
return `0x${output.toString('hex')}` as TypeOutputReturnType[T]
|
145
|
+
}
|
146
|
+
}
|