@aggris2/ssz
Version:
Simple Serialize
320 lines • 12.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UintBigintType = exports.UintNumberType = exports.uintBigintByteLens = exports.uintNumberByteLens = void 0;
const persistent_merkle_tree_1 = require("@chainsafe/persistent-merkle-tree");
const basic_1 = require("./basic");
/* eslint-disable @typescript-eslint/member-ordering */
const MAX_SAFE_INTEGER_BN = BigInt(Number.MAX_SAFE_INTEGER);
const BIGINT_2_POW_64 = BigInt(2) ** BigInt(64);
const BIGINT_2_POW_128 = BigInt(2) ** BigInt(128);
const BIGINT_2_POW_192 = BigInt(2) ** BigInt(192);
// const BIGINT_64_MAX = BigInt("0xffffffffffffffff");
const NUMBER_2_POW_32 = 2 ** 32;
const NUMBER_32_MAX = 0xffffffff;
exports.uintNumberByteLens = [1, 2, 4, 8];
exports.uintBigintByteLens = [1, 2, 4, 8, 16, 32];
/**
* Uint: N-bit unsigned integer (where N in [8, 16, 32, 64, 128, 256])
* - Notation: uintN
*
* UintNumber is represented as the Javascript primitive value 'Number'.
*
* The Number type is a double-precision 64-bit binary format IEEE 754 value (numbers between -(2^53 − 1) and
* 2^53 − 1). It also has the symbolic value: +Infinity.
*
* As of 2021 performance of 'Number' is extremely faster than 'BigInt'. Some values are spec'ed as Uint64 but
* practically they will never exceed 53 bits, such as any unit time or simple counters. This type is an optimization
* for these cases, as UintNumber64 can represent any value between 0 and 2^53−1 as well as the max value 2^64-1.
*/
class UintNumberType extends basic_1.BasicType {
constructor(byteLength, opts) {
super();
this.byteLength = byteLength;
if (byteLength > 8) {
throw Error("UintNumber byteLength limit is 8");
}
if (Math.log2(byteLength) % 1 !== 0) {
throw Error("byteLength must be a power of 2");
}
this.typeName = `uint${byteLength * 8}`;
if (opts?.clipInfinity)
this.typeName += "Inf";
if (opts?.setBitwiseOR)
this.typeName += "OR";
this.itemsPerChunk = 32 / this.byteLength;
this.fixedSize = byteLength;
this.minSize = byteLength;
this.maxSize = byteLength;
this.maxDecimalStr = (BigInt(2) ** BigInt(this.byteLength * 8) - BigInt(1)).toString(10);
this.clipInfinity = opts?.clipInfinity === true;
this.setBitwiseOR = opts?.setBitwiseOR === true;
}
defaultValue() {
return 0;
}
// Serialization + deserialization
value_serializeToBytes({ dataView }, offset, value) {
switch (this.byteLength) {
case 1:
dataView.setInt8(offset, value);
break;
case 2:
dataView.setUint16(offset, value, true);
break;
case 4:
dataView.setUint32(offset, value, true);
break;
case 8:
if (value === Infinity) {
// TODO: Benchmark if it's faster to set BIGINT_64_MAX once
dataView.setUint32(offset, 0xffffffff);
dataView.setUint32(offset + 4, 0xffffffff);
}
else {
dataView.setUint32(offset, value & 0xffffffff, true);
dataView.setUint32(offset + 4, (value / NUMBER_2_POW_32) & 0xffffffff, true);
}
break;
}
return offset + this.byteLength;
}
value_deserializeFromBytes({ dataView }, start, end) {
this.assertValidSize(end - start);
switch (this.byteLength) {
case 1:
return dataView.getUint8(start);
case 2:
return dataView.getUint16(start, true);
case 4:
return dataView.getUint32(start, true);
case 8: {
const a = dataView.getUint32(start, true);
const b = dataView.getUint32(start + 4, true);
if (b === NUMBER_32_MAX && a === NUMBER_32_MAX && this.clipInfinity) {
return Infinity;
}
else {
return b * NUMBER_2_POW_32 + a;
}
}
}
}
tree_serializeToBytes(output, offset, node) {
const value = node.getUint(this.byteLength, 0, this.clipInfinity);
this.value_serializeToBytes(output, offset, value);
return offset + this.byteLength;
}
tree_deserializeFromBytes(data, start, end) {
this.assertValidSize(end - start);
const value = this.value_deserializeFromBytes(data, start, end);
const node = persistent_merkle_tree_1.LeafNode.fromZero();
node.setUint(this.byteLength, 0, value, this.clipInfinity);
return node;
}
// Fast Tree access
tree_getFromNode(leafNode) {
return leafNode.getUint(this.byteLength, 0, this.clipInfinity);
}
tree_setToNode(leafNode, value) {
this.tree_setToPackedNode(leafNode, 0, value);
}
tree_getFromPackedNode(leafNode, index) {
const offsetBytes = this.byteLength * (index % this.itemsPerChunk);
return leafNode.getUint(this.byteLength, offsetBytes, this.clipInfinity);
}
tree_setToPackedNode(leafNode, index, value) {
const offsetBytes = this.byteLength * (index % this.itemsPerChunk);
// TODO: Benchmark the cost of this if, and consider using a different class
if (this.setBitwiseOR) {
leafNode.bitwiseOrUint(this.byteLength, offsetBytes, value);
}
else {
leafNode.setUint(this.byteLength, offsetBytes, value, this.clipInfinity);
}
}
// JSON
fromJson(json) {
if (typeof json === "number") {
return json;
}
else if (typeof json === "string") {
if (this.clipInfinity && json === this.maxDecimalStr) {
// Allow to handle max possible number
return Infinity;
}
else {
const num = parseInt(json, 10);
if (isNaN(num)) {
throw Error("JSON invalid number isNaN");
}
else if (num > Number.MAX_SAFE_INTEGER) {
// Throw to prevent decimal precision errors downstream
throw Error("JSON invalid number > MAX_SAFE_INTEGER");
}
else {
return num;
}
}
}
else if (typeof json === "bigint") {
if (json > MAX_SAFE_INTEGER_BN) {
// Throw to prevent decimal precision errors downstream
throw Error("JSON invalid number > MAX_SAFE_INTEGER_BN");
}
else {
return Number(json);
}
}
else {
throw Error(`JSON invalid type ${typeof json} expected number`);
}
}
toJson(value) {
if (value === Infinity) {
return this.maxDecimalStr;
}
else {
return value.toString(10);
}
}
}
exports.UintNumberType = UintNumberType;
/**
* Uint: N-bit unsigned integer (where N in [8, 16, 32, 64, 128, 256])
* - Notation: uintN
*
* UintBigint is represented as the Javascript primitive value 'BigInt'.
*
* The BigInt type is a numeric primitive in JavaScript that can represent integers with arbitrary precision.
* With BigInts, you can safely store and operate on large integers even beyond the safe integer limit for Numbers.
*
* As of 2021 performance of 'Number' is extremely faster than 'BigInt'. For Uint values under 53 bits use UintNumber.
* For other values that may exceed 53 bits, use UintBigint.
*/
class UintBigintType extends basic_1.BasicType {
constructor(byteLength) {
super();
this.byteLength = byteLength;
if (byteLength > 32) {
throw Error("UintBigint byteLength limit is 32");
}
if (Math.log2(byteLength) % 1 !== 0) {
throw Error("byteLength must be a power of 2");
}
this.typeName = `uintBigint${byteLength * 8}`;
this.byteLength = byteLength;
this.itemsPerChunk = 32 / this.byteLength;
this.fixedSize = byteLength;
this.minSize = byteLength;
this.maxSize = byteLength;
}
defaultValue() {
return BigInt(0);
}
// Serialization + deserialization
value_serializeToBytes({ dataView }, offset, value) {
switch (this.byteLength) {
case 1:
dataView.setInt8(offset, Number(value));
break;
case 2:
dataView.setUint16(offset, Number(value), true);
break;
case 4:
dataView.setUint32(offset, Number(value), true);
break;
case 8:
dataView.setBigUint64(offset, value, true);
break;
default: {
for (let i = 0; i < this.byteLength; i += 8) {
if (i > 0)
value = value / BIGINT_2_POW_64;
const lo = BigInt.asUintN(64, value);
dataView.setBigUint64(offset + i, lo, true);
}
}
}
return offset + this.byteLength;
}
value_deserializeFromBytes({ dataView }, start, end) {
const size = end - start;
if (size !== this.byteLength) {
throw Error(`Invalid size ${size} expected ${this.byteLength}`);
}
// Note: pre-assigning the right function at the constructor to avoid this switch is not faster
switch (this.byteLength) {
case 1:
return BigInt(dataView.getUint8(start));
case 2:
return BigInt(dataView.getUint16(start, true));
case 4:
return BigInt(dataView.getUint32(start, true));
case 8:
return dataView.getBigUint64(start, true);
case 16: {
const a = dataView.getBigUint64(start, true);
const b = dataView.getBigUint64(start + 8, true);
return b * BIGINT_2_POW_64 + a;
}
case 32: {
const a = dataView.getBigUint64(start, true);
const b = dataView.getBigUint64(start + 8, true);
const c = dataView.getBigUint64(start + 16, true);
const d = dataView.getBigUint64(start + 24, true);
return d * BIGINT_2_POW_192 + c * BIGINT_2_POW_128 + b * BIGINT_2_POW_64 + a;
}
}
}
tree_serializeToBytes(output, offset, node) {
const value = node.getUintBigint(this.byteLength, 0);
this.value_serializeToBytes(output, offset, value);
return offset + this.byteLength;
}
tree_deserializeFromBytes(data, start, end) {
const size = end - start;
if (size !== this.byteLength) {
throw Error(`Invalid size ${size} expected ${this.byteLength}`);
}
const value = this.value_deserializeFromBytes(data, start, end);
const node = persistent_merkle_tree_1.LeafNode.fromZero();
node.setUintBigint(this.byteLength, 0, value);
return node;
}
// Fast Tree access
tree_getFromNode(leafNode) {
return leafNode.getUintBigint(this.byteLength, 0);
}
/** Mutates node to set value */
tree_setToNode(leafNode, value) {
this.tree_setToPackedNode(leafNode, 0, value);
}
/** EXAMPLE of `tree_getFromNode` */
tree_getFromPackedNode(leafNode, index) {
const offsetBytes = this.byteLength * (index % this.itemsPerChunk);
return leafNode.getUintBigint(this.byteLength, offsetBytes);
}
/** Mutates node to set value */
tree_setToPackedNode(leafNode, index, value) {
const offsetBytes = this.byteLength * (index % this.itemsPerChunk);
// TODO: Not-optimized, copy pasted from UintNumberType
leafNode.setUintBigint(this.byteLength, offsetBytes, value);
}
// JSON
fromJson(json) {
if (typeof json === "bigint") {
return json;
}
else if (typeof json === "string" || typeof json === "number") {
return BigInt(json);
}
else {
throw Error(`JSON invalid type ${typeof json} expected bigint`);
}
}
toJson(value) {
return value.toString(10);
}
}
exports.UintBigintType = UintBigintType;
//# sourceMappingURL=uint.js.map