-
Notifications
You must be signed in to change notification settings - Fork 4.9k
/
hash.ts
376 lines (338 loc) · 10.6 KB
/
hash.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/
import { keccak256 } from 'ethereum-cryptography/keccak.js';
import { utf8ToBytes } from 'ethereum-cryptography/utils.js';
import {
InvalidAddressError,
InvalidBooleanError,
InvalidBytesError,
InvalidLargeValueError,
InvalidSizeError,
InvalidStringError,
InvalidUnsignedIntegerError,
} from 'web3-errors';
import {
Bytes,
EncodingTypes,
Numbers,
Sha3Input,
TypedObject,
TypedObjectAbbreviated,
} from 'web3-types';
import { isAddress, isNullish, isHexStrict } from 'web3-validator';
import {
bytesToUint8Array,
bytesToHex,
hexToBytes,
toBigInt,
toHex,
toNumber,
utf8ToHex,
} from './converters.js';
import { leftPad, rightPad, toTwosComplement } from './string_manipulation.js';
const SHA3_EMPTY_BYTES = '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470';
/**
* computes the Keccak-256 hash of the input and returns a hexstring
* @param data - the input to hash
* @returns - the Keccak-256 hash of the input
*
* @example
* ```ts
* console.log(web3.utils.sha3('web3.js'));
* > 0x63667efb1961039c9bb0d6ea7a5abdd223a3aca7daa5044ad894226e1f83919a
*
* console.log(web3.utils.sha3(''));
* > undefined
* ```
*/
export const sha3 = (data: Bytes): string | undefined => {
let updatedData: Uint8Array;
if (typeof data === 'string') {
if (data.startsWith('0x') && isHexStrict(data)) {
updatedData = hexToBytes(data);
} else {
updatedData = utf8ToBytes(data);
}
} else {
updatedData = data;
}
const hash = bytesToHex(keccak256(updatedData));
// EIP-1052 if hash is equal to c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470, keccak was given empty data
return hash === SHA3_EMPTY_BYTES ? undefined : hash;
};
/**
* Will calculate the sha3 of the input but does return the hash value instead of null if for example a empty string is passed.
* @param data - the input to hash
* @returns - the Keccak-256 hash of the input
*
* @example
* ```ts
* conosle.log(web3.utils.sha3Raw('web3.js'));
* > 0x63667efb1961039c9bb0d6ea7a5abdd223a3aca7daa5044ad894226e1f83919a
*
* console.log(web3.utils.sha3Raw(''));
* > 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
* ```
*/
export const sha3Raw = (data: Bytes): string => {
const hash = sha3(data);
if (isNullish(hash)) {
return SHA3_EMPTY_BYTES;
}
return hash;
};
/**
* A wrapper for ethereum-cryptography/keccak256 to allow hashing a `string` and a `bigint` in addition to `UInt8Array`
* @param data - the input to hash
* @returns - the Keccak-256 hash of the input
*
* @example
* ```ts
* console.log(web3.utils.keccak256Wrapper('web3.js'));
* > 0x63667efb1961039c9bb0d6ea7a5abdd223a3aca7daa5044ad894226e1f83919a
*
* console.log(web3.utils.keccak256Wrapper(1));
* > 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6
*
* console.log(web3.utils.keccak256Wrapper(0xaf12fd));
* > 0x358640fd4719fa923525d74ab5ae80a594301aba5543e3492b052bf4598b794c
* ```
*/
export const keccak256Wrapper = (
data: Bytes | Numbers | string | ReadonlyArray<number>,
): string => {
let processedData;
if (typeof data === 'bigint' || typeof data === 'number') {
processedData = utf8ToBytes(data.toString());
} else if (Array.isArray(data)) {
processedData = new Uint8Array(data);
} else if (typeof data === 'string' && !isHexStrict(data)) {
processedData = utf8ToBytes(data);
} else {
processedData = bytesToUint8Array(data as Bytes);
}
return bytesToHex(keccak256(processedData));
};
export { keccak256Wrapper as keccak256 };
/**
* returns type and value
* @param arg - the input to return the type and value
* @returns - the type and value of the input
*/
const getType = (arg: Sha3Input): [string, EncodingTypes] => {
if (Array.isArray(arg)) {
throw new Error('Autodetection of array types is not supported.');
}
let type;
let value;
// if type is given
if (
typeof arg === 'object' &&
('t' in arg || 'type' in arg) &&
('v' in arg || 'value' in arg)
) {
type = 't' in arg ? arg.t : arg.type;
value = 'v' in arg ? arg.v : arg.value;
type = type.toLowerCase() === 'bigint' ? 'int' : type;
} else if (typeof arg === 'bigint') {
return ['int', arg];
}
// otherwise try to guess the type
else {
type = toHex(arg, true);
value = toHex(arg);
if (!type.startsWith('int') && !type.startsWith('uint')) {
type = 'bytes';
}
}
if (
(type.startsWith('int') || type.startsWith('uint')) &&
typeof value === 'string' &&
!/^(-)?0x/i.test(value)
) {
value = toBigInt(value);
}
return [type, value];
};
/**
* returns the type with size if uint or int
* @param name - the input to return the type with size
* @returns - the type with size of the input
*/
const elementaryName = (name: string): string => {
if (name.startsWith('int[')) {
return `int256${name.slice(3)}`;
}
if (name === 'int') {
return 'int256';
}
if (name.startsWith('uint[')) {
return `uint256'${name.slice(4)}`;
}
if (name === 'uint') {
return 'uint256';
}
return name;
};
/**
* returns the size of the value of type 'byte'
*/
const parseTypeN = (value: string, typeLength: number): number => {
const typesize = /^(\d+).*$/.exec(value.slice(typeLength));
return typesize ? parseInt(typesize[1], 10) : 0;
};
/**
* returns the bit length of the value
* @param value - the input to return the bit length
* @returns - the bit length of the input
*/
const bitLength = (value: bigint | number): number => {
const updatedVal = value.toString(2);
return updatedVal.length;
};
/**
* Pads the value based on size and type
* returns a string of the padded value
* @param type - the input to pad
* @returns = the padded value
*/
const solidityPack = (type: string, val: EncodingTypes): string => {
const value = val.toString();
if (type === 'string') {
if (typeof val === 'string') return utf8ToHex(val);
throw new InvalidStringError(val);
}
if (type === 'bool' || type === 'boolean') {
if (typeof val === 'boolean') return val ? '01' : '00';
throw new InvalidBooleanError(val);
}
if (type === 'address') {
if (!isAddress(value)) {
throw new InvalidAddressError(value);
}
return value;
}
const name = elementaryName(type);
if (type.startsWith('uint')) {
const size = parseTypeN(name, 'uint'.length);
if (size % 8 || size < 8 || size > 256) {
throw new InvalidSizeError(value);
}
const num = toNumber(value);
if (bitLength(num) > size) {
throw new InvalidLargeValueError(value);
}
if (num < BigInt(0)) {
throw new InvalidUnsignedIntegerError(value);
}
return size ? leftPad(num.toString(16), (size / 8) * 2) : num.toString(16);
}
if (type.startsWith('int')) {
const size = parseTypeN(name, 'int'.length);
if (size % 8 || size < 8 || size > 256) {
throw new InvalidSizeError(type);
}
const num = toNumber(value);
if (bitLength(num) > size) {
throw new InvalidLargeValueError(value);
}
if (num < BigInt(0)) {
return toTwosComplement(num.toString(), (size / 8) * 2);
}
return size ? leftPad(num.toString(16), size / 4) : num.toString(16);
}
if (name === 'bytes') {
if (value.replace(/^0x/i, '').length % 2 !== 0) {
throw new InvalidBytesError(value);
}
return value;
}
if (type.startsWith('bytes')) {
if (value.replace(/^0x/i, '').length % 2 !== 0) {
throw new InvalidBytesError(value);
}
const size = parseTypeN(type, 'bytes'.length);
if (!size || size < 1 || size > 64 || size < value.replace(/^0x/i, '').length / 2) {
throw new InvalidBytesError(value);
}
return rightPad(value, size * 2);
}
return '';
};
/**
* returns a string of the tightly packed value given based on the type
* @param arg - the input to return the tightly packed value
* @returns - the tightly packed value
*/
export const processSolidityEncodePackedArgs = (arg: Sha3Input): string => {
const [type, val] = getType(arg);
// array case
if (Array.isArray(val)) {
// go through each element of the array and use map function to create new hexarg list
const hexArg = val.map((v: Numbers | boolean) => solidityPack(type, v).replace('0x', ''));
return hexArg.join('');
}
const hexArg = solidityPack(type, val);
return hexArg.replace('0x', '');
};
/**
* Encode packed arguments to a hexstring
*/
export const encodePacked = (...values: Sha3Input[]): string => {
const args = Array.prototype.slice.call(values);
const hexArgs = args.map(processSolidityEncodePackedArgs);
return `0x${hexArgs.join('').toLowerCase()}`;
};
/**
* Will tightly pack values given in the same way solidity would then hash.
* returns a hash string, or null if input is empty
* @param values - the input to return the tightly packed values
* @returns - the keccack246 of the tightly packed values
*
* @example
* ```ts
* console.log([{ type: 'string', value: '31323334' }]);
* console.log(web3.utils.soliditySha3({ type: "string", value: "31323334" }));
* > 0xf15f8da2ad27e486d632dc37d24912f634398918d6f9913a0a0ff84e388be62b
* ```
*/
export const soliditySha3 = (...values: Sha3Input[]): string | undefined =>
sha3(encodePacked(...values));
/**
* Will tightly pack values given in the same way solidity would then hash.
* returns a hash string, if input is empty will return `0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470`
* @param values - the input to return the tightly packed values
* @returns - the keccack246 of the tightly packed values
*
* @example
* ```ts
* console.log(web3.utils.soliditySha3Raw({ type: "string", value: "helloworld" }))
* > 0xfa26db7ca85ead399216e7c6316bc50ed24393c3122b582735e7f3b0f91b93f0
* ```
*/
export const soliditySha3Raw = (...values: TypedObject[] | TypedObjectAbbreviated[]): string =>
sha3Raw(encodePacked(...values));
/**
* Get slot number for storage long string in contract. Basically for getStorage method
* returns slotNumber where will data placed
* @param mainSlotNumber - the slot number where will be stored hash of long string
* @returns - the slot number where will be stored long string
*/
export const getStorageSlotNumForLongString = (mainSlotNumber: number | string) =>
sha3(
`0x${(typeof mainSlotNumber === 'number'
? mainSlotNumber.toString()
: mainSlotNumber
).padStart(64, '0')}`,
);