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 1 commit
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
3 changes: 1 addition & 2 deletions packages/web3-eth-accounts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@
"dependencies": {
"web3-errors": "1.0.0-alpha.0",
"@ethereumjs/tx": "^3.4.0",
"ethereum-cryptography": "^0.2.1",
"secp256k1": "^4.0.2",
"ethereum-cryptography": "^1.1.0",
"web3-common": "^1.0.0-alpha.0",
"web3-utils": "^4.0.0-alpha.1",
"web3-validator": "^0.1.0-alpha.0"
Expand Down
181 changes: 93 additions & 88 deletions packages/web3-eth-accounts/src/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,55 +15,83 @@ 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, TypedTransaction } from '@ethereumjs/tx';
import { decrypt as createDecipheriv, encrypt as createCipheriv } from 'ethereum-cryptography/aes';
import { pbkdf2Sync } from 'ethereum-cryptography/pbkdf2';
import { scryptSync } from 'ethereum-cryptography/scrypt';
import { getPublicKey, recoverPublicKey, signSync, utils } from 'ethereum-cryptography/secp256k1';
import {
InvalidKdfError,
InvalidPasswordError,
InvalidPrivateKeyError,
PrivateKeyLengthError,
UndefinedRawTransactionError,
SignerError,
InvalidSignatureError,
InvalidKdfError,
IVLengthError,
KeyDerivationError,
KeyStoreVersionError,
InvalidPasswordError,
IVLengthError,
PBKDF2IterationsError,
PrivateKeyLengthError,
SignerError,
UndefinedRawTransactionError,
} from 'web3-errors';
import { utils, getPublicKey } from 'ethereum-cryptography/secp256k1';
import { keccak256 } from 'ethereum-cryptography/keccak';
import { TransactionFactory, TypedTransaction } from '@ethereumjs/tx';
import { ecdsaSign, ecdsaRecover } from 'secp256k1';
import { pbkdf2Sync } from 'ethereum-cryptography/pbkdf2';
import { scryptSync } from 'ethereum-cryptography/scrypt';
import { encrypt as createCipheriv, decrypt as createDecipheriv } from 'ethereum-cryptography/aes';
import {
toChecksumAddress,
Address,
Bytes,
bytesToBuffer,
bytesToHex,
sha3Raw,
HexString,
randomBytes,
hexToBytes,
Address,
isHexStrict,
numberToHex,
randomBytes,
sha3Raw,
toChecksumAddress,
utf8ToHex,
} from 'web3-utils';
import { validator, isBuffer, isHexString32Bytes, isString, isNullish } from 'web3-validator';
import { isBuffer, isNullish, isString, validator } from 'web3-validator';
import { keyStoreSchema } from './schemas';
import {
CipherOptions,
KeyStore,
PBKDF2SHA256Params,
ScryptParams,
SignatureObject,
SignResult,
SignTransactionResult,
KeyStore,
ScryptParams,
PBKDF2SHA256Params,
CipherOptions,
Web3Account,
} from './types';
import { keyStoreSchema } from './schemas';

/**
* Get the private key buffer after the validation
*
* @param data - The data in any bytes format
* @returns
*/
export const parseAndValidatePrivateKey = (data: Bytes): Buffer => {
let privateKeyBuffer: Buffer;

// To avoid the case of 1 character less in a hex string which is prefixed with '0' by using 'bytesToBuffer'
if (typeof data === 'string' && isHexStrict(data) && data.length !== 66) {
throw new PrivateKeyLengthError();
}

try {
privateKeyBuffer = Buffer.isBuffer(data) ? data : bytesToBuffer(data);
} catch {
throw new InvalidPrivateKeyError();
}

if (privateKeyBuffer.byteLength !== 32) {
throw new PrivateKeyLengthError();
}

return privateKeyBuffer;
};

/**
*
* 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.
* @param message - A message to hash, if its HEX it will be UTF8 decoded.
* @returns The hashed message
* ```ts
* hashMessage("Hello world")
Expand All @@ -77,11 +105,14 @@ export const hashMessage = (message: string): string => {

const messageBytes = hexToBytes(messageHex);

const preamble = `\x19Ethereum Signed Message:\n${messageBytes.length}`;
const preamble = Buffer.from(
`\x19Ethereum Signed Message:\n${messageBytes.byteLength}`,
'utf8',
);

const ethMessage = Buffer.concat([Buffer.from(preamble), Buffer.from(messageBytes)]);
const ethMessage = Buffer.concat([preamble, messageBytes]);

return `0x${Buffer.from(keccak256(ethMessage)).toString('hex')}`;
return sha3Raw(ethMessage);
};

/**
Expand All @@ -103,31 +134,27 @@ export const hashMessage = (message: string): string => {
* }
* ```
*/
export const sign = (data: string, privateKey: HexString): SignResult => {
const privateKeyParam = privateKey.startsWith('0x') ? privateKey.substring(2) : privateKey;

if (!isHexString32Bytes(privateKeyParam, false)) {
throw new PrivateKeyLengthError();
}
export const sign = (data: string, privateKey: Bytes): SignResult => {
const privateKeyBuffer = parseAndValidatePrivateKey(privateKey);

const hash = hashMessage(data);

const signObj = ecdsaSign(
Buffer.from(hash.substring(2), 'hex'),
Buffer.from(privateKeyParam, 'hex'),
);
const [signature, recoverId] = signSync(hash.substring(2), privateKeyBuffer, {
recovered: true,
der: false,
});

const r = Buffer.from(signObj.signature.slice(0, 32));
const s = Buffer.from(signObj.signature.slice(32, 64));
const v = signObj.recid + 27;
const r = Buffer.from(signature.slice(0, 32));
const s = Buffer.from(signature.slice(32, 64));
const v = recoverId + 27;

return {
message: data,
messageHash: hash,
v: `0x${v.toString(16)}`,
r: `0x${r.toString('hex')}`,
s: `0x${s.toString('hex')}`,
signature: `0x${Buffer.from(signObj.signature).toString('hex')}${v.toString(16)}`,
v: numberToHex(v),
r: bytesToHex(r),
s: bytesToHex(s),
signature: `0x${Buffer.from(signature).toString('hex')}${v.toString(16)}`,
};
};

Expand Down Expand Up @@ -234,7 +261,7 @@ export const signTransaction = async (
}

const rawTx = bytesToHex(signedTx.serialize());
const txHash = keccak256(hexToBytes(rawTx));
const txHash = sha3Raw(rawTx);

return {
messageHash: bytesToHex(Buffer.from(signedTx.getMessageToSign(true))),
Expand Down Expand Up @@ -302,10 +329,10 @@ export const recover = (

const v = signature.substring(V_INDEX); // 0x + r + s + v

const ecPublicKey = ecdsaRecover(
const ecPublicKey = recoverPublicKey(
Buffer.from(hashedMessage.substring(2), 'hex'),
Buffer.from(signature.substring(2, V_INDEX), 'hex'),
parseInt(v, 16) - 27,
Buffer.from(hashedMessage.substring(2), 'hex'),
false,
);

Expand Down Expand Up @@ -360,29 +387,19 @@ const uuidV4 = (): string => {
* > "0xEB014f8c8B418Db6b45774c326A0E64C78914dC0"
* ```
*/
export const privateKeyToAddress = (privateKey: string | Buffer): string => {
if (!(isString(privateKey) || isBuffer(privateKey))) {
throw new InvalidPrivateKeyError();
}

const stringPrivateKey = Buffer.isBuffer(privateKey)
? Buffer.from(privateKey).toString('hex')
: privateKey;
export const privateKeyToAddress = (privateKey: Bytes): string => {
const privateKeyBuffer = parseAndValidatePrivateKey(privateKey);

const stringPrivateKeyNoPrefix = stringPrivateKey.startsWith('0x')
? stringPrivateKey.slice(2)
: stringPrivateKey;
// Get public key from private key
const publicKey = getPublicKey(privateKeyBuffer);

if (!isHexString32Bytes(stringPrivateKeyNoPrefix, false)) {
throw new PrivateKeyLengthError();
}
// Hash the last 64 bytes of the public key
const publicKeyHash = sha3Raw(publicKey.slice(-64));

const publicKey = getPublicKey(stringPrivateKeyNoPrefix);
// To get the address, take the last 20 bytes of the public hash
const address = publicKeyHash.slice(-40);

const publicKeyString = `0x${publicKey.slice(2)}`;
const publicHash = sha3Raw(publicKeyString);
const publicHashHex = bytesToHex(publicHash);
return toChecksumAddress(publicHashHex.slice(-40)); // To get the address, take the last 20 bytes of the public hash
return toChecksumAddress(`0x${address}`);
};

/**
Expand Down Expand Up @@ -460,21 +477,11 @@ export const privateKeyToAddress = (privateKey: string | Buffer): string => {
*```
*/
export const encrypt = async (
privateKey: HexString,
privateKey: Bytes,
password: string | Buffer,
options?: CipherOptions,
): Promise<KeyStore> => {
if (!(isString(privateKey) || isBuffer(privateKey))) {
throw new InvalidPrivateKeyError();
}

const stringPrivateKey = Buffer.isBuffer(privateKey)
? Buffer.from(privateKey).toString('hex')
: privateKey;

if (!isHexString32Bytes(stringPrivateKey)) {
throw new PrivateKeyLengthError();
}
const privateKeyBuffer = parseAndValidatePrivateKey(privateKey);

// if given salt or iv is a string, convert it to a Uint8Array
let salt;
Expand Down Expand Up @@ -546,10 +553,8 @@ export const encrypt = async (
throw new InvalidKdfError();
}

const cipherKey = Buffer.from(stringPrivateKey.replace('0x', ''), 'hex');

const cipher = await createCipheriv(
cipherKey,
privateKeyBuffer,
Buffer.from(derivedKey.slice(0, 16)),
initializationVector,
'aes-128-ctr',
Expand All @@ -562,7 +567,7 @@ export const encrypt = async (
return {
version: 3,
id: uuidV4(),
address: privateKeyToAddress(stringPrivateKey).toLowerCase().replace('0x', ''),
address: privateKeyToAddress(privateKeyBuffer).toLowerCase().replace('0x', ''),
crypto: {
ciphertext,
cipherparams: {
Expand Down Expand Up @@ -596,19 +601,19 @@ export const encrypt = async (
* }
* ```
*/
export const privateKeyToAccount = (privateKey: string | Buffer): Web3Account => {
const pKey = Buffer.isBuffer(privateKey) ? Buffer.from(privateKey).toString('hex') : privateKey;
export const privateKeyToAccount = (privateKey: Bytes): Web3Account => {
const privateKeyBuffer = parseAndValidatePrivateKey(privateKey);

return {
address: privateKeyToAddress(pKey),
privateKey: pKey,
address: privateKeyToAddress(privateKeyBuffer),
privateKey: bytesToHex(privateKeyBuffer),
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),
sign(typeof data === 'string' ? data : JSON.stringify(data), privateKeyBuffer),
encrypt: async (password: string, options?: Record<string, unknown>) => {
const data = await encrypt(pKey, password, options);
const data = await encrypt(privateKeyBuffer, password, options);

return JSON.stringify(data);
},
Expand Down
Loading