UNPKG

9.4 kBPlain TextView Raw
1import assert from 'assert'
2import BN from 'bn.js'
3import * as rlp from 'rlp'
4import {
5 privateKeyVerify,
6 publicKeyCreate,
7 publicKeyVerify,
8 publicKeyConvert,
9} from 'ethereum-cryptography/secp256k1'
10import { stripHexPrefix } from './internal'
11import { KECCAK256_RLP, KECCAK256_NULL } from './constants'
12import { zeros, bufferToHex, toBuffer } from './bytes'
13import { keccak, keccak256, keccakFromString, rlphash } from './hash'
14import { assertIsString, assertIsHexString, assertIsBuffer } from './helpers'
15import { BNLike, BufferLike, bnToUnpaddedBuffer, toType, TypeOutput } from './types'
16
17export interface AccountData {
18 nonce?: BNLike
19 balance?: BNLike
20 stateRoot?: BufferLike
21 codeHash?: BufferLike
22}
23
24export class Account {
25 nonce: BN
26 balance: BN
27 stateRoot: Buffer
28 codeHash: Buffer
29
30 static fromAccountData(accountData: AccountData) {
31 const { nonce, balance, stateRoot, codeHash } = accountData
32
33 return new Account(
34 nonce ? new BN(toBuffer(nonce)) : undefined,
35 balance ? new BN(toBuffer(balance)) : undefined,
36 stateRoot ? toBuffer(stateRoot) : undefined,
37 codeHash ? toBuffer(codeHash) : undefined
38 )
39 }
40
41 public static fromRlpSerializedAccount(serialized: Buffer) {
42 const values = rlp.decode(serialized)
43
44 if (!Array.isArray(values)) {
45 throw new Error('Invalid serialized account input. Must be array')
46 }
47
48 return this.fromValuesArray(values)
49 }
50
51 public static fromValuesArray(values: Buffer[]) {
52 const [nonce, balance, stateRoot, codeHash] = values
53
54 return new Account(new BN(nonce), new BN(balance), stateRoot, codeHash)
55 }
56
57 /**
58 * This constructor assigns and validates the values.
59 * Use the static factory methods to assist in creating an Account from varying data types.
60 */
61 constructor(
62 nonce = new BN(0),
63 balance = new BN(0),
64 stateRoot = KECCAK256_RLP,
65 codeHash = KECCAK256_NULL
66 ) {
67 this.nonce = nonce
68 this.balance = balance
69 this.stateRoot = stateRoot
70 this.codeHash = codeHash
71
72 this._validate()
73 }
74
75 private _validate() {
76 if (this.nonce.lt(new BN(0))) {
77 throw new Error('nonce must be greater than zero')
78 }
79 if (this.balance.lt(new BN(0))) {
80 throw new Error('balance must be greater than zero')
81 }
82 if (this.stateRoot.length !== 32) {
83 throw new Error('stateRoot must have a length of 32')
84 }
85 if (this.codeHash.length !== 32) {
86 throw new Error('codeHash must have a length of 32')
87 }
88 }
89
90 /**
91 * Returns a Buffer Array of the raw Buffers for the account, in order.
92 */
93 raw(): Buffer[] {
94 return [
95 bnToUnpaddedBuffer(this.nonce),
96 bnToUnpaddedBuffer(this.balance),
97 this.stateRoot,
98 this.codeHash,
99 ]
100 }
101
102 /**
103 * Returns the RLP serialization of the account as a `Buffer`.
104 */
105 serialize(): Buffer {
106 return rlp.encode(this.raw())
107 }
108
109 /**
110 * Returns a `Boolean` determining if the account is a contract.
111 */
112 isContract(): boolean {
113 return !this.codeHash.equals(KECCAK256_NULL)
114 }
115
116 /**
117 * Returns a `Boolean` determining if the account is empty complying to the definition of
118 * account emptiness in [EIP-161](https://eips.ethereum.org/EIPS/eip-161):
119 * "An account is considered empty when it has no code and zero nonce and zero balance."
120 */
121 isEmpty(): boolean {
122 return this.balance.isZero() && this.nonce.isZero() && this.codeHash.equals(KECCAK256_NULL)
123 }
124}
125
126/**
127 * Checks if the address is a valid. Accepts checksummed addresses too.
128 */
129export const isValidAddress = function (hexAddress: string): boolean {
130 try {
131 assertIsString(hexAddress)
132 } catch (e: any) {
133 return false
134 }
135
136 return /^0x[0-9a-fA-F]{40}$/.test(hexAddress)
137}
138
139/**
140 * Returns a checksummed address.
141 *
142 * If an eip1191ChainId is provided, the chainId will be included in the checksum calculation. This
143 * has the effect of checksummed addresses for one chain having invalid checksums for others.
144 * For more details see [EIP-1191](https://eips.ethereum.org/EIPS/eip-1191).
145 *
146 * WARNING: Checksums with and without the chainId will differ and the EIP-1191 checksum is not
147 * backwards compatible to the original widely adopted checksum format standard introduced in
148 * [EIP-55](https://eips.ethereum.org/EIPS/eip-55), so this will break in existing applications.
149 * Usage of this EIP is therefore discouraged unless you have a very targeted use case.
150 */
151export const toChecksumAddress = function (hexAddress: string, eip1191ChainId?: BNLike): string {
152 assertIsHexString(hexAddress)
153 const address = stripHexPrefix(hexAddress).toLowerCase()
154
155 let prefix = ''
156 if (eip1191ChainId) {
157 const chainId = toType(eip1191ChainId, TypeOutput.BN)
158 prefix = chainId.toString() + '0x'
159 }
160
161 const hash = keccakFromString(prefix + address).toString('hex')
162 let ret = '0x'
163
164 for (let i = 0; i < address.length; i++) {
165 if (parseInt(hash[i], 16) >= 8) {
166 ret += address[i].toUpperCase()
167 } else {
168 ret += address[i]
169 }
170 }
171
172 return ret
173}
174
175/**
176 * Checks if the address is a valid checksummed address.
177 *
178 * See toChecksumAddress' documentation for details about the eip1191ChainId parameter.
179 */
180export const isValidChecksumAddress = function (
181 hexAddress: string,
182 eip1191ChainId?: BNLike
183): boolean {
184 return isValidAddress(hexAddress) && toChecksumAddress(hexAddress, eip1191ChainId) === hexAddress
185}
186
187/**
188 * Generates an address of a newly created contract.
189 * @param from The address which is creating this new address
190 * @param nonce The nonce of the from account
191 */
192export const generateAddress = function (from: Buffer, nonce: Buffer): Buffer {
193 assertIsBuffer(from)
194 assertIsBuffer(nonce)
195 const nonceBN = new BN(nonce)
196
197 if (nonceBN.isZero()) {
198 // in RLP we want to encode null in the case of zero nonce
199 // read the RLP documentation for an answer if you dare
200 return rlphash([from, null]).slice(-20)
201 }
202
203 // Only take the lower 160bits of the hash
204 return rlphash([from, Buffer.from(nonceBN.toArray())]).slice(-20)
205}
206
207/**
208 * Generates an address for a contract created using CREATE2.
209 * @param from The address which is creating this new address
210 * @param salt A salt
211 * @param initCode The init code of the contract being created
212 */
213export const generateAddress2 = function (from: Buffer, salt: Buffer, initCode: Buffer): Buffer {
214 assertIsBuffer(from)
215 assertIsBuffer(salt)
216 assertIsBuffer(initCode)
217
218 assert(from.length === 20)
219 assert(salt.length === 32)
220
221 const address = keccak256(
222 Buffer.concat([Buffer.from('ff', 'hex'), from, salt, keccak256(initCode)])
223 )
224
225 return address.slice(-20)
226}
227
228/**
229 * Checks if the private key satisfies the rules of the curve secp256k1.
230 */
231export const isValidPrivate = function (privateKey: Buffer): boolean {
232 return privateKeyVerify(privateKey)
233}
234
235/**
236 * Checks if the public key satisfies the rules of the curve secp256k1
237 * and the requirements of Ethereum.
238 * @param publicKey The two points of an uncompressed key, unless sanitize is enabled
239 * @param sanitize Accept public keys in other formats
240 */
241export const isValidPublic = function (publicKey: Buffer, sanitize: boolean = false): boolean {
242 assertIsBuffer(publicKey)
243 if (publicKey.length === 64) {
244 // Convert to SEC1 for secp256k1
245 return publicKeyVerify(Buffer.concat([Buffer.from([4]), publicKey]))
246 }
247
248 if (!sanitize) {
249 return false
250 }
251
252 return publicKeyVerify(publicKey)
253}
254
255/**
256 * Returns the ethereum address of a given public key.
257 * Accepts "Ethereum public keys" and SEC1 encoded keys.
258 * @param pubKey The two points of an uncompressed key, unless sanitize is enabled
259 * @param sanitize Accept public keys in other formats
260 */
261export const pubToAddress = function (pubKey: Buffer, sanitize: boolean = false): Buffer {
262 assertIsBuffer(pubKey)
263 if (sanitize && pubKey.length !== 64) {
264 pubKey = Buffer.from(publicKeyConvert(pubKey, false).slice(1))
265 }
266 assert(pubKey.length === 64)
267 // Only take the lower 160bits of the hash
268 return keccak(pubKey).slice(-20)
269}
270export const publicToAddress = pubToAddress
271
272/**
273 * Returns the ethereum public key of a given private key.
274 * @param privateKey A private key must be 256 bits wide
275 */
276export const privateToPublic = function (privateKey: Buffer): Buffer {
277 assertIsBuffer(privateKey)
278 // skip the type flag and use the X, Y points
279 return Buffer.from(publicKeyCreate(privateKey, false)).slice(1)
280}
281
282/**
283 * Returns the ethereum address of a given private key.
284 * @param privateKey A private key must be 256 bits wide
285 */
286export const privateToAddress = function (privateKey: Buffer): Buffer {
287 return publicToAddress(privateToPublic(privateKey))
288}
289
290/**
291 * Converts a public key to the Ethereum format.
292 */
293export const importPublic = function (publicKey: Buffer): Buffer {
294 assertIsBuffer(publicKey)
295 if (publicKey.length !== 64) {
296 publicKey = Buffer.from(publicKeyConvert(publicKey, false).slice(1))
297 }
298 return publicKey
299}
300
301/**
302 * Returns the zero address.
303 */
304export const zeroAddress = function (): string {
305 const addressLength = 20
306 const addr = zeros(addressLength)
307 return bufferToHex(addr)
308}
309
310/**
311 * Checks if a given address is the zero address.
312 */
313export const isZeroAddress = function (hexAddress: string): boolean {
314 try {
315 assertIsString(hexAddress)
316 } catch (e: any) {
317 return false
318 }
319
320 const zeroAddr = zeroAddress()
321 return zeroAddr === hexAddress
322}