diff --git a/package.json b/package.json index 586fd205..b7cad95a 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@dapplion/benchmark": "^0.2.2", "@types/chai": "^4.2.15", "@types/mocha": "^8.2.2", - "@types/node": "^14.14.17", + "@types/node": "^16.11.55", "@typescript-eslint/eslint-plugin": "^4.19.0", "@typescript-eslint/parser": "^4.19.0", "babel-loader": "^8.2.2", diff --git a/packages/as-sha256/src/index.ts b/packages/as-sha256/src/index.ts index 6fa451bd..c678a78d 100644 --- a/packages/as-sha256/src/index.ts +++ b/packages/as-sha256/src/index.ts @@ -1,6 +1,6 @@ import {newInstance} from "./wasm"; import {HashObject, byteArrayToHashObject, hashObjectToByteArray} from "./hashObject"; -import SHA256 from "./sha256"; +import SHA256, {allocUnsafe} from "./sha256"; export {HashObject, byteArrayToHashObject, hashObjectToByteArray, SHA256}; const ctx = newInstance(); @@ -18,7 +18,7 @@ export function digest(data: Uint8Array): Uint8Array { if (data.length <= ctx.INPUT_LENGTH) { inputUint8Array.set(data); ctx.digest(data.length); - const output = new Uint8Array(32); + const output = allocUnsafe(32); output.set(outputUint8Array); return output; } @@ -32,7 +32,7 @@ export function digest64(data: Uint8Array): Uint8Array { if (data.length === 64) { inputUint8Array.set(data); ctx.digest64(wasmInputValue, wasmOutputValue); - const output = new Uint8Array(32); + const output = allocUnsafe(32); output.set(outputUint8Array); return output; } @@ -44,7 +44,7 @@ export function digest2Bytes32(bytes1: Uint8Array, bytes2: Uint8Array): Uint8Arr inputUint8Array.set(bytes1); inputUint8Array.set(bytes2, 32); ctx.digest64(wasmInputValue, wasmOutputValue); - const output = new Uint8Array(32); + const output = allocUnsafe(32); output.set(outputUint8Array); return output; } @@ -98,7 +98,7 @@ function update(data: Uint8Array): void { function final(): Uint8Array { ctx.final(wasmOutputValue); - const output = new Uint8Array(32); + const output = allocUnsafe(32); output.set(outputUint8Array); return output; } diff --git a/packages/as-sha256/src/sha256.ts b/packages/as-sha256/src/sha256.ts index a06e8369..829f7194 100644 --- a/packages/as-sha256/src/sha256.ts +++ b/packages/as-sha256/src/sha256.ts @@ -40,8 +40,24 @@ export default class SHA256 { final(): Uint8Array { this.ctx.final(this.wasmOutputValue); - const output = new Uint8Array(32); + const output = allocUnsafe(32); output.set(this.uint8OutputArray); return output; } } + +/** + * Where possible returns a Uint8Array of the requested size that references + * uninitialized memory. Only use if you are certain you will immediately + * overwrite every value in the returned `Uint8Array`. + * + * @param {number} [size] + * @returns {Uint8Array} + */ +export function allocUnsafe(size = 0): Uint8Array { + if (globalThis.Buffer != null && globalThis.Buffer.allocUnsafe != null) { + return globalThis.Buffer.allocUnsafe(size); + } + + return new Uint8Array(size); +} diff --git a/packages/persistent-merkle-tree/src/hash.ts b/packages/persistent-merkle-tree/src/hash.ts index 4952a9ea..e2d553ca 100644 --- a/packages/persistent-merkle-tree/src/hash.ts +++ b/packages/persistent-merkle-tree/src/hash.ts @@ -5,8 +5,9 @@ import { HashObject, hashObjectToByteArray, } from "@chainsafe/as-sha256"; +import {allocUnsafe} from "./proof/util"; -const input = new Uint8Array(64); +const input = allocUnsafe(64); /** * Hash two 32 byte arrays @@ -25,7 +26,7 @@ export function hashTwoObjects(a: HashObject, b: HashObject): HashObject { } export function hashObjectToUint8Array(obj: HashObject): Uint8Array { - const byteArr = new Uint8Array(32); + const byteArr = allocUnsafe(32); hashObjectToByteArray(obj, byteArr, 0); return byteArr; } diff --git a/packages/persistent-merkle-tree/src/proof/index.ts b/packages/persistent-merkle-tree/src/proof/index.ts index 4523c272..da651ab7 100644 --- a/packages/persistent-merkle-tree/src/proof/index.ts +++ b/packages/persistent-merkle-tree/src/proof/index.ts @@ -9,6 +9,7 @@ import { deserializeTreeOffsetProof, serializeTreeOffsetProof, } from "./treeOffset"; +import {allocUnsafe} from "./util"; export enum ProofType { single = "single", @@ -128,7 +129,7 @@ export function serializeProof(proof: Proof): Uint8Array { case ProofType.multi: throw new Error("Not implemented"); case ProofType.treeOffset: { - const output = new Uint8Array(1 + computeTreeOffsetProofSerializedLength(proof.offsets, proof.leaves)); + const output = allocUnsafe(1 + computeTreeOffsetProofSerializedLength(proof.offsets, proof.leaves)); output[0] = ProofTypeSerialized.indexOf(ProofType.treeOffset); serializeTreeOffsetProof(output, 1, proof.offsets, proof.leaves); return output; diff --git a/packages/persistent-merkle-tree/src/proof/util.ts b/packages/persistent-merkle-tree/src/proof/util.ts index 5e197e1e..147ba3fb 100644 --- a/packages/persistent-merkle-tree/src/proof/util.ts +++ b/packages/persistent-merkle-tree/src/proof/util.ts @@ -156,3 +156,19 @@ export function computeMultiProofBitstrings( return Array.from(proof); } } + +/** + * Where possible returns a Uint8Array of the requested size that references + * uninitialized memory. Only use if you are certain you will immediately + * overwrite every value in the returned `Uint8Array`. + * + * @param {number} [size] + * @returns {Uint8Array} + */ +export function allocUnsafe(size = 0): Uint8Array { + if (globalThis.Buffer != null && globalThis.Buffer.allocUnsafe != null) { + return globalThis.Buffer.allocUnsafe(size); + } + + return new Uint8Array(size); +} diff --git a/packages/ssz/src/type/abstract.ts b/packages/ssz/src/type/abstract.ts index b96b7355..8a714ff1 100644 --- a/packages/ssz/src/type/abstract.ts +++ b/packages/ssz/src/type/abstract.ts @@ -1,4 +1,5 @@ import {Node} from "@chainsafe/persistent-merkle-tree"; +import {alloc} from "../util/byteArray"; /* eslint-disable @typescript-eslint/member-ordering */ @@ -94,7 +95,7 @@ export abstract class Type { /** INTERNAL METHOD: Merkleize value to tree */ value_toTree(value: V): Node { // TODO: Un-performant path but useful for prototyping. Overwrite in Type if performance is important - const uint8Array = new Uint8Array(this.value_serializedSize(value)); + const uint8Array = alloc(this.value_serializedSize(value)); const dataView = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength); this.value_serializeToBytes({uint8Array, dataView}, 0, value); return this.tree_deserializeFromBytes({uint8Array, dataView}, 0, uint8Array.length); @@ -103,7 +104,7 @@ export abstract class Type { /** INTERNAL METHOD: Un-merkleize tree to value */ tree_toValue(node: Node): V { // TODO: Un-performant path but useful for prototyping. Overwrite in Type if performance is important - const uint8Array = new Uint8Array(this.tree_serializedSize(node)); + const uint8Array = alloc(this.tree_serializedSize(node)); const dataView = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength); this.tree_serializeToBytes({uint8Array, dataView}, 0, node); return this.value_deserializeFromBytes({uint8Array, dataView}, 0, uint8Array.length); @@ -116,7 +117,7 @@ export abstract class Type { /** Serialize a value to binary data */ serialize(value: V): Uint8Array { - const uint8Array = new Uint8Array(this.value_serializedSize(value)); + const uint8Array = alloc(this.value_serializedSize(value)); const dataView = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength); this.value_serializeToBytes({uint8Array, dataView}, 0, value); return uint8Array; diff --git a/packages/ssz/src/type/basic.ts b/packages/ssz/src/type/basic.ts index 0260ea49..79c2465d 100644 --- a/packages/ssz/src/type/basic.ts +++ b/packages/ssz/src/type/basic.ts @@ -1,4 +1,5 @@ import {LeafNode} from "@chainsafe/persistent-merkle-tree"; +import {alloc} from "../util/byteArray"; import {Type} from "./abstract"; /* eslint-disable @typescript-eslint/member-ordering */ @@ -31,7 +32,7 @@ export abstract class BasicType extends Type { hashTreeRoot(value: V): Uint8Array { // TODO: Optimize - const uint8Array = new Uint8Array(32); + const uint8Array = alloc(32); const dataView = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength); this.value_serializeToBytes({uint8Array, dataView}, 0, value); return uint8Array; diff --git a/packages/ssz/src/type/byteArray.ts b/packages/ssz/src/type/byteArray.ts index 01de7a19..c71e79fb 100644 --- a/packages/ssz/src/type/byteArray.ts +++ b/packages/ssz/src/type/byteArray.ts @@ -1,5 +1,5 @@ import {concatGindices, Gindex, Node, toGindex, Tree} from "@chainsafe/persistent-merkle-tree"; -import {fromHexString, toHexString, byteArrayEquals} from "../util/byteArray"; +import {fromHexString, toHexString, byteArrayEquals, alloc} from "../util/byteArray"; import {splitIntoRootChunks} from "../util/merkleize"; import {ByteViews} from "./abstract"; import {CompositeType, LENGTH_GINDEX} from "./composite"; @@ -22,7 +22,7 @@ export abstract class ByteArrayType extends CompositeType tree_fromProofNode(node: Node): {node: Node; done: boolean} { // TODO: Figure out from `node` alone if it contains complete data. // Otherwise throw a nice error "ContainerNodeStruct type requires proofs for all its data" - const uint8Array = new Uint8Array(super.tree_serializedSize(node)); + const uint8Array = alloc(super.tree_serializedSize(node)); const dataView = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength); super.tree_serializeToBytes({uint8Array, dataView}, 0, node); const value = this.value_deserializeFromBytes({uint8Array, dataView}, 0, uint8Array.length); @@ -99,7 +100,7 @@ export class ContainerNodeStructType // TODO: Optimize conversion private valueToTree(value: ValueOfFields): Node { - const uint8Array = new Uint8Array(this.value_serializedSize(value)); + const uint8Array = alloc(this.value_serializedSize(value)); const dataView = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength); this.value_serializeToBytes({uint8Array, dataView}, 0, value); return super.tree_deserializeFromBytes({uint8Array, dataView}, 0, uint8Array.length); diff --git a/packages/ssz/src/type/listBasic.ts b/packages/ssz/src/type/listBasic.ts index ae427d26..7b8df1ee 100644 --- a/packages/ssz/src/type/listBasic.ts +++ b/packages/ssz/src/type/listBasic.ts @@ -15,6 +15,7 @@ import {ArrayBasicType} from "../view/arrayBasic"; import {ListBasicTreeView} from "../view/listBasic"; import {ListBasicTreeViewDU} from "../viewDU/listBasic"; import {ArrayType} from "./array"; +import {alloc} from "../util/byteArray"; /* eslint-disable @typescript-eslint/member-ordering */ @@ -138,7 +139,7 @@ export class ListBasicType> } protected getRoots(value: ValueOf[]): Uint8Array[] { - const uint8Array = new Uint8Array(this.value_serializedSize(value)); + const uint8Array = alloc(this.value_serializedSize(value)); const dataView = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength); value_serializeToBytesArrayBasic(this.elementType, value.length, {uint8Array, dataView}, 0, value); return splitIntoRootChunks(uint8Array); diff --git a/packages/ssz/src/type/vectorBasic.ts b/packages/ssz/src/type/vectorBasic.ts index 4181907a..928711d6 100644 --- a/packages/ssz/src/type/vectorBasic.ts +++ b/packages/ssz/src/type/vectorBasic.ts @@ -11,6 +11,7 @@ import { import {ArrayBasicType, ArrayBasicTreeView} from "../view/arrayBasic"; import {ArrayBasicTreeViewDU} from "../viewDU/arrayBasic"; import {ArrayType} from "./array"; +import {alloc} from "../util/byteArray"; /* eslint-disable @typescript-eslint/member-ordering */ @@ -129,7 +130,7 @@ export class VectorBasicType> // Merkleization protected getRoots(value: ValueOf[]): Uint8Array[] { - const uint8Array = new Uint8Array(this.fixedSize); + const uint8Array = alloc(this.fixedSize); const dataView = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength); value_serializeToBytesArrayBasic(this.elementType, this.length, {uint8Array, dataView}, 0, value); return splitIntoRootChunks(uint8Array); diff --git a/packages/ssz/src/util/byteArray.ts b/packages/ssz/src/util/byteArray.ts index 095fe543..6778cd20 100644 --- a/packages/ssz/src/util/byteArray.ts +++ b/packages/ssz/src/util/byteArray.ts @@ -28,7 +28,7 @@ export function fromHexString(hex: string): Uint8Array { } const byteLen = hex.length / 2; - const bytes = new Uint8Array(byteLen); + const bytes = allocUnsafe(byteLen); for (let i = 0; i < byteLen; i++) { const byte = parseInt(hex.slice(i * 2, (i + 1) * 2), 16); bytes[i] = byte; @@ -45,3 +45,34 @@ export function byteArrayEquals(a: Uint8Array, b: Uint8Array): boolean { } return true; } + +/** + * Returns a `Uint8Array` of the requested size. Referenced memory will + * be initialized to 0. + * + * @param {number} [size] + * @returns {Uint8Array} + */ +export function alloc(size = 0): Uint8Array { + if (globalThis.Buffer != null && globalThis.Buffer.alloc != null) { + return globalThis.Buffer.alloc(size); + } + + return new Uint8Array(size); +} + +/** + * Where possible returns a Uint8Array of the requested size that references + * uninitialized memory. Only use if you are certain you will immediately + * overwrite every value in the returned `Uint8Array`. + * + * @param {number} [size] + * @returns {Uint8Array} + */ +export function allocUnsafe(size = 0): Uint8Array { + if (globalThis.Buffer != null && globalThis.Buffer.allocUnsafe != null) { + return globalThis.Buffer.allocUnsafe(size); + } + + return new Uint8Array(size); +} diff --git a/packages/ssz/src/util/merkleize.ts b/packages/ssz/src/util/merkleize.ts index b35b73e9..40e51e6e 100644 --- a/packages/ssz/src/util/merkleize.ts +++ b/packages/ssz/src/util/merkleize.ts @@ -1,4 +1,5 @@ import {digest2Bytes32} from "@chainsafe/as-sha256"; +import {alloc} from "./byteArray"; import {zeroHash} from "./zeros"; export function hash64(bytes32A: Uint8Array, bytes32B: Uint8Array): Uint8Array { @@ -43,7 +44,7 @@ export function splitIntoRootChunks(longChunk: Uint8Array): Uint8Array[] { const chunks = new Array(chunkCount); for (let i = 0; i < chunkCount; i++) { - const chunk = new Uint8Array(32); + const chunk = alloc(32); chunk.set(longChunk.slice(i * 32, (i + 1) * 32)); chunks[i] = chunk; } diff --git a/packages/ssz/src/util/zeros.ts b/packages/ssz/src/util/zeros.ts index 9dc14d1e..2918e766 100644 --- a/packages/ssz/src/util/zeros.ts +++ b/packages/ssz/src/util/zeros.ts @@ -1,7 +1,8 @@ import {digest2Bytes32} from "@chainsafe/as-sha256"; +import {alloc} from "./byteArray"; // create array of "zero hashes", successively hashed zero chunks -const zeroHashes = [new Uint8Array(32)]; +const zeroHashes = [alloc(32)]; export function zeroHash(depth: number): Uint8Array { if (depth >= zeroHashes.length) { diff --git a/packages/ssz/src/value/bitArray.ts b/packages/ssz/src/value/bitArray.ts index 1767817d..6efe908e 100644 --- a/packages/ssz/src/value/bitArray.ts +++ b/packages/ssz/src/value/bitArray.ts @@ -1,3 +1,5 @@ +import {alloc} from "../util/byteArray"; + /** Globally cache this information. @see getUint8ByteToBitBooleanArray */ const uint8ByteToBitBooleanArrays = new Array(256); @@ -29,7 +31,7 @@ export class BitArray { /** Returns a zero'ed BitArray of `bitLen` */ static fromBitLen(bitLen: number): BitArray { - return new BitArray(new Uint8Array(Math.ceil(bitLen / 8)), bitLen); + return new BitArray(alloc(Math.ceil(bitLen / 8)), bitLen); } /** Returns a BitArray of `bitLen` with a single bit set to true at position `bitIndex` */ diff --git a/packages/ssz/src/view/abstract.ts b/packages/ssz/src/view/abstract.ts index 883f62a8..d4316fda 100644 --- a/packages/ssz/src/view/abstract.ts +++ b/packages/ssz/src/view/abstract.ts @@ -1,6 +1,7 @@ import {Node, Tree, Proof} from "@chainsafe/persistent-merkle-tree"; import {ValueOf, JsonPath} from "../type/abstract"; import {CompositeType} from "../type/composite"; +import {alloc} from "../util/byteArray"; /** * A Tree View is a wrapper around a type and an SSZ Tree that contains: @@ -21,7 +22,7 @@ export abstract class TreeView