ethers.js/src.ts/utils/convert.ts
2018-06-13 15:39:39 -04:00

246 lines
6.4 KiB
TypeScript

/**
* Conversion Utilities
*
*/
import errors = require('./errors');
export type Arrayish = string | ArrayLike<number>;
export type Signature = { r: string, s: string, v: number };
declare class BigNumber {
toHexString(): string;
}
function isBigNumber(value: any): value is BigNumber {
return !!value._bn;
}
function addSlice(array: Uint8Array): Uint8Array {
if (array.slice) { return array; }
array.slice = function() {
var args = Array.prototype.slice.call(arguments);
return new Uint8Array(Array.prototype.slice.apply(array, args));
}
return array;
}
export function isArrayish(value: any): boolean {
if (!value || parseInt(value.length) != value.length || typeof(value) === 'string') {
return false;
}
for (var i = 0; i < value.length; i++) {
var v = value[i];
if (v < 0 || v >= 256 || parseInt(v) != v) {
return false;
}
}
return true;
}
export function arrayify(value: Arrayish | BigNumber): Uint8Array {
if (value == null) {
errors.throwError('cannot convert null value to array', errors.INVALID_ARGUMENT, { arg: 'value', value: value });
}
if (isBigNumber(value)) {
value = value.toHexString();
}
if (typeof(value) === 'string') {
let match = value.match(/^(0x)?[0-9a-fA-F]*$/);
if (!match) {
errors.throwError('invalid hexidecimal string', errors.INVALID_ARGUMENT, { arg: 'value', value: value });
}
if (match[1] !== '0x') {
errors.throwError('hex string must have 0x prefix', errors.INVALID_ARGUMENT, { arg: 'value', value: value });
}
value = value.substring(2);
if (value.length % 2) { value = '0' + value; }
var result = [];
for (var i = 0; i < value.length; i += 2) {
result.push(parseInt(value.substr(i, 2), 16));
}
return addSlice(new Uint8Array(result));
} else if (typeof(value) === 'string') {
}
if (isArrayish(value)) {
return addSlice(new Uint8Array(value));
}
errors.throwError('invalid arrayify value', null, { arg: 'value', value: value, type: typeof(value) });
return null;
}
export function concat(objects: Array<Arrayish>): Uint8Array {
var arrays = [];
var length = 0;
for (var i = 0; i < objects.length; i++) {
var object = arrayify(objects[i])
arrays.push(object);
length += object.length;
}
var result = new Uint8Array(length);
var offset = 0;
for (var i = 0; i < arrays.length; i++) {
result.set(arrays[i], offset);
offset += arrays[i].length;
}
return addSlice(result);
}
export function stripZeros(value: Arrayish): Uint8Array {
let result: Uint8Array = arrayify(value);
if (result.length === 0) { return result; }
// Find the first non-zero entry
var start = 0;
while (result[start] === 0) { start++ }
// If we started with zeros, strip them
if (start) {
result = result.slice(start);
}
return result;
}
export function padZeros(value: Arrayish, length: number): Uint8Array {
value = arrayify(value);
if (length < value.length) { throw new Error('cannot pad'); }
var result = new Uint8Array(length);
result.set(value, length - value.length);
return addSlice(result);
}
export function isHexString(value: any, length?: number): boolean {
if (typeof(value) !== 'string' || !value.match(/^0x[0-9A-Fa-f]*$/)) {
return false
}
if (length && value.length !== 2 + 2 * length) { return false; }
return true;
}
const HexCharacters: string = '0123456789abcdef';
// @TODO: Do not use any here
export function hexlify(value: Arrayish | BigNumber | number): string {
if (isBigNumber(value)) {
return value.toHexString();
}
if (typeof(value) === 'number') {
if (value < 0) {
errors.throwError('cannot hexlify negative value', errors.INVALID_ARGUMENT, { arg: 'value', value: value });
}
var hex = '';
while (value) {
hex = HexCharacters[value & 0x0f] + hex;
value = Math.trunc(value / 16);
}
if (hex.length) {
if (hex.length % 2) { hex = '0' + hex; }
return '0x' + hex;
}
return '0x00';
}
if (typeof(value) === 'string') {
let match = value.match(/^(0x)?[0-9a-fA-F]*$/);
if (!match) {
errors.throwError('invalid hexidecimal string', errors.INVALID_ARGUMENT, { arg: 'value', value: value });
}
if (match[1] !== '0x') {
errors.throwError('hex string must have 0x prefix', errors.INVALID_ARGUMENT, { arg: 'value', value: value });
}
if (value.length % 2) {
value = '0x0' + value.substring(2);
}
return value;
}
if (isArrayish(value)) {
var result = [];
for (var i = 0; i < value.length; i++) {
var v = value[i];
result.push(HexCharacters[(v & 0xf0) >> 4] + HexCharacters[v & 0x0f]);
}
return '0x' + result.join('');
}
errors.throwError('invalid hexlify value', null, { arg: 'value', value: value });
return 'never';
}
export function hexStripZeros(value: string): string {
if (!isHexString(value)) {
errors.throwError('invalid hex string', errors.INVALID_ARGUMENT, { arg: 'value', value: value });
}
while (value.length > 3 && value.substring(0, 3) === '0x0') {
value = '0x' + value.substring(3);
}
return value;
}
export function hexZeroPad(value: string, length: number): string {
if (!isHexString(value)) {
errors.throwError('invalid hex string', errors.INVALID_ARGUMENT, { arg: 'value', value: value });
}
while (value.length < 2 * length + 2) {
value = '0x0' + value.substring(2);
}
return value;
}
/* @TODO: Add something like this to make slicing code easier to understand
function hexSlice(hex, start, end) {
hex = hexlify(hex);
return '0x' + hex.substring(2 + start * 2, 2 + end * 2);
}
*/
export function splitSignature(signature: Arrayish): Signature {
let bytes: Uint8Array = arrayify(signature);
if (bytes.length !== 65) {
throw new Error('invalid signature');
}
var v = bytes[64];
if (v !== 27 && v !== 28) {
v = 27 + (v % 2);
}
return {
r: hexlify(bytes.slice(0, 32)),
s: hexlify(bytes.slice(32, 64)),
v: v
}
}