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" "test": "test"
}, },
"dependencies": { "dependencies": {
"bn.js": "^5.1.2",
"create-hash": "^1.2.0", "create-hash": "^1.2.0",
"decimal.js": "^10.2.0", "decimal.js": "^10.2.0",
"inherits": "^2.0.4", "inherits": "^2.0.4",

View File

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

View File

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

View File

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

View File

@@ -33,7 +33,7 @@ class BinaryParser {
* @return The bytes * @return The bytes
*/ */
read(n: number): Buffer { 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); const slice = this.bytes.slice(0, n);
this.skip(n); this.skip(n);

View File

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

View File

@@ -1,12 +1,44 @@
import { makeClass } from "../utils/make-class";
import { UInt } from "./uint"; import { UInt } from "./uint";
import { BinaryParser } from "../serdes/binary-parser";
const UInt16 = makeClass( /**
{ * Derived UInt class for serializing/deserializing 16 bit UInt
inherits: UInt, */
statics: { width: 2 }, class UInt16 extends UInt {
}, protected static readonly width: number = 16 / 8 //2
undefined 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 }; export { UInt16 };

View File

@@ -1,12 +1,44 @@
import { makeClass } from "../utils/make-class";
import { UInt } from "./uint"; import { UInt } from "./uint";
import { BinaryParser } from "../serdes/binary-parser";
const UInt32 = makeClass( /**
{ * Derived UInt class for serializing/deserializing 32 bit UInt
inherits: UInt, */
statics: { width: 4 }, class UInt32 extends UInt {
}, protected static readonly width: number = 32 / 8 //4
undefined 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 }; 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 { UInt } from "./uint";
import { BinaryParser } from "../serdes/binary-parser";
const HEX_REGEX = /^[A-F0-9]{16}$/; const HEX_REGEX = /^[A-F0-9]{16}$/;
const UInt64 = makeClass( /**
{ * Derived UInt class for serializing/deserializing 64 bit UInt
inherits: UInt, */
statics: { width: 8 }, class UInt64 extends UInt {
UInt64(arg: any = 0) { protected static readonly width: number = 64 / 8 //8
const argType = typeof arg; static readonly defaultUInt64: UInt64 = new UInt64(Buffer.alloc(UInt64.width))
if (argType === "number") {
assert(arg >= 0); constructor(bytes: Buffer) {
this._bytes = new Uint8Array(8); super(bytes ?? UInt64.defaultUInt64.bytes)
this._bytes.set(serializeUIntN(arg, 4), 4); }
} else if (arg instanceof BN) {
this._bytes = parseBytes(arg.toArray("be", 8), Uint8Array); static fromParser(parser: BinaryParser): UInt {
this._toBN = arg; return new UInt64(parser.read(UInt64.width));
} else { }
if (argType === "string") {
if (!HEX_REGEX.test(arg)) { /**
throw new Error(`${arg} is not a valid UInt64 hex string`); * Construct a UInt64 object
} *
} * @param val A UInt64, hex-string, bigint, or number
this._bytes = parseBytes(arg, Uint8Array); * @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); buf.writeBigUInt64BE(BigInt(val));
}, }
toJSON() { else if (typeof val === "string") {
return bytesToHex(this._bytes); if(!HEX_REGEX.test(val)) {
}, throw new Error(val + "is not a valid hex-string")
valueOf() { }
return this.toBN(); buf = Buffer.from(val, "hex")
}, }
cached: { else { // typeof val === bigint
toBN() { buf.writeBigUInt64BE(val)
return new BN(this._bytes); }
},
}, return new UInt64(buf);
toBytes() { }
return this._bytes;
}, /**
}, * The JSON representation of a UInt64 object
undefined *
); * @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 }; export { UInt64 };

View File

@@ -1,12 +1,44 @@
import { makeClass } from "../utils/make-class";
import { UInt } from "./uint"; import { UInt } from "./uint";
import { BinaryParser } from "../serdes/binary-parser";
const UInt8 = makeClass( /**
{ * Derived UInt class for serializing/deserializing 8 bit UInt
inherits: UInt, */
statics: { width: 1 }, class UInt8 extends UInt {
}, protected static readonly width: number = 8 / 8 //1
undefined 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 }; export { UInt8 };

View File

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

View File

@@ -1,33 +1,67 @@
import { makeClass } from "../utils/make-class"; import { SerializedTypeClass } from "./serialized-type";
const { Hash256 } = require("./hash-256"); import { BinaryParser } from "../serdes/binary-parser";
const { ensureArrayLikeIs, SerializedType } = require("./serialized-type"); import { Hash256 } from "./hash-256";
import { BytesList } from "../serdes/binary-serializer";
const Vector256 = makeClass( /**
{ * Class for serializing and deserializing vectors of Hash256
mixins: SerializedType, */
inherits: Array, class Vector256 extends SerializedTypeClass {
statics: { constructor(bytes: Buffer) {
fromParser(parser, hint) { super(bytes)
const vector256 = new this(); }
const bytes = hint !== null ? hint : parser.size() - parser.pos();
const hashes = bytes / 32; /**
for (let i = 0; i < hashes; i++) { * Construct a Vector256 from a BinaryParser
vector256.push(Hash256.fromParser(parser)); *
} * @param parser BinaryParser to
return vector256; * @param hint length of the vector, in bytes, optional
}, * @returns a Vector256 object
from(value) { */
return ensureArrayLikeIs(Vector256, value).withChildren(Hash256); static fromParser(parser: BinaryParser, hint?: number): Vector256 {
}, let bytesList = new BytesList();
}, const bytes = hint ?? parser.size();
toBytesSink(sink) { const hashes = bytes / 32;
this.forEach((h) => h.toBytesSink(sink)); for (let i = 0; i < hashes; i++) {
}, Hash256.fromParser(parser).toBytesSink(bytesList);
toJSON() { }
return this.map((hash) => hash.toJSON()); return new Vector256(bytesList.toBytes());
}, }
},
undefined /**
); * 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 }; export { Vector256 };

View File

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

View File

@@ -1,6 +1,5 @@
/* eslint-disable func-style */ /* eslint-disable func-style */
const { BN } = require('bn.js')
const { binary } = require('../dist/coretypes') const { binary } = require('../dist/coretypes')
const { encode } = require('../dist') const { encode } = require('../dist')
const { makeParser, BytesList, BinarySerializer } = binary 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, -1, 'throws')
check(UInt64, 0, [0, 0, 0, 0, 0, 0, 0, 0]) check(UInt64, 0, [0, 0, 0, 0, 0, 0, 0, 0])
check(UInt64, 1, [0, 0, 0, 0, 0, 0, 0, 1]) 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() { // function parseLedger4320278() {
// test('can parse object', done => { // test('can parse object', done => {

View File

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

View File

@@ -916,11 +916,6 @@ bcrypt-pbkdf@^1.0.0:
dependencies: dependencies:
tweetnacl "^0.14.3" 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: brace-expansion@^1.1.7:
version "1.1.11" version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"