Skip to content

Commit

Permalink
EIP 712 fixes (#7095)
Browse files Browse the repository at this point in the history
* Fix encodeData error condition

* hash fixes

* lint:fix

* revert some changes

* revert noPreamble

* revert recover

* fix integration tests

* fix integration tests
  • Loading branch information
avkos authored Jun 17, 2024
1 parent 16252f9 commit e47e1da
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 95 deletions.
21 changes: 10 additions & 11 deletions packages/web3-eth-abi/src/eip_712.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.

/**
* The web3.eth.abi functions let you encode and decode parameters to ABI (Application Binary Interface) for function calls to the EVM (Ethereum Virtual Machine).
*
*
* For using Web3 ABI functions, first install Web3 package using `npm i web3` or `yarn add web3`.
* After that, Web3 ABI functions will be available.
* After that, Web3 ABI functions will be available.
* ```ts
* import { Web3 } from 'web3';
*
*
* const web3 = new Web3();
* const encoded = web3.eth.abi.encodeFunctionSignature({
* name: 'myMethod',
Expand All @@ -35,14 +35,14 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
* name: 'myString'
* }]
* });
*
*
* ```
*
*
* For using individual package install `web3-eth-abi` package using `npm i web3-eth-abi` or `yarn add web3-eth-abi` and only import required functions.
* This is more efficient approach for building lightweight applications.
* This is more efficient approach for building lightweight applications.
* ```ts
* import { encodeFunctionSignature } from 'web3-eth-abi';
*
*
* const encoded = encodeFunctionSignature({
* name: 'myMethod',
* type: 'function',
Expand All @@ -54,13 +54,12 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
* name: 'myString'
* }]
* });
*
*
* ```
*
*
* @module ABI
*/


// This code was taken from: https://github.com/Mrtenz/eip-712/tree/master

import { Eip712TypedData } from 'web3-types';
Expand Down Expand Up @@ -231,7 +230,7 @@ const encodeData = (
): string => {
const [types, values] = typedData.types[type].reduce<[string[], unknown[]]>(
([_types, _values], field) => {
if (isNullish(data[field.name]) || isNullish(data[field.name])) {
if (isNullish(data[field.name]) || isNullish(field.type)) {
throw new AbiError(`Cannot encode data: missing data for '${field.name}'`, {
data,
field,
Expand Down
71 changes: 35 additions & 36 deletions packages/web3-eth-accounts/src/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,29 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.

/**
* The web3 accounts package contains functions to generate Ethereum accounts and sign transactions & data.
*
*
* For using accounts functions, first install Web3 package using `npm i web3` or `yarn add web3` based on your package manager usage.
* After that, Accounts functions will be available as mentioned in following snippet.
* After that, Accounts functions will be available as mentioned in following snippet.
* ```ts
* import {Web3} from 'web3';
*
*
* const web3 = new Web3();
* const account = web3.eth.accounts.create();
* const result = web3.eth.accounts.hashMessage("Test Message");
*
*
* ```
*
*
* For using individual package install `web3-eth-accounts` package using `npm i web3-eth-accounts` or `yarn add web3-eth-accounts` and only import required functions.
* This is more efficient approach for building lightweight applications.
* This is more efficient approach for building lightweight applications.
* ```ts
* import {create,hashMessage} from 'web3-eth-accounts';
*
*
* const account = create();
* const result = hashMessage("Test Message");
*
*
* ```
* @module Accounts
*
*
*/

import {
Expand Down Expand Up @@ -98,24 +98,23 @@ import type {
SignResult,
} from './types.js';


/**
* Get the private key Uint8Array after the validation.
* Note: This function is not exported through main web3 package, so for using it directly import from accounts package.
* Note: This function is not exported through main web3 package, so for using it directly import from accounts package.
* @param data - Private key
* @param ignoreLength - Optional, ignore length check during validation
* @param ignoreLength - Optional, ignore length check during validation
* @returns The Uint8Array private key
*
* ```ts
* parseAndValidatePrivateKey("0x08c673022000ece7964ea4db2d9369c50442b2869cbd8fc21baaca59e18f642c")
*
*
* > Uint8Array(32) [
* 186, 26, 143, 168, 235, 179, 90, 75,
* 101, 63, 84, 221, 152, 150, 30, 203,
* 8, 113, 94, 226, 53, 213, 216, 5,
* 194, 159, 17, 53, 219, 97, 121, 248
* ]
*
*
* ```
*/
export const parseAndValidatePrivateKey = (data: Bytes, ignoreLength?: boolean): Uint8Array => {
Expand All @@ -127,7 +126,7 @@ export const parseAndValidatePrivateKey = (data: Bytes, ignoreLength?: boolean):
}

try {
privateKeyUint8Array = isUint8Array(data) ? (data ) : bytesToUint8Array(data);
privateKeyUint8Array = isUint8Array(data) ? data : bytesToUint8Array(data);
} catch {
throw new InvalidPrivateKeyError();
}
Expand All @@ -149,11 +148,11 @@ export const parseAndValidatePrivateKey = (data: Bytes, ignoreLength?: boolean):
*
* ```ts
* web3.eth.accounts.hashMessage("Hello world")
*
*
* > "0x8144a6fa26be252b86456491fbcd43c1de7e022241845ffea1c3df066f7cfede"
*
*
* web3.eth.accounts.hashMessage(web3.utils.utf8ToHex("Hello world")) // Will be hex decoded in hashMessage
*
*
* > "0x8144a6fa26be252b86456491fbcd43c1de7e022241845ffea1c3df066f7cfede"
* ```
*/
Expand Down Expand Up @@ -228,7 +227,7 @@ export const sign = (data: string, privateKey: Bytes): SignResult => {
* Signing a legacy transaction
* ```ts
* import {signTransaction, Transaction} from 'web3-eth-accounts';
*
*
* signTransaction(new Transaction({
* to: '0x118C2E5F57FD62C2B5b46a5ae9216F4FF4011a07',
* value: '0x186A0',
Expand All @@ -238,7 +237,7 @@ export const sign = (data: string, privateKey: Bytes): SignResult => {
* chainId: 1,
* nonce: 0 }),
* '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318')
*
*
* > {
* messageHash: '0x28b7b75f7ba48d588a902c1ff4d5d13cc0ca9ac0aaa39562368146923fb853bf',
* v: '0x25',
Expand All @@ -247,11 +246,11 @@ export const sign = (data: string, privateKey: Bytes): SignResult => {
* rawTransaction: '0xf869808609184e72a0008352081294118c2e5f57fd62c2b5b46a5ae9216f4ff4011a07830186a08025a00601b0017b0e20dd0eeda4b895fbc1a9e8968990953482214f880bae593e71b5a0690d984493560552e3ebdcc19a65b9c301ea9ddc82d3ab8cfde60485fd5722ce',
* transactionHash: '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'
* ```
*
*
* Signing an eip 1559 transaction
* ```ts
* import {signTransaction, Transaction} from 'web3-eth-accounts';
*
*
* signTransaction(new Transaction({
* to: '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55',
* maxPriorityFeePerGas: '0x3B9ACA00',
Expand All @@ -271,11 +270,11 @@ export const sign = (data: string, privateKey: Bytes): SignResult => {
* transactionHash: '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'
* }
* ```
*
*
* Signing an eip 2930 transaction
* ```ts
* import {signTransaction, Transaction} from 'web3-eth-accounts';
*
*
* signTransaction(new Transaction ({
* chainId: 1,
* nonce: 0,
Expand All @@ -294,7 +293,7 @@ export const sign = (data: string, privateKey: Bytes): SignResult => {
* },
* ],
* }),"0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318")
*
*
* > {
* messageHash: '0xc55ea24bdb4c379550a7c9a6818ac39ca33e75bc78ddb862bd82c31cc1c7a073',
* v: '0x26',
Expand Down Expand Up @@ -366,11 +365,11 @@ export const recoverTransaction = (rawTransaction: HexString): Address => {
* @param s - S value in signature
* @param prefixed - (default: false) If the last parameter is true, the given message will NOT automatically be prefixed with `"\\x19Ethereum Signed Message:\\n" + message.length + message`, and assumed to be already prefixed.
* @returns The Ethereum address used to sign this data
*
*
* ```ts
* const data = 'Some data';
* const sigObj = web3.eth.accounts.sign(data, '0xbe6383dad004f233317e46ddb46ad31b16064d14447a95cc1d8c8d4bc61c3728')
*
*
* > {
* message: 'Some data',
* messageHash: '0x1da44b586eb0729ff70a73c326926f6ed5a25f5b056e7f47fbc6e58d86871655',
Expand All @@ -379,10 +378,10 @@ export const recoverTransaction = (rawTransaction: HexString): Address => {
* s: '0x53e41351267b20d4a89ebfe9c8f03c04de9b345add4a52f15bd026b63c8fb150',
* signature: '0xa8037a6116c176a25e6fc224947fde9e79a2deaa0dd8b67b366fbdfdbffc01f953e41351267b20d4a89ebfe9c8f03c04de9b345add4a52f15bd026b63c8fb1501b'
* }
*
*
* // now recover
* web3.eth.accounts.recover(data, sigObj.v, sigObj.r, sigObj.s)
*
*
* > 0xEB014f8c8B418Db6b45774c326A0E64C78914dC0
* ```
*/
Expand Down Expand Up @@ -422,7 +421,7 @@ export const recover = (
const address = toChecksumAddress(`0x${publicHash.slice(-40)}`);

return address;
};;
};

/**
* Get the ethereum Address from a private key
Expand All @@ -433,7 +432,7 @@ export const recover = (
* @example
* ```ts
* web3.eth.accounts.privateKeyToAddress("0xbe6383dad004f233317e46ddb46ad31b16064d14447a95cc1d8c8d4bc61c3728")
*
*
* > "0xEB014f8c8B418Db6b45774c326A0E64C78914dC0"
* ```
*/
Expand Down Expand Up @@ -462,7 +461,7 @@ export const privateKeyToAddress = (privateKey: Bytes): string => {
* @example
* ```ts
* web3.eth.accounts.privateKeyToPublicKey("0x1e046a882bb38236b646c9f135cf90ad90a140810f439875f2a6dd8e50fa261f", true)
*
*
* > "0x42beb65f179720abaa3ec9a70a539629cbbc5ec65bb57e7fc78977796837e537662dd17042e6449dc843c281067a4d6d8d1a1775a13c41901670d5de7ee6503a" // uncompressed public key
* ```
*/
Expand All @@ -485,7 +484,7 @@ export const privateKeyToPublicKey = (privateKey: Bytes, isCompressed: boolean):
*
* Encrypt using scrypt options:
* ```ts
*
*
* web3.eth.accounts.encrypt(
* '0x67f476289210e3bef3c1c75e4de993ff0a00663df00def84e73aa7411eac18a6',
* '123',
Expand Down Expand Up @@ -659,7 +658,7 @@ export const encrypt = async (
*
* ```ts
* web3.eth.accounts.privateKeyToAccount("0x348ce564d427a3311b6536bbcff9390d69395b06ed6c486954e971d960fe8709");
*
*
* > {
* address: '0xb8CE9ab6943e0eCED004cDe8e3bBed6568B2Fa01',
* privateKey: '0x348ce564d427a3311b6536bbcff9390d69395b06ed6c486954e971d960fe8709',
Expand Down Expand Up @@ -740,8 +739,8 @@ export const create = (): Web3Account => {
* mac: 'efbf6d3409f37c0084a79d5fdf9a6f5d97d11447517ef1ea8374f51e581b7efd'
* }
* }, '123').then(console.log);
*
*
*
*
* > {
* address: '0xcdA9A91875fc35c8Ac1320E098e584495d66e47c',
* privateKey: '67f476289210e3bef3c1c75e4de993ff0a00663df00def84e73aa7411eac18a6',
Expand Down
6 changes: 5 additions & 1 deletion packages/web3-eth-accounts/src/tx/baseTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,11 @@ export abstract class BaseTransaction<TransactionObject> {
return postfix;
}
// eslint-disable-next-line class-methods-use-this
private _ecsign(msgHash: Uint8Array, privateKey: Uint8Array, chainId?: bigint): ECDSASignature {
protected _ecsign(
msgHash: Uint8Array,
privateKey: Uint8Array,
chainId?: bigint,
): ECDSASignature {
const signature = secp256k1.sign(msgHash, privateKey);
const signatureBytes = signature.toCompactRawBytes();

Expand Down
72 changes: 36 additions & 36 deletions packages/web3-utils/src/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,41 @@ import { leftPad, rightPad, toTwosComplement } from './string_manipulation.js';

const SHA3_EMPTY_BYTES = '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470';

/**
* 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(validatorUtils.ensureIfUint8Array(processedData)));
};

export { keccak256Wrapper as keccak256 };

/**
* computes the Keccak-256 hash of the input and returns a hexstring
* @param data - the input to hash
Expand All @@ -99,7 +134,7 @@ export const sha3 = (data: Bytes): string | undefined => {
} else {
updatedData = data;
}
const hash = bytesToHex(keccak256(validatorUtils.ensureIfUint8Array(updatedData)));
const hash = keccak256Wrapper(updatedData);

// EIP-1052 if hash is equal to c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470, keccak was given empty data
return hash === SHA3_EMPTY_BYTES ? undefined : hash;
Expand Down Expand Up @@ -128,41 +163,6 @@ export const sha3Raw = (data: Bytes): string => {
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(validatorUtils.ensureIfUint8Array(processedData)));
};

export { keccak256Wrapper as keccak256 };

/**
* returns type and value
* @param arg - the input to return the type and value
Expand Down
Loading

1 comment on commit e47e1da

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: e47e1da Previous: 16252f9 Ratio
processingTx 8969 ops/sec (±5.32%) 8718 ops/sec (±4.07%) 0.97
processingContractDeploy 38761 ops/sec (±7.89%) 38913 ops/sec (±7.29%) 1.00
processingContractMethodSend 15900 ops/sec (±7.01%) 18130 ops/sec (±7.07%) 1.14
processingContractMethodCall 28097 ops/sec (±6.28%) 36290 ops/sec (±6.10%) 1.29
abiEncode 44753 ops/sec (±7.71%) 39776 ops/sec (±7.72%) 0.89
abiDecode 29942 ops/sec (±8.29%) 28800 ops/sec (±6.54%) 0.96
sign 1578 ops/sec (±0.97%) 1530 ops/sec (±3.94%) 0.97
verify 365 ops/sec (±3.03%) 363 ops/sec (±0.60%) 0.99

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.