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
8 changes: 4 additions & 4 deletions packages/web3-core/src/web3_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@ export class Web3Context<
public static readonly providers = Web3RequestManager.providers;
public static givenProvider?: SupportedProviders<never>;
public readonly providers = Web3RequestManager.providers;
private _requestManager: Web3RequestManager<API>;
private _subscriptionManager?: Web3SubscriptionManager<API, RegisteredSubs>;
private _accountProvider?: Web3AccountProvider<Web3BaseWalletAccount>;
private _wallet?: Web3BaseWallet<Web3BaseWalletAccount>;
protected _requestManager: Web3RequestManager<API>;
protected _subscriptionManager?: Web3SubscriptionManager<API, RegisteredSubs>;
protected _accountProvider?: Web3AccountProvider<Web3BaseWalletAccount>;
protected _wallet?: Web3BaseWallet<Web3BaseWalletAccount>;

public constructor(
providerOrContext?:
Expand Down
58 changes: 30 additions & 28 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';

/**
*
Expand Down Expand Up @@ -106,7 +101,7 @@ export const hashMessage = (message: string): string => {
* }
* ```
*/
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 @@ -135,12 +130,16 @@ export const sign = (data: string, privateKey: HexString): signResult => {
};

/**
*
* Signs an Ethereum transaction with a given private key.
*
* @param transaction - The transaction, must be a legacy, EIP2930 or EIP 1559 transaction type
* @param privateKey - The private key to import. This is 32 bytes of random data.
* @returns A signTransactionResult object that contains message hash, r, s, v, transaction hash and raw transaction.
*
* This function is not stateful here. We need network access to get the account `nonce` and `chainId` to sign the transaction.
* This function will rely on user to provide the full transaction to be signed. If you want to sign a partial transaction object
* Use {@link Web3.eth.accounts.signTransaction} instead.
*
* Signing a legacy transaction
* ```ts
* signTransaction({
Expand Down Expand Up @@ -212,15 +211,13 @@ export const sign = (data: string, privateKey: HexString): signResult => {
* }
* ```
*/
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 @@ -234,17 +231,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 Down Expand Up @@ -286,7 +282,7 @@ export const recoverTransaction = (rawTransaction: HexString): Address => {
* ```
*/
export const recover = (
data: string | signatureObject,
data: string | SignatureObject,
signature?: string,
prefixed?: boolean,
): Address => {
Expand Down Expand Up @@ -578,8 +574,13 @@ export const encrypt = async (

/**
* Get an Account object from the privateKey
*
* @param privateKey String or buffer of 32 bytes
* @returns A Web3Account object
*
* The `Web3Account.signTransaction` is not stateful here. We need network access to get the account `nonce` and `chainId` to sign the transaction.
* Use {@link Web3.eth.accounts.signTransaction} instead.
*
* ```ts
* privateKeyToAccount("0x348ce564d427a3311b6536bbcff9390d69395b06ed6c486954e971d960fe8709");
* > {
Expand All @@ -589,7 +590,6 @@ export const encrypt = async (
* signTransaction,
* encrypt,
* }
*
* ```
*/
export const privateKeyToAccount = (privateKey: string | Buffer): Web3Account => {
Expand All @@ -598,7 +598,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('Do 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 @@ -41,3 +41,4 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
export * from './wallet';
export * from './account';
export * from './types';
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
Loading