ripple-binary-codec refactor (#88)

Refactored all components in ripple-binary-codec /src
This commit is contained in:
Nathan Nichols
2020-07-31 09:14:11 -05:00
parent 383ab88d62
commit 2e6c68ba73
37 changed files with 1932 additions and 1707 deletions

View File

@@ -14,12 +14,10 @@
"dependencies": {
"create-hash": "^1.2.0",
"decimal.js": "^10.2.0",
"inherits": "^2.0.4",
"lodash": "^4.17.15",
"ripple-address-codec": "^4.1.1"
},
"devDependencies": {
"@types/jest": "^26.0.0",
"@types/jest": "^26.0.7",
"@types/lodash": "^4.14.155",
"@types/node": "^14.0.10",
"@typescript-eslint/eslint-plugin": "^3.2.0",

View File

@@ -1 +1,3 @@
# ripple-binary-codec
# ripple-binary-codec
Serialize and deserialize transactions according to the XRP Ledger protocol.

View File

@@ -1,36 +1,107 @@
/* eslint-disable func-style */
import { coreTypes } from "./types";
const { HashPrefix } = require("./hash-prefixes");
const { BinaryParser } = require("./serdes/binary-parser");
const { BinarySerializer, BytesList } = require("./serdes/binary-serializer");
const { bytesToHex, slice, parseBytes } = require("./utils/bytes-utils");
import { BinaryParser } from "./serdes/binary-parser";
import { AccountID } from "./types/account-id";
import { HashPrefix } from "./hash-prefixes";
import { BinarySerializer, BytesList } from "./serdes/binary-serializer";
import { sha512Half, transactionID } from "./hashes";
import { FieldInstance } from "./enums";
import { STObject } from "./types/st-object";
import { JsonObject } from "./types/serialized-type";
const { sha512Half, transactionID } = require("./hashes");
/**
* Construct a BinaryParser
*
* @param bytes hex-string to construct BinaryParser from
* @returns A BinaryParser
*/
const makeParser = (bytes: string): BinaryParser => new BinaryParser(bytes);
const makeParser = (bytes) => new BinaryParser(bytes);
const readJSON = (parser) => parser.readType(coreTypes.STObject).toJSON();
const binaryToJSON = (bytes) => readJSON(makeParser(bytes));
/**
* Parse BinaryParser into JSON
*
* @param parser BinaryParser object
* @returns JSON for the bytes in the BinaryParser
*/
const readJSON = (parser: BinaryParser): JsonObject =>
(parser.readType(coreTypes.STObject) as STObject).toJSON();
function serializeObject(object, opts = <any>{}) {
/**
* Parse a hex-string into its JSON interpretation
*
* @param bytes hex-string to parse into JSON
* @returns JSON
*/
const binaryToJSON = (bytes: string): JsonObject => readJSON(makeParser(bytes));
/**
* Interface for passing parameters to SerializeObject
*
* @field set signingFieldOnly to true if you want to serialize only signing fields
*/
interface OptionObject {
prefix?: Buffer;
suffix?: Buffer;
signingFieldsOnly?: boolean;
}
/**
* Function to serialize JSON object representing a transaction
*
* @param object JSON object to serialize
* @param opts options for serializing, including optional prefix, suffix, and signingFieldOnly
* @returns A Buffer containing the serialized object
*/
function serializeObject(object: JsonObject, opts: OptionObject = {}): Buffer {
const { prefix, suffix, signingFieldsOnly = false } = opts;
const bytesList = new BytesList();
if (prefix) {
bytesList.put(prefix);
}
const filter = signingFieldsOnly ? (f) => f.isSigningField : undefined;
const filter = signingFieldsOnly
? (f: FieldInstance): boolean => f.isSigningField
: undefined;
coreTypes.STObject.from(object, filter).toBytesSink(bytesList);
if (suffix) {
bytesList.put(suffix);
}
return bytesList.toBytes();
}
function signingData(tx, prefix = HashPrefix.transactionSig) {
return serializeObject(tx, { prefix, signingFieldsOnly: true });
/**
* Serialize an object for signing
*
* @param transaction Transaction to serialize
* @param prefix Prefix bytes to put before the serialized object
* @returns A Buffer with the serialized object
*/
function signingData(
transaction: JsonObject,
prefix: Buffer = HashPrefix.transactionSig
): Buffer {
return serializeObject(transaction, { prefix, signingFieldsOnly: true });
}
function signingClaimData(claim) {
/**
* Interface describing fields required for a Claim
*/
interface ClaimObject extends JsonObject {
channel: string;
amount: string | number;
}
/**
* Serialize a signingClaim
*
* @param claim A claim object to serialize
* @returns the serialized object with appropriate prefix
*/
function signingClaimData(claim: ClaimObject): Buffer {
const prefix = HashPrefix.paymentChannelClaim;
const channel = coreTypes.Hash256.from(claim.channel).toBytes();
const amount = coreTypes.UInt64.from(BigInt(claim.amount)).toBytes();
@@ -43,26 +114,38 @@ function signingClaimData(claim) {
return bytesList.toBytes();
}
function multiSigningData(tx, signingAccount) {
/**
* Serialize a transaction object for multiSigning
*
* @param transaction transaction to serialize
* @param signingAccount Account to sign the transaction with
* @returns serialized transaction with appropriate prefix and suffix
*/
function multiSigningData(
transaction: JsonObject,
signingAccount: string | AccountID
): Buffer {
const prefix = HashPrefix.transactionMultiSig;
const suffix = coreTypes.AccountID.from(signingAccount).toBytes();
return serializeObject(tx, { prefix, suffix, signingFieldsOnly: true });
return serializeObject(transaction, {
prefix,
suffix,
signingFieldsOnly: true,
});
}
export {
BinaryParser,
BinarySerializer,
BytesList,
ClaimObject,
makeParser,
serializeObject,
readJSON,
bytesToHex,
parseBytes,
multiSigningData,
signingData,
signingClaimData,
binaryToJSON,
sha512Half,
transactionID,
slice,
};

View File

@@ -5,13 +5,13 @@ import {
Type,
TransactionResult,
} from "./enums";
const types = require("./types");
const binary = require("./binary");
const { ShaMap } = require("./shamap");
const ledgerHashes = require("./ledger-hashes");
const hashes = require("./hashes");
const quality = require("./quality");
const { HashPrefix } = require("./hash-prefixes");
import * as types from "./types";
import * as binary from "./binary";
import { ShaMap } from "./shamap";
import * as ledgerHashes from "./ledger-hashes";
import * as hashes from "./hashes";
import { quality } from "./quality";
import { HashPrefix } from "./hash-prefixes";
export {
hashes,

View File

@@ -1,4 +1,3 @@
import { serializeUIntN } from "../utils/bytes-utils";
import * as enums from "./definitions.json";
import { SerializedType } from "../types/serialized-type";
@@ -37,7 +36,10 @@ class Bytes {
readonly ordinal: number,
readonly ordinalWidth: number
) {
this.bytes = serializeUIntN(ordinal, ordinalWidth);
this.bytes = Buffer.alloc(ordinalWidth);
for (let i = 0; i < ordinalWidth; i++) {
this.bytes[ordinalWidth - i - 1] = (ordinal >>> (i * 8)) & 0xff;
}
}
toJSON(): string {
@@ -57,7 +59,7 @@ class Bytes {
* @brief: Collection of Bytes objects, mapping bidirectionally
*/
class BytesLookup {
constructor(types: { [key: string]: number }, readonly ordinalWidth: number) {
constructor(types: Record<string, number>, readonly ordinalWidth: number) {
Object.entries(types).forEach(([k, v]) => {
this[k] = new Bytes(k, v, ordinalWidth);
this[v.toString()] = this[k];

View File

@@ -1,10 +1,19 @@
import { serializeUIntN } from "./utils/bytes-utils";
function bytes(uint32) {
return serializeUIntN(uint32, 4);
/**
* Write a 32 bit integer to a Buffer
*
* @param uint32 32 bit integer to write to buffer
* @returns a buffer with the bytes representation of uint32
*/
function bytes(uint32: number): Buffer {
const result = Buffer.alloc(4);
result.writeUInt32BE(uint32);
return result;
}
const HashPrefix = {
/**
* Maps HashPrefix names to their byte representation
*/
const HashPrefix: Record<string, Buffer> = {
transactionID: bytes(0x54584e00),
// transaction plus metadata
transaction: bytes(0x534e4400),

View File

@@ -1,44 +1,76 @@
import { makeClass } from "./utils/make-class";
import { HashPrefix } from "./hash-prefixes";
import { coreTypes } from "./types";
import { parseBytes } from "./utils/bytes-utils";
import * as createHash from "create-hash";
import { Hash256 } from "./types/hash-256";
import { BytesList } from "./serdes/binary-serializer";
const Sha512Half = makeClass(
{
Sha512Half() {
this.hash = createHash("sha512");
},
statics: {
put(bytes) {
return new this().put(bytes);
},
},
put(bytes) {
this.hash.update(parseBytes(bytes, Buffer));
return this;
},
finish256() {
const bytes = this.hash.digest();
return bytes.slice(0, 32);
},
finish() {
return new coreTypes.Hash256(this.finish256());
},
},
undefined
);
/**
* Class for hashing with SHA512
* @extends BytesList So SerializedTypes can write bytes to a Sha512Half
*/
class Sha512Half extends BytesList {
private hash: createHash = createHash("sha512");
function sha512Half(...args) {
const hash = new Sha512Half();
args.forEach((a) => hash.put(a));
return parseBytes(hash.finish256(), Uint8Array);
/**
* Construct a new Sha512Hash and write bytes this.hash
*
* @param bytes bytes to write to this.hash
* @returns the new Sha512Hash object
*/
static put(bytes: Buffer): Sha512Half {
return new Sha512Half().put(bytes);
}
/**
* Write bytes to an existing Sha512Hash
*
* @param bytes bytes to write to object
* @returns the Sha512 object
*/
put(bytes: Buffer): Sha512Half {
this.hash.update(bytes);
return this;
}
/**
* Compute SHA512 hash and slice in half
*
* @returns half of a SHA512 hash
*/
finish256(): Buffer {
const bytes: Buffer = this.hash.digest();
return bytes.slice(0, 32);
}
/**
* Constructs a Hash256 from the Sha512Half object
*
* @returns a Hash256 object
*/
finish(): Hash256 {
return new Hash256(this.finish256());
}
}
function transactionID(serialized) {
return new coreTypes.Hash256(
sha512Half(HashPrefix.transactionID, serialized)
);
/**
* compute SHA512 hash of a list of bytes
*
* @param args zero or more arguments to hash
* @returns the sha512half hash of the arguments.
*/
function sha512Half(...args: Buffer[]): Buffer {
const hash = new Sha512Half();
args.forEach((a) => hash.put(a));
return hash.finish256();
}
/**
* Construct a transactionID from a Serialized Transaction
*
* @param serialized bytes to hash
* @returns a Hash256 object
*/
function transactionID(serialized: Buffer): Hash256 {
return new Hash256(sha512Half(HashPrefix.transactionID, serialized));
}
export { Sha512Half, sha512Half, transactionID };

View File

@@ -1,69 +1,98 @@
import { strict as assert } from "assert";
import * as assert from "assert";
import { quality, binary } from "./coretypes";
import { coreTypes } from "./types";
import { decodeLedgerData } from "./ledger-hashes";
import { ClaimObject } from "./binary";
import { JsonObject } from "./types/serialized-type";
const {
bytesToHex,
signingData,
signingClaimData,
multiSigningData,
binaryToJSON,
serializeObject,
BinaryParser,
} = binary;
function decodeLedgerData(binary) {
assert(typeof binary === "string", "binary must be a hex string");
const parser = new BinaryParser(binary);
return {
ledger_index: parser.readUInt32(),
total_coins: parser.readType(coreTypes.UInt64).valueOf().toString(),
parent_hash: parser.readType(coreTypes.Hash256).toHex(),
transaction_hash: parser.readType(coreTypes.Hash256).toHex(),
account_hash: parser.readType(coreTypes.Hash256).toHex(),
parent_close_time: parser.readUInt32(),
close_time: parser.readUInt32(),
close_time_resolution: parser.readUInt8(),
close_flags: parser.readUInt8(),
};
}
function decode(binary) {
/**
* Decode a transaction
*
* @param binary hex-string of the encoded transaction
* @returns the JSON representation of the transaction
*/
function decode(binary: string): JsonObject {
assert(typeof binary === "string", "binary must be a hex string");
return binaryToJSON(binary);
}
function encode(json) {
/**
* Encode a transaction
*
* @param json The JSON representation of a transaction
* @returns A hex-string of the encoded transaction
*/
function encode(json: JsonObject): string {
assert(typeof json === "object");
return bytesToHex(serializeObject(json));
return serializeObject(json).toString("hex").toUpperCase();
}
function encodeForSigning(json) {
/**
* Encode a transaction and prepare for signing
*
* @param json JSON object representing the transaction
* @param signer string representing the account to sign the transaction with
* @returns a hex string of the encoded transaction
*/
function encodeForSigning(json: JsonObject): string {
assert(typeof json === "object");
return bytesToHex(signingData(json));
return signingData(json).toString("hex").toUpperCase();
}
function encodeForSigningClaim(json) {
/**
* Encode a transaction and prepare for signing with a claim
*
* @param json JSON object representing the transaction
* @param signer string representing the account to sign the transaction with
* @returns a hex string of the encoded transaction
*/
function encodeForSigningClaim(json: ClaimObject): string {
assert(typeof json === "object");
return bytesToHex(signingClaimData(json));
return signingClaimData(json).toString("hex").toUpperCase();
}
function encodeForMultisigning(json, signer) {
/**
* Encode a transaction and prepare for multi-signing
*
* @param json JSON object representing the transaction
* @param signer string representing the account to sign the transaction with
* @returns a hex string of the encoded transaction
*/
function encodeForMultisigning(json: JsonObject, signer: string): string {
assert(typeof json === "object");
assert.equal(json.SigningPubKey, "");
return bytesToHex(multiSigningData(json, signer));
return multiSigningData(json, signer).toString("hex").toUpperCase();
}
function encodeQuality(value) {
/**
* Encode a quality value
*
* @param value string representation of a number
* @returns a hex-string representing the quality
*/
function encodeQuality(value: string): string {
assert(typeof value === "string");
return bytesToHex(quality.encode(value));
return quality.encode(value).toString("hex").toUpperCase();
}
function decodeQuality(value) {
/**
* Decode a quality value
*
* @param value hex-string of a quality
* @returns a string representing the quality
*/
function decodeQuality(value: string): string {
assert(typeof value === "string");
return quality.decode(value).toString();
}
module.exports = {
export {
decode,
encode,
encodeForSigning,

View File

@@ -1,19 +1,49 @@
import * as _ from "lodash";
import { strict as assert } from "assert";
import { coreTypes } from "./types";
const { STObject, Hash256 } = coreTypes;
import { ShaMap } from "./shamap";
import * as assert from "assert";
import { ShaMap, ShaMapNode, ShaMapLeaf } from "./shamap";
import { HashPrefix } from "./hash-prefixes";
import { Sha512Half } from "./hashes";
import { BinarySerializer, serializeObject } from "./binary";
import { Hash256 } from "./types/hash-256";
import { STObject } from "./types/st-object";
import { UInt64 } from "./types/uint-64";
import { UInt32 } from "./types/uint-32";
import { UInt8 } from "./types/uint-8";
import { BinaryParser } from "./serdes/binary-parser";
import { JsonObject } from "./types/serialized-type";
function computeHash(itemizer, itemsJson) {
/**
* Computes the hash of a list of objects
*
* @param itemizer Converts an item into a format that can be added to SHAMap
* @param itemsJson Array of items to add to a SHAMap
* @returns the hash of the SHAMap
*/
function computeHash(
itemizer: (item: JsonObject) => [Hash256?, ShaMapNode?, ShaMapLeaf?],
itemsJson: Array<JsonObject>
): Hash256 {
const map = new ShaMap();
itemsJson.forEach((item) => map.addItem(...itemizer(item)));
return map.hash();
}
function transactionItem(json) {
/**
* Interface describing a transaction item
*/
interface transactionItemObject extends JsonObject {
hash: string;
metaData: JsonObject;
}
/**
* Convert a transaction into an index and an item
*
* @param json transaction with metadata
* @returns a tuple of index and item to be added to SHAMap
*/
function transactionItemizer(
json: transactionItemObject
): [Hash256, ShaMapNode, undefined] {
assert(json.hash);
const index = Hash256.from(json.hash);
const item = {
@@ -25,11 +55,26 @@ function transactionItem(json) {
serializer.writeLengthEncoded(STObject.from(json));
serializer.writeLengthEncoded(STObject.from(json.metaData));
},
};
return [index, item];
} as ShaMapNode;
return [index, item, undefined];
}
function entryItem(json) {
/**
* Interface describing an entry item
*/
interface entryItemObject extends JsonObject {
index: string;
}
/**
* Convert an entry to a pair Hash256 and ShaMapNode
*
* @param json JSON describing a ledger entry item
* @returns a tuple of index and item to be added to SHAMap
*/
function entryItemizer(
json: entryItemObject
): [Hash256, ShaMapNode, undefined] {
const index = Hash256.from(json.index);
const bytes = serializeObject(json);
const item = {
@@ -39,29 +84,95 @@ function entryItem(json) {
toBytesSink(sink) {
sink.put(bytes);
},
};
return [index, item];
} as ShaMapNode;
return [index, item, undefined];
}
const transactionTreeHash = _.partial(computeHash, transactionItem);
const accountStateHash = _.partial(computeHash, entryItem);
/**
* Function computing the hash of a transaction tree
*
* @param param An array of transaction objects to hash
* @returns A Hash256 object
*/
function transactionTreeHash(param: Array<JsonObject>): Hash256 {
const itemizer = transactionItemizer as (
json: JsonObject
) => [Hash256, ShaMapNode, undefined];
return computeHash(itemizer, param);
}
function ledgerHash(header) {
/**
* Function computing the hash of accountState
*
* @param param A list of accountStates hash
* @returns A Hash256 object
*/
function accountStateHash(param: Array<JsonObject>): Hash256 {
const itemizer = entryItemizer as (
json: JsonObject
) => [Hash256, ShaMapNode, undefined];
return computeHash(itemizer, param);
}
/**
* Interface describing a ledger header
*/
interface ledgerObject {
ledger_index: number;
total_coins: string | number | bigint;
parent_hash: string;
transaction_hash: string;
account_hash: string;
parent_close_time: number;
close_time: number;
close_time_resolution: number;
close_flags: number;
}
/**
* Serialize and hash a ledger header
*
* @param header a ledger header
* @returns the hash of header
*/
function ledgerHash(header: ledgerObject): Hash256 {
const hash = new Sha512Half();
hash.put(HashPrefix.ledgerHeader);
assert(header.parent_close_time !== undefined);
assert(header.close_flags !== undefined);
coreTypes.UInt32.from(header.ledger_index).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);
coreTypes.UInt32.from(header.parent_close_time).toBytesSink(hash);
coreTypes.UInt32.from(header.close_time).toBytesSink(hash);
coreTypes.UInt8.from(header.close_time_resolution).toBytesSink(hash);
coreTypes.UInt8.from(header.close_flags).toBytesSink(hash);
UInt32.from<number>(header.ledger_index).toBytesSink(hash);
UInt64.from<bigint>(BigInt(header.total_coins)).toBytesSink(hash);
Hash256.from(header.parent_hash).toBytesSink(hash);
Hash256.from(header.transaction_hash).toBytesSink(hash);
Hash256.from(header.account_hash).toBytesSink(hash);
UInt32.from<number>(header.parent_close_time).toBytesSink(hash);
UInt32.from<number>(header.close_time).toBytesSink(hash);
UInt8.from<number>(header.close_time_resolution).toBytesSink(hash);
UInt8.from<number>(header.close_flags).toBytesSink(hash);
return hash.finish();
}
export { accountStateHash, transactionTreeHash, ledgerHash };
/**
* Decodes a serialized ledger header
*
* @param binary A serialized ledger header
* @returns A JSON object describing a ledger header
*/
function decodeLedgerData(binary: string): ledgerObject {
assert(typeof binary === "string", "binary must be a hex string");
const parser = new BinaryParser(binary);
return {
ledger_index: parser.readUInt32(),
total_coins: parser.readType(UInt64).valueOf().toString(),
parent_hash: parser.readType(Hash256).toHex(),
transaction_hash: parser.readType(Hash256).toHex(),
account_hash: parser.readType(Hash256).toHex(),
parent_close_time: parser.readUInt32(),
close_time: parser.readUInt32(),
close_time_resolution: parser.readUInt8(),
close_flags: parser.readUInt8(),
};
}
export { accountStateHash, transactionTreeHash, ledgerHash, decodeLedgerData };

View File

@@ -1,23 +1,37 @@
const Decimal = require("decimal.js");
import { bytesToHex, slice, parseBytes } from "./utils/bytes-utils";
import { coreTypes } from "./types";
import { Decimal } from "decimal.js";
module.exports = {
encode(arg) {
const quality = arg instanceof Decimal ? arg : new Decimal(arg);
const exponent = quality.e - 15;
const qualityString = quality
.times("1e" + -exponent)
.abs()
.toString();
/**
* class for encoding and decoding quality
*/
class quality {
/**
* Encode quality amount
*
* @param arg string representation of an amount
* @returns Serialized quality
*/
static encode(quality: string): Buffer {
const decimal = new Decimal(quality);
const exponent = decimal.e - 15;
const qualityString = decimal.times(`1e${-exponent}`).abs().toString();
const bytes = coreTypes.UInt64.from(BigInt(qualityString)).toBytes();
bytes[0] = exponent + 100;
return bytes;
},
decode(arg) {
const bytes = slice(parseBytes(arg), -8);
}
/**
* Decode quality amount
*
* @param arg hex-string denoting serialized quality
* @returns deserialized quality
*/
static decode(quality: string): Decimal {
const bytes = Buffer.from(quality, "hex").slice(-8);
const exponent = bytes[0] - 100;
const mantissa = new Decimal("0x" + bytesToHex(slice(bytes, 1)));
return mantissa.times("1e" + exponent);
},
};
const mantissa = new Decimal(`0x${bytes.slice(1).toString("hex")}`);
return mantissa.times(`1e${exponent}`);
}
}
export { quality };

View File

@@ -127,7 +127,8 @@ class BinarySerializer {
*/
writeFieldAndValue(field: FieldInstance, value: SerializedType): void {
const associatedValue = field.associatedType.from(value);
assert(associatedValue.toBytesSink, field.name);
assert(associatedValue.toBytesSink !== undefined);
assert(field.name !== undefined);
this.sink.put(field.header);

View File

@@ -1,118 +1,181 @@
import { strict as assert } from "assert";
import { makeClass } from "./utils/make-class";
import { coreTypes } from "./types";
import { HashPrefix } from "./hash-prefixes";
import { Sha512Half } from "./hashes";
import { Hash256 } from "./types/hash-256";
import { BytesList } from "./serdes/binary-serializer";
const ShaMapNode = makeClass(
{
virtuals: {
hashPrefix() {},
isLeaf() {},
isInner() {},
},
cached: {
hash() {
const hasher = Sha512Half.put(this.hashPrefix());
this.toBytesSink(hasher);
return hasher.finish();
},
},
},
undefined
);
/**
* Abstract class describing a SHAMapNode
*/
abstract class ShaMapNode {
abstract hashPrefix(): Buffer;
abstract isLeaf(): boolean;
abstract isInner(): boolean;
abstract toBytesSink(list: BytesList): void;
abstract hash(): Hash256;
}
const ShaMapLeaf = makeClass(
{
inherits: ShaMapNode,
ShaMapLeaf(index, item) {
ShaMapNode.call(this);
this.index = index;
this.item = item;
},
isLeaf() {
return true;
},
isInner() {
return false;
},
hashPrefix() {
return this.item.hashPrefix();
},
toBytesSink(sink) {
this.item.toBytesSink(sink);
this.index.toBytesSink(sink);
},
},
undefined
);
/**
* Class describing a Leaf of SHAMap
*/
class ShaMapLeaf extends ShaMapNode {
constructor(public index: Hash256, public item?: ShaMapNode) {
super();
}
const $uper = ShaMapNode.prototype;
/**
* @returns true as ShaMapLeaf is a leaf node
*/
isLeaf(): boolean {
return true;
}
const ShaMapInner = makeClass(
{
inherits: ShaMapNode,
ShaMapInner(depth = 0) {
ShaMapNode.call(this);
this.depth = depth;
this.slotBits = 0;
this.branches = Array(16);
},
isInner() {
return true;
},
isLeaf() {
return false;
},
hashPrefix() {
return HashPrefix.innerNode;
},
setBranch(slot, branch) {
this.slotBits = this.slotBits | (1 << slot);
this.branches[slot] = branch;
},
empty() {
return this.slotBits === 0;
},
hash() {
if (this.empty()) {
return coreTypes.Hash256.ZERO_256;
}
return $uper.hash.call(this);
},
toBytesSink(sink) {
for (let i = 0; i < this.branches.length; i++) {
const branch = this.branches[i];
const hash = branch ? branch.hash() : coreTypes.Hash256.ZERO_256;
hash.toBytesSink(sink);
}
},
addItem(index, item, leaf) {
assert(index instanceof coreTypes.Hash256);
const nibble = index.nibblet(this.depth);
const existing = this.branches[nibble];
if (!existing) {
this.setBranch(nibble, leaf || new ShaMapLeaf(index, item));
} else if (existing.isLeaf()) {
const newInner = new ShaMapInner(this.depth + 1);
newInner.addItem(existing.index, null, existing);
newInner.addItem(index, item, leaf);
this.setBranch(nibble, newInner);
} else if (existing.isInner()) {
existing.addItem(index, item, leaf);
} else {
assert(false);
}
},
},
undefined
);
/**
* @returns false as ShaMapLeaf is not an inner node
*/
isInner(): boolean {
return false;
}
const ShaMap = makeClass(
{
inherits: ShaMapInner,
},
undefined
);
/**
* Get the prefix of the this.item
*
* @returns The hash prefix, unless this.item is undefined, then it returns an empty Buffer
*/
hashPrefix(): Buffer {
return this.item === undefined ? Buffer.alloc(0) : this.item.hashPrefix();
}
export { ShaMap };
/**
* Hash the bytes representation of this
*
* @returns hash of this.item concatenated with this.index
*/
hash(): Hash256 {
const hash = Sha512Half.put(this.hashPrefix());
this.toBytesSink(hash);
return hash.finish();
}
/**
* Write the bytes representation of this to a BytesList
* @param list BytesList to write bytes to
*/
toBytesSink(list: BytesList): void {
if (this.item !== undefined) {
this.item.toBytesSink(list);
}
this.index.toBytesSink(list);
}
}
/**
* Class defining an Inner Node of a SHAMap
*/
class ShaMapInner extends ShaMapNode {
private slotBits = 0;
private branches: Array<ShaMapNode> = Array(16);
constructor(private depth: number = 0) {
super();
}
/**
* @returns true as ShaMapInner is an inner node
*/
isInner(): boolean {
return true;
}
/**
* @returns false as ShaMapInner is not a leaf node
*/
isLeaf(): boolean {
return false;
}
/**
* Get the hash prefix for this node
*
* @returns hash prefix describing an inner node
*/
hashPrefix(): Buffer {
return HashPrefix.innerNode;
}
/**
* Set a branch of this node to be another node
*
* @param slot Slot to add branch to this.branches
* @param branch Branch to add
*/
setBranch(slot: number, branch: ShaMapNode): void {
this.slotBits = this.slotBits | (1 << slot);
this.branches[slot] = branch;
}
/**
* @returns true if node is empty
*/
empty(): boolean {
return this.slotBits === 0;
}
/**
* Compute the hash of this node
*
* @returns The hash of this node
*/
hash(): Hash256 {
if (this.empty()) {
return coreTypes.Hash256.ZERO_256;
}
const hash = Sha512Half.put(this.hashPrefix());
this.toBytesSink(hash);
return hash.finish();
}
/**
* Writes the bytes representation of this node to a BytesList
*
* @param list BytesList to write bytes to
*/
toBytesSink(list: BytesList): void {
for (let i = 0; i < this.branches.length; i++) {
const branch = this.branches[i];
const hash = branch ? branch.hash() : coreTypes.Hash256.ZERO_256;
hash.toBytesSink(list);
}
}
/**
* Add item to the SHAMap
*
* @param index Hash of the index of the item being inserted
* @param item Item to insert in the map
* @param leaf Leaf node to insert when branch doesn't exist
*/
addItem(index?: Hash256, item?: ShaMapNode, leaf?: ShaMapLeaf): void {
assert(index !== undefined);
const nibble = index.nibblet(this.depth);
const existing = this.branches[nibble];
if (existing === undefined) {
this.setBranch(nibble, leaf || new ShaMapLeaf(index, item));
} else if (existing instanceof ShaMapLeaf) {
const newInner = new ShaMapInner(this.depth + 1);
newInner.addItem(existing.index, undefined, existing);
newInner.addItem(index, item, leaf);
this.setBranch(nibble, newInner);
} else if (existing instanceof ShaMapInner) {
existing.addItem(index, item, leaf);
} else {
throw new Error("invalid ShaMap.addItem call");
}
}
}
class ShaMap extends ShaMapInner {}
export { ShaMap, ShaMapNode, ShaMapLeaf };

View File

@@ -17,13 +17,18 @@ class AccountID extends Hash160 {
* @param value either an existing AccountID, a hex-string, or a base58 r-Address
* @returns an AccountID object
*/
static from(value: AccountID | string): AccountID {
if (value instanceof this) {
static from<T extends Hash160 | string>(value: T): AccountID {
if (value instanceof AccountID) {
return value;
}
return /^r/.test(value)
? this.fromBase58(value)
: new AccountID(Buffer.from(value, "hex"));
if (typeof value === "string") {
return /^r/.test(value)
? this.fromBase58(value)
: new AccountID(Buffer.from(value, "hex"));
}
throw new Error("Cannot construct AccountID from value given");
}
/**

View File

@@ -1,8 +1,10 @@
import { Decimal } from "decimal.js";
import { SerializedType } from "./serialized-type";
import { BinaryParser } from "../serdes/binary-parser";
import { Currency } from "./currency";
import { AccountID } from "./account-id";
import { Currency } from "./currency";
import { JsonObject, SerializedType } from "./serialized-type";
/**
* Constants for validating amounts
@@ -24,12 +26,25 @@ Decimal.config({
/**
* Interface for JSON objects that represent amounts
*/
interface AmountObject {
interface AmountObject extends JsonObject {
value: string;
currency: string;
issuer: string;
}
/**
* Type guard for AmountObject
*/
function isAmountObject(arg): arg is AmountObject {
const keys = Object.keys(arg).sort();
return (
keys.length === 3 &&
keys[0] === "currency" &&
keys[1] === "issuer" &&
keys[2] === "value"
);
}
/**
* Class for serializing/Deserializing Amounts
*/
@@ -45,10 +60,11 @@ class Amount extends SerializedType {
/**
* Construct an amount from an IOU or string amount
*
* @param value An Amount, object representing an IOU, or a string representing an integer amount
* @param value An Amount, object representing an IOU, or a string
* representing an integer amount
* @returns An Amount object
*/
static from(value: Amount | AmountObject | string): Amount {
static from<T extends Amount | AmountObject | string>(value: T): Amount {
if (value instanceof Amount) {
return value;
}
@@ -63,7 +79,9 @@ class Amount extends SerializedType {
amount[0] |= 0x40;
return new Amount(amount);
} else if (typeof value === "object") {
}
if (isAmountObject(value)) {
const number = new Decimal(value.value);
Amount.assertIouIsValid(number);
@@ -92,6 +110,7 @@ class Amount extends SerializedType {
const issuer = AccountID.from(value.issuer).toBytes();
return new Amount(Buffer.concat([amount, currency, issuer]));
}
throw new Error("Invalid type to construct an Amount");
}
@@ -123,8 +142,8 @@ class Amount extends SerializedType {
} else {
const parser = new BinaryParser(this.toString());
const mantissa = parser.read(8);
const currency = Currency.fromParser(parser);
const issuer = AccountID.fromParser(parser);
const currency = Currency.fromParser(parser) as Currency;
const issuer = AccountID.fromParser(parser) as AccountID;
const b1 = mantissa[0];
const b2 = mantissa[1];
@@ -189,7 +208,8 @@ class Amount extends SerializedType {
}
/**
* Ensure that the value after being multiplied by the exponent does not contain a decimal.
* Ensure that the value after being multiplied by the exponent does not
* contain a decimal.
*
* @param decimal a Decimal object
* @returns a string of the object without a decimal
@@ -215,4 +235,4 @@ class Amount extends SerializedType {
}
}
export { Amount };
export { Amount, AmountObject };

View File

@@ -26,8 +26,16 @@ class Blob extends SerializedType {
* @param value existing Blob object or a hex-string
* @returns A Blob object
*/
static from(value: Blob | string): Blob {
return value instanceof Blob ? value : new Blob(Buffer.from(value, "hex"));
static from<T extends Blob | string>(value: T): Blob {
if (value instanceof Blob) {
return value;
}
if (typeof value === "string") {
return new Blob(Buffer.from(value, "hex"));
}
throw new Error("Cannot construct Blob from value given");
}
}

View File

@@ -113,10 +113,16 @@ class Currency extends Hash160 {
*
* @param val Currency object or a string representation of a currency
*/
static from(val: Currency | string): Currency {
return val instanceof this
? val
: new Currency(bytesFromRepresentation(val));
static from<T extends Hash160 | string>(value: T): Currency {
if (value instanceof Currency) {
return value;
}
if (typeof value === "string") {
return new Currency(bytesFromRepresentation(value));
}
throw new Error("Cannot construct Currency from value given");
}
/**

View File

@@ -15,9 +15,18 @@ class Hash extends Comparable {
* Construct a Hash object from an existing Hash object or a hex-string
*
* @param value A hash object or hex-string of a hash
*/
static from(value: Hash | string): Hash {
return value instanceof this ? value : new this(Buffer.from(value, "hex"));
*/
static from<T extends Hash | string>(value: T): Hash {
if(value instanceof this) {
return value
}
if(typeof value === "string") {
return new this(Buffer.from(value, "hex"));
}
throw new Error("Cannot construct Hash from given value");
}
/**

View File

@@ -1,7 +1,7 @@
import { AccountID } from "./account-id";
import { Currency } from "./currency";
import { BinaryParser } from "../serdes/binary-parser";
import { SerializedType } from "./serialized-type";
import { SerializedType, JsonObject } from "./serialized-type";
/**
* Constants for separating Paths in a PathSet
@@ -19,12 +19,33 @@ const TYPE_ISSUER = 0x20;
/**
* The object representation of a Hop, an issuer AccountID, an account AccountID, and a Currency
*/
interface HopObject {
interface HopObject extends JsonObject {
issuer?: string;
account?: string;
currency?: string;
}
/**
* TypeGuard for HopObject
*/
function isHopObject(arg): arg is HopObject {
return (arg.issuer !== undefined ||
arg.account !== undefined ||
arg.currency !== undefined
);
}
/**
* TypeGuard for PathSet
*/
function isPathSet(arg): arg is Array<Array<HopObject>> {
return (
Array.isArray(arg) && arg.length === 0 ||
Array.isArray(arg) && Array.isArray(arg[0]) && arg[0].length === 0 ||
Array.isArray(arg) && Array.isArray(arg[0]) && isHopObject(arg[0][0])
);
}
/**
* Serialize and Deserialize a Hop
*/
@@ -96,15 +117,15 @@ class Hop extends SerializedType {
const result: HopObject = {};
if (type & TYPE_ACCOUNT) {
result.account = AccountID.fromParser(hopParser).toJSON();
result.account = (AccountID.fromParser(hopParser) as AccountID).toJSON();
}
if (type & TYPE_CURRENCY) {
result.currency = Currency.fromParser(hopParser).toJSON();
result.currency = (Currency.fromParser(hopParser) as Currency).toJSON();
}
if (type & TYPE_ISSUER) {
result.issuer = AccountID.fromParser(hopParser).toJSON();
result.issuer = (AccountID.fromParser(hopParser) as AccountID).toJSON();
}
return result;
@@ -169,7 +190,7 @@ class Path extends SerializedType {
*
* @returns an Array of HopObject constructed from this.bytes
*/
toJSON() {
toJSON(): Array<HopObject> {
const json: Array<HopObject> = [];
const pathParser = new BinaryParser(this.toString());
@@ -191,21 +212,25 @@ class PathSet extends SerializedType {
* @param value A PathSet or Array of Array of HopObjects
* @returns the PathSet constructed from value
*/
static from(value: PathSet | Array<Array<HopObject>>): PathSet {
static from<T extends PathSet | Array<Array<HopObject>>>(value: T): PathSet {
if (value instanceof PathSet) {
return value;
}
const bytes: Array<Buffer> = [];
if (isPathSet(value)) {
const bytes: Array<Buffer> = [];
value.forEach((path: Array<HopObject>) => {
bytes.push(Path.from(path).toBytes());
bytes.push(Buffer.from([PATH_SEPARATOR_BYTE]));
});
value.forEach((path: Array<HopObject>) => {
bytes.push(Path.from(path).toBytes());
bytes.push(Buffer.from([PATH_SEPARATOR_BYTE]));
});
bytes[bytes.length - 1] = Buffer.from([PATHSET_END_BYTE]);
bytes[bytes.length - 1] = Buffer.from([PATHSET_END_BYTE]);
return new PathSet(Buffer.concat(bytes));
return new PathSet(Buffer.concat(bytes));
}
throw new Error("Cannot construct PathSet from given value");
}
/**

View File

@@ -1,6 +1,10 @@
import { BytesList } from "../serdes/binary-serializer";
import { BinaryParser } from "../serdes/binary-parser";
type JSON = string | number | boolean | null | undefined | JSON[] | JsonObject;
type JsonObject = { [key: string]: JSON };
/**
* The base class for all binary-codec types
*/
@@ -16,7 +20,7 @@ class SerializedType {
return this.fromParser(parser, hint);
}
static from(value: any): SerializedType {
static from(value: SerializedType | JSON | bigint): SerializedType {
throw new Error("from not implemented");
return this.from(value);
}
@@ -58,7 +62,7 @@ class SerializedType {
*
* @returns any type, if not overloaded returns hexString representation of bytes
*/
toJSON(): any {
toJSON(): JSON {
return this.toHex();
}
@@ -101,8 +105,10 @@ class Comparable extends SerializedType {
* @returns A number denoting the relationship of this and other
*/
compareTo(other: Comparable): number {
throw new Error(`cannot compare ${this} and ${other}`);
throw new Error(
`cannot compare ${this.toString()} and ${other.toString()}`
);
}
}
export { SerializedType, Comparable };
export { SerializedType, Comparable, JSON, JsonObject };

View File

@@ -1,4 +1,4 @@
import { SerializedType } from "./serialized-type";
import { SerializedType, JsonObject } from "./serialized-type";
import { STObject } from "./st-object";
import { BinaryParser } from "../serdes/binary-parser";
@@ -7,6 +7,15 @@ const ARRAY_END_MARKER_NAME = "ArrayEndMarker";
const OBJECT_END_MARKER = Buffer.from([0xe1]);
/**
* TypeGuard for Array<JsonObject>
*/
function isObjects(args): args is Array<JsonObject> {
return (
Array.isArray(args) && (args.length === 0 || typeof args[0] === "object")
);
}
/**
* Class for serializing and deserializing Arrays of Objects
*/
@@ -43,18 +52,22 @@ class STArray extends SerializedType {
* @param value STArray or Array of Objects to parse into an STArray
* @returns An STArray object
*/
static from(value: STArray | Array<object>): STArray {
static from<T extends STArray | Array<JsonObject>>(value: T): STArray {
if (value instanceof STArray) {
return value;
}
const bytes: Array<Buffer> = [];
value.forEach((obj) => {
bytes.push(STObject.from(obj).toBytes());
});
if (isObjects(value)) {
const bytes: Array<Buffer> = [];
value.forEach((obj) => {
bytes.push(STObject.from(obj).toBytes());
});
bytes.push(ARRAY_END_MARKER);
return new STArray(Buffer.concat(bytes));
bytes.push(ARRAY_END_MARKER);
return new STArray(Buffer.concat(bytes));
}
throw new Error("Cannot construct Currency from value given");
}
/**
@@ -62,8 +75,8 @@ class STArray extends SerializedType {
*
* @returns An Array of JSON objects
*/
toJSON(): Array<object> {
const result: Array<object> = [];
toJSON(): Array<JsonObject> {
const result: Array<JsonObject> = [];
const arrayParser = new BinaryParser(this.toString());

View File

@@ -1,5 +1,5 @@
import { Field } from "../enums";
import { SerializedType } from "./serialized-type";
import { Field, FieldInstance } from "../enums";
import { SerializedType, JsonObject } from "./serialized-type";
import { BinaryParser } from "../serdes/binary-parser";
import { BinarySerializer, BytesList } from "../serdes/binary-serializer";
@@ -45,8 +45,8 @@ class STObject extends SerializedType {
* @param filter optional, denote which field to include in serialized object
* @returns a STObject object
*/
static from(
value: STObject | object,
static from<T extends STObject | JsonObject>(
value: T,
filter?: (...any) => boolean
): STObject {
if (value instanceof STObject) {
@@ -57,8 +57,8 @@ class STObject extends SerializedType {
const bytes: BinarySerializer = new BinarySerializer(list);
let sorted = Object.keys(value)
.map((f) => Field[f])
.filter((f) => f !== undefined && f.isSerialized)
.map((f: string): FieldInstance => Field[f] as FieldInstance)
.filter((f: FieldInstance): boolean => f !== undefined && f.isSerialized)
.sort((a, b) => {
return a.ordinal - b.ordinal;
});
@@ -84,7 +84,7 @@ class STObject extends SerializedType {
*
* @returns a JSON object
*/
toJSON(): object {
toJSON(): JsonObject {
const objectParser = new BinaryParser(this.toString());
const accumulator = {};

View File

@@ -23,14 +23,18 @@ class UInt16 extends UInt {
*
* @param val UInt16 object or number
*/
static from(val: UInt16 | number): UInt16 {
static from<T extends UInt16 | number>(val: T): UInt16 {
if (val instanceof UInt16) {
return val;
}
const buf = Buffer.alloc(UInt16.width);
buf.writeUInt16BE(val);
return new UInt16(buf);
if (typeof val === "number") {
const buf = Buffer.alloc(UInt16.width);
buf.writeUInt16BE(val);
return new UInt16(buf);
}
throw new Error("Can not construct UInt16 with given value");
}
/**

View File

@@ -23,14 +23,26 @@ class UInt32 extends UInt {
*
* @param val UInt32 object or number
*/
static from(val: UInt32 | number): UInt32 {
static from<T extends UInt32 | number | string>(val: T): UInt32 {
if (val instanceof UInt32) {
return val;
}
const buf = Buffer.alloc(UInt32.width);
buf.writeUInt32BE(val);
return new UInt32(buf);
if (typeof val === "string") {
const num = Number.parseInt(val);
buf.writeUInt32BE(num);
return new UInt32(buf);
}
if (typeof val === "number") {
buf.writeUInt32BE(val);
return new UInt32(buf);
}
console.log(typeof val);
throw new Error("Cannot construct UInt32 from given value");
}
/**

View File

@@ -26,7 +26,7 @@ class UInt64 extends UInt {
* @param val A UInt64, hex-string, bigint, or number
* @returns A UInt64 object
*/
static from(val: UInt64 | string | bigint | number): UInt64 {
static from<T extends UInt64 | string | bigint | number>(val: T): UInt64 {
if (val instanceof UInt64) {
return val;
}
@@ -38,17 +38,23 @@ class UInt64 extends UInt {
throw new Error("value must be an unsigned integer");
}
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);
}
return new UInt64(buf);
if (typeof val === "string") {
if (!HEX_REGEX.test(val)) {
throw new Error(`${val} is not a valid hex-string`);
}
buf = Buffer.from(val, "hex");
return new UInt64(buf);
}
if (typeof val === "bigint") {
buf.writeBigUInt64BE(val);
return new UInt64(buf);
}
throw new Error("Cannot construct UInt64 from given value");
}
/**

View File

@@ -21,14 +21,18 @@ class UInt8 extends UInt {
*
* @param val UInt8 object or number
*/
static from(val: UInt8 | number): UInt8 {
static from<T extends UInt8 | number>(val: T): UInt8 {
if (val instanceof UInt8) {
return val;
}
const buf = Buffer.alloc(UInt8.width);
buf.writeUInt8(val);
return new UInt8(buf);
if (typeof val === "number") {
const buf = Buffer.alloc(UInt8.width);
buf.writeUInt8(val);
return new UInt8(buf);
}
throw new Error("Cannot construct UInt8 from given value");
}
/**

View File

@@ -3,6 +3,13 @@ import { BinaryParser } from "../serdes/binary-parser";
import { Hash256 } from "./hash-256";
import { BytesList } from "../serdes/binary-serializer";
/**
* TypeGuard for Array<string>
*/
function isStrings(arg): arg is Array<string> {
return Array.isArray(arg) && (arg.length === 0 || typeof arg[0] === "string");
}
/**
* Class for serializing and deserializing vectors of Hash256
*/
@@ -34,16 +41,20 @@ class Vector256 extends SerializedType {
* @param value A Vector256 object or array of hex-strings representing Hash256's
* @returns a Vector256 object
*/
static from(value: Vector256 | Array<string>): Vector256 {
static from<T extends Vector256 | Array<string>>(value: T): Vector256 {
if (value instanceof Vector256) {
return value;
}
const bytesList = new BytesList();
value.forEach((hash) => {
Hash256.from(hash).toBytesSink(bytesList);
});
return new Vector256(bytesList.toBytes());
if (isStrings(value)) {
const bytesList = new BytesList();
value.forEach((hash) => {
Hash256.from(hash).toBytesSink(bytesList);
});
return new Vector256(bytesList.toBytes());
}
throw new Error("Cannot construct Vector256 from given value");
}
/**

View File

@@ -1,107 +0,0 @@
import { strict as assert } from "assert";
function signum(a, b) {
return a < b ? -1 : a === b ? 0 : 1;
}
const hexLookup = (function () {
const res = <any>{};
const reverse = (res.reverse = new Array(256));
for (let i = 0; i < 16; i++) {
const char = i.toString(16).toUpperCase();
res[char] = i;
for (let j = 0; j < 16; j++) {
const char2 = j.toString(16).toUpperCase();
const byte = (i << 4) + j;
const byteHex = char + char2;
res[byteHex] = byte;
reverse[byte] = byteHex;
}
}
return res;
})();
const reverseHexLookup = hexLookup.reverse;
function bytesToHex(sequence) {
const buf = Array(sequence.length);
for (let i = sequence.length - 1; i >= 0; i--) {
buf[i] = reverseHexLookup[sequence[i]];
}
return buf.join("");
}
function byteForHex(hex) {
const byte = hexLookup[hex];
if (byte === undefined) {
throw new Error(`\`${hex}\` is not a valid hex representation of a byte`);
}
return byte;
}
function parseBytes(val, Output = <any>Array) {
if (!val || val.length === undefined) {
throw new Error(`${val} is not a sequence`);
}
if (typeof val === "string") {
const start = val.length % 2;
const res = new Output((val.length + start) / 2);
for (let i = val.length, to = res.length - 1; to >= start; i -= 2, to--) {
res[to] = byteForHex(val.slice(i - 2, i));
}
if (start === 1) {
res[0] = byteForHex(val[0]);
}
return res;
} else if (val instanceof Output) {
return val;
} else if (Output === Uint8Array) {
return new Output(val);
}
const res = new Output(val.length);
for (let i = val.length - 1; i >= 0; i--) {
res[i] = val[i];
}
return res;
}
function serializeUIntN(val, width) {
const newBytes = new Uint8Array(width);
const lastIx = width - 1;
for (let i = 0; i < width; i++) {
newBytes[lastIx - i] = (val >>> (i * 8)) & 0xff;
}
return newBytes;
}
function compareBytes(a, b) {
assert(a.length === b.length);
for (let i = 0; i < a.length; i++) {
const cmp = signum(a[i], b[i]);
if (cmp !== 0) {
return cmp;
}
}
return 0;
}
function slice(val, startIx = 0, endIx = val.length, Output = val.constructor) {
/* eslint-disable no-param-reassign */
if (startIx < 0) {
startIx += val.length;
}
if (endIx < 0) {
endIx += val.length;
}
/* eslint-enable no-param-reassign */
const len = endIx - startIx;
const res = new Output(len);
for (let i = endIx - 1; i >= startIx; i--) {
res[i - startIx] = val[i];
}
return res;
}
export { parseBytes, bytesToHex, slice, compareBytes, serializeUIntN };

View File

@@ -1,85 +0,0 @@
import _ = require("lodash");
const inherits = require("inherits");
function forEach(obj, func) {
Object.keys(obj || {}).forEach((k) => {
func(obj[k], k);
});
}
function ensureArray(val) {
return Array.isArray(val) ? val : [val];
}
export function makeClass(klass_, definition_) {
const definition = definition_ || klass_;
let klass = typeof klass_ === "function" ? klass_ : null;
if (klass === null) {
for (const k in definition) {
if (k[0].match(/[A-Z]/)) {
klass = definition[k];
break;
}
}
}
const parent = definition.inherits;
if (parent) {
if (klass === null) {
klass = function () {
parent.apply(this, arguments);
};
}
inherits(klass, parent);
_.defaults(klass, parent);
}
if (klass === null) {
klass = function () {};
}
const proto = klass.prototype;
function addFunc(original, name, wrapper) {
proto[name] = wrapper || original;
}
(definition.getters || []).forEach((k) => {
const key = "_" + k;
proto[k] = function () {
return this[key];
};
});
forEach(definition.virtuals, (f, n) => {
addFunc(f, n, function () {
throw new Error("unimplemented");
});
});
forEach(definition.methods, addFunc);
forEach(definition, (f, n) => {
if (_.isFunction(f) && f !== klass) {
addFunc(f, n, undefined);
}
});
_.assign(klass, definition.statics);
if (typeof klass.init === "function") {
klass.init();
}
forEach(definition.cached, (f, n) => {
const key = "_" + n;
addFunc(f, n, function () {
let value = this[key];
if (value === undefined) {
value = this[key] = f.call(this);
}
return value;
});
});
if (definition.mixins) {
const mixins = {};
// Right-most in the list win
ensureArray(definition.mixins)
.reverse()
.forEach((o) => {
_.defaults(mixins, o);
});
_.defaults(proto, mixins);
}
return klass;
}

View File

@@ -5,7 +5,7 @@ const { Amount } = coreTypes
const fixtures = loadFixture('data-driven-tests.json')
function amountErrorTests () {
_.filter(fixtures.values_tests, { type: 'Amount' }).forEach(f => {
fixtures.values_tests.filter(obj => obj.type === 'Amount').forEach(f => {
// We only want these with errors
if (!f.error) {
return

View File

@@ -10,7 +10,6 @@ const { Amount, Hash160 } = coreTypes
const { makeParser, readJSON } = binary
const { Field, TransactionType } = require('./../dist/enums')
const { parseHexOnly, hexOnly, loadFixture } = require('./utils')
const { bytesToHex } = require('../dist/utils/bytes-utils')
const fixtures = loadFixture('data-driven-tests.json')
const { BytesList } = require('../dist/serdes/binary-serializer')
@@ -107,9 +106,9 @@ function transactionParsingTests () {
expect(parser.read(8)).not.toEqual([])
expect(parser.readField()).toEqual(Field.SigningPubKey)
expect(parser.readVariableLengthLength()).toBe(33)
expect(bytesToHex(parser.read(33))).toEqual(tx_json.SigningPubKey)
expect(parser.read(33).toString('hex').toUpperCase()).toEqual(tx_json.SigningPubKey)
expect(parser.readField()).toEqual(Field.TxnSignature)
expect(bytesToHex(parser.readVariableLength())).toEqual(tx_json.TxnSignature)
expect(parser.readVariableLength().toString('hex').toUpperCase()).toEqual(tx_json.TxnSignature)
expect(parser.readField()).toEqual(Field.Account)
expect(encodeAccountID(parser.readVariableLength())).toEqual(tx_json.Account)
expect(parser.end()).toBe(true)
@@ -180,12 +179,13 @@ function transactionParsingTests () {
const parser = makeParser(transaction.binary)
const jsonFromBinary = readJSON(parser)
expect(jsonFromBinary instanceof coreTypes.STObject).toBe(false)
expect(_.isPlainObject(jsonFromBinary)).toBe(true)
expect(jsonFromBinary instanceof Object).toBe(true);
expect(jsonFromBinary.prototype).toBe(undefined)
})
}
function amountParsingTests () {
_.filter(fixtures.values_tests, { type: 'Amount' }).forEach((f, i) => {
fixtures.values_tests.filter(obj => obj.type === 'Amount').forEach((f, i) => {
if (f.error) {
return
}
@@ -246,7 +246,7 @@ function assertRecyclable (json, forField) {
function nestedObjectTests () {
function disabled (i) {
unused(i)
return false // !_.includes([2], i);
return false
}
fixtures.whole_objects.forEach((f, i) => {

View File

@@ -1,70 +0,0 @@
const { slice, compareBytes, parseBytes, bytesToHex } = require('../dist/utils/bytes-utils')
describe('bytes-utils', function () {
describe('parseBytes', function () {
test('can decode hex', function () {
expect(parseBytes('0012')).toEqual([0x00, 0x12])
expect(parseBytes('0012')).toEqual([0x00, 0x12])
expect(parseBytes('00AA')).toEqual([0x00, 0xaa])
})
test('can decode hex to a Uint8Array', function () {
const result = parseBytes('0012', Uint8Array)
expect(result instanceof Uint8Array).toBe(true)
expect(result).toEqual(Uint8Array.from([0x00, 0x12]))
})
test('can convert a list to a Uint8Array', function () {
const result = parseBytes([0x00, 0x12], Uint8Array)
expect(result instanceof Uint8Array).toBe(true)
expect(result).toEqual(Uint8Array.from([0x00, 0x12]))
})
test('can decode hex to a Buffer', function () {
const result = parseBytes('0012', Buffer)
expect(result instanceof Buffer).toBe(true)
expect(result.toJSON().data).toEqual([0x00, 0x12])
})
})
describe('bytesToHex', function () {
test('can encode an array as hex', function () {
expect(bytesToHex([0x00, 0xaa])).toBe('00AA')
expect(bytesToHex([0xaa])).toBe('AA')
})
test('can encode Uint8Array as hex', function () {
expect(bytesToHex(new Uint8Array([0x00, 0xaa]))).toBe('00AA')
expect(bytesToHex(new Uint8Array([0xaa]))).toBe('AA')
})
})
describe('compareBytes', function () {
test('compares the bytes sequence as big endian number', function () {
expect(compareBytes([0, 1, 2], [1, 2, 3])).toBe(-1)
})
test('throws when the bytes sequences are of unlike length', function () {
expect(() => compareBytes([0, 1], [1])).toThrow()
})
})
describe('slice', function () {
const val = [1, 2, 3, 4, 5]
test('creates a slice of the same type as first arg', function () {
expect(Array.isArray(slice(val))).toBe(true)
})
test('the 2nd arg is the start position [2:]', function () {
expect(val.slice(2)).toEqual([3, 4, 5])
expect(slice(val, 2)).toEqual([3, 4, 5])
})
test('the 3rd arg is the end position [2:4]', function () {
expect(slice(val, 2, 4)).toEqual([3, 4])
})
test('can slice using negative numbers [-3:]', function () {
expect(slice(val, -3)).toEqual([3, 4, 5])
})
test('can slice using negative numbers [-3:-1]', function () {
expect(slice(val, -3, -1)).toEqual([3, 4])
})
test('the 4th arg is the output class type', function () {
expect(slice(val, 2, 4, Buffer).toJSON().data).toEqual([3, 4])
expect(slice(val, 2, 4, Uint8Array)).toEqual(Uint8Array.from([3, 4]))
})
})
})

View File

@@ -1,4 +1,4 @@
const { quality, binary } = require('../dist/coretypes')
const { quality } = require('../dist/coretypes')
describe('Quality encode/decode', function () {
const bookDirectory =
@@ -10,6 +10,6 @@ describe('Quality encode/decode', function () {
})
test('can encode', function () {
const bytes = quality.encode(expectedQuality)
expect(binary.bytesToHex(bytes)).toBe(bookDirectory.slice(-16))
expect(bytes.toString('hex').toUpperCase()).toBe(bookDirectory.slice(-16))
})
})

View File

@@ -21,7 +21,7 @@ function makeItem (indexArg) {
index.toBytesSink(sink)
},
hashPrefix () {
return [1, 3, 3, 7]
return Buffer.from([1, 3, 3, 7])
}
}
return [index, item]

View File

@@ -60,7 +60,7 @@ describe('Signing data', function () {
})
test('can create multi signing blobs', function () {
const signingAccount = 'rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN'
const signingJson = _.assign({}, tx_json, { SigningPubKey: '' })
const signingJson = Object.assign({}, tx_json, { SigningPubKey: '' })
const actual = encodeForMultisigning(signingJson, signingAccount)
expect(actual).toBe(
['534D5400', // signingPrefix

View File

@@ -3,7 +3,7 @@ const { coreTypes } = require('../dist/types')
const { SerializedType } = require('../dist/types/serialized-type')
describe('SerializedType interfaces', () => {
_.forOwn(coreTypes, (Value, name) => {
Object.entries(coreTypes).forEach(([name, Value]) => {
test(`${name} has a \`from\` static constructor`, () => {
expect(Value.from && Value.from !== Array.from).toBe(true)
})
@@ -27,7 +27,7 @@ describe('SerializedType interfaces', () => {
expect(Value.from(newJSON).toJSON()).toEqual(newJSON)
})
describe(`${name} supports all methods of the SerializedType mixin`, () => {
_.keys(SerializedType.prototype).forEach(k => {
Object.keys(SerializedType.prototype).forEach(k => {
test(`new ${name}.prototype.${k} !== undefined`, () => {
expect(Value.prototype[k]).not.toBe(undefined)
})

View File

@@ -1,5 +1,4 @@
const fs = require('fs')
const { parseBytes } = require('../dist/utils/bytes-utils')
function hexOnly (hex) {
return hex.replace(/[^a-fA-F0-9]/g, '')
@@ -7,8 +6,8 @@ function hexOnly (hex) {
function unused () {}
function parseHexOnly (hex, to) {
return parseBytes(hexOnly(hex), to)
function parseHexOnly (hex) {
return Buffer.from(hexOnly(hex), 'hex');
}
function loadFixture (relativePath) {

File diff suppressed because it is too large Load Diff