Skip to content
This repository was archived by the owner on Mar 5, 2025. It is now read-only.
Merged
4 changes: 2 additions & 2 deletions packages/web3-common/src/web3_base_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ export interface Web3BaseWalletAccount {
[key: string]: unknown;
readonly address: string;
readonly privateKey: string;
readonly signTransaction: (tx: Record<string, unknown>) => {
readonly signTransaction: (tx: Record<string, unknown>) => Promise<{
readonly messageHash: HexString;
readonly r: HexString;
readonly s: HexString;
readonly v: HexString;
readonly rawTransaction: HexString;
readonly transactionHash: HexString;
};
}>;
readonly sign: (data: Record<string, unknown> | string) => {
readonly messageHash: HexString;
readonly r: HexString;
Expand Down
46 changes: 20 additions & 26 deletions packages/web3-eth-accounts/src/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,7 @@ import {
} from 'web3-errors';
import { utils, getPublicKey } from 'ethereum-cryptography/secp256k1';
import { keccak256 } from 'ethereum-cryptography/keccak';
import {
TransactionFactory,
FeeMarketEIP1559TxData,
AccessListEIP2930TxData,
TxData,
} from '@ethereumjs/tx';
import { TransactionFactory, TypedTransaction } from '@ethereumjs/tx';
import { ecdsaSign, ecdsaRecover } from 'secp256k1';
import { pbkdf2Sync } from 'ethereum-cryptography/pbkdf2';
import { scryptSync } from 'ethereum-cryptography/scrypt';
Expand All @@ -53,16 +48,16 @@ import {
} from 'web3-utils';
import { validator, isBuffer, isHexString32Bytes, isString, isNullish } from 'web3-validator';
import {
signatureObject,
signResult,
signTransactionResult,
SignatureObject,
SignResult,
SignTransactionResult,
KeyStore,
ScryptParams,
PBKDF2SHA256Params,
CipherOptions,
keyStoreSchema,
Web3Account,
} from './types';
import { keyStoreSchema } from './schemas';

/**
* Hashes the given message. The data will be UTF-8 HEX decoded and enveloped as follows: "\x19Ethereum Signed Message:\n" + message.length + message and hashed using keccak256.
Expand All @@ -86,7 +81,7 @@ export const hashMessage = (message: string): string => {
* @param data
* @param privateKey
*/
export const sign = (data: string, privateKey: HexString): signResult => {
export const sign = (data: string, privateKey: HexString): SignResult => {
const privateKeyParam = privateKey.startsWith('0x') ? privateKey.substring(2) : privateKey;

if (!isHexString32Bytes(privateKeyParam, false)) {
Expand Down Expand Up @@ -120,15 +115,13 @@ export const sign = (data: string, privateKey: HexString): signResult => {
* @param transaction
* @param privateKey
*/
export const signTransaction = (
transaction: TxData | AccessListEIP2930TxData | FeeMarketEIP1559TxData,
export const signTransaction = async (
transaction: TypedTransaction,
privateKey: HexString,
): signTransactionResult => {
// TODO : Send calls to web3.transaction package for :
// Transaction Validation checks

const tx = TransactionFactory.fromTxData(transaction);
const signedTx = tx.sign(Buffer.from(privateKey.substring(2), 'hex'));
// To make it compatible with rest of the API, have to keep it async
// eslint-disable-next-line @typescript-eslint/require-await
): Promise<SignTransactionResult> => {
const signedTx = transaction.sign(Buffer.from(privateKey.substring(2), 'hex'));
if (isNullish(signedTx.v) || isNullish(signedTx.r) || isNullish(signedTx.s))
throw new SignerError('Signer Error');

Expand All @@ -142,17 +135,16 @@ export const signTransaction = (
throw new SignerError(errorString);
}

const rlpEncoded = signedTx.serialize().toString('hex');
const rawTx = `0x${rlpEncoded}`;
const txHash = keccak256(Buffer.from(rawTx, 'hex'));
const rawTx = bytesToHex(signedTx.serialize());
const txHash = keccak256(hexToBytes(rawTx));

return {
messageHash: `0x${Buffer.from(signedTx.getMessageToSign(true)).toString('hex')}`,
messageHash: bytesToHex(Buffer.from(signedTx.getMessageToSign(true))),
v: `0x${signedTx.v.toString('hex')}`,
r: `0x${signedTx.r.toString('hex')}`,
s: `0x${signedTx.s.toString('hex')}`,
rawTransaction: rawTx,
transactionHash: `0x${Buffer.from(txHash).toString('hex')}`,
transactionHash: bytesToHex(txHash),
};
};

Expand All @@ -177,7 +169,7 @@ export const recoverTransaction = (rawTransaction: HexString): Address => {
* @param hashed
*/
export const recover = (
data: string | signatureObject,
data: string | SignatureObject,
signature?: string,
hashed?: boolean,
): Address => {
Expand Down Expand Up @@ -398,7 +390,9 @@ export const privateKeyToAccount = (privateKey: string | Buffer): Web3Account =>
return {
address: privateKeyToAddress(pKey),
privateKey: pKey,
signTransaction: (tx: Record<string, unknown>) => signTransaction(tx, pKey),
signTransaction: (_tx: Record<string, unknown>) => {
throw new SignerError('Don not have network access to sign the transaction');
},
sign: (data: Record<string, unknown> | string) =>
sign(typeof data === 'string' ? data : JSON.stringify(data), pKey),
encrypt: async (password: string, options?: Record<string, unknown>) => {
Expand Down
1 change: 1 addition & 0 deletions packages/web3-eth-accounts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
export * from './account';
export * from './types';
export * from './wallet';
export * from './schemas';
39 changes: 39 additions & 0 deletions packages/web3-eth-accounts/src/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
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/>.
*/

export const keyStoreSchema = {
type: 'object',
required: ['crypto', 'id', 'version', 'address'],
properties: {
crypto: {
type: 'object',
required: ['cipher', 'ciphertext', 'cipherparams', 'kdf', 'kdfparams', 'mac'],
properties: {
cipher: { type: 'string' },
ciphertext: { type: 'string' },
cipherparams: { type: 'object' },
kdf: { type: 'string' },
kdfparams: { type: 'object' },
salt: { type: 'string' },
mac: { type: 'string' },
},
},
id: { type: 'string' },
version: { type: 'number' },
address: { type: 'string' },
},
};
35 changes: 6 additions & 29 deletions packages/web3-eth-accounts/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,55 +19,32 @@ import { FeeMarketEIP1559TxData, AccessListEIP2930TxData, TxData } from '@ethere
import { Web3BaseWalletAccount } from 'web3-common';
import { HexString } from 'web3-utils';

export type signatureObject = {
export type SignatureObject = {
messageHash: string;
r: string;
s: string;
v: string;
};

export const keyStoreSchema = {
type: 'object',
required: ['crypto', 'id', 'version', 'address'],
properties: {
crypto: {
type: 'object',
required: ['cipher', 'ciphertext', 'cipherparams', 'kdf', 'kdfparams', 'mac'],
properties: {
cipher: { type: 'string' },
ciphertext: { type: 'string' },
cipherparams: { type: 'object' },
kdf: { type: 'string' },
kdfparams: { type: 'object' },
salt: { type: 'string' },
mac: { type: 'string' },
},
},
id: { type: 'string' },
version: { type: 'number' },
address: { type: 'string' },
},
};

export type signTransactionResult = signatureObject & {
export type SignTransactionResult = SignatureObject & {
rawTransaction: string;
transactionHash: string;
};

export type signTransactionFunction = (
export type SignTransactionFunction = (
transaction:
| TxData
| AccessListEIP2930TxData
| FeeMarketEIP1559TxData
| Record<string, unknown>,
) => signTransactionResult;
) => SignTransactionResult;

export type signResult = signatureObject & {
export type SignResult = SignatureObject & {
message?: string;
signature: string;
};

export type signFunction = (data: string, privateKey: string) => signResult;
export type SignFunction = (data: string, privateKey: string) => SignResult;

// https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition

Expand Down
15 changes: 11 additions & 4 deletions packages/web3-eth-accounts/test/unit/account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ 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 { TransactionFactory } from '@ethereumjs/tx';
import { Web3ValidatorError } from 'web3-validator';
import { isHexStrict, Address, utf8ToHex } from 'web3-utils';
import {
Expand Down Expand Up @@ -70,10 +71,13 @@ describe('accounts', () => {
});

describe('Signing and Recovery of Transaction', () => {
it.each(transactionsTestData)('sign transaction', txData => {
it.each(transactionsTestData)('sign transaction', async txData => {
const account = create();

const signedResult = signTransaction(txData, account.privateKey);
const signedResult = await signTransaction(
TransactionFactory.fromTxData(txData),
account.privateKey,
);
expect(signedResult).toBeDefined();
expect(signedResult.messageHash).toBeDefined();
expect(signedResult.rawTransaction).toBeDefined();
Expand All @@ -83,10 +87,13 @@ describe('accounts', () => {
expect(signedResult.v).toBeDefined();
});

it.each(transactionsTestData)('Recover transaction', txData => {
it.each(transactionsTestData)('Recover transaction', async txData => {
const account = create();
const txObj = { ...txData, from: account.address };
const signedResult = signTransaction(txObj, account.privateKey);
const signedResult = await signTransaction(
TransactionFactory.fromTxData(txObj),
account.privateKey,
);
expect(signedResult).toBeDefined();

const address: Address = recoverTransaction(signedResult.rawTransaction);
Expand Down
2 changes: 1 addition & 1 deletion packages/web3-eth/src/rpc_method_wrappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ export function sendTransaction<
}

if (wallet) {
const signedTransaction = wallet.signTransaction(
const signedTransaction = await wallet.signTransaction(
transactionFormatted as Record<string, unknown>,
);

Expand Down
43 changes: 11 additions & 32 deletions packages/web3-eth/src/utils/prepare_transaction_for_signing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.

import Common from '@ethereumjs/common';
import { TransactionFactory, TxOptions } from '@ethereumjs/tx';
import { EthExecutionAPI, FMT_BYTES, FMT_NUMBER, FormatType } from 'web3-common';
import { EthExecutionAPI, FormatType, ETH_DATA_FORMAT } from 'web3-common';
import { Web3Context } from 'web3-core';
import { HexString, toNumber } from 'web3-utils';
import { isNullish } from 'web3-validator';
Expand All @@ -32,10 +32,7 @@ import { formatTransaction } from './format_transaction';
import { transactionBuilder } from './transaction_builder';

const getEthereumjsTxDataFromTransaction = (
transaction: FormatType<
PopulatedUnsignedTransaction,
{ number: FMT_NUMBER.HEX; bytes: FMT_BYTES.HEX }
>,
transaction: FormatType<PopulatedUnsignedTransaction, typeof ETH_DATA_FORMAT>,
) => ({
nonce: transaction.nonce,
gasPrice: transaction.gasPrice,
Expand All @@ -46,30 +43,18 @@ const getEthereumjsTxDataFromTransaction = (
type: transaction.type,
chainId: transaction.chainId,
accessList: (
transaction as FormatType<
PopulatedUnsignedEip2930Transaction,
{ number: FMT_NUMBER.HEX; bytes: FMT_BYTES.HEX }
>
transaction as FormatType<PopulatedUnsignedEip2930Transaction, typeof ETH_DATA_FORMAT>
).accessList,
maxPriorityFeePerGas: (
transaction as FormatType<
PopulatedUnsignedEip1559Transaction,
{ number: FMT_NUMBER.HEX; bytes: FMT_BYTES.HEX }
>
transaction as FormatType<PopulatedUnsignedEip1559Transaction, typeof ETH_DATA_FORMAT>
).maxPriorityFeePerGas,
maxFeePerGas: (
transaction as FormatType<
PopulatedUnsignedEip1559Transaction,
{ number: FMT_NUMBER.HEX; bytes: FMT_BYTES.HEX }
>
transaction as FormatType<PopulatedUnsignedEip1559Transaction, typeof ETH_DATA_FORMAT>
).maxFeePerGas,
});

const getEthereumjsTransactionOptions = (
transaction: FormatType<
PopulatedUnsignedTransaction,
{ number: FMT_NUMBER.HEX; bytes: FMT_BYTES.HEX }
>,
transaction: FormatType<PopulatedUnsignedTransaction, typeof ETH_DATA_FORMAT>,
web3Context: Web3Context<EthExecutionAPI>,
) => {
const hasTransactionSigningOptions =
Expand Down Expand Up @@ -118,19 +103,13 @@ export const prepareTransactionForSigning = async (
privateKey,
})) as unknown as PopulatedUnsignedTransaction;

const formattedTransaction = formatTransaction(populatedTransaction, {
number: FMT_NUMBER.HEX,
bytes: FMT_BYTES.HEX,
}) as unknown as FormatType<
PopulatedUnsignedTransaction,
{ number: FMT_NUMBER.HEX; bytes: FMT_BYTES.HEX }
>;
const formattedTransaction = formatTransaction(
populatedTransaction,
ETH_DATA_FORMAT,
) as unknown as FormatType<PopulatedUnsignedTransaction, typeof ETH_DATA_FORMAT>;

validateTransactionForSigning(
formattedTransaction as unknown as FormatType<
Transaction,
{ number: FMT_NUMBER.HEX; bytes: FMT_BYTES.HEX }
>,
formattedTransaction as unknown as FormatType<Transaction, typeof ETH_DATA_FORMAT>,
);

return TransactionFactory.fromTxData(
Expand Down
Loading