From a3bfdc8858dfd89ebc7d154e62da45a6ddbdebd3 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Fri, 25 Sep 2020 14:20:15 -0700 Subject: [PATCH 01/11] add Account class and tests --- CHANGELOG.md | 23 +- README.md | 1 + docs/README.md | 1 + docs/classes/_account_.account.md | 167 +++++++++++ docs/interfaces/_account_.accountdata.md | 48 ++++ docs/modules/__types_ethjs_util_index_.md | 5 + docs/modules/_account_.md | 36 ++- docs/modules/_externals_.md | 1 - docs/modules/_hash_.md | 22 +- package.json | 2 +- src/account.ts | 156 +++++++++-- src/index.ts | 2 +- test/account.spec.ts | 327 +++++++++++++--------- typedoc.js | 6 +- 14 files changed, 606 insertions(+), 191 deletions(-) create mode 100644 docs/classes/_account_.account.md create mode 100644 docs/interfaces/_account_.accountdata.md create mode 100644 docs/modules/__types_ethjs_util_index_.md diff --git a/CHANGELOG.md b/CHANGELOG.md index b9a0e598..d4474e09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,28 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) ## [7.0.6] - [UNRELEASED] -[ ADD REFERENCES TO YOUR WORK HERE UPON PRs. PLEASE ADOPT THE VERSION IF YOUR PR REQUIRES. ] +This release adds a new `Account` class intended as a modern replacement for `ethereumjs-account`. It has a shape of `Account(nonce?: BN, balance?: BN, stateRoot?: Buffer, codeHash?: Buffer)`. + +**Instantiation** + +The static factory methods assist in creating an `Account` object from varying data types: `Object: fromAccountData`, `RLP: fromRlpSerializedAccount`, and `Array: fromValuesArray`. + +**Methods**: `isEmpty(): boolean`, `isContract(): boolean`, `serialize(): Buffer` + +Example usage: + +```typescript +import { Account, BN } from 'ethereumjs-util' + +const account = new Account( + new BN(0), // nonce, default: 0 + new BN(10).pow(new BN(18)), // balance, default: 0 + undefined, // stateRoot, default: KECCAK256_RLP (hash of RLP of null) + undefined, // codeHash, default: KECCAK256_NULL (hash of null) +) +``` + +For more info see the documentation or examples of usage in `test/account.spec.ts`. ## [7.0.5] - 2020-09-09 diff --git a/README.md b/README.md index 95733ae0..654c9561 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ assert.equal(new BN('dead', 16).add(new BN('101010', 2)), 57047) ### Modules - [account](docs/modules/_account_.md) + - Account class - Private/public key and address-related functionality (creation, validation, conversion) - [address](docs/modules/_address_.md) - Address class and type diff --git a/docs/README.md b/docs/README.md index 9a7b907a..7dff9c06 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,6 +6,7 @@ ### Modules +* ["@types/ethjs-util/index"](modules/__types_ethjs_util_index_.md) * ["account"](modules/_account_.md) * ["address"](modules/_address_.md) * ["bytes"](modules/_bytes_.md) diff --git a/docs/classes/_account_.account.md b/docs/classes/_account_.account.md new file mode 100644 index 00000000..4f3d33d8 --- /dev/null +++ b/docs/classes/_account_.account.md @@ -0,0 +1,167 @@ +[ethereumjs-util](../README.md) › ["account"](../modules/_account_.md) › [Account](_account_.account.md) + +# Class: Account + +## Hierarchy + +* **Account** + +## Index + +### Constructors + +* [constructor](_account_.account.md#constructor) + +### Properties + +* [balance](_account_.account.md#balance) +* [codeHash](_account_.account.md#codehash) +* [nonce](_account_.account.md#nonce) +* [stateRoot](_account_.account.md#stateroot) + +### Methods + +* [isContract](_account_.account.md#iscontract) +* [isEmpty](_account_.account.md#isempty) +* [serialize](_account_.account.md#serialize) +* [fromAccountData](_account_.account.md#static-fromaccountdata) +* [fromRlpSerializedAccount](_account_.account.md#static-fromrlpserializedaccount) +* [fromValuesArray](_account_.account.md#static-fromvaluesarray) + +## Constructors + +### constructor + +\+ **new Account**(`nonce`: BN‹›, `balance`: BN‹›, `stateRoot`: Buffer‹›, `codeHash`: Buffer‹›): *[Account](_account_.account.md)* + +*Defined in [account.ts:77](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L77)* + +This constructor takes the values, validates and assigns them. +Use the static factory methods to assist in creating an Account from varying data types. + +**Parameters:** + +Name | Type | Default | +------ | ------ | ------ | +`nonce` | BN‹› | new BN(0) | +`balance` | BN‹› | new BN(0) | +`stateRoot` | Buffer‹› | KECCAK256_RLP | +`codeHash` | Buffer‹› | KECCAK256_NULL | + +**Returns:** *[Account](_account_.account.md)* + +## Properties + +### balance + +• **balance**: *BN* + +*Defined in [account.ts:43](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L43)* + +___ + +### codeHash + +• **codeHash**: *Buffer* + +*Defined in [account.ts:45](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L45)* + +___ + +### nonce + +• **nonce**: *BN* + +*Defined in [account.ts:42](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L42)* + +___ + +### stateRoot + +• **stateRoot**: *Buffer* + +*Defined in [account.ts:44](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L44)* + +## Methods + +### isContract + +▸ **isContract**(): *boolean* + +*Defined in [account.ts:112](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L112)* + +Returns a `Boolean` deteremining if the account is a contract. + +**Returns:** *boolean* + +___ + +### isEmpty + +▸ **isEmpty**(): *boolean* + +*Defined in [account.ts:119](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L119)* + +Returns a `Boolean` determining if the account is empty. + +**Returns:** *boolean* + +___ + +### serialize + +▸ **serialize**(): *Buffer* + +*Defined in [account.ts:105](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L105)* + +Returns the RLP serialization of the account as a `Buffer`. + +**Returns:** *Buffer* + +___ + +### `Static` fromAccountData + +▸ **fromAccountData**(`accountData`: [AccountData](../interfaces/_account_.accountdata.md)): *[Account](_account_.account.md)‹›* + +*Defined in [account.ts:47](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L47)* + +**Parameters:** + +Name | Type | +------ | ------ | +`accountData` | [AccountData](../interfaces/_account_.accountdata.md) | + +**Returns:** *[Account](_account_.account.md)‹›* + +___ + +### `Static` fromRlpSerializedAccount + +▸ **fromRlpSerializedAccount**(`serialized`: Buffer): *[Account](_account_.account.md)‹›* + +*Defined in [account.ts:58](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L58)* + +**Parameters:** + +Name | Type | +------ | ------ | +`serialized` | Buffer | + +**Returns:** *[Account](_account_.account.md)‹›* + +___ + +### `Static` fromValuesArray + +▸ **fromValuesArray**(`values`: Buffer[]): *[Account](_account_.account.md)‹›* + +*Defined in [account.ts:68](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L68)* + +**Parameters:** + +Name | Type | +------ | ------ | +`values` | Buffer[] | + +**Returns:** *[Account](_account_.account.md)‹›* diff --git a/docs/interfaces/_account_.accountdata.md b/docs/interfaces/_account_.accountdata.md new file mode 100644 index 00000000..698b49df --- /dev/null +++ b/docs/interfaces/_account_.accountdata.md @@ -0,0 +1,48 @@ +[ethereumjs-util](../README.md) › ["account"](../modules/_account_.md) › [AccountData](_account_.accountdata.md) + +# Interface: AccountData + +## Hierarchy + +* **AccountData** + +## Index + +### Properties + +* [balance](_account_.accountdata.md#optional-balance) +* [codeHash](_account_.accountdata.md#optional-codehash) +* [nonce](_account_.accountdata.md#optional-nonce) +* [stateRoot](_account_.accountdata.md#optional-stateroot) + +## Properties + +### `Optional` balance + +• **balance**? : *BNLike* + +*Defined in [account.ts:19](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L19)* + +___ + +### `Optional` codeHash + +• **codeHash**? : *BufferLike* + +*Defined in [account.ts:21](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L21)* + +___ + +### `Optional` nonce + +• **nonce**? : *BNLike* + +*Defined in [account.ts:18](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L18)* + +___ + +### `Optional` stateRoot + +• **stateRoot**? : *BufferLike* + +*Defined in [account.ts:20](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L20)* diff --git a/docs/modules/__types_ethjs_util_index_.md b/docs/modules/__types_ethjs_util_index_.md new file mode 100644 index 00000000..45cd1a7f --- /dev/null +++ b/docs/modules/__types_ethjs_util_index_.md @@ -0,0 +1,5 @@ +[ethereumjs-util](../README.md) › ["@types/ethjs-util/index"](__types_ethjs_util_index_.md) + +# Module: "@types/ethjs-util/index" + + diff --git a/docs/modules/_account_.md b/docs/modules/_account_.md index b7a054eb..08125b1d 100644 --- a/docs/modules/_account_.md +++ b/docs/modules/_account_.md @@ -4,6 +4,14 @@ ## Index +### Classes + +* [Account](../classes/_account_.account.md) + +### Interfaces + +* [AccountData](../interfaces/_account_.accountdata.md) + ### Variables * [publicToAddress](_account_.md#const-publictoaddress) @@ -30,7 +38,7 @@ • **publicToAddress**: *pubToAddress* = pubToAddress -*Defined in [account.ts:160](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L160)* +*Defined in [account.ts:257](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L257)* ## Functions @@ -38,7 +46,7 @@ ▸ **generateAddress**(`from`: Buffer, `nonce`: Buffer): *Buffer* -*Defined in [account.ts:82](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L82)* +*Defined in [account.ts:179](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L179)* Generates an address of a newly created contract. @@ -57,7 +65,7 @@ ___ ▸ **generateAddress2**(`from`: Buffer, `salt`: Buffer, `initCode`: Buffer): *Buffer* -*Defined in [account.ts:103](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L103)* +*Defined in [account.ts:200](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L200)* Generates an address for a contract created using CREATE2. @@ -77,7 +85,7 @@ ___ ▸ **importPublic**(`publicKey`: Buffer): *Buffer* -*Defined in [account.ts:183](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L183)* +*Defined in [account.ts:280](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L280)* Converts a public key to the Ethereum format. @@ -95,7 +103,7 @@ ___ ▸ **isValidAddress**(`hexAddress`: string): *boolean* -*Defined in [account.ts:21](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L21)* +*Defined in [account.ts:127](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L127)* Checks if the address is a valid. Accepts checksummed addresses too. @@ -113,7 +121,7 @@ ___ ▸ **isValidChecksumAddress**(`hexAddress`: string, `eip1191ChainId?`: undefined | number): *boolean* -*Defined in [account.ts:70](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L70)* +*Defined in [account.ts:167](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L167)* Checks if the address is a valid checksummed address. @@ -134,7 +142,7 @@ ___ ▸ **isValidPrivate**(`privateKey`: Buffer): *boolean* -*Defined in [account.ts:121](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L121)* +*Defined in [account.ts:218](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L218)* Checks if the private key satisfies the rules of the curve secp256k1. @@ -152,7 +160,7 @@ ___ ▸ **isValidPublic**(`publicKey`: Buffer, `sanitize`: boolean): *boolean* -*Defined in [account.ts:131](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L131)* +*Defined in [account.ts:228](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L228)* Checks if the public key satisfies the rules of the curve secp256k1 and the requirements of Ethereum. @@ -172,7 +180,7 @@ ___ ▸ **isZeroAddress**(`hexAddress`: string): *boolean* -*Defined in [account.ts:29](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L29)* +*Defined in [account.ts:300](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L300)* Checks if a given address is a zero address. @@ -190,7 +198,7 @@ ___ ▸ **privateToAddress**(`privateKey`: Buffer): *Buffer* -*Defined in [account.ts:166](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L166)* +*Defined in [account.ts:263](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L263)* Returns the ethereum address of a given private key. @@ -208,7 +216,7 @@ ___ ▸ **privateToPublic**(`privateKey`: Buffer): *Buffer* -*Defined in [account.ts:174](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L174)* +*Defined in [account.ts:271](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L271)* Returns the ethereum public key of a given private key. @@ -226,7 +234,7 @@ ___ ▸ **pubToAddress**(`pubKey`: Buffer, `sanitize`: boolean): *Buffer* -*Defined in [account.ts:151](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L151)* +*Defined in [account.ts:248](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L248)* Returns the ethereum address of a given public key. Accepts "Ethereum public keys" and SEC1 encoded keys. @@ -246,7 +254,7 @@ ___ ▸ **toChecksumAddress**(`hexAddress`: string, `eip1191ChainId?`: undefined | number): *string* -*Defined in [account.ts:45](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L45)* +*Defined in [account.ts:142](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L142)* Returns a checksummed address. @@ -272,7 +280,7 @@ ___ ▸ **zeroAddress**(): *string* -*Defined in [account.ts:12](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L12)* +*Defined in [account.ts:291](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L291)* Returns a zero address. diff --git a/docs/modules/_externals_.md b/docs/modules/_externals_.md index 2d9a6773..6381f333 100644 --- a/docs/modules/_externals_.md +++ b/docs/modules/_externals_.md @@ -3,5 +3,4 @@ # Module: "externals" Re-exports commonly used modules: -* Adds [`ethjs-util`](https://github.com/ethjs/ethjs-util) methods. * Exports [`BN`](https://github.com/indutny/bn.js), [`rlp`](https://github.com/ethereumjs/rlp). diff --git a/docs/modules/_hash_.md b/docs/modules/_hash_.md index da56fa04..5f515614 100644 --- a/docs/modules/_hash_.md +++ b/docs/modules/_hash_.md @@ -44,7 +44,7 @@ ___ ▸ **keccak256**(`a`: Buffer): *Buffer* -*Defined in [hash.ts:24](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L24)* +*Defined in [hash.ts:38](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L38)* Creates Keccak-256 hash of the input, alias for keccak(a, 256). @@ -62,7 +62,7 @@ ___ ▸ **keccakFromArray**(`a`: number[], `bits`: number): *Buffer‹›* -*Defined in [hash.ts:54](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L54)* +*Defined in [hash.ts:68](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L68)* Creates Keccak hash of a number array input @@ -81,7 +81,7 @@ ___ ▸ **keccakFromHexString**(`a`: string, `bits`: number): *Buffer‹›* -*Defined in [hash.ts:44](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L44)* +*Defined in [hash.ts:58](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L58)* Creates Keccak hash of an 0x-prefixed string input @@ -100,7 +100,7 @@ ___ ▸ **keccakFromString**(`a`: string, `bits`: number): *Buffer‹›* -*Defined in [hash.ts:33](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L33)* +*Defined in [hash.ts:47](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L47)* Creates Keccak hash of a utf-8 string input @@ -119,7 +119,7 @@ ___ ▸ **ripemd160**(`a`: Buffer, `padded`: boolean): *Buffer* -*Defined in [hash.ts:102](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L102)* +*Defined in [hash.ts:116](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L116)* Creates RIPEMD160 hash of a Buffer input. @@ -138,7 +138,7 @@ ___ ▸ **ripemd160FromArray**(`a`: number[], `padded`: boolean): *Buffer* -*Defined in [hash.ts:122](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L122)* +*Defined in [hash.ts:136](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L136)* Creates RIPEMD160 hash of a number[] input. @@ -157,7 +157,7 @@ ___ ▸ **ripemd160FromString**(`a`: string, `padded`: boolean): *Buffer* -*Defined in [hash.ts:112](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L112)* +*Defined in [hash.ts:126](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L126)* Creates RIPEMD160 hash of a string input. @@ -176,7 +176,7 @@ ___ ▸ **rlphash**(`a`: rlp.Input): *Buffer* -*Defined in [hash.ts:148](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L148)* +*Defined in [hash.ts:162](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L162)* Creates SHA-3 hash of the RLP encoded version of the input. @@ -194,7 +194,7 @@ ___ ▸ **sha256**(`a`: Buffer): *Buffer* -*Defined in [hash.ts:63](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L63)* +*Defined in [hash.ts:77](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L77)* Creates SHA256 hash of a Buffer input. @@ -212,7 +212,7 @@ ___ ▸ **sha256FromArray**(`a`: number[]): *Buffer* -*Defined in [hash.ts:81](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L81)* +*Defined in [hash.ts:95](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L95)* Creates SHA256 hash of a number[] input. @@ -230,7 +230,7 @@ ___ ▸ **sha256FromString**(`a`: string): *Buffer* -*Defined in [hash.ts:72](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L72)* +*Defined in [hash.ts:86](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/hash.ts#L86)* Creates SHA256 hash of a string input. diff --git a/package.json b/package.json index 2ee6ec6d..da275c6e 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "lint:fix": "ethereumjs-config-lint-fix", "test": "npm run lint && npm run test:node && npm run test:browser", "test:browser": "karma start karma.conf.js", - "test:node": "nyc --reporter=lcov mocha 'test/*.spec.ts' -- --require ts-node/register", + "test:node": "nyc --reporter=lcov mocha --require ts-node/register 'test/*.spec.ts'", "tsc": "ethereumjs-config-tsc", "tslint": "ethereumjs-config-tslint", "tslint:fix": "ethereumjs-config-tslint-fix" diff --git a/src/account.ts b/src/account.ts index 125ef2d9..ed2f0aed 100644 --- a/src/account.ts +++ b/src/account.ts @@ -1,23 +1,124 @@ -import * as ethjsUtil from 'ethjs-util' +import * as assert from 'assert' +import * as BN from 'bn.js' +import * as rlp from 'rlp' +import { stripHexPrefix } from 'ethjs-util' +import { KECCAK256_RLP, KECCAK256_NULL } from './constants' +import { zeros, bufferToHex, toBuffer, unpadBuffer } from './bytes' +import { keccak, keccak256, keccakFromString, rlphash } from './hash' +import { assertIsHexString, assertIsBuffer } from './helpers' + const { privateKeyVerify, publicKeyCreate, publicKeyVerify, publicKeyConvert, } = require('ethereum-cryptography/secp256k1') -import * as assert from 'assert' -import * as BN from 'bn.js' -import { zeros, bufferToHex } from './bytes' -import { keccak, keccak256, keccakFromString, rlphash } from './hash' -import { assertIsHexString, assertIsBuffer } from './helpers' -/** - * Returns a zero address. - */ -export const zeroAddress = function(): string { - const addressLength = 20 - const addr = zeros(addressLength) - return bufferToHex(addr) +export interface AccountData { + nonce?: BNLike + balance?: BNLike + stateRoot?: BufferLike + codeHash?: BufferLike +} + +type BNLike = BN | string | number + +type BufferLike = Buffer | TransformableToBuffer | PrefixedHexString | number + +type PrefixedHexString = string + +interface TransformableToBuffer { + toBuffer(): Buffer +} + +/* Helper for Account.serialize() */ +function bnToRlp(value: BN): Buffer { + // using `bn.toArrayLike(Buffer)` instead of `bn.toBuffer()` + // for compatibility with browserify and similar tools + return unpadBuffer(value.toArrayLike(Buffer)) +} + +export class Account { + nonce: BN + balance: BN + stateRoot: Buffer + codeHash: Buffer + + static fromAccountData(accountData: AccountData) { + const { nonce, balance, stateRoot, codeHash } = accountData + + return new Account( + nonce ? new BN(toBuffer(nonce)) : undefined, + balance ? new BN(toBuffer(balance)) : undefined, + stateRoot ? toBuffer(stateRoot) : undefined, + codeHash ? toBuffer(codeHash) : undefined, + ) + } + + public static fromRlpSerializedAccount(serialized: Buffer) { + const values = rlp.decode(serialized) + + if (!Array.isArray(values)) { + throw new Error('Invalid serialized account input. Must be array') + } + + return this.fromValuesArray(values) + } + + public static fromValuesArray(values: Buffer[]) { + const [nonce, balance, stateRoot, codeHash] = values + + return new Account( + nonce ? new BN(nonce) : undefined, + balance ? new BN(balance) : undefined, + stateRoot, + codeHash, + ) + } + + /** + * This constructor takes the values, validates and assigns them. + * Use the static factory methods to assist in creating an Account from varying data types. + */ + constructor( + nonce = new BN(0), + balance = new BN(0), + stateRoot = KECCAK256_RLP, + codeHash = KECCAK256_NULL, + ) { + if (stateRoot.length !== 32) { + throw new Error('stateRoot must have a length of 32') + } + if (codeHash.length !== 32) { + throw new Error('codeHash must have a length of 32') + } + + this.nonce = nonce + this.balance = balance + this.stateRoot = stateRoot + this.codeHash = codeHash + } + + /** + * Returns the RLP serialization of the account as a `Buffer`. + */ + serialize(): Buffer { + return rlp.encode([bnToRlp(this.nonce), bnToRlp(this.balance), this.stateRoot, this.codeHash]) + } + + /** + * Returns a `Boolean` deteremining if the account is a contract. + */ + isContract(): boolean { + return !this.codeHash.equals(KECCAK256_NULL) + } + + /** + * Returns a `Boolean` determining if the account is empty. + */ + isEmpty(): boolean { + return this.balance.isZero() && this.nonce.isZero() && this.codeHash.equals(KECCAK256_NULL) + } } /** @@ -28,15 +129,6 @@ export const isValidAddress = function(hexAddress: string): boolean { return /^0x[0-9a-fA-F]{40}$/.test(hexAddress) } -/** - * Checks if a given address is a zero address. - */ -export const isZeroAddress = function(hexAddress: string): boolean { - assertIsHexString(hexAddress) - const zeroAddr = zeroAddress() - return zeroAddr === hexAddress -} - /** * Returns a checksummed address. * @@ -49,7 +141,7 @@ export const isZeroAddress = function(hexAddress: string): boolean { */ export const toChecksumAddress = function(hexAddress: string, eip1191ChainId?: number): string { assertIsHexString(hexAddress) - const address = ethjsUtil.stripHexPrefix(hexAddress).toLowerCase() + const address = stripHexPrefix(hexAddress).toLowerCase() const prefix = eip1191ChainId !== undefined ? eip1191ChainId.toString() + '0x' : '' @@ -192,3 +284,21 @@ export const importPublic = function(publicKey: Buffer): Buffer { } return publicKey } + +/** + * Returns a zero address. + */ +export const zeroAddress = function(): string { + const addressLength = 20 + const addr = zeros(addressLength) + return bufferToHex(addr) +} + +/** + * Checks if a given address is a zero address. + */ +export const isZeroAddress = function(hexAddress: string): boolean { + assertIsHexString(hexAddress) + const zeroAddr = zeroAddress() + return zeroAddr === hexAddress +} diff --git a/src/index.ts b/src/index.ts index bf462b49..ff60d581 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ export * from './constants' /** - * Public-key cryptography (secp256k1) and addresses + * Account class and helper functions */ export * from './account' diff --git a/test/account.spec.ts b/test/account.spec.ts index 44df2c17..241768ad 100644 --- a/test/account.spec.ts +++ b/test/account.spec.ts @@ -1,6 +1,8 @@ import * as assert from 'assert' import * as BN from 'bn.js' +import * as rlp from 'rlp' import { + Account, isValidPrivate, isValidPublic, importPublic, @@ -16,6 +18,187 @@ import { } from '../src' const eip1014Testdata = require('./testdata/eip1014Examples.json') +describe('Account', function() { + describe('empty constructor', function() { + const account = new Account() + it('should have zero nonce', function() { + assert.ok(account.nonce.isZero()) + }) + it('should have zero balance', function() { + assert.ok(account.balance.isZero()) + }) + it('should have stateRoot equal to KECCAK256_RLP', function() { + assert.ok( + account.stateRoot.toString('hex'), + '56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + ) + }) + it('should have codeHash equal to KECCAK256_NULL', function() { + assert.equal( + account.codeHash.toString('hex'), + 'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', + ) + }) + }) + + describe('from Array data', function() { + const raw = [ + '0x02', // nonce + '0x0384', // balance + '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', // stateRoot + '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', // codeHash + ] + const account = Account.fromValuesArray(raw.map(toBuffer)) + it('should have correct nonce', function() { + assert.ok(account.nonce.eqn(2)) + }) + it('should have correct balance', function() { + assert.ok(account.balance.eqn(900)) + }) + it('should have correct stateRoot', function() { + assert.equal( + account.stateRoot.toString('hex'), + '56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + ) + }) + it('should have correct codeHash', function() { + assert.equal( + account.codeHash.toString('hex'), + 'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', + ) + }) + }) + + describe('from Object data', function() { + const raw = { + nonce: '0x02', + balance: '0x0384', + stateRoot: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + codeHash: '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', + } + const account = Account.fromAccountData(raw) + it('should have correct nonce', function() { + assert.ok(account.nonce.eqn(2)) + }) + it('should have correct balance', function() { + assert.ok(account.balance.eqn(900)) + }) + it('should have correct stateRoot', function() { + assert.equal( + account.stateRoot.toString('hex'), + '56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + ) + }) + it('should have correct codeHash', function() { + assert.equal( + account.codeHash.toString('hex'), + 'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', + ) + }) + }) + + describe('from RLP data', function() { + const accountRlp = Buffer.from( + 'f84602820384a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', + 'hex', + ) + const account = Account.fromRlpSerializedAccount(accountRlp) + it('should have correct nonce', function() { + assert.ok(account.nonce.eqn(2)) + }) + it('should have correct balance', function() { + assert.ok(account.balance.eqn(900)) + }) + it('should have correct stateRoot', function() { + assert.equal( + account.stateRoot.toString('hex'), + '56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + ) + }) + it('should have correct codeHash', function() { + assert.equal( + account.codeHash.toString('hex'), + 'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', + ) + }) + }) + + describe('serialize', function() { + const raw = { + nonce: '0x01', + balance: '0x42', + stateRoot: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + codeHash: '0xc5d2461236f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', + } + const account = Account.fromAccountData(raw) + const accountRlp = rlp.encode([raw.nonce, raw.balance, raw.stateRoot, raw.codeHash]) + it('should serialize correctly', function() { + assert.ok(account.serialize().equals(accountRlp)) + }) + }) + + describe('isContract', function() { + it('should return false for a non-contract account', function() { + const accountRlp = Buffer.from( + 'f84602820384a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', + 'hex', + ) + const account = Account.fromRlpSerializedAccount(accountRlp) + assert.equal(account.isContract(), false) + }) + + it('should return true for a contract account', function() { + const raw = { + nonce: '0x01', + balance: '0x0042', + stateRoot: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + codeHash: '0xc5d2461236f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', + } + const account = Account.fromAccountData(raw) + assert.ok(account.isContract()) + }) + }) + + describe('isEmpty', function() { + it('should return true for an empty account', function() { + const account = new Account() + assert.ok(account.isEmpty()) + }) + + it('should return false for a non-empty account', function() { + const raw = { + nonce: '0x01', + balance: '0x0042', + stateRoot: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + codeHash: '0xc5d2461236f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', + } + const account = Account.fromAccountData(raw) + assert.equal(account.isEmpty(), false) + }) + }) + + describe('validation', function() { + it('should only accept length 32 buffer for stateRoot', function() { + assert.throws(() => { + new Account(undefined, undefined, Buffer.from('hey'), undefined) + }) + }) + + it('should only accept length 32 buffer for codeHash', function() { + assert.throws(() => { + new Account(undefined, undefined, undefined, Buffer.from('hey')) + }) + }) + + it('should only accept an array in fromRlpSerializedAccount', function() { + const data = { balance: new BN(5) } + assert.throws(() => { + Account.fromRlpSerializedAccount(data as any) + }) + }) + }) +}) + describe('isValidPrivate', function() { const SECP256K1_N = new BN('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 16) it('should fail on short input', function() { @@ -196,112 +379,16 @@ describe('privateToPublic', function() { it('should produce a public key given a private key', function() { const pubKey = '3a443d8381a6798a70c6ff9304bdc8cb0163c23211d11628fae52ef9e0dca11a001cf066d56a8156fc201cd5df8a36ef694eecd258903fca7086c1fae7441e1d' - const privateKey = Buffer.from([ - 234, - 84, - 189, - 197, - 45, - 22, - 63, - 136, - 201, - 58, - 176, - 97, - 87, - 130, - 207, - 113, - 138, - 46, - 251, - 158, - 81, - 167, - 152, - 154, - 171, - 27, - 8, - 6, - 126, - 156, - 28, - 95, - ]) + // prettier-ignore + const privateKey = Buffer.from([234, 84, 189, 197, 45, 22, 63, 136, 201, 58, 176, 97, 87, 130, 207, 113, 138, 46, 251, 158, 81, 167, 152, 154, 171, 27, 8, 6, 126, 156, 28, 95,]) const r: any = privateToPublic(privateKey).toString('hex') assert.equal(r.toString('hex'), pubKey) }) it("shouldn't produce a public key given an invalid private key", function() { - const privateKey1 = Buffer.from([ - 234, - 84, - 189, - 197, - 45, - 22, - 63, - 136, - 201, - 58, - 176, - 97, - 87, - 130, - 207, - 113, - 138, - 46, - 251, - 158, - 81, - 167, - 152, - 154, - 171, - 27, - 8, - 6, - 126, - 156, - 28, - 95, - 42, - ]) - const privateKey2 = Buffer.from([ - 234, - 84, - 189, - 197, - 45, - 22, - 63, - 136, - 201, - 58, - 176, - 97, - 87, - 130, - 207, - 113, - 138, - 46, - 251, - 158, - 81, - 167, - 152, - 154, - 171, - 27, - 8, - 6, - 126, - 156, - 28, - ]) + // prettier-ignore + const privateKey1 = Buffer.from([234, 84, 189, 197, 45, 22, 63, 136, 201, 58, 176, 97, 87, 130, 207, 113, 138, 46, 251, 158, 81, 167, 152, 154, 171, 27, 8, 6, 126, 156, 28, 95, 42,]) + // prettier-ignore + const privateKey2 = Buffer.from([234, 84, 189, 197, 45, 22, 63, 136, 201, 58, 176, 97, 87, 130, 207, 113, 138, 46, 251, 158, 81, 167, 152, 154, 171, 27, 8, 6, 126, 156, 28,]) assert.throws(function() { privateToPublic(privateKey1) }) @@ -325,40 +412,8 @@ describe('privateToAddress', function() { it('should produce an address given a private key', function() { const address = '2f015c60e0be116b1f0cd534704db9c92118fb6a' // Our private key - const privateKey = Buffer.from([ - 234, - 84, - 189, - 197, - 45, - 22, - 63, - 136, - 201, - 58, - 176, - 97, - 87, - 130, - 207, - 113, - 138, - 46, - 251, - 158, - 81, - 167, - 152, - 154, - 171, - 27, - 8, - 6, - 126, - 156, - 28, - 95, - ]) + // prettier-ignore + const privateKey = Buffer.from([234, 84, 189, 197, 45, 22, 63, 136, 201, 58, 176, 97, 87, 130, 207, 113, 138, 46, 251, 158, 81, 167, 152, 154, 171, 27, 8, 6, 126, 156, 28, 95,]) const r: any = privateToAddress(privateKey).toString('hex') assert.equal(r.toString('hex'), address) }) diff --git a/typedoc.js b/typedoc.js index 3bf7fa03..c8e893a9 100644 --- a/typedoc.js +++ b/typedoc.js @@ -5,8 +5,8 @@ module.exports = { plugin: 'typedoc-plugin-markdown', readme: 'none', gitRevision: 'master', - exclude: '**/*+(index|.spec|.e2e).ts', + exclude: ['src/index.ts', 'test/*.ts'], excludeNotExported: true, - // excludePrivate: true, - // excludeProtected: true, + excludePrivate: true, + excludeProtected: true, } From 3f858dbc5e58e7e86dbbe365b70b48244407b2d5 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Sun, 27 Sep 2020 20:06:32 -0700 Subject: [PATCH 02/11] export helpful types --- src/account.ts | 48 ++++++++++++++++-------------------------------- src/helpers.ts | 24 ++++++++++++++++++------ src/index.ts | 7 ++++++- src/types.ts | 11 +++++++++++ 4 files changed, 51 insertions(+), 39 deletions(-) create mode 100644 src/types.ts diff --git a/src/account.ts b/src/account.ts index ed2f0aed..cf5eca84 100644 --- a/src/account.ts +++ b/src/account.ts @@ -3,9 +3,10 @@ import * as BN from 'bn.js' import * as rlp from 'rlp' import { stripHexPrefix } from 'ethjs-util' import { KECCAK256_RLP, KECCAK256_NULL } from './constants' -import { zeros, bufferToHex, toBuffer, unpadBuffer } from './bytes' +import { zeros, bufferToHex, toBuffer } from './bytes' import { keccak, keccak256, keccakFromString, rlphash } from './hash' -import { assertIsHexString, assertIsBuffer } from './helpers' +import { assertIsHexString, assertIsBuffer, bnToRlp } from './helpers' +import { BNLike, BufferLike } from './types' const { privateKeyVerify, @@ -21,23 +22,6 @@ export interface AccountData { codeHash?: BufferLike } -type BNLike = BN | string | number - -type BufferLike = Buffer | TransformableToBuffer | PrefixedHexString | number - -type PrefixedHexString = string - -interface TransformableToBuffer { - toBuffer(): Buffer -} - -/* Helper for Account.serialize() */ -function bnToRlp(value: BN): Buffer { - // using `bn.toArrayLike(Buffer)` instead of `bn.toBuffer()` - // for compatibility with browserify and similar tools - return unpadBuffer(value.toArrayLike(Buffer)) -} - export class Account { nonce: BN balance: BN @@ -124,7 +108,7 @@ export class Account { /** * Checks if the address is a valid. Accepts checksummed addresses too. */ -export const isValidAddress = function(hexAddress: string): boolean { +export const isValidAddress = function (hexAddress: string): boolean { assertIsHexString(hexAddress) return /^0x[0-9a-fA-F]{40}$/.test(hexAddress) } @@ -139,7 +123,7 @@ export const isValidAddress = function(hexAddress: string): boolean { * WARNING: Checksums with and without the chainId will differ. As of 2019-06-26, the most commonly * used variation in Ethereum was without the chainId. This may change in the future. */ -export const toChecksumAddress = function(hexAddress: string, eip1191ChainId?: number): string { +export const toChecksumAddress = function (hexAddress: string, eip1191ChainId?: number): string { assertIsHexString(hexAddress) const address = stripHexPrefix(hexAddress).toLowerCase() @@ -164,7 +148,7 @@ export const toChecksumAddress = function(hexAddress: string, eip1191ChainId?: n * * See toChecksumAddress' documentation for details about the eip1191ChainId parameter. */ -export const isValidChecksumAddress = function( +export const isValidChecksumAddress = function ( hexAddress: string, eip1191ChainId?: number, ): boolean { @@ -176,7 +160,7 @@ export const isValidChecksumAddress = function( * @param from The address which is creating this new address * @param nonce The nonce of the from account */ -export const generateAddress = function(from: Buffer, nonce: Buffer): Buffer { +export const generateAddress = function (from: Buffer, nonce: Buffer): Buffer { assertIsBuffer(from) assertIsBuffer(nonce) const nonceBN = new BN(nonce) @@ -197,7 +181,7 @@ export const generateAddress = function(from: Buffer, nonce: Buffer): Buffer { * @param salt A salt * @param initCode The init code of the contract being created */ -export const generateAddress2 = function(from: Buffer, salt: Buffer, initCode: Buffer): Buffer { +export const generateAddress2 = function (from: Buffer, salt: Buffer, initCode: Buffer): Buffer { assertIsBuffer(from) assertIsBuffer(salt) assertIsBuffer(initCode) @@ -215,7 +199,7 @@ export const generateAddress2 = function(from: Buffer, salt: Buffer, initCode: B /** * Checks if the private key satisfies the rules of the curve secp256k1. */ -export const isValidPrivate = function(privateKey: Buffer): boolean { +export const isValidPrivate = function (privateKey: Buffer): boolean { return privateKeyVerify(privateKey) } @@ -225,7 +209,7 @@ export const isValidPrivate = function(privateKey: Buffer): boolean { * @param publicKey The two points of an uncompressed key, unless sanitize is enabled * @param sanitize Accept public keys in other formats */ -export const isValidPublic = function(publicKey: Buffer, sanitize: boolean = false): boolean { +export const isValidPublic = function (publicKey: Buffer, sanitize: boolean = false): boolean { assertIsBuffer(publicKey) if (publicKey.length === 64) { // Convert to SEC1 for secp256k1 @@ -245,7 +229,7 @@ export const isValidPublic = function(publicKey: Buffer, sanitize: boolean = fal * @param pubKey The two points of an uncompressed key, unless sanitize is enabled * @param sanitize Accept public keys in other formats */ -export const pubToAddress = function(pubKey: Buffer, sanitize: boolean = false): Buffer { +export const pubToAddress = function (pubKey: Buffer, sanitize: boolean = false): Buffer { assertIsBuffer(pubKey) if (sanitize && pubKey.length !== 64) { pubKey = Buffer.from(publicKeyConvert(pubKey, false).slice(1)) @@ -260,7 +244,7 @@ export const publicToAddress = pubToAddress * Returns the ethereum address of a given private key. * @param privateKey A private key must be 256 bits wide */ -export const privateToAddress = function(privateKey: Buffer): Buffer { +export const privateToAddress = function (privateKey: Buffer): Buffer { return publicToAddress(privateToPublic(privateKey)) } @@ -268,7 +252,7 @@ export const privateToAddress = function(privateKey: Buffer): Buffer { * Returns the ethereum public key of a given private key. * @param privateKey A private key must be 256 bits wide */ -export const privateToPublic = function(privateKey: Buffer): Buffer { +export const privateToPublic = function (privateKey: Buffer): Buffer { assertIsBuffer(privateKey) // skip the type flag and use the X, Y points return Buffer.from(publicKeyCreate(privateKey, false)).slice(1) @@ -277,7 +261,7 @@ export const privateToPublic = function(privateKey: Buffer): Buffer { /** * Converts a public key to the Ethereum format. */ -export const importPublic = function(publicKey: Buffer): Buffer { +export const importPublic = function (publicKey: Buffer): Buffer { assertIsBuffer(publicKey) if (publicKey.length !== 64) { publicKey = Buffer.from(publicKeyConvert(publicKey, false).slice(1)) @@ -288,7 +272,7 @@ export const importPublic = function(publicKey: Buffer): Buffer { /** * Returns a zero address. */ -export const zeroAddress = function(): string { +export const zeroAddress = function (): string { const addressLength = 20 const addr = zeros(addressLength) return bufferToHex(addr) @@ -297,7 +281,7 @@ export const zeroAddress = function(): string { /** * Checks if a given address is a zero address. */ -export const isZeroAddress = function(hexAddress: string): boolean { +export const isZeroAddress = function (hexAddress: string): boolean { assertIsHexString(hexAddress) const zeroAddr = zeroAddress() return zeroAddr === hexAddress diff --git a/src/helpers.ts b/src/helpers.ts index 5f48ae39..2c027d75 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,11 +1,13 @@ -import * as ethjsUtil from 'ethjs-util' +import * as BN from 'bn.js' +import { isHexString } from 'ethjs-util' +import { unpadBuffer } from './bytes' /** * Throws if a string is not hex prefixed * @param {string} input string to check hex prefix of */ -export const assertIsHexString = function(input: string): void { - if (!ethjsUtil.isHexString(input)) { +export const assertIsHexString = function (input: string): void { + if (!isHexString(input)) { const msg = `This method only supports 0x-prefixed hex strings but input was: ${input}` throw new Error(msg) } @@ -15,7 +17,7 @@ export const assertIsHexString = function(input: string): void { * Throws if input is not a buffer * @param {Buffer} input value to check */ -export const assertIsBuffer = function(input: Buffer): void { +export const assertIsBuffer = function (input: Buffer): void { if (!Buffer.isBuffer(input)) { const msg = `This method only supports Buffer but input was: ${input}` throw new Error(msg) @@ -26,7 +28,7 @@ export const assertIsBuffer = function(input: Buffer): void { * Throws if input is not an array * @param {number[]} input value to check */ -export const assertIsArray = function(input: number[]): void { +export const assertIsArray = function (input: number[]): void { if (!Array.isArray(input)) { const msg = `This method only supports number arrays but input was: ${input}` throw new Error(msg) @@ -37,9 +39,19 @@ export const assertIsArray = function(input: number[]): void { * Throws if input is not a string * @param {string} input value to check */ -export const assertIsString = function(input: string): void { +export const assertIsString = function (input: string): void { if (typeof input !== 'string') { const msg = `This method only supports strings but input was: ${input}` throw new Error(msg) } } + +/** + * Convert value from BN to RLP (unpadded buffer) + * @param value value to convert + */ +export function bnToRlp(value: BN): Buffer { + // Using `bn.toArrayLike(Buffer)` instead of `bn.toBuffer()` + // for compatibility with browserify and similar tools + return unpadBuffer(value.toArrayLike(Buffer)) +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index ff60d581..971a2876 100644 --- a/src/index.ts +++ b/src/index.ts @@ -39,7 +39,12 @@ export * from './object' */ export * from './externals' +/** + * Helpful TypeScript types + */ +export * from './types' + /** * Export ethjs-util methods */ -export * from 'ethjs-util' +export * from 'ethjs-util' \ No newline at end of file diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..02618431 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,11 @@ +import * as BN from 'bn.js' + +export type BNLike = BN | string | number + +export type BufferLike = Buffer | TransformableToBuffer | PrefixedHexString | number + +export type PrefixedHexString = string + +export interface TransformableToBuffer { + toBuffer(): Buffer +} \ No newline at end of file From cfe8a9a13d94a4d2c9ac05d2e590261f7b9a3f5b Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Sun, 27 Sep 2020 20:14:55 -0700 Subject: [PATCH 03/11] check stateRoot in isEmpty --- src/account.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/account.ts b/src/account.ts index cf5eca84..af9dc324 100644 --- a/src/account.ts +++ b/src/account.ts @@ -101,7 +101,7 @@ export class Account { * Returns a `Boolean` determining if the account is empty. */ isEmpty(): boolean { - return this.balance.isZero() && this.nonce.isZero() && this.codeHash.equals(KECCAK256_NULL) + return this.balance.isZero() && this.nonce.isZero() && this.stateRoot.equals(KECCAK256_RLP) && this.codeHash.equals(KECCAK256_NULL) } } From 6c89111e4b60b4fd3a1d98205199d6d274b61959 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Sun, 27 Sep 2020 20:15:42 -0700 Subject: [PATCH 04/11] use hex strings for private keys --- test/account.spec.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/test/account.spec.ts b/test/account.spec.ts index 241768ad..2548fbfa 100644 --- a/test/account.spec.ts +++ b/test/account.spec.ts @@ -379,16 +379,22 @@ describe('privateToPublic', function() { it('should produce a public key given a private key', function() { const pubKey = '3a443d8381a6798a70c6ff9304bdc8cb0163c23211d11628fae52ef9e0dca11a001cf066d56a8156fc201cd5df8a36ef694eecd258903fca7086c1fae7441e1d' - // prettier-ignore - const privateKey = Buffer.from([234, 84, 189, 197, 45, 22, 63, 136, 201, 58, 176, 97, 87, 130, 207, 113, 138, 46, 251, 158, 81, 167, 152, 154, 171, 27, 8, 6, 126, 156, 28, 95,]) + const privateKey = Buffer.from( + 'ea54bdc52d163f88c93ab0615782cf718a2efb9e51a7989aab1b08067e9c1c5f', + 'hex', + ) const r: any = privateToPublic(privateKey).toString('hex') assert.equal(r.toString('hex'), pubKey) }) it("shouldn't produce a public key given an invalid private key", function() { - // prettier-ignore - const privateKey1 = Buffer.from([234, 84, 189, 197, 45, 22, 63, 136, 201, 58, 176, 97, 87, 130, 207, 113, 138, 46, 251, 158, 81, 167, 152, 154, 171, 27, 8, 6, 126, 156, 28, 95, 42,]) - // prettier-ignore - const privateKey2 = Buffer.from([234, 84, 189, 197, 45, 22, 63, 136, 201, 58, 176, 97, 87, 130, 207, 113, 138, 46, 251, 158, 81, 167, 152, 154, 171, 27, 8, 6, 126, 156, 28,]) + const privateKey1 = Buffer.from( + 'ea54bdc52d163f88c93ab0615782cf718a2efb9e51a7989aab1b08067e9c1c5f2a', + 'hex', + ) + const privateKey2 = Buffer.from( + 'ea54bdc52d163f88c93ab0615782cf718a2efb9e51a7989aab1b08067e9c1c', + 'hex', + ) assert.throws(function() { privateToPublic(privateKey1) }) @@ -412,8 +418,10 @@ describe('privateToAddress', function() { it('should produce an address given a private key', function() { const address = '2f015c60e0be116b1f0cd534704db9c92118fb6a' // Our private key - // prettier-ignore - const privateKey = Buffer.from([234, 84, 189, 197, 45, 22, 63, 136, 201, 58, 176, 97, 87, 130, 207, 113, 138, 46, 251, 158, 81, 167, 152, 154, 171, 27, 8, 6, 126, 156, 28, 95,]) + const privateKey = Buffer.from( + 'ea54bdc52d163f88c93ab0615782cf718a2efb9e51a7989aab1b08067e9c1c5f', + 'hex', + ) const r: any = privateToAddress(privateKey).toString('hex') assert.equal(r.toString('hex'), address) }) From 18e90bff7b301e030567a311a2796610d7b5d327 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Sun, 27 Sep 2020 20:15:47 -0700 Subject: [PATCH 05/11] lint --- src/account.ts | 33 +++++++++++++++++++-------------- src/helpers.ts | 10 +++++----- src/index.ts | 2 +- src/types.ts | 4 ++-- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/account.ts b/src/account.ts index af9dc324..77fae23f 100644 --- a/src/account.ts +++ b/src/account.ts @@ -101,14 +101,19 @@ export class Account { * Returns a `Boolean` determining if the account is empty. */ isEmpty(): boolean { - return this.balance.isZero() && this.nonce.isZero() && this.stateRoot.equals(KECCAK256_RLP) && this.codeHash.equals(KECCAK256_NULL) + return ( + this.balance.isZero() && + this.nonce.isZero() && + this.stateRoot.equals(KECCAK256_RLP) && + this.codeHash.equals(KECCAK256_NULL) + ) } } /** * Checks if the address is a valid. Accepts checksummed addresses too. */ -export const isValidAddress = function (hexAddress: string): boolean { +export const isValidAddress = function(hexAddress: string): boolean { assertIsHexString(hexAddress) return /^0x[0-9a-fA-F]{40}$/.test(hexAddress) } @@ -123,7 +128,7 @@ export const isValidAddress = function (hexAddress: string): boolean { * WARNING: Checksums with and without the chainId will differ. As of 2019-06-26, the most commonly * used variation in Ethereum was without the chainId. This may change in the future. */ -export const toChecksumAddress = function (hexAddress: string, eip1191ChainId?: number): string { +export const toChecksumAddress = function(hexAddress: string, eip1191ChainId?: number): string { assertIsHexString(hexAddress) const address = stripHexPrefix(hexAddress).toLowerCase() @@ -148,7 +153,7 @@ export const toChecksumAddress = function (hexAddress: string, eip1191ChainId?: * * See toChecksumAddress' documentation for details about the eip1191ChainId parameter. */ -export const isValidChecksumAddress = function ( +export const isValidChecksumAddress = function( hexAddress: string, eip1191ChainId?: number, ): boolean { @@ -160,7 +165,7 @@ export const isValidChecksumAddress = function ( * @param from The address which is creating this new address * @param nonce The nonce of the from account */ -export const generateAddress = function (from: Buffer, nonce: Buffer): Buffer { +export const generateAddress = function(from: Buffer, nonce: Buffer): Buffer { assertIsBuffer(from) assertIsBuffer(nonce) const nonceBN = new BN(nonce) @@ -181,7 +186,7 @@ export const generateAddress = function (from: Buffer, nonce: Buffer): Buffer { * @param salt A salt * @param initCode The init code of the contract being created */ -export const generateAddress2 = function (from: Buffer, salt: Buffer, initCode: Buffer): Buffer { +export const generateAddress2 = function(from: Buffer, salt: Buffer, initCode: Buffer): Buffer { assertIsBuffer(from) assertIsBuffer(salt) assertIsBuffer(initCode) @@ -199,7 +204,7 @@ export const generateAddress2 = function (from: Buffer, salt: Buffer, initCode: /** * Checks if the private key satisfies the rules of the curve secp256k1. */ -export const isValidPrivate = function (privateKey: Buffer): boolean { +export const isValidPrivate = function(privateKey: Buffer): boolean { return privateKeyVerify(privateKey) } @@ -209,7 +214,7 @@ export const isValidPrivate = function (privateKey: Buffer): boolean { * @param publicKey The two points of an uncompressed key, unless sanitize is enabled * @param sanitize Accept public keys in other formats */ -export const isValidPublic = function (publicKey: Buffer, sanitize: boolean = false): boolean { +export const isValidPublic = function(publicKey: Buffer, sanitize: boolean = false): boolean { assertIsBuffer(publicKey) if (publicKey.length === 64) { // Convert to SEC1 for secp256k1 @@ -229,7 +234,7 @@ export const isValidPublic = function (publicKey: Buffer, sanitize: boolean = fa * @param pubKey The two points of an uncompressed key, unless sanitize is enabled * @param sanitize Accept public keys in other formats */ -export const pubToAddress = function (pubKey: Buffer, sanitize: boolean = false): Buffer { +export const pubToAddress = function(pubKey: Buffer, sanitize: boolean = false): Buffer { assertIsBuffer(pubKey) if (sanitize && pubKey.length !== 64) { pubKey = Buffer.from(publicKeyConvert(pubKey, false).slice(1)) @@ -244,7 +249,7 @@ export const publicToAddress = pubToAddress * Returns the ethereum address of a given private key. * @param privateKey A private key must be 256 bits wide */ -export const privateToAddress = function (privateKey: Buffer): Buffer { +export const privateToAddress = function(privateKey: Buffer): Buffer { return publicToAddress(privateToPublic(privateKey)) } @@ -252,7 +257,7 @@ export const privateToAddress = function (privateKey: Buffer): Buffer { * Returns the ethereum public key of a given private key. * @param privateKey A private key must be 256 bits wide */ -export const privateToPublic = function (privateKey: Buffer): Buffer { +export const privateToPublic = function(privateKey: Buffer): Buffer { assertIsBuffer(privateKey) // skip the type flag and use the X, Y points return Buffer.from(publicKeyCreate(privateKey, false)).slice(1) @@ -261,7 +266,7 @@ export const privateToPublic = function (privateKey: Buffer): Buffer { /** * Converts a public key to the Ethereum format. */ -export const importPublic = function (publicKey: Buffer): Buffer { +export const importPublic = function(publicKey: Buffer): Buffer { assertIsBuffer(publicKey) if (publicKey.length !== 64) { publicKey = Buffer.from(publicKeyConvert(publicKey, false).slice(1)) @@ -272,7 +277,7 @@ export const importPublic = function (publicKey: Buffer): Buffer { /** * Returns a zero address. */ -export const zeroAddress = function (): string { +export const zeroAddress = function(): string { const addressLength = 20 const addr = zeros(addressLength) return bufferToHex(addr) @@ -281,7 +286,7 @@ export const zeroAddress = function (): string { /** * Checks if a given address is a zero address. */ -export const isZeroAddress = function (hexAddress: string): boolean { +export const isZeroAddress = function(hexAddress: string): boolean { assertIsHexString(hexAddress) const zeroAddr = zeroAddress() return zeroAddr === hexAddress diff --git a/src/helpers.ts b/src/helpers.ts index 2c027d75..bcceaa22 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -6,7 +6,7 @@ import { unpadBuffer } from './bytes' * Throws if a string is not hex prefixed * @param {string} input string to check hex prefix of */ -export const assertIsHexString = function (input: string): void { +export const assertIsHexString = function(input: string): void { if (!isHexString(input)) { const msg = `This method only supports 0x-prefixed hex strings but input was: ${input}` throw new Error(msg) @@ -17,7 +17,7 @@ export const assertIsHexString = function (input: string): void { * Throws if input is not a buffer * @param {Buffer} input value to check */ -export const assertIsBuffer = function (input: Buffer): void { +export const assertIsBuffer = function(input: Buffer): void { if (!Buffer.isBuffer(input)) { const msg = `This method only supports Buffer but input was: ${input}` throw new Error(msg) @@ -28,7 +28,7 @@ export const assertIsBuffer = function (input: Buffer): void { * Throws if input is not an array * @param {number[]} input value to check */ -export const assertIsArray = function (input: number[]): void { +export const assertIsArray = function(input: number[]): void { if (!Array.isArray(input)) { const msg = `This method only supports number arrays but input was: ${input}` throw new Error(msg) @@ -39,7 +39,7 @@ export const assertIsArray = function (input: number[]): void { * Throws if input is not a string * @param {string} input value to check */ -export const assertIsString = function (input: string): void { +export const assertIsString = function(input: string): void { if (typeof input !== 'string') { const msg = `This method only supports strings but input was: ${input}` throw new Error(msg) @@ -54,4 +54,4 @@ export function bnToRlp(value: BN): Buffer { // Using `bn.toArrayLike(Buffer)` instead of `bn.toBuffer()` // for compatibility with browserify and similar tools return unpadBuffer(value.toArrayLike(Buffer)) -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 971a2876..848d0c77 100644 --- a/src/index.ts +++ b/src/index.ts @@ -47,4 +47,4 @@ export * from './types' /** * Export ethjs-util methods */ -export * from 'ethjs-util' \ No newline at end of file +export * from 'ethjs-util' diff --git a/src/types.ts b/src/types.ts index 02618431..894c9133 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,5 +7,5 @@ export type BufferLike = Buffer | TransformableToBuffer | PrefixedHexString | nu export type PrefixedHexString = string export interface TransformableToBuffer { - toBuffer(): Buffer -} \ No newline at end of file + toBuffer(): Buffer +} From 0f5418ef1d86d0d3c54cfcb01df76352205b2888 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Sun, 27 Sep 2020 20:26:06 -0700 Subject: [PATCH 06/11] rebuild docs, add types docs --- CHANGELOG.md | 8 ++++ README.md | 2 + docs/README.md | 1 + docs/classes/_account_.account.md | 22 +++++------ docs/interfaces/_account_.accountdata.md | 16 ++++---- .../_types_.transformabletobuffer.md | 23 +++++++++++ docs/modules/_account_.md | 28 ++++++------- docs/modules/_helpers_.md | 27 +++++++++++-- docs/modules/_types_.md | 39 +++++++++++++++++++ 9 files changed, 129 insertions(+), 37 deletions(-) create mode 100644 docs/interfaces/_types_.transformabletobuffer.md create mode 100644 docs/modules/_types_.md diff --git a/CHANGELOG.md b/CHANGELOG.md index d4474e09..14a8b6f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) ## [7.0.6] - [UNRELEASED] +### New `Account` class + This release adds a new `Account` class intended as a modern replacement for `ethereumjs-account`. It has a shape of `Account(nonce?: BN, balance?: BN, stateRoot?: Buffer, codeHash?: Buffer)`. **Instantiation** @@ -31,6 +33,12 @@ const account = new Account( For more info see the documentation or examples of usage in `test/account.spec.ts`. +### New export: TypeScript types + +A new file with helpful TypeScript types has been added to the exports of this project. + +In this release it contains `BNLike`, `BufferLike`, and `TransformableToBuffer`. + ## [7.0.5] - 2020-09-09 This release adds a new module `address` - see [README](https://github.com/ethereumjs/ethereumjs-util#modules) - diff --git a/README.md b/README.md index 654c9561..1d22e35c 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ assert.equal(new BN('dead', 16).add(new BN('101010', 2)), 57047) - Helper function for creating a binary object (`DEPRECATED`) - [signature](docs/modules/_signature_.md) - Signing, signature validation, conversion, recovery +- [types](docs/modules/_types_.md) + - Helpful TypeScript types - [externals](docs/modules/_externals_.md) - Helper methods from `ethjs-util` - Re-exports of `BN`, `rlp` diff --git a/docs/README.md b/docs/README.md index 7dff9c06..bdfde4cb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,3 +16,4 @@ * ["helpers"](modules/_helpers_.md) * ["object"](modules/_object_.md) * ["signature"](modules/_signature_.md) +* ["types"](modules/_types_.md) diff --git a/docs/classes/_account_.account.md b/docs/classes/_account_.account.md index 4f3d33d8..42391678 100644 --- a/docs/classes/_account_.account.md +++ b/docs/classes/_account_.account.md @@ -34,7 +34,7 @@ \+ **new Account**(`nonce`: BN‹›, `balance`: BN‹›, `stateRoot`: Buffer‹›, `codeHash`: Buffer‹›): *[Account](_account_.account.md)* -*Defined in [account.ts:77](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L77)* +*Defined in [account.ts:61](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L61)* This constructor takes the values, validates and assigns them. Use the static factory methods to assist in creating an Account from varying data types. @@ -56,7 +56,7 @@ Name | Type | Default | • **balance**: *BN* -*Defined in [account.ts:43](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L43)* +*Defined in [account.ts:27](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L27)* ___ @@ -64,7 +64,7 @@ ___ • **codeHash**: *Buffer* -*Defined in [account.ts:45](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L45)* +*Defined in [account.ts:29](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L29)* ___ @@ -72,7 +72,7 @@ ___ • **nonce**: *BN* -*Defined in [account.ts:42](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L42)* +*Defined in [account.ts:26](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L26)* ___ @@ -80,7 +80,7 @@ ___ • **stateRoot**: *Buffer* -*Defined in [account.ts:44](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L44)* +*Defined in [account.ts:28](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L28)* ## Methods @@ -88,7 +88,7 @@ ___ ▸ **isContract**(): *boolean* -*Defined in [account.ts:112](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L112)* +*Defined in [account.ts:96](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L96)* Returns a `Boolean` deteremining if the account is a contract. @@ -100,7 +100,7 @@ ___ ▸ **isEmpty**(): *boolean* -*Defined in [account.ts:119](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L119)* +*Defined in [account.ts:103](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L103)* Returns a `Boolean` determining if the account is empty. @@ -112,7 +112,7 @@ ___ ▸ **serialize**(): *Buffer* -*Defined in [account.ts:105](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L105)* +*Defined in [account.ts:89](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L89)* Returns the RLP serialization of the account as a `Buffer`. @@ -124,7 +124,7 @@ ___ ▸ **fromAccountData**(`accountData`: [AccountData](../interfaces/_account_.accountdata.md)): *[Account](_account_.account.md)‹›* -*Defined in [account.ts:47](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L47)* +*Defined in [account.ts:31](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L31)* **Parameters:** @@ -140,7 +140,7 @@ ___ ▸ **fromRlpSerializedAccount**(`serialized`: Buffer): *[Account](_account_.account.md)‹›* -*Defined in [account.ts:58](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L58)* +*Defined in [account.ts:42](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L42)* **Parameters:** @@ -156,7 +156,7 @@ ___ ▸ **fromValuesArray**(`values`: Buffer[]): *[Account](_account_.account.md)‹›* -*Defined in [account.ts:68](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L68)* +*Defined in [account.ts:52](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L52)* **Parameters:** diff --git a/docs/interfaces/_account_.accountdata.md b/docs/interfaces/_account_.accountdata.md index 698b49df..126266a0 100644 --- a/docs/interfaces/_account_.accountdata.md +++ b/docs/interfaces/_account_.accountdata.md @@ -19,30 +19,30 @@ ### `Optional` balance -• **balance**? : *BNLike* +• **balance**? : *[BNLike](../modules/_types_.md#bnlike)* -*Defined in [account.ts:19](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L19)* +*Defined in [account.ts:20](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L20)* ___ ### `Optional` codeHash -• **codeHash**? : *BufferLike* +• **codeHash**? : *[BufferLike](../modules/_types_.md#bufferlike)* -*Defined in [account.ts:21](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L21)* +*Defined in [account.ts:22](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L22)* ___ ### `Optional` nonce -• **nonce**? : *BNLike* +• **nonce**? : *[BNLike](../modules/_types_.md#bnlike)* -*Defined in [account.ts:18](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L18)* +*Defined in [account.ts:19](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L19)* ___ ### `Optional` stateRoot -• **stateRoot**? : *BufferLike* +• **stateRoot**? : *[BufferLike](../modules/_types_.md#bufferlike)* -*Defined in [account.ts:20](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L20)* +*Defined in [account.ts:21](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L21)* diff --git a/docs/interfaces/_types_.transformabletobuffer.md b/docs/interfaces/_types_.transformabletobuffer.md new file mode 100644 index 00000000..7ab3daaf --- /dev/null +++ b/docs/interfaces/_types_.transformabletobuffer.md @@ -0,0 +1,23 @@ +[ethereumjs-util](../README.md) › ["types"](../modules/_types_.md) › [TransformableToBuffer](_types_.transformabletobuffer.md) + +# Interface: TransformableToBuffer + +## Hierarchy + +* **TransformableToBuffer** + +## Index + +### Methods + +* [toBuffer](_types_.transformabletobuffer.md#tobuffer) + +## Methods + +### toBuffer + +▸ **toBuffer**(): *Buffer* + +*Defined in [types.ts:10](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/types.ts#L10)* + +**Returns:** *Buffer* diff --git a/docs/modules/_account_.md b/docs/modules/_account_.md index 08125b1d..0abc65de 100644 --- a/docs/modules/_account_.md +++ b/docs/modules/_account_.md @@ -38,7 +38,7 @@ • **publicToAddress**: *pubToAddress* = pubToAddress -*Defined in [account.ts:257](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L257)* +*Defined in [account.ts:246](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L246)* ## Functions @@ -46,7 +46,7 @@ ▸ **generateAddress**(`from`: Buffer, `nonce`: Buffer): *Buffer* -*Defined in [account.ts:179](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L179)* +*Defined in [account.ts:168](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L168)* Generates an address of a newly created contract. @@ -65,7 +65,7 @@ ___ ▸ **generateAddress2**(`from`: Buffer, `salt`: Buffer, `initCode`: Buffer): *Buffer* -*Defined in [account.ts:200](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L200)* +*Defined in [account.ts:189](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L189)* Generates an address for a contract created using CREATE2. @@ -85,7 +85,7 @@ ___ ▸ **importPublic**(`publicKey`: Buffer): *Buffer* -*Defined in [account.ts:280](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L280)* +*Defined in [account.ts:269](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L269)* Converts a public key to the Ethereum format. @@ -103,7 +103,7 @@ ___ ▸ **isValidAddress**(`hexAddress`: string): *boolean* -*Defined in [account.ts:127](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L127)* +*Defined in [account.ts:116](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L116)* Checks if the address is a valid. Accepts checksummed addresses too. @@ -121,7 +121,7 @@ ___ ▸ **isValidChecksumAddress**(`hexAddress`: string, `eip1191ChainId?`: undefined | number): *boolean* -*Defined in [account.ts:167](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L167)* +*Defined in [account.ts:156](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L156)* Checks if the address is a valid checksummed address. @@ -142,7 +142,7 @@ ___ ▸ **isValidPrivate**(`privateKey`: Buffer): *boolean* -*Defined in [account.ts:218](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L218)* +*Defined in [account.ts:207](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L207)* Checks if the private key satisfies the rules of the curve secp256k1. @@ -160,7 +160,7 @@ ___ ▸ **isValidPublic**(`publicKey`: Buffer, `sanitize`: boolean): *boolean* -*Defined in [account.ts:228](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L228)* +*Defined in [account.ts:217](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L217)* Checks if the public key satisfies the rules of the curve secp256k1 and the requirements of Ethereum. @@ -180,7 +180,7 @@ ___ ▸ **isZeroAddress**(`hexAddress`: string): *boolean* -*Defined in [account.ts:300](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L300)* +*Defined in [account.ts:289](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L289)* Checks if a given address is a zero address. @@ -198,7 +198,7 @@ ___ ▸ **privateToAddress**(`privateKey`: Buffer): *Buffer* -*Defined in [account.ts:263](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L263)* +*Defined in [account.ts:252](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L252)* Returns the ethereum address of a given private key. @@ -216,7 +216,7 @@ ___ ▸ **privateToPublic**(`privateKey`: Buffer): *Buffer* -*Defined in [account.ts:271](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L271)* +*Defined in [account.ts:260](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L260)* Returns the ethereum public key of a given private key. @@ -234,7 +234,7 @@ ___ ▸ **pubToAddress**(`pubKey`: Buffer, `sanitize`: boolean): *Buffer* -*Defined in [account.ts:248](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L248)* +*Defined in [account.ts:237](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L237)* Returns the ethereum address of a given public key. Accepts "Ethereum public keys" and SEC1 encoded keys. @@ -254,7 +254,7 @@ ___ ▸ **toChecksumAddress**(`hexAddress`: string, `eip1191ChainId?`: undefined | number): *string* -*Defined in [account.ts:142](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L142)* +*Defined in [account.ts:131](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L131)* Returns a checksummed address. @@ -280,7 +280,7 @@ ___ ▸ **zeroAddress**(): *string* -*Defined in [account.ts:291](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L291)* +*Defined in [account.ts:280](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/account.ts#L280)* Returns a zero address. diff --git a/docs/modules/_helpers_.md b/docs/modules/_helpers_.md index 70afc005..5f6b892b 100644 --- a/docs/modules/_helpers_.md +++ b/docs/modules/_helpers_.md @@ -10,6 +10,7 @@ * [assertIsBuffer](_helpers_.md#const-assertisbuffer) * [assertIsHexString](_helpers_.md#const-assertishexstring) * [assertIsString](_helpers_.md#const-assertisstring) +* [bnToRlp](_helpers_.md#bntorlp) ## Functions @@ -17,7 +18,7 @@ ▸ **assertIsArray**(`input`: number[]): *void* -*Defined in [helpers.ts:29](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/helpers.ts#L29)* +*Defined in [helpers.ts:31](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/helpers.ts#L31)* Throws if input is not an array @@ -35,7 +36,7 @@ ___ ▸ **assertIsBuffer**(`input`: Buffer): *void* -*Defined in [helpers.ts:18](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/helpers.ts#L18)* +*Defined in [helpers.ts:20](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/helpers.ts#L20)* Throws if input is not a buffer @@ -53,7 +54,7 @@ ___ ▸ **assertIsHexString**(`input`: string): *void* -*Defined in [helpers.ts:7](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/helpers.ts#L7)* +*Defined in [helpers.ts:9](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/helpers.ts#L9)* Throws if a string is not hex prefixed @@ -71,7 +72,7 @@ ___ ▸ **assertIsString**(`input`: string): *void* -*Defined in [helpers.ts:40](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/helpers.ts#L40)* +*Defined in [helpers.ts:42](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/helpers.ts#L42)* Throws if input is not a string @@ -82,3 +83,21 @@ Name | Type | Description | `input` | string | value to check | **Returns:** *void* + +___ + +### bnToRlp + +▸ **bnToRlp**(`value`: BN): *Buffer* + +*Defined in [helpers.ts:53](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/helpers.ts#L53)* + +Convert value from BN to RLP (unpadded buffer) + +**Parameters:** + +Name | Type | Description | +------ | ------ | ------ | +`value` | BN | value to convert | + +**Returns:** *Buffer* diff --git a/docs/modules/_types_.md b/docs/modules/_types_.md new file mode 100644 index 00000000..bf0509b6 --- /dev/null +++ b/docs/modules/_types_.md @@ -0,0 +1,39 @@ +[ethereumjs-util](../README.md) › ["types"](_types_.md) + +# Module: "types" + +## Index + +### Interfaces + +* [TransformableToBuffer](../interfaces/_types_.transformabletobuffer.md) + +### Type aliases + +* [BNLike](_types_.md#bnlike) +* [BufferLike](_types_.md#bufferlike) +* [PrefixedHexString](_types_.md#prefixedhexstring) + +## Type aliases + +### BNLike + +Ƭ **BNLike**: *BN | string | number* + +*Defined in [types.ts:3](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/types.ts#L3)* + +___ + +### BufferLike + +Ƭ **BufferLike**: *Buffer | [TransformableToBuffer](../interfaces/_types_.transformabletobuffer.md) | [PrefixedHexString](_types_.md#prefixedhexstring) | number* + +*Defined in [types.ts:5](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/types.ts#L5)* + +___ + +### PrefixedHexString + +Ƭ **PrefixedHexString**: *string* + +*Defined in [types.ts:7](https://github.com/ethereumjs/ethereumjs-util/blob/master/src/types.ts#L7)* From b2fce00e9b330417a3719f7a26329acede43746f Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Mon, 28 Sep 2020 12:46:29 -0700 Subject: [PATCH 07/11] fix typo --- src/account.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/account.ts b/src/account.ts index 77fae23f..b057b93a 100644 --- a/src/account.ts +++ b/src/account.ts @@ -91,7 +91,7 @@ export class Account { } /** - * Returns a `Boolean` deteremining if the account is a contract. + * Returns a `Boolean` determining if the account is a contract. */ isContract(): boolean { return !this.codeHash.equals(KECCAK256_NULL) From 36a070008a4048461d315db487cfbe9f83e16b24 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Mon, 28 Sep 2020 12:47:22 -0700 Subject: [PATCH 08/11] make codeHash more different-looking than KECCAK256_NULL --- test/account.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/account.spec.ts b/test/account.spec.ts index 2548fbfa..2691502f 100644 --- a/test/account.spec.ts +++ b/test/account.spec.ts @@ -170,7 +170,7 @@ describe('Account', function() { nonce: '0x01', balance: '0x0042', stateRoot: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', - codeHash: '0xc5d2461236f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', + codeHash: '0xd748bf26ab37599c944babfdbeecf6690801bd61bf2670efb0a34adfc6dca10b', } const account = Account.fromAccountData(raw) assert.equal(account.isEmpty(), false) From 69a5ef403663599b9ddadb83fb4853118adaecc1 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Tue, 29 Sep 2020 12:58:37 -0700 Subject: [PATCH 09/11] move `bnToRlp` to types file so it can be exported in the main package for use in monorepo --- src/account.ts | 4 ++-- src/helpers.ts | 12 ------------ src/types.ts | 11 +++++++++++ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/account.ts b/src/account.ts index b057b93a..1cc691d7 100644 --- a/src/account.ts +++ b/src/account.ts @@ -5,8 +5,8 @@ import { stripHexPrefix } from 'ethjs-util' import { KECCAK256_RLP, KECCAK256_NULL } from './constants' import { zeros, bufferToHex, toBuffer } from './bytes' import { keccak, keccak256, keccakFromString, rlphash } from './hash' -import { assertIsHexString, assertIsBuffer, bnToRlp } from './helpers' -import { BNLike, BufferLike } from './types' +import { assertIsHexString, assertIsBuffer } from './helpers' +import { BNLike, BufferLike, bnToRlp } from './types' const { privateKeyVerify, diff --git a/src/helpers.ts b/src/helpers.ts index bcceaa22..3fd4eacd 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,6 +1,4 @@ -import * as BN from 'bn.js' import { isHexString } from 'ethjs-util' -import { unpadBuffer } from './bytes' /** * Throws if a string is not hex prefixed @@ -45,13 +43,3 @@ export const assertIsString = function(input: string): void { throw new Error(msg) } } - -/** - * Convert value from BN to RLP (unpadded buffer) - * @param value value to convert - */ -export function bnToRlp(value: BN): Buffer { - // Using `bn.toArrayLike(Buffer)` instead of `bn.toBuffer()` - // for compatibility with browserify and similar tools - return unpadBuffer(value.toArrayLike(Buffer)) -} diff --git a/src/types.ts b/src/types.ts index 894c9133..6cb819b4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,5 @@ import * as BN from 'bn.js' +import { unpadBuffer } from './bytes' export type BNLike = BN | string | number @@ -9,3 +10,13 @@ export type PrefixedHexString = string export interface TransformableToBuffer { toBuffer(): Buffer } + +/** + * Convert value from BN to RLP (unpadded buffer) + * @param value value to convert + */ +export function bnToRlp(value: BN): Buffer { + // Using `bn.toArrayLike(Buffer)` instead of `bn.toBuffer()` + // for compatibility with browserify and similar tools + return unpadBuffer(value.toArrayLike(Buffer)) +} From b0aacc0eff2ded594a12b614508a294ad9c63f4b Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Wed, 30 Sep 2020 09:37:52 -0700 Subject: [PATCH 10/11] add validation for nonce and balance --- src/account.ts | 26 ++++++++++++++++++-------- test/account.spec.ts | 12 ++++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/account.ts b/src/account.ts index 1cc691d7..e7cbb349 100644 --- a/src/account.ts +++ b/src/account.ts @@ -61,7 +61,7 @@ export class Account { } /** - * This constructor takes the values, validates and assigns them. + * This constructor assigns and validates the values. * Use the static factory methods to assist in creating an Account from varying data types. */ constructor( @@ -70,17 +70,27 @@ export class Account { stateRoot = KECCAK256_RLP, codeHash = KECCAK256_NULL, ) { - if (stateRoot.length !== 32) { - throw new Error('stateRoot must have a length of 32') - } - if (codeHash.length !== 32) { - throw new Error('codeHash must have a length of 32') - } - this.nonce = nonce this.balance = balance this.stateRoot = stateRoot this.codeHash = codeHash + + this._validate() + } + + private _validate() { + if (this.nonce.lt(new BN(0))) { + throw new Error('nonce must be greater than zero') + } + if (this.balance.lt(new BN(0))) { + throw new Error('balance must be greater than zero') + } + if (this.stateRoot.length !== 32) { + throw new Error('stateRoot must have a length of 32') + } + if (this.codeHash.length !== 32) { + throw new Error('codeHash must have a length of 32') + } } /** diff --git a/test/account.spec.ts b/test/account.spec.ts index 2691502f..c999b357 100644 --- a/test/account.spec.ts +++ b/test/account.spec.ts @@ -196,6 +196,18 @@ describe('Account', function() { Account.fromRlpSerializedAccount(data as any) }) }) + + it('should not accept nonce less than 0', function() { + assert.throws(() => { + new Account(new BN(-5)) + }) + }) + + it('should not accept balance less than 0', function() { + assert.throws(() => { + new Account(undefined, new BN(-5)) + }) + }) }) }) From e48478d3449fed6142cd6f611dffab5d4ed46ee3 Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Wed, 30 Sep 2020 10:22:44 -0700 Subject: [PATCH 11/11] more docs, fixups --- src/account.ts | 8 +++++--- src/types.ts | 21 ++++++++++++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/account.ts b/src/account.ts index e7cbb349..e3d2c6df 100644 --- a/src/account.ts +++ b/src/account.ts @@ -109,6 +109,8 @@ export class Account { /** * Returns a `Boolean` determining if the account is empty. + * For more details about account emptiness see [EIP-161](https://eips.ethereum.org/EIPS/eip-161). + * Note: The stateRoot is also checked to be empty since in Frontier it was possible to create a contract with no code where nonce remained 0 but some values were written to storage in the constructor (thus stateRoot is not KECCAK256_RLP). */ isEmpty(): boolean { return ( @@ -133,7 +135,7 @@ export const isValidAddress = function(hexAddress: string): boolean { * * If a eip1191ChainId is provided, the chainId will be included in the checksum calculation. This * has the effect of checksummed addresses for one chain having invalid checksums for others. - * For more details, consult EIP-1191. + * For more details see [EIP-1191](https://eips.ethereum.org/EIPS/eip-1191). * * WARNING: Checksums with and without the chainId will differ. As of 2019-06-26, the most commonly * used variation in Ethereum was without the chainId. This may change in the future. @@ -285,7 +287,7 @@ export const importPublic = function(publicKey: Buffer): Buffer { } /** - * Returns a zero address. + * Returns the zero address. */ export const zeroAddress = function(): string { const addressLength = 20 @@ -294,7 +296,7 @@ export const zeroAddress = function(): string { } /** - * Checks if a given address is a zero address. + * Checks if a given address is the zero address. */ export const isZeroAddress = function(hexAddress: string): boolean { assertIsHexString(hexAddress) diff --git a/src/types.ts b/src/types.ts index 6cb819b4..64ec1a35 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,12 +1,31 @@ import * as BN from 'bn.js' import { unpadBuffer } from './bytes' +/* + * A type that represents a BNLike input that can be converted to a BN. + */ export type BNLike = BN | string | number -export type BufferLike = Buffer | TransformableToBuffer | PrefixedHexString | number +/* + * A type that represents a BufferLike input that can be converted to a Buffer. + */ +export type BufferLike = + | Buffer + | Uint8Array + | number[] + | number + | BN + | TransformableToBuffer + | PrefixedHexString +/* + * A type that represents a `0x`-prefixed hex string. + */ export type PrefixedHexString = string +/* + * A type that represents an object that has a `toBuffer()` method. + */ export interface TransformableToBuffer { toBuffer(): Buffer }