@chainsafe/ssz
Version:
Simple Serialize
126 lines • 5.33 kB
JavaScript
import { allocUnsafe } from "@chainsafe/as-sha256";
import { getNodesAtDepth, merkleizeBlockArray, merkleizeBlocksBytes, packedNodeRootsToBytes, packedRootsBytesToNode, } from "@chainsafe/persistent-merkle-tree";
import { maxChunksToDepth } from "../util/merkleize.js";
import { namedClass } from "../util/named.js";
import { addLengthNode, getChunksNodeFromRootNode, getLengthFromRootNode } from "./arrayBasic.js";
import { ByteArrayType } from "./byteArray.js";
/**
* ByteList: Immutable alias of List[byte, N]
* - Notation: `ByteList[N]`
* - Value: `Uint8Array`
* - View: `Uint8Array`
* - ViewDU: `Uint8Array`
*
* ByteList is an immutable value which is represented by a Uint8Array for memory efficiency and performance.
* Note: Consumers of this type MUST never mutate the `Uint8Array` representation of a ByteList.
*
* For a `ByteListType` with mutability, use `ListBasicType(byteType)`
*/
export class ByteListType extends ByteArrayType {
limitBytes;
typeName;
// Immutable characteristics
depth;
chunkDepth;
fixedSize = null;
minSize;
maxSize;
maxChunkCount;
isList = true;
blockArray = [];
blockBytesLen = 0;
mixInLengthBlockBytes = new Uint8Array(64);
mixInLengthBuffer = Buffer.from(this.mixInLengthBlockBytes.buffer, this.mixInLengthBlockBytes.byteOffset, this.mixInLengthBlockBytes.byteLength);
constructor(limitBytes, opts) {
super();
this.limitBytes = limitBytes;
if (limitBytes === 0)
throw Error("List limit must be > 0");
this.typeName = opts?.typeName ?? `ByteList[${limitBytes}]`;
this.maxChunkCount = Math.ceil(this.limitBytes / 32);
this.chunkDepth = maxChunksToDepth(this.maxChunkCount);
this.depth = 1 + this.chunkDepth;
this.minSize = 0;
this.maxSize = this.limitBytes;
}
static named(limitBits, opts) {
return new (namedClass(ByteListType, opts.typeName))(limitBits, opts);
}
// Views: inherited from ByteArrayType
// Serialization + deserialization
value_serializedSize(value) {
return value.length;
}
// value_* inherited from ByteArrayType
tree_serializedSize(node) {
return getLengthFromRootNode(node);
}
tree_serializeToBytes(output, offset, node) {
const chunksNode = getChunksNodeFromRootNode(node);
const byteLen = getLengthFromRootNode(node);
const chunkLen = Math.ceil(byteLen / 32);
const nodes = getNodesAtDepth(chunksNode, this.chunkDepth, 0, chunkLen);
packedNodeRootsToBytes(output.dataView, offset, byteLen, nodes);
return offset + byteLen;
}
tree_deserializeFromBytes(data, start, end) {
this.assertValidSize(end - start);
const chunksNode = packedRootsBytesToNode(this.chunkDepth, data.dataView, start, end);
return addLengthNode(chunksNode, end - start);
}
tree_getByteLen(node) {
if (!node)
throw new Error("ByteListType requires a node to get leaves");
return getLengthFromRootNode(node);
}
// Merkleization: inherited from ByteArrayType
hashTreeRoot(value) {
const root = allocUnsafe(32);
this.hashTreeRootInto(value, root, 0);
return root;
}
/**
* Use merkleizeBlockArray() instead of merkleizeBlocksBytes() to avoid big memory allocation
*/
hashTreeRootInto(value, output, offset) {
// should not call super.hashTreeRoot() here
// use merkleizeBlockArray() instead of merkleizeBlocksBytes() to avoid big memory allocation
// reallocate this.blockArray if needed
if (value.length > this.blockBytesLen) {
const newBlockCount = Math.ceil(value.length / 64);
// this.blockBytesLen should be a multiple of 64
const oldBlockCount = Math.ceil(this.blockBytesLen / 64);
const blockDiff = newBlockCount - oldBlockCount;
const newBlocksBytes = new Uint8Array(blockDiff * 64);
for (let i = 0; i < blockDiff; i++) {
this.blockArray.push(newBlocksBytes.subarray(i * 64, (i + 1) * 64));
this.blockBytesLen += 64;
}
}
// populate this.blockArray
for (let i = 0; i < value.length; i += 64) {
const block = this.blockArray[i / 64];
// zero out the last block if it's over value.length
if (i + 64 > value.length) {
block.fill(0);
}
block.set(value.subarray(i, Math.min(i + 64, value.length)));
}
// compute hashTreeRoot
const blockLimit = Math.ceil(value.length / 64);
merkleizeBlockArray(this.blockArray, blockLimit, this.maxChunkCount, this.mixInLengthBlockBytes, 0);
// mixInLength
this.mixInLengthBuffer.writeUIntLE(value.length, 32, 6);
// one for hashTreeRoot(value), one for length
const chunkCount = 2;
merkleizeBlocksBytes(this.mixInLengthBlockBytes, chunkCount, output, offset);
}
// Proofs: inherited from BitArrayType
// JSON: inherited from ByteArrayType
assertValidSize(size) {
if (size > this.limitBytes) {
throw Error(`ByteList invalid size ${size} limit ${this.limitBytes}`);
}
}
}
//# sourceMappingURL=byteList.js.map