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

Make bytes methods type strict #244

Merged
merged 6 commits into from
Apr 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 58 additions & 17 deletions src/bytes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const ethjsUtil = require('ethjs-util')
import * as BN from 'bn.js'
import { assertIsBuffer, assertIsArray, assertIsHexString } from './helpers'

/**
* Returns a buffer filled with 0s.
Expand All @@ -10,16 +11,39 @@ export const zeros = function(bytes: number): Buffer {
}

/**
* Left Pads an `Array` or `Buffer` with leading zeros till it has `length` bytes.
* Left Pads a `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 msg the value to pad (Buffer)
* @param length the number of bytes the output should be
* @return (Buffer)
*/
export const setLengthLeft = function(msg: Buffer, length: number) {
assertIsBuffer(msg)
return setLength(msg, length, false)
}

/**
* Right Pads a `Buffer` with trailing zeros till it has `length` bytes.
* it truncates the end if it exceeds.
* @param msg the value to pad (Buffer)
* @param length the number of bytes the output should be
* @return (Buffer)
*/
export const setLengthRight = function(msg: Buffer, length: number) {
assertIsBuffer(msg)
return setLength(msg, length, true)
}

/**
* Pads a `Buffer` with zeros till it has `length` bytes.
* Truncates the beginning or end of input if its length exceeds `length`.
* @param msg the value to pad (Buffer)
* @param length the number of bytes the output should be
* @param right whether to start padding form the left or right
* @return (Buffer|Array)
* @return (Buffer)
*/
export const setLengthLeft = function(msg: any, length: number, right: boolean = false) {
const setLength = function(msg: Buffer, length: number, right: boolean) {
const buf = zeros(length)
msg = toBuffer(msg)
if (right) {
if (msg.length < length) {
msg.copy(buf)
Expand All @@ -34,34 +58,51 @@ export const setLengthLeft = function(msg: any, length: number, right: boolean =
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)
* Trims leading zeros from a `Buffer`.
* @param a (Buffer)
* @return (Buffer)
*/
export const setLengthRight = function(msg: any, length: number) {
return setLength(msg, length, true)
export const unpadBuffer = function(a: Buffer): Buffer {
assertIsBuffer(a)
return stripZeros(a) as Buffer
}

/**
* Trims leading zeros from an `Array` (of numbers).
* @param a (number[])
* @return (number[])
*/
export const unpadArray = function(a: number[]): number[] {
assertIsArray(a)
return stripZeros(a) as number[]
}

/**
* Trims leading zeros from a `Buffer` or an `Array`.
* Trims leading zeros from a hex-prefixed `String`.
* @param a (String)
* @return (String)
*/
export const unpadHexString = function(a: string): string {
assertIsHexString(a)
a = ethjsUtil.stripHexPrefix(a)
return stripZeros(a) as string
}

/**
* Trims leading zeros from a `Buffer`, `String` or `Number[]`.
* @param a (Buffer|Array|String)
* @return (Buffer|Array|String)
*/
export const unpad = function(a: any) {
a = ethjsUtil.stripHexPrefix(a)
const stripZeros = function(a: any): Buffer | number[] | string {
let first = a[0]
while (a.length > 0 && first.toString() === '0') {
a = a.slice(1)
first = a[0]
}
return a
}
export const stripZeros = unpad
cgewecke marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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.
Expand Down
4 changes: 2 additions & 2 deletions src/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const createKeccakHash = require('keccak')
const createHash = require('create-hash')
const ethjsUtil = require('ethjs-util')
import * as rlp from 'rlp'
import { toBuffer, setLength } from './bytes'
import { toBuffer, setLengthLeft } from './bytes'

/**
* Creates Keccak hash of the input
Expand Down Expand Up @@ -54,7 +54,7 @@ export const ripemd160 = function(a: any, padded: boolean): Buffer {
.update(a)
.digest()
if (padded === true) {
return setLength(hash, 32)
return setLengthLeft(hash, 32)
} else {
return hash
}
Expand Down
11 changes: 11 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,14 @@ export const assertIsBuffer = function(input: Buffer): void {
throw new Error(msg)
}
}

/**
* Throws if input is not an array
* @param {number[]} input value to check
*/
export const assertIsArray = function(input: number[]): void {
const msg = `This method only supports number arrays but input was: ${input}`
if (!Array.isArray(input)) {
throw new Error(msg)
}
}
4 changes: 2 additions & 2 deletions src/object.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const ethjsUtil = require('ethjs-util')
import * as assert from 'assert'
import * as rlp from 'rlp'
import { toBuffer, baToJSON, stripZeros } from './bytes'
import { toBuffer, baToJSON, unpadBuffer } from './bytes'

/**
* Defines properties on a `Object`. It make the assumption that underlying data is binary.
Expand Down Expand Up @@ -48,7 +48,7 @@ export const defineProperties = function(self: any, fields: any, data?: any) {
}

if (field.allowLess && field.length) {
v = stripZeros(v)
v = unpadBuffer(v)
assert(
field.length >= v.length,
`The field ${field.name} must not have more ${field.length} bytes`,
Expand Down
4 changes: 2 additions & 2 deletions src/signature.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as secp256k1 from 'secp256k1'
import * as BN from 'bn.js'
import { toBuffer, setLength, setLengthLeft, bufferToHex } from './bytes'
import { toBuffer, setLengthLeft, bufferToHex } from './bytes'
import { keccak } from './hash'

export interface ECDSASignature {
Expand Down Expand Up @@ -40,7 +40,7 @@ export const ecrecover = function(
s: Buffer,
chainId?: number,
): Buffer {
const signature = Buffer.concat([setLength(r, 32), setLength(s, 32)], 64)
const signature = Buffer.concat([setLengthLeft(r, 32), setLengthLeft(s, 32)], 64)
const recovery = calculateSigRecovery(v, chainId)
if (!isValidSigRecovery(recovery)) {
throw new Error('Invalid signature v value')
Expand Down
72 changes: 48 additions & 24 deletions test/bytes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import {
zeros,
zeroAddress,
isZeroAddress,
unpad,
setLength,
unpadBuffer,
unpadArray,
unpadHexString,
setLengthLeft,
setLengthRight,
bufferToHex,
Expand Down Expand Up @@ -48,55 +49,78 @@ describe('is zero address', function() {
})
})

describe('unpad', function() {
it('should unpad a string', function() {
const str = '0000000006600'
const r = unpad(str)
assert.equal(r, '6600')
describe('unpadBuffer', function() {
it('should unpad a Buffer', function() {
const buf = toBuffer('0x0000000006600')
const r = unpadBuffer(buf)
assert.deepEqual(r, toBuffer('0x6600'))
})
it('should throw if input is not a Buffer', function() {
assert.throws(function() {
unpadBuffer((<unknown>'0000000006600') as Buffer)
})
})
})

describe('unpad a hex string', function() {
it('should unpad a string', function() {
describe('unpadArray', function() {
it('should unpad an Array', function() {
const arr = [0, 0, 0, 1]
const r = unpadArray(arr)
assert.deepEqual(r, [1])
})
it('should throw if input is not an Array', function() {
assert.throws(function() {
unpadArray((<unknown>toBuffer([0, 0, 0, 1])) as number[])
})
})
})

describe('unpadHexString', function() {
it('should unpad a hex prefixed string', function() {
const str = '0x0000000006600'
const r = unpad(str)
const r = unpadHexString(str)
assert.equal(r, '6600')
})
it('should throw if input is not hex-prefixed', function() {
assert.throws(function() {
unpadHexString('0000000006600')
})
})
})

describe('pad', function() {
describe('setLengthLeft', function() {
it('should left pad a Buffer', function() {
const buf = Buffer.from([9, 9])
const padded = setLength(buf, 3)
const padded = setLengthLeft(buf, 3)
assert.equal(padded.toString('hex'), '000909')
})
it('should left truncate a Buffer', function() {
const buf = Buffer.from([9, 0, 9])
const padded = setLength(buf, 2)
const padded = setLengthLeft(buf, 2)
assert.equal(padded.toString('hex'), '0009')
})
it('should left pad a Buffer - alias', function() {
const buf = Buffer.from([9, 9])
const padded = setLengthLeft(buf, 3)
assert.equal(padded.toString('hex'), '000909')
it('should throw if input is not a Buffer', function() {
assert.throws(function() {
setLengthLeft((<unknown>[9, 9]) as Buffer, 3)
})
})
})

describe('rpad', function() {
describe('setLengthRight', function() {
it('should right pad a Buffer', function() {
const buf = Buffer.from([9, 9])
const padded = setLength(buf, 3, true)
const padded = setLengthRight(buf, 3)
assert.equal(padded.toString('hex'), '090900')
})
it('should right truncate a Buffer', function() {
const buf = Buffer.from([9, 0, 9])
const padded = setLength(buf, 2, true)
const padded = setLengthRight(buf, 2)
assert.equal(padded.toString('hex'), '0900')
})
it('should right pad a Buffer - alias', function() {
const buf = Buffer.from([9, 9])
const padded = setLengthRight(buf, 3)
assert.equal(padded.toString('hex'), '090900')
it('should throw if input is not a Buffer', function() {
assert.throws(function() {
setLengthRight((<unknown>[9, 9]) as Buffer, 3)
})
})
})

Expand Down