Skip to content
This repository has been archived by the owner on Jun 17, 2021. It is now read-only.

Commit

Permalink
Merge e74c305 into ca4eef1
Browse files Browse the repository at this point in the history
  • Loading branch information
s1na committed Feb 22, 2019
2 parents ca4eef1 + e74c305 commit 61cd643
Show file tree
Hide file tree
Showing 7 changed files with 676 additions and 635 deletions.
1 change: 0 additions & 1 deletion package.json
Expand Up @@ -88,7 +88,6 @@
"ethjs-util": "0.1.6",
"keccak": "^1.0.2",
"rlp": "^2.0.0",
"safe-buffer": "^5.1.1",
"secp256k1": "^3.0.1"
},
"devDependencies": {
Expand Down
180 changes: 180 additions & 0 deletions src/account.ts
@@ -0,0 +1,180 @@
const assert = require('assert')
const ethjsUtil = require('ethjs-util')
const secp256k1 = require('secp256k1')
import BN = require('bn.js')
import { toBuffer, addHexPrefix, zeros, bufferToHex, unpad } from './bytes'
import { keccak, keccak256, rlphash } from './hash'

/**
* Returns a zero address.
*/
export const zeroAddress = function(): string {
const addressLength = 20
const addr = zeros(addressLength)
return bufferToHex(addr)
}

/**
* Checks if the address is a valid. Accepts checksummed addresses too.
*/
export const isValidAddress = function(address: string): boolean {
return /^0x[0-9a-fA-F]{40}$/.test(address)
}

/**
* Checks if a given address is a zero address.
*/
export const isZeroAddress = function(address: string): boolean {
const zeroAddr = zeroAddress()
return zeroAddr === addHexPrefix(address)
}

/**
* Returns a checksummed address.
*/
export const toChecksumAddress = function(address: string): string {
address = ethjsUtil.stripHexPrefix(address).toLowerCase()
const hash = keccak(address).toString('hex')
let ret = '0x'

for (let i = 0; i < address.length; i++) {
if (parseInt(hash[i], 16) >= 8) {
ret += address[i].toUpperCase()
} else {
ret += address[i]
}
}

return ret
}

/**
* Checks if the address is a valid checksummed address.
*/
export const isValidChecksumAddress = function(address: string): boolean {
return isValidAddress(address) && toChecksumAddress(address) === address
}

/**
* Generates an address of a newly created contract.
* @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 {
from = toBuffer(from)
const nonceBN = new BN(nonce)

if (nonceBN.isZero()) {
// in RLP we want to encode null in the case of zero nonce
// read the RLP documentation for an answer if you dare
return rlphash([from, null]).slice(-20)
}

// Only take the lower 160bits of the hash
return rlphash([from, Buffer.from(nonceBN.toArray())]).slice(-20)
}

/**
* Generates an address for a contract created using CREATE2.
* @param from The address which is creating this new address
* @param salt A salt
* @param initCode The init code of the contract being created
*/
export const generateAddress2 = function(
from: Buffer | string,
salt: Buffer | string,
initCode: Buffer | string,
): Buffer {
const fromBuf = toBuffer(from)
const saltBuf = toBuffer(salt)
const initCodeBuf = toBuffer(initCode)

assert(fromBuf.length === 20)
assert(saltBuf.length === 32)

const address = keccak256(
Buffer.concat([Buffer.from('ff', 'hex'), fromBuf, saltBuf, keccak256(initCodeBuf)]),
)

return address.slice(-20)
}

/**
* Returns true if the supplied address belongs to a precompiled account (Byzantium).
*/
export const isPrecompiled = function(address: Buffer | string): boolean {
const a = unpad(address)
return a.length === 1 && a[0] >= 1 && a[0] <= 8
}

/**
* Checks if the private key satisfies the rules of the curve secp256k1.
*/
export const isValidPrivate = function(privateKey: Buffer): boolean {
return secp256k1.privateKeyVerify(privateKey)
}

/**
* Checks if the public key satisfies the rules of the curve secp256k1
* and the requirements of Ethereum.
* @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 {
if (publicKey.length === 64) {
// Convert to SEC1 for secp256k1
return secp256k1.publicKeyVerify(Buffer.concat([Buffer.from([4]), publicKey]))
}

if (!sanitize) {
return false
}

return secp256k1.publicKeyVerify(publicKey)
}

/**
* Returns the ethereum address of a given public key.
* Accepts "Ethereum public keys" and SEC1 encoded keys.
* @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 {
pubKey = toBuffer(pubKey)
if (sanitize && pubKey.length !== 64) {
pubKey = secp256k1.publicKeyConvert(pubKey, false).slice(1)
}
assert(pubKey.length === 64)
// Only take the lower 160bits of the hash
return keccak(pubKey).slice(-20)
}
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 {
return publicToAddress(privateToPublic(privateKey))
}

/**
* 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 {
privateKey = toBuffer(privateKey)
// skip the type flag and use the X, Y points
return secp256k1.publicKeyCreate(privateKey, false).slice(1)
}

/**
* Converts a public key to the Ethereum format.
*/
export const importPublic = function(publicKey: Buffer): Buffer {
publicKey = toBuffer(publicKey)
if (publicKey.length !== 64) {
publicKey = secp256k1.publicKeyConvert(publicKey, false).slice(1)
}
return publicKey
}
156 changes: 156 additions & 0 deletions src/bytes.ts
@@ -0,0 +1,156 @@
const ethjsUtil = require('ethjs-util')
import BN = require('bn.js')

/**
* Returns a buffer filled with 0s.
* @param bytes the number of bytes the buffer should be
*/
export const zeros = function(bytes: number): Buffer {
return Buffer.allocUnsafe(bytes).fill(0)
}

/**
* Left Pads an `Array` or `Buffer` with leading zeros till it has `length` bytes.
* Or it truncates the beginning if it exceeds.
* @param msg the value to pad (Buffer|Array)
* @param length the number of bytes the output should be
* @param right whether to start padding form the left or right
* @return (Buffer|Array)
*/
export const setLengthLeft = function(msg: any, length: number, right: boolean = false) {
const buf = zeros(length)
msg = toBuffer(msg)
if (right) {
if (msg.length < length) {
msg.copy(buf)
return buf
}
return msg.slice(0, length)
} else {
if (msg.length < length) {
msg.copy(buf, length - msg.length)
return buf
}
return msg.slice(-length)
}
}
export const setLength = setLengthLeft

/**
* Right Pads an `Array` or `Buffer` with leading zeros till it has `length` bytes.
* Or it truncates the beginning if it exceeds.
* @param msg the value to pad (Buffer|Array)
* @param length the number of bytes the output should be
* @return (Buffer|Array)
*/
export const setLengthRight = function(msg: any, length: number) {
return setLength(msg, length, true)
}

/**
* Trims leading zeros from a `Buffer` or an `Array`.
* @param a (Buffer|Array|String)
* @return (Buffer|Array|String)
*/
export const unpad = function(a: any) {
a = ethjsUtil.stripHexPrefix(a)
let first = a[0]
while (a.length > 0 && first.toString() === '0') {
a = a.slice(1)
first = a[0]
}
return a
}
export const stripZeros = unpad

/**
* Attempts to turn a value into a `Buffer`. As input it supports `Buffer`, `String`, `Number`, null/undefined, `BN` and other objects with a `toArray()` method.
* @param v the value
*/
export const toBuffer = function(v: any): Buffer {
if (!Buffer.isBuffer(v)) {
if (Array.isArray(v)) {
v = Buffer.from(v)
} else if (typeof v === 'string') {
if (ethjsUtil.isHexString(v)) {
v = Buffer.from(ethjsUtil.padToEven(ethjsUtil.stripHexPrefix(v)), 'hex')
} else {
v = Buffer.from(v)
}
} else if (typeof v === 'number') {
v = ethjsUtil.intToBuffer(v)
} else if (v === null || v === undefined) {
v = Buffer.allocUnsafe(0)
} else if (BN.isBN(v)) {
v = v.toArrayLike(Buffer)
} else if (v.toArray) {
// converts a BN to a Buffer
v = Buffer.from(v.toArray())
} else {
throw new Error('invalid type')
}
}
return v
}

/**
* Converts a `Buffer` to a `Number`.
* @param buf `Buffer` object to convert
* @throws If the input number exceeds 53 bits.
*/
export const bufferToInt = function(buf: Buffer): number {
return new BN(toBuffer(buf)).toNumber()
}

/**
* Converts a `Buffer` into a hex `String`.
* @param buf `Buffer` object to convert
*/
export const bufferToHex = function(buf: Buffer): string {
buf = toBuffer(buf)
return '0x' + buf.toString('hex')
}

/**
* Interprets a `Buffer` as a signed integer and returns a `BN`. Assumes 256-bit numbers.
* @param num Signed integer value
*/
export const fromSigned = function(num: Buffer): BN {
return new BN(num).fromTwos(256)
}

/**
* Converts a `BN` to an unsigned integer and returns it as a `Buffer`. Assumes 256-bit numbers.
* @param num
*/
export const toUnsigned = function(num: BN): Buffer {
return Buffer.from(num.toTwos(256).toArray())
}

/**
* Adds "0x" to a given `String` if it does not already start with "0x".
*/
export const addHexPrefix = function(str: string): string {
if (typeof str !== 'string') {
return str
}

return ethjsUtil.isHexPrefixed(str) ? str : '0x' + str
}

/**
* Converts a `Buffer` or `Array` to JSON.
* @param ba (Buffer|Array)
* @return (Array|String|null)
*/
export const baToJSON = function(ba: any): any {
if (Buffer.isBuffer(ba)) {
return `0x${ba.toString('hex')}`
} else if (ba instanceof Array) {
const array = []
for (let i = 0; i < ba.length; i++) {
array.push(baToJSON(ba[i]))
}
return array
}
}

0 comments on commit 61cd643

Please sign in to comment.