Skip to content

Commit

Permalink
Accounts Creation - 4.x rewrite (#4577)
Browse files Browse the repository at this point in the history
* started creating a few functions for account create

* updating packages

* adding create method for accounts

* updated toCheckSumAddress and added testcases

* added create and fromprivate functions for account

* removing keythereum and implementing privatekeys with ethereum-cryptography

* from private function complete and few tests added

* cleaning up and updating changelog

* rename fromPrivate to privateKeyToAccount

* linter

* fixing lint warnings

* addressing  feedback

* fixing up

* fixing function name

* addressing feedback

* addressing feedback

* update changelog

* fixing testcases
  • Loading branch information
Alex authored Dec 6, 2021
1 parent c919c9b commit 9d7ef2f
Show file tree
Hide file tree
Showing 22 changed files with 276 additions and 43 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,15 @@ Released with 1.0.0-beta.37 code base.
1. The function `outputBigNumberFormatter` in `web3-core-helper` renamed to `outputBigIntFormatter` under `web3-common`
2. Removed `this.defaultBlock` context from `inputDefaultBlockNumberFormatter` in `web3-core-helper` and converted to additional parameter
3. Removed `this.defaultBlock` context from `inputTransactionFormatter` in `web3-core-helper` and converted to additional parameter

#### web3-utils

1. The following functions `soliditySha3` `soliditySha3Raw` `encodePacked` now includes type validation and requires type specficiation, instead of guessing the value type
2. The functions `soliditySha3` `soliditySha3Raw` `encodePacked` does not support BN and now supports `BigInt`
3. The functions `flattenTypes` and `jsonInterfaceMethodToString` moved to the `web3-eth-abi` package
4. The function `isAddress` now includes an optional parameter `checkChecksum` type boolean

### web3-eth-accounts

1. `create` function does not take in the optional parameter `entropy`
2. `ignoreLength` will be removed as an optional parameter for `privateKeyToAccount`
3 changes: 3 additions & 0 deletions packages/web3-common/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,7 @@ export const ERR_INVALID_PROVIDER = 601;
export const ERR_INVALID_CLIENT = 602;
export const ERR_SUBSCRIPTION = 603;

export const ERR_PRIVATE_KEY_LENGTH = 701;
export const ERR_INVALID_PRIVATE_KEY = 702;

export const GENESIS_BLOCK_NUMBER = '0x0';
16 changes: 16 additions & 0 deletions packages/web3-common/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import {
ERR_OPERATION_TIMEOUT,
ERR_OPERATION_ABORT,
ERR_ABI_ENCODING,
ERR_INVALID_PRIVATE_KEY,
ERR_PRIVATE_KEY_LENGTH,
} from './constants';
import { isResponseWithError } from './json_rpc';

Expand Down Expand Up @@ -393,3 +395,17 @@ export class OperationAbortError extends Web3Error {
export class AbiError extends Web3Error {
public code = ERR_ABI_ENCODING;
}

export class PrivateKeyLengthError extends Web3Error {
public code = ERR_PRIVATE_KEY_LENGTH;
public constructor(value: string | Buffer) {
super(`Invalid value given "${value.toString()}". Error: Private key must be 32 bytes.`);
}
}

export class InvalidPrivateKeyError extends Web3Error {
public code = ERR_INVALID_PRIVATE_KEY;
public constructor(value: string | Buffer) {
super(`Invalid value given "${String(value)}". Error: not a valid string or buffer.`);
}
}
5 changes: 5 additions & 0 deletions packages/web3-eth-accounts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,10 @@
"prettier": "^2.4.1",
"ts-jest": "^27.0.7",
"typescript": "^4.5.2"
},
"dependencies": {
"ethereum-cryptography": "^0.2.0",
"web3-common": "^1.0.0-alpha.0",
"web3-utils": "^4.0.0-alpha.0"
}
}
79 changes: 79 additions & 0 deletions packages/web3-eth-accounts/src/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { utils, getPublicKey } from 'ethereum-cryptography/secp256k1';
import {
toChecksumAddress,
bytesToHex,
sha3Raw,
HexString,
isBuffer,
isValidString,
} from 'web3-utils';
import { InvalidPrivateKeyError, PrivateKeyLengthError } from 'web3-common';

// TODO Will be added later
export const encrypt = (): boolean => true;

// TODO Will be added later
export const sign = (): boolean => true;

// TODO Will be added later
export const signTransaction = (): boolean => true;

/**
* Get account from private key
*/
export const privateKeyToAccount = (
privateKey: string | Buffer,
): {
address: string;
privateKey: string;
signTransaction: () => boolean; // From 1.x
sign: () => boolean;
encrypt: () => boolean;
} => {
if (!(isValidString(privateKey) || isBuffer(privateKey))) {
throw new InvalidPrivateKeyError(privateKey);
}

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

const stringPrivateKeyNoPrefix = stringPrivateKey.startsWith('0x')
? stringPrivateKey.slice(2)
: stringPrivateKey;

// TODO Replace with isHexString32Bytes function in web3-eth PR:
// Must be 64 hex characters
if (stringPrivateKeyNoPrefix.length !== 64) {
throw new PrivateKeyLengthError(stringPrivateKeyNoPrefix);
}

const publicKey = getPublicKey(stringPrivateKeyNoPrefix);

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

/**
* Returns an acoount
*/
export const create = (): {
address: HexString;
privateKey: string;
signTransaction: () => boolean; // From 1.x
sign: () => boolean;
encrypt: () => boolean;
} => {
const privateKey = utils.randomPrivateKey();
const address = getPublicKey(privateKey);
return {
privateKey: `0x${Buffer.from(privateKey).toString('hex')}`,
address: `0x${Buffer.from(address).toString('hex')}`,
signTransaction,
sign,
encrypt,
};
};
Empty file.
1 change: 1 addition & 0 deletions packages/web3-eth-accounts/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './account';
Empty file.
31 changes: 31 additions & 0 deletions packages/web3-eth-accounts/test/fixtures/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { sign, signTransaction, encrypt } from '../../src/account';

export const validPrivateKeytoAccountData: [string, any][] = [
[
'0x348ce564d427a3311b6536bbcff9390d69395b06ed6c486954e971d960fe8709',
{
address: '0xb8CE9ab6943e0eCED004cDe8e3bBed6568B2Fa01',
privateKey: '0x348ce564d427a3311b6536bbcff9390d69395b06ed6c486954e971d960fe8709',
sign,
signTransaction,
encrypt,
},
],
[
'0x9e93921f9bca358a96aa66efcccbde12850473be95f63c1453e29656feafeb35',
{
address: '0x118C2E5F57FD62C2B5b46a5ae9216F4FF4011a07',
privateKey: '0x9e93921f9bca358a96aa66efcccbde12850473be95f63c1453e29656feafeb35',
sign,
signTransaction,
encrypt,
},
],
];

export const invalidPrivateKeytoAccountData: [any, string][] = [
['', 'Invalid value given "". Error: Private key must be 32 bytes.'],
[Buffer.from([]), 'Invalid value given "". Error: Private key must be 32 bytes.'],
[undefined, 'Invalid value given "undefined". Error: not a valid string or buffer.'],
[null, 'Invalid value given "null". Error: not a valid string or buffer.'],
];
32 changes: 32 additions & 0 deletions packages/web3-eth-accounts/test/unit/account.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { isHexStrict } from 'web3-utils';
import { create, privateKeyToAccount } from '../../src/account';
import { validPrivateKeytoAccountData, invalidPrivateKeytoAccountData } from '../fixtures/account';

describe('accounts', () => {
describe('create', () => {
describe('valid cases', () => {
it('%s', () => {
const account = create();
expect(typeof account.privateKey).toBe('string');
expect(typeof account.address).toBe('string');
expect(isHexStrict(account.address)).toBe(true);
expect(typeof account.encrypt).toBe('function');
expect(typeof account.sign).toBe('function');
expect(typeof account.signTransaction).toBe('function');
});
});
});

describe('privateKeyToAccount', () => {
describe('valid cases', () => {
it.each(validPrivateKeytoAccountData)('%s', (input, output) => {
expect(privateKeyToAccount(input)).toEqual(output);
});
});
describe('invalid cases', () => {
it.each(invalidPrivateKeytoAccountData)('%s', (input, output) => {
expect(() => privateKeyToAccount(input)).toThrow(output);
});
});
});
});
20 changes: 0 additions & 20 deletions packages/web3-eth-accounts/test/unit/constructor.test.ts

This file was deleted.

16 changes: 9 additions & 7 deletions packages/web3-utils/src/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,28 +355,30 @@ export const toWei = (number: Numbers, unit: EtherUnits): string => {
};

export const toChecksumAddress = (address: Address): string => {
if (typeof address === 'undefined') return '';

if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) {
if (!isAddress(address, false)) {
throw new InvalidAddressError(address);
}

const lowerCaseAddress = address.toLowerCase().replace(/^0x/i, '');
const hash = (keccak256(Buffer.from(lowerCaseAddress)) as Buffer).toString('hex');

const hash = bytesToHex(keccak256(Buffer.from(lowerCaseAddress)));

if (
hash === null ||
hash === 'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'
)
return ''; // // EIP-1052 if hash is equal to c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470, keccak was given empty data

const addressHash = hash.replace(/^0x/i, '');

let checksumAddress = '0x';

for (let i = 0; i < address.length; i += 1) {
for (let i = 0; i < lowerCaseAddress.length; i += 1) {
// If ith character is 8 to f then make it uppercase
if (parseInt(addressHash[i], 16) > 7) {
checksumAddress += address[i].toUpperCase();
checksumAddress += lowerCaseAddress[i].toUpperCase();
} else {
checksumAddress += address[i];
checksumAddress += lowerCaseAddress[i];
}
}
return checksumAddress;
Expand Down
4 changes: 2 additions & 2 deletions packages/web3-utils/src/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const SHA3_EMPTY_BYTES = '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfa

/**
*
* computes the Keccak-256 hash of the string input and returns a hexstring
* computes the Keccak-256 hash of the input and returns a hexstring
*/
export const sha3 = (data: Bytes): string | null => {
const updatedData = typeof data === 'string' && isHexStrict(data) ? hexToBytes(data) : data;
Expand All @@ -32,7 +32,7 @@ export const sha3 = (data: Bytes): string | null => {
/**
*Will calculate the sha3 of the input but does return the hash value instead of null if for example a empty string is passed.
*/
export const sha3Raw = (data: string): string => {
export const sha3Raw = (data: Bytes): string => {
const hash = sha3(data);
if (hash === null) {
return SHA3_EMPTY_BYTES;
Expand Down
1 change: 1 addition & 0 deletions packages/web3-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export * from './errors';
export * from './validation';
export * from './types';
export * from './hash';
export * from './random';
export * from './string_manipulation';
26 changes: 26 additions & 0 deletions packages/web3-utils/src/random.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { randomBytes as cryptoRandomBytes } from 'crypto';

/**
* Returns a random byte array by the given bytes size
*
* @param {Number} size
* @returns {Buffer}
*/
export const randomBytes = (byteSize: number): Buffer => {
const randomValues =
typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues
? window.crypto.getRandomValues(new Uint8Array(byteSize))
: cryptoRandomBytes(byteSize);
return Buffer.from(randomValues);
};


/**
* Returns a random hex string by the given bytes size
*
* @param {Number} size
* @returns {string}
*/
export const randomHex = (byteSize: number): string => `0x${randomBytes(byteSize).toString('hex')}`;


14 changes: 12 additions & 2 deletions packages/web3-utils/src/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ export const validateNumbersInput = (
}
};

/**
* checks input if typeof data is valid string input
*/
export const isValidString = (data: any) => typeof data === 'string';

/**
* checks input if typeof data is valid buffer input
*/
export const isBuffer = (data: any) => Buffer.isBuffer(data);

/**
* checks input for valid string, otherwise throws error
*/
Expand Down Expand Up @@ -212,7 +222,7 @@ export const checkAddressCheckSum = (data: string): boolean => {
/**
* Checks if a given string is a valid Ethereum address. It will also check the checksum, if the address has upper and lowercase letters.
*/
export const isAddress = (address: string): boolean => {
export const isAddress = (address: string, checkChecksum = true): boolean => {
// check if it has the basic requirements of an address
if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) {
return false;
Expand All @@ -222,7 +232,7 @@ export const isAddress = (address: string): boolean => {
return true;
// Otherwise check each case
}
return checkAddressCheckSum(address);
return checkChecksum ? checkAddressCheckSum(address) : true;
};

/**
Expand Down
5 changes: 5 additions & 0 deletions packages/web3-utils/test/fixtures/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,8 @@ export const toWeiInvalidData: [[any, any], string][] = [
[['data', 'kwei'], 'Invalid value given "data". Error: not a valid number.'],
[['1234', 'uwei'], 'Invalid value given "uwei". Error: invalid unit.'],
];
export const toCheckSumValidData: [string, string][] = [
['0x0089d53f703f7e0843953d48133f74ce247184c2', '0x0089d53F703f7E0843953D48133f74cE247184c2'],
['0x5fbc2b6c19ee3dd5f9af96ff337ddc89e30ceaef', '0x5FBc2b6C19EE3DD5f9Af96ff337DDC89e30ceAef'],
['0xa54D3c09E34aC96807c1CC397404bF2B98DC4eFb', '0xa54d3c09E34aC96807c1CC397404bF2B98DC4eFb'],
];
1 change: 1 addition & 0 deletions packages/web3-utils/test/fixtures/random.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const randomHexData: number[] = [2,4,8,16,32,64,128,256]
24 changes: 13 additions & 11 deletions packages/web3-utils/test/fixtures/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,19 @@ export const checkAddressCheckSumValidData: [any, boolean][] = [
['0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb', true],
];

export const isAddressValidData: [any, boolean][] = [
['0xc6d9d2cd449a754c494264e1809c50e34d64562b', true],
['c6d9d2cd449a754c494264e1809c50e34d64562b', true],
['0xE247A45c287191d435A8a5D72A7C8dc030451E9F', true],
['0xE247a45c287191d435A8a5D72A7C8dc030451E9F', false],
['0xe247a45c287191d435a8a5d72a7c8dc030451e9f', true],
['0xE247A45C287191D435A8A5D72A7C8DC030451E9F', true],
['0XE247A45C287191D435A8A5D72A7C8DC030451E9F', true],
['0123', false],
['0x12', false],
[123, false],
export const isAddressValidData: [[any, boolean], boolean][] = [
[['0xc6d9d2cd449a754c494264e1809c50e34d64562b', true], true],
[['c6d9d2cd449a754c494264e1809c50e34d64562b', true], true],
[['0xE247A45c287191d435A8a5D72A7C8dc030451E9F', true], true],
[['0xE247a45c287191d435A8a5D72A7C8dc030451E9F', true], false],
[['0xe247a45c287191d435a8a5d72a7c8dc030451e9f', true], true],
[['0xE247A45C287191D435A8A5D72A7C8DC030451E9F', true], true],
[['0XE247A45C287191D435A8A5D72A7C8DC030451E9F', true], true],
[['0xa54D3c09E34aC96807c1CC397404bF2B98DC4eFb', false], true],
[['0xa54D3c09E34aC96807c1CC397404bF2B98DC4eFb', true], false],
[['0123', true], false],
[['0x12', true], false],
[[123, true], false],
];

export const compareBlockNumbersValidData: [[Numbers, Numbers], number][] = [
Expand Down
Loading

0 comments on commit 9d7ef2f

Please sign in to comment.