Skip to content
This repository was archived by the owner on Mar 5, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/web3-common/src/web3_base_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export abstract class Web3BaseWallet<T extends Web3BaseWalletAccount> {
}

public abstract create(numberOfAccounts: number): this;
public abstract add(account: T | string): boolean;
public abstract add(account: T | string): this;
public abstract get(addressOrIndex: string | number): T;
public abstract remove(addressOrIndex: string | number): boolean;
public abstract clear(): this;
Expand Down
304 changes: 274 additions & 30 deletions packages/web3-eth-accounts/src/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,17 @@ import {
} from './types';

/**
*
* 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.
* @param message A message to hash, if its HEX it will be UTF8 decoded.
* @returns The hashed message
* ```ts
* hashMessage("Hello world")
* > "0x8144a6fa26be252b86456491fbcd43c1de7e022241845ffea1c3df066f7cfede"
* hashMessage(utf8ToHex("Hello world")) // Will be hex decoded in hashMessage
* > "0x8144a6fa26be252b86456491fbcd43c1de7e022241845ffea1c3df066f7cfede"
* ```
*/

export const hashMessage = (message: string): string => {
const messageHex = isHexStrict(message) ? message : utf8ToHex(message);

Expand All @@ -81,10 +89,22 @@ export const hashMessage = (message: string): string => {
};

/**
* Signs arbitrary data. The value passed as the data parameter will be UTF-8 HEX decoded and wrapped as follows: "\x19Ethereum Signed Message:\n" + message.length + message
*
* @param data
* @param privateKey
* Signs arbitrary data.
* **_NOTE:_** The value passed as the data parameter will be UTF-8 HEX decoded and wrapped as follows: "\x19Ethereum Signed Message:\n" + message.length + message
* @param data - The data to sign
* @param privateKey - The 32 byte private key to sign with
* @returns The signature Object containing the message, messageHash, r, s, v
* ```ts
* web3.eth.accounts.sign('Some data', '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318')
* > {
* message: 'Some data',
* messageHash: '0x1da44b586eb0729ff70a73c326926f6ed5a25f5b056e7f47fbc6e58d86871655',
* v: '0x1c',
* r: '0xb91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd',
* s: '0x6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a029',
* signature: '0xb91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c'
* }
* ```
*/
export const sign = (data: string, privateKey: HexString): signResult => {
const privateKeyParam = privateKey.startsWith('0x') ? privateKey.substring(2) : privateKey;
Expand Down Expand Up @@ -115,10 +135,82 @@ export const sign = (data: string, privateKey: HexString): signResult => {
};

/**
* Signs an Ethereum transaction with a given private key.
*
* @param transaction
* @param privateKey
* 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.
*
* Signing a legacy transaction
* ```ts
* signTransaction({
* to: '0x118C2E5F57FD62C2B5b46a5ae9216F4FF4011a07',
* value: '0x186A0',
* gasLimit: '0x520812',
* gasPrice: '0x09184e72a000',
* data: '',
* chainId: 1,
* nonce: 0,
* }, '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318'))
* }
* > {
* messageHash: '0x28b7b75f7ba48d588a902c1ff4d5d13cc0ca9ac0aaa39562368146923fb853bf',
* v: '0x25',
* r: '0x601b0017b0e20dd0eeda4b895fbc1a9e8968990953482214f880bae593e71b5',
* s: '0x690d984493560552e3ebdcc19a65b9c301ea9ddc82d3ab8cfde60485fd5722ce',
* rawTransaction: '0xf869808609184e72a0008352081294118c2e5f57fd62c2b5b46a5ae9216f4ff4011a07830186a08025a00601b0017b0e20dd0eeda4b895fbc1a9e8968990953482214f880bae593e71b5a0690d984493560552e3ebdcc19a65b9c301ea9ddc82d3ab8cfde60485fd5722ce',
* transactionHash: '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'
* ```
* Signing an eip 1559 transaction
* ```ts
* signTransaction({
* to: '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55',
* maxPriorityFeePerGas: '0x3B9ACA00',
* maxFeePerGas: '0xB2D05E00',
* gasLimit: '0x6A4012',
* value: '0x186A0',
* data: '',
* chainId: 1,
* nonce: 0,
* },"0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318")
* > {
* messageHash: '0x5744f24d5f0aff6c70487c8e85adf07d8564e50b08558788f00479611d7bae5f',
* v: '0x25',
* r: '0x78a5a6b2876c3985f90f82073d18d57ac299b608cc76a4ba697b8bb085048347',
* s: '0x9cfcb40cc7d505ed17ff2d3337b51b066648f10c6b7e746117de69b2eb6358d',
* rawTransaction: '0xf8638080836a401294f0109fc8df283027b6285cc889f5aa624eac1f55830186a08025a078a5a6b2876c3985f90f82073d18d57ac299b608cc76a4ba697b8bb085048347a009cfcb40cc7d505ed17ff2d3337b51b066648f10c6b7e746117de69b2eb6358d',
* transactionHash: '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'
* }
* ```
* Signing an eip 2930 transaction
* ```ts
* signTransaction({
* chainId: 1,
* nonce: 0,
* gasPrice: '0x09184e72a000',
* gasLimit: '0x2710321',
* to: '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55',
* value: '0x186A0',
* data: '',
* accessList: [
* {
* address: '0x0000000000000000000000000000000000000101',
* storageKeys: [
* '0x0000000000000000000000000000000000000000000000000000000000000000',
* '0x00000000000000000000000000000000000000000000000000000000000060a7',
* ],
* },
* ],
* },"0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318")
* > {
* messageHash: '0xc55ea24bdb4c379550a7c9a6818ac39ca33e75bc78ddb862bd82c31cc1c7a073',
* v: '0x26',
* r: '0x27344e77871c8b2068bc998bf28e0b5f9920867a69c455b2ed0c1c150fec098e',
* s: '0x519f0130a1d662841d4a28082e9c9bb0a15e0e59bb46cfc39a52f0e285dec6b9',
* rawTransaction: '0xf86a808609184e72a000840271032194f0109fc8df283027b6285cc889f5aa624eac1f55830186a08026a027344e77871c8b2068bc998bf28e0b5f9920867a69c455b2ed0c1c150fec098ea0519f0130a1d662841d4a28082e9c9bb0a15e0e59bb46cfc39a52f0e285dec6b9',
* transactionHash: '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'
* }
* ```
*/
export const signTransaction = (
transaction: TxData | AccessListEIP2930TxData | FeeMarketEIP1559TxData,
Expand Down Expand Up @@ -158,8 +250,12 @@ export const signTransaction = (

/**
* Recovers the Ethereum address which was used to sign the given RLP encoded transaction.
*
* @param rawTransaction
* @param rawTransaction - The RLP encoded transaction
* @returns The Ethereum address used to sign this transaction
* ```ts
* recoverTransaction('0xf869808504e3b29200831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a0c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895a0727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68');
* > "0x2c7536E3605D9C16a7a3D7b1898e529396a65c23"
* ```
*/
export const recoverTransaction = (rawTransaction: HexString): Address => {
if (isNullish(rawTransaction)) throw new UndefinedRawTransactionError();
Expand All @@ -171,10 +267,23 @@ export const recoverTransaction = (rawTransaction: HexString): Address => {

/**
* Recovers the Ethereum address which was used to sign the given data
*
* @param data
* @param signature
* @param hashed
* @param data - Either a signed message, hash, or the {@link signatureObject}
* @param signature - The raw RLP encoded signature
* @param hashed - If the message is hashed or not
* @returns The Ethereum address used to sign this data
* ```ts
* sign('Some data', '0xbe6383dad004f233317e46ddb46ad31b16064d14447a95cc1d8c8d4bc61c3728');
* > {
* message: 'Some data',
* messageHash: '0x1da44b586eb0729ff70a73c326926f6ed5a25f5b056e7f47fbc6e58d86871655',
* v: '0x1b',
* r: '0xa8037a6116c176a25e6fc224947fde9e79a2deaa0dd8b67b366fbdfdbffc01f9',
* s: '0x53e41351267b20d4a89ebfe9c8f03c04de9b345add4a52f15bd026b63c8fb150',
* signature: '0xa8037a6116c176a25e6fc224947fde9e79a2deaa0dd8b67b366fbdfdbffc01f953e41351267b20d4a89ebfe9c8f03c04de9b345add4a52f15bd026b63c8fb1501b'
* }
* recover('0xa8037a6116c176a25e6fc224947fde9e79a2deaa0dd8b67b366fbdfdbffc01f953e41351267b20d4a89ebfe9c8f03c04de9b345add4a52f15bd026b63c8fb1501b');
* > '0xEB014f8c8B418Db6b45774c326A0E64C78914dC0'
* ```
*/
export const recover = (
data: string | signatureObject,
Expand Down Expand Up @@ -209,12 +318,15 @@ export const recover = (
return address;
};

// Generate a version 4 uuid
// https://github.com/uuidjs/uuid/blob/main/src/v4.js#L5
// https://github.com/ethers-io/ethers.js/blob/ce8f1e4015c0f27bf178238770b1325136e3351a/packages/json-wallets/src.ts/utils.ts#L54
const uuidV4 = () => {
/**
* Generate a version 4 (random) uuid
* https://github.com/uuidjs/uuid/blob/main/src/v4.js#L5
* */

const uuidV4 = (): string => {
const bytes = randomBytes(16);

// https://github.com/ethers-io/ethers.js/blob/ce8f1e4015c0f27bf178238770b1325136e3351a/packages/json-wallets/src.ts/utils.ts#L54
// Section: 4.1.3:
// - time_hi_and_version[12:16] = 0b0100
/* eslint-disable-next-line */
Expand All @@ -237,6 +349,16 @@ const uuidV4 = () => {
].join('-');
};

/**
* Get the ethereum Address from a private key
* @param privateKey String or buffer of 32 bytes
* @returns The Ethereum address
* @example
* ```ts
* privateKeyToAddress("0xbe6383dad004f233317e46ddb46ad31b16064d14447a95cc1d8c8d4bc61c3728")
* > "0xEB014f8c8B418Db6b45774c326A0E64C78914dC0"
* ```
*/
export const privateKeyToAddress = (privateKey: string | Buffer): string => {
if (!(isString(privateKey) || isBuffer(privateKey))) {
throw new InvalidPrivateKeyError();
Expand All @@ -263,12 +385,79 @@ export const privateKeyToAddress = (privateKey: string | Buffer): string => {
};

/**
* encrypt a private key given a password, returns a V3 JSON Keystore
* https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
* encrypt a private key with a password, returns a V3 JSON Keystore
*
* Read more: https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
*
* @param privateKey - The private key to encrypt, 32 bytes.
* @param password - The password used for encryption.
* @param options - Options to configure to encrypt the keystore either scrypt or pbkdf2
* @returns Returns a V3 JSON Keystore
*
*
* Encrypt using scrypt options
* ```ts
* encrypt('0x67f476289210e3bef3c1c75e4de993ff0a00663df00def84e73aa7411eac18a6',
* '123',
* {
* n: 8192,
* iv: Buffer.from('bfb43120ae00e9de110f8325143a2709', 'hex'),
* salt: Buffer.from(
* '210d0ec956787d865358ac45716e6dd42e68d48e346d795746509523aeb477dd',
* 'hex',
* ),
* }).then(console.log)
* > {
* version: 3,
* id: 'c0cb0a94-4702-4492-b6e6-eb2ac404344a',
* address: 'cda9a91875fc35c8ac1320e098e584495d66e47c',
* crypto: {
* ciphertext: 'cb3e13e3281ff3861a3f0257fad4c9a51b0eb046f9c7821825c46b210f040b8f',
* cipherparams: { iv: 'bfb43120ae00e9de110f8325143a2709' },
* cipher: 'aes-128-ctr',
* kdf: 'scrypt',
* kdfparams: {
* n: 8192,
* r: 8,
* p: 1,
* dklen: 32,
* salt: '210d0ec956787d865358ac45716e6dd42e68d48e346d795746509523aeb477dd'
* },
* mac: 'efbf6d3409f37c0084a79d5fdf9a6f5d97d11447517ef1ea8374f51e581b7efd'
* }
*}
*```
* Encrypting using pbkdf2 options
* ```ts
* encrypt('0x348ce564d427a3311b6536bbcff9390d69395b06ed6c486954e971d960fe8709',
*'123',
*{
* iv: 'bfb43120ae00e9de110f8325143a2709',
* salt: '210d0ec956787d865358ac45716e6dd42e68d48e346d795746509523aeb477dd',
* c: 262144,
* kdf: 'pbkdf2',
*}).then(console.log)
* >
* {
* version: 3,
* id: '77381417-0973-4e4b-b590-8eb3ace0fe2d',
* address: 'b8ce9ab6943e0eced004cde8e3bbed6568b2fa01',
* crypto: {
* ciphertext: '76512156a34105fa6473ad040c666ae7b917d14c06543accc0d2dc28e6073b12',
* cipherparams: { iv: 'bfb43120ae00e9de110f8325143a2709' },
* cipher: 'aes-128-ctr',
* kdf: 'pbkdf2',
* kdfparams: {
* dklen: 32,
* salt: '210d0ec956787d865358ac45716e6dd42e68d48e346d795746509523aeb477dd',
* c: 262144,
* prf: 'hmac-sha256'
* },
* mac: '46eb4884e82dc43b5aa415faba53cc653b7038e9d61cc32fd643cf8c396189b7'
* }
* }
*```
*
* @param privateKey
* @param password
* @param options
*/
export const encrypt = async (
privateKey: HexString,
Expand Down Expand Up @@ -388,9 +577,20 @@ export const encrypt = async (
};

/**
* Get account from private key
* Get an Account object from the privateKey
* @param privateKey String or buffer of 32 bytes
* @returns A Web3Account object
* ```ts
* privateKeyToAccount("0x348ce564d427a3311b6536bbcff9390d69395b06ed6c486954e971d960fe8709");
* > {
* address: '0xb8CE9ab6943e0eCED004cDe8e3bBed6568B2Fa01',
* privateKey: '0x348ce564d427a3311b6536bbcff9390d69395b06ed6c486954e971d960fe8709',
* sign,
* signTransaction,
* encrypt,
* }
*
* @param privateKey
* ```
*/
export const privateKeyToAccount = (privateKey: string | Buffer): Web3Account => {
const pKey = Buffer.isBuffer(privateKey) ? Buffer.from(privateKey).toString('hex') : privateKey;
Expand All @@ -410,7 +610,20 @@ export const privateKeyToAccount = (privateKey: string | Buffer): Web3Account =>
};

/**
* Returns an acoount
*
* Generates and returns a Web3Account object that includes the private and public key
* @returns A Web3Account object
* ```ts
* web3.eth.accounts.create();
* {
* address: '0xbD504f977021b5E5DdccD8741A368b147B3B38bB',
* privateKey: '0x964ced1c69ad27a311c432fdc0d8211e987595f7eb34ab405a5f16bdc9563ec5',
* signTransaction: [Function: signTransaction],
* sign: [Function: sign],
* encrypt: [AsyncFunction: encrypt]
* }
* ```

*/
export const create = (): Web3Account => {
const privateKey = utils.randomPrivateKey();
Expand All @@ -419,11 +632,42 @@ export const create = (): Web3Account => {
};

/**
* Decrypts a v3 keystore JSON, and creates the account.
* Decrypts a v3 keystore JSON, and creates the account.
* @param keystore - the encrypted Keystore object or string to decrypt
* @param password - The password that was used for encryption
* @param nonStrict - if true and given a json string, the keystore will be parsed as lowercase.
* @returns Returns the decrypted Web3Account object
* * Decrypting scrypt
*
* ```ts
* decrypt({
* version: 3,
* id: 'c0cb0a94-4702-4492-b6e6-eb2ac404344a',
* address: 'cda9a91875fc35c8ac1320e098e584495d66e47c',
* crypto: {
* ciphertext: 'cb3e13e3281ff3861a3f0257fad4c9a51b0eb046f9c7821825c46b210f040b8f',
* cipherparams: { iv: 'bfb43120ae00e9de110f8325143a2709' },
* cipher: 'aes-128-ctr',
* kdf: 'scrypt',
* kdfparams: {
* n: 8192,
* r: 8,
* p: 1,
* dklen: 32,
* salt: '210d0ec956787d865358ac45716e6dd42e68d48e346d795746509523aeb477dd'
* },
* mac: 'efbf6d3409f37c0084a79d5fdf9a6f5d97d11447517ef1ea8374f51e581b7efd'
* }
* }, '123').then(console.log)
* > {
* address: '0xcdA9A91875fc35c8Ac1320E098e584495d66e47c',
* privateKey: '67f476289210e3bef3c1c75e4de993ff0a00663df00def84e73aa7411eac18a6',
* signTransaction: [Function: signTransaction],
* sign: [Function: sign],
* encrypt: [AsyncFunction: encrypt]
* }
* ```
*
* @param keystore
* @param password
* @param nonStrict
*/
export const decrypt = async (
keystore: KeyStore | string,
Expand Down
Loading