Refactored UInt and Derived Classes (#83)

Refactored UInt and Derived classes to be constructed from Buffers, and swapped out BN.js in favor of BigInt to reduce dependencies.
This commit is contained in:
Nathan Nichols
2020-07-10 15:24:49 -05:00
parent 2b8fba0c8a
commit ba04ea5f1f
16 changed files with 316 additions and 176 deletions

View File

@@ -12,7 +12,6 @@
"test": "test"
},
"dependencies": {
"bn.js": "^5.1.2",
"create-hash": "^1.2.0",
"decimal.js": "^10.2.0",
"inherits": "^2.0.4",

View File

@@ -1,6 +1,5 @@
/* eslint-disable func-style */
import { BN } from "bn.js";
import { coreTypes } from "./types";
const { HashPrefix } = require("./hash-prefixes");
const { BinaryParser } = require("./serdes/binary-parser");
@@ -34,7 +33,7 @@ function signingData(tx, prefix = HashPrefix.transactionSig) {
function signingClaimData(claim) {
const prefix = HashPrefix.paymentChannelClaim;
const channel = coreTypes.Hash256.from(claim.channel).toBytes();
const amount = new coreTypes.UInt64(new BN(claim.amount)).toBytes();
const amount = coreTypes.UInt64.from(BigInt(claim.amount)).toBytes();
const bytesList = new BytesList();

View File

@@ -1,5 +1,4 @@
import * as _ from "lodash";
import { BN } from "bn.js";
import { strict as assert } from "assert";
import { coreTypes } from "./types";
const { STObject, Hash256 } = coreTypes;
@@ -54,7 +53,7 @@ function ledgerHash(header) {
assert(header.close_flags !== undefined);
coreTypes.UInt32.from(header.ledger_index).toBytesSink(hash);
coreTypes.UInt64.from(new BN(header.total_coins)).toBytesSink(hash);
coreTypes.UInt64.from(BigInt(header.total_coins)).toBytesSink(hash);
coreTypes.Hash256.from(header.parent_hash).toBytesSink(hash);
coreTypes.Hash256.from(header.transaction_hash).toBytesSink(hash);
coreTypes.Hash256.from(header.account_hash).toBytesSink(hash);

View File

@@ -1,7 +1,6 @@
const Decimal = require("decimal.js");
import { bytesToHex, slice, parseBytes } from "./utils/bytes-utils";
import { coreTypes } from "./types";
import { BN } from "bn.js";
module.exports = {
encode(arg) {
@@ -11,7 +10,7 @@ module.exports = {
.times("1e" + -exponent)
.abs()
.toString();
const bytes = new coreTypes.UInt64(new BN(qualityString)).toBytes();
const bytes = coreTypes.UInt64.from(BigInt(qualityString)).toBytes();
bytes[0] = exponent + 100;
return bytes;
},

View File

@@ -33,7 +33,7 @@ class BinaryParser {
* @return The bytes
*/
read(n: number): Buffer {
assert(n <= this.bytes.byteLength);
assert(n <= this.bytes.byteLength, n + " greater than " + this.bytes.byteLength);
const slice = this.bytes.slice(0, n);
this.skip(n);

View File

@@ -1,7 +1,6 @@
import { makeClass } from "../utils/make-class";
const _ = require("lodash");
const assert = require("assert");
const BN = require("bn.js");
const Decimal = require("decimal.js");
const { SerializedType } = require("./serialized-type");
const { bytesToHex } = require("../utils/bytes-utils");
@@ -155,7 +154,7 @@ const Amount = makeClass(
// This is a tertiary fix for #31
const integerNumberString = this.verifyNoDecimal();
return new UInt64(new BN(integerNumberString));
return UInt64.from(BigInt(integerNumberString));
},
verifyNoDecimal() {
const integerNumberString = this.value

View File

@@ -1,12 +1,44 @@
import { makeClass } from "../utils/make-class";
import { UInt } from "./uint";
import { BinaryParser } from "../serdes/binary-parser";
const UInt16 = makeClass(
{
inherits: UInt,
statics: { width: 2 },
},
undefined
);
/**
* Derived UInt class for serializing/deserializing 16 bit UInt
*/
class UInt16 extends UInt {
protected static readonly width: number = 16 / 8 //2
static readonly defaultUInt16: UInt16 = new UInt16(Buffer.alloc(UInt16.width))
constructor(bytes: Buffer) {
super(bytes ?? UInt16.defaultUInt16.bytes)
}
static fromParser(parser: BinaryParser): UInt {
return new UInt16(parser.read(UInt16.width));
}
/**
* Construct a UInt16 object from a number
*
* @param val UInt16 object or number
*/
static from(val: UInt16 | number): UInt16 {
if(val instanceof UInt16) {
return val;
}
let buf = Buffer.alloc(UInt16.width);
buf.writeUInt16BE(val);
return new UInt16(buf);
}
/**
* get the value of a UInt16 object
*
* @returns the number represented by this.bytes
*/
valueOf(): number {
return this.bytes.readUInt16BE();
}
}
export { UInt16 };

View File

@@ -1,12 +1,44 @@
import { makeClass } from "../utils/make-class";
import { UInt } from "./uint";
import { BinaryParser } from "../serdes/binary-parser";
const UInt32 = makeClass(
{
inherits: UInt,
statics: { width: 4 },
},
undefined
);
/**
* Derived UInt class for serializing/deserializing 32 bit UInt
*/
class UInt32 extends UInt {
protected static readonly width: number = 32 / 8 //4
static readonly defaultUInt32: UInt32 = new UInt32(Buffer.alloc(UInt32.width))
constructor(bytes: Buffer) {
super(bytes ?? UInt32.defaultUInt32.bytes)
}
static fromParser(parser: BinaryParser): UInt {
return new UInt32(parser.read(UInt32.width));
}
/**
* Construct a UInt32 object from a number
*
* @param val UInt32 object or number
*/
static from(val: UInt32 | number): UInt32 {
if(val instanceof UInt32) {
return val;
}
let buf = Buffer.alloc(UInt32.width);
buf.writeUInt32BE(val);
return new UInt32(buf);
}
/**
* get the value of a UInt32 object
*
* @returns the number represented by this.bytes
*/
valueOf(): number {
return this.bytes.readUInt32BE();
}
}
export { UInt32 };

View File

@@ -1,50 +1,81 @@
import { strict as assert } from "assert";
import { BN } from "bn.js";
import { makeClass } from "../utils/make-class";
import { bytesToHex, parseBytes, serializeUIntN } from "../utils/bytes-utils";
import { UInt } from "./uint";
import { BinaryParser } from "../serdes/binary-parser";
const HEX_REGEX = /^[A-F0-9]{16}$/;
const UInt64 = makeClass(
{
inherits: UInt,
statics: { width: 8 },
UInt64(arg: any = 0) {
const argType = typeof arg;
if (argType === "number") {
assert(arg >= 0);
this._bytes = new Uint8Array(8);
this._bytes.set(serializeUIntN(arg, 4), 4);
} else if (arg instanceof BN) {
this._bytes = parseBytes(arg.toArray("be", 8), Uint8Array);
this._toBN = arg;
} else {
if (argType === "string") {
if (!HEX_REGEX.test(arg)) {
throw new Error(`${arg} is not a valid UInt64 hex string`);
}
}
this._bytes = parseBytes(arg, Uint8Array);
/**
* Derived UInt class for serializing/deserializing 64 bit UInt
*/
class UInt64 extends UInt {
protected static readonly width: number = 64 / 8 //8
static readonly defaultUInt64: UInt64 = new UInt64(Buffer.alloc(UInt64.width))
constructor(bytes: Buffer) {
super(bytes ?? UInt64.defaultUInt64.bytes)
}
static fromParser(parser: BinaryParser): UInt {
return new UInt64(parser.read(UInt64.width));
}
/**
* Construct a UInt64 object
*
* @param val A UInt64, hex-string, bigint, or number
* @returns A UInt64 object
*/
static from(val: UInt64 | string | bigint | number): UInt64 {
if(val instanceof UInt64) {
return val;
}
let buf = Buffer.alloc(UInt64.width);
if(typeof val === "number") {
if(val < 0) {
throw new Error("value must be an unsigned integer");
}
assert(this._bytes.length === 8);
},
toJSON() {
return bytesToHex(this._bytes);
},
valueOf() {
return this.toBN();
},
cached: {
toBN() {
return new BN(this._bytes);
},
},
toBytes() {
return this._bytes;
},
},
undefined
);
buf.writeBigUInt64BE(BigInt(val));
}
else if (typeof val === "string") {
if(!HEX_REGEX.test(val)) {
throw new Error(val + "is not a valid hex-string")
}
buf = Buffer.from(val, "hex")
}
else { // typeof val === bigint
buf.writeBigUInt64BE(val)
}
return new UInt64(buf);
}
/**
* The JSON representation of a UInt64 object
*
* @returns a hex-string
*/
toJSON(): string {
return this.bytes.toString('hex').toUpperCase();
}
/**
* Get the value of the UInt64
*
* @returns the number represented buy this.bytes
*/
valueOf(): bigint {
return this.bytes.readBigUInt64BE();
}
/**
* Get the bytes representation of the UInt64 object
*
* @returns 8 bytes representing the UInt64
*/
toBytes(): Buffer {
return this.bytes;
}
}
export { UInt64 };

View File

@@ -1,12 +1,44 @@
import { makeClass } from "../utils/make-class";
import { UInt } from "./uint";
import { BinaryParser } from "../serdes/binary-parser";
const UInt8 = makeClass(
{
inherits: UInt,
statics: { width: 1 },
},
undefined
);
/**
* Derived UInt class for serializing/deserializing 8 bit UInt
*/
class UInt8 extends UInt {
protected static readonly width: number = 8 / 8 //1
static readonly defaultUInt8: UInt8 = new UInt8(Buffer.alloc(UInt8.width))
constructor(bytes: Buffer) {
super(bytes ?? UInt8.defaultUInt8.bytes)
}
static fromParser(parser: BinaryParser): UInt {
return new UInt8(parser.read(UInt8.width));
}
/**
* Construct a UInt8 object from a number
*
* @param val UInt8 object or number
*/
static from(val: UInt8 | number): UInt8 {
if(val instanceof UInt8) {
return val;
}
let buf = Buffer.alloc(UInt8.width);
buf.writeUInt8(val);
return new UInt8(buf);
}
/**
* get the value of a UInt8 object
*
* @returns the number represented by this.bytes
*/
valueOf(): number {
return this.bytes.readUInt8();
}
}
export { UInt8 };

View File

@@ -1,64 +1,54 @@
import { strict as assert } from "assert";
import { BN } from "bn.js";
import { makeClass } from "../utils/make-class";
const { Comparable, SerializedType } = require("./serialized-type");
const { serializeUIntN } = require("../utils/bytes-utils");
const MAX_VALUES = [0, 255, 65535, 16777215, 4294967295];
import { ComparableClass } from "./serialized-type";
function signum(a, b) {
return a < b ? -1 : a === b ? 0 : 1;
/**
* Compare numbers and bigints n1 and n2
*
* @param n1 First object to compare
* @param n2 Second object to compare
* @returns -1, 0, or 1, depending on how the two objects compare
*/
function compare(n1: number | bigint, n2: number | bigint): number {
return n1 < n2 ? -1 : n1 == n2 ? 0 : 1
}
const UInt = makeClass(
{
mixins: [Comparable, SerializedType],
UInt(val = 0) {
const max = MAX_VALUES[this.constructor.width];
if (val < 0 || !(val <= max)) {
throw new Error(`${val} not in range 0 <= $val <= ${max}`);
}
this.val = val;
},
statics: {
width: 0,
fromParser(parser) {
const val =
this.width > 4
? parser.read(this.width)
: parser.readUIntN(this.width);
return new this(val);
},
from(val) {
return val instanceof this ? val : new this(val);
},
},
toJSON() {
return this.val;
},
valueOf() {
return this.val;
},
compareTo(other) {
const thisValue = this.valueOf();
const otherValue = other.valueOf();
if (thisValue instanceof BN) {
return otherValue instanceof BN
? thisValue.cmp(otherValue)
: thisValue.cmpn(otherValue);
} else if (otherValue instanceof BN) {
return -other.compareTo(this);
}
assert(typeof otherValue === "number");
return signum(thisValue, otherValue);
},
toBytesSink(sink) {
sink.put(this.toBytes());
},
toBytes() {
return serializeUIntN(this.val, this.constructor.width);
},
},
undefined
);
/**
* Base class for serializing and deserializing unsigned integers.
*/
abstract class UInt extends ComparableClass {
protected static width: number
constructor(bytes: Buffer) {
super(bytes)
}
/**
* Overload of compareTo for Comparable
*
* @param other other UInt to compare this to
* @returns -1, 0, or 1 depending on how the objects relate to each other
*/
compareTo(other: UInt): number {
return compare(this.valueOf(), other.valueOf());
}
/**
* Convert a UInt object to JSON
*
* @returns number or string represented by this.bytes
*/
toJSON(): number | string {
let val = this.valueOf()
return typeof val === "number"
? val
: val.toString();
}
/**
* Get the value of the UInt represented by this.bytes
*
* @returns the value
*/
abstract valueOf(): number | bigint;
}
export { UInt };

View File

@@ -1,33 +1,67 @@
import { makeClass } from "../utils/make-class";
const { Hash256 } = require("./hash-256");
const { ensureArrayLikeIs, SerializedType } = require("./serialized-type");
import { SerializedTypeClass } from "./serialized-type";
import { BinaryParser } from "../serdes/binary-parser";
import { Hash256 } from "./hash-256";
import { BytesList } from "../serdes/binary-serializer";
const Vector256 = makeClass(
{
mixins: SerializedType,
inherits: Array,
statics: {
fromParser(parser, hint) {
const vector256 = new this();
const bytes = hint !== null ? hint : parser.size() - parser.pos();
const hashes = bytes / 32;
for (let i = 0; i < hashes; i++) {
vector256.push(Hash256.fromParser(parser));
}
return vector256;
},
from(value) {
return ensureArrayLikeIs(Vector256, value).withChildren(Hash256);
},
},
toBytesSink(sink) {
this.forEach((h) => h.toBytesSink(sink));
},
toJSON() {
return this.map((hash) => hash.toJSON());
},
},
undefined
);
/**
* Class for serializing and deserializing vectors of Hash256
*/
class Vector256 extends SerializedTypeClass {
constructor(bytes: Buffer) {
super(bytes)
}
/**
* Construct a Vector256 from a BinaryParser
*
* @param parser BinaryParser to
* @param hint length of the vector, in bytes, optional
* @returns a Vector256 object
*/
static fromParser(parser: BinaryParser, hint?: number): Vector256 {
let bytesList = new BytesList();
const bytes = hint ?? parser.size();
const hashes = bytes / 32;
for (let i = 0; i < hashes; i++) {
Hash256.fromParser(parser).toBytesSink(bytesList);
}
return new Vector256(bytesList.toBytes());
}
/**
* Construct a Vector256 object from an array of hashes
*
* @param value A Vector256 object or array of hex-strings representing Hash256's
* @returns a Vector256 object
*/
static from(value: Vector256 | Array<string>): Vector256 {
if(value instanceof Vector256) {
return value;
}
let bytesList = new BytesList();
value.forEach(hash => {
Hash256.from(hash).toBytesSink(bytesList);
});
return new Vector256(bytesList.toBytes());
}
/**
* Return an Array of hex-strings represented by this.bytes
*
* @returns An Array of strings representing the Hash256 objects
*/
toJSON(): Array<string> {
if(this.bytes.byteLength % 32 !== 0) {
throw new Error("Invalid bytes for Vector256")
}
let result: Array<string> = []
for(let i = 0; i < this.bytes.byteLength; i += 32) {
result.push(this.bytes.slice(i,i+32).toString('hex').toUpperCase())
}
return result
}
}
export { Vector256 };

View File

@@ -128,12 +128,12 @@ function transactionParsingTests () {
{
const [field, value] = readField()
expect(field).toEqual(Field.Flags)
expect(value.val).toEqual(0)
expect(value.valueOf()).toEqual(0)
}
{
const [field, value] = readField()
expect(field).toEqual(Field.Sequence)
expect(value.val).toEqual(103929)
expect(value.valueOf()).toEqual(103929)
}
{
const [field, value] = readField()

View File

@@ -1,6 +1,5 @@
/* eslint-disable func-style */
const { BN } = require('bn.js')
const { binary } = require('../dist/coretypes')
const { encode } = require('../dist')
const { makeParser, BytesList, BinarySerializer } = binary
@@ -110,7 +109,7 @@ check(UInt64, 0xFEFFFFFF, [0, 0, 0, 0, 254, 255, 255, 255])
check(UInt64, -1, 'throws')
check(UInt64, 0, [0, 0, 0, 0, 0, 0, 0, 0])
check(UInt64, 1, [0, 0, 0, 0, 0, 0, 0, 1])
check(UInt64, new BN(1), [0, 0, 0, 0, 0, 0, 0, 1])
check(UInt64, BigInt(1), [0, 0, 0, 0, 0, 0, 0, 1])
// function parseLedger4320278() {
// test('can parse object', done => {

View File

@@ -2,7 +2,7 @@ const { coreTypes } = require('../dist/types')
const { UInt8, UInt64 } = coreTypes
test('compareToTests', () => {
expect(UInt8.from(124).compareTo(UInt64.from(124))).toBe(-0)
expect(UInt8.from(124).compareTo(UInt64.from(124))).toBe(0)
})
test('compareToTest', () => {

View File

@@ -916,11 +916,6 @@ bcrypt-pbkdf@^1.0.0:
dependencies:
tweetnacl "^0.14.3"
bn.js@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.2.tgz#c9686902d3c9a27729f43ab10f9d79c2004da7b0"
integrity sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"