@chainsafe/ssz
Version:
Simple Serialize
117 lines • 5.33 kB
JavaScript
import { allocUnsafe } from "@chainsafe/as-sha256";
import { Tree, concatGindices, merkleizeBlocksBytes, packedNodeRootsToBytes, packedRootsBytesToLeafNodes, } from "@chainsafe/persistent-merkle-tree";
import { byteArrayEquals } from "../util/byteArray.js";
import { namedClass } from "../util/named.js";
import { BitArray } from "../value/bitArray.js";
import { addLengthNode, getChunksNodeFromRootNode, getLengthFromRootNode } from "./arrayBasic.js";
import { BitArrayType } from "./bitArray.js";
import { deserializeUint8ArrayBitListFromBytes } from "./bitList.js";
import { PROGRESSIVE_LIST_MAX_SIZE, getNodesAtProgressiveDepth, merkleizeProgressiveBytes, progressiveChunkGindex, progressiveSubtreeFillToContents, } from "./progressive.js";
const CHUNKS_GINDEX = BigInt(2);
const LENGTH_GINDEX = BigInt(3);
/**
* ProgressiveBitList: variable-length collection of boolean values without a limit.
* - Serialization is identical to BitList, including the padding bit.
* - Merkleization uses EIP-7916 progressive merkleization.
*/
export class ProgressiveBitListType extends BitArrayType {
typeName;
depth = 1;
chunkDepth = 0;
fixedSize = null;
minSize = 1;
maxSize = PROGRESSIVE_LIST_MAX_SIZE;
maxChunkCount = Number.MAX_SAFE_INTEGER;
isList = true;
mixInLengthBlockBytes = new Uint8Array(64);
mixInLengthBuffer = Buffer.from(this.mixInLengthBlockBytes.buffer, this.mixInLengthBlockBytes.byteOffset, this.mixInLengthBlockBytes.byteLength);
constructor(opts) {
super();
this.typeName = opts?.typeName ?? "ProgressiveBitList";
}
static named(opts) {
return new (namedClass(ProgressiveBitListType, opts.typeName))(opts);
}
defaultValue() {
return BitArray.fromBitLen(0);
}
createFromProof(proof, root) {
const rootNode = Tree.createFromProof(proof).rootNode;
if (root !== undefined && !byteArrayEquals(rootNode.root, root)) {
throw new Error("Proof does not match trusted root");
}
return this.getView(new Tree(rootNode));
}
value_serializedSize(value) {
return bitLenToSerializedLength(value.bitLen);
}
value_serializeToBytes(output, offset, value) {
output.uint8Array.set(value.uint8Array, offset);
return applyPaddingBit(output.uint8Array, offset, value.bitLen);
}
value_deserializeFromBytes(data, start, end, reuseBytes) {
const { uint8Array, bitLen } = deserializeUint8ArrayBitListFromBytes(data.uint8Array, start, end, reuseBytes);
return new BitArray(uint8Array, bitLen);
}
tree_serializedSize(node) {
return bitLenToSerializedLength(getLengthFromRootNode(node));
}
tree_serializeToBytes(output, offset, node) {
const chunksNode = getChunksNodeFromRootNode(node);
const bitLen = getLengthFromRootNode(node);
const byteLen = Math.ceil(bitLen / 8);
const chunkLen = Math.ceil(byteLen / 32);
const nodes = getNodesAtProgressiveDepth(chunksNode, chunkLen);
packedNodeRootsToBytes(output.dataView, offset, byteLen, nodes);
return applyPaddingBit(output.uint8Array, offset, bitLen);
}
tree_deserializeFromBytes(data, start, end) {
const { uint8Array, bitLen } = deserializeUint8ArrayBitListFromBytes(data.uint8Array, start, end);
const dataView = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength);
const nodes = packedRootsBytesToLeafNodes(dataView, 0, uint8Array.length);
return addLengthNode(progressiveSubtreeFillToContents(nodes), bitLen);
}
tree_getByteLen(node) {
if (!node)
throw new Error("ProgressiveBitListType requires a node to get leaves");
return Math.ceil(getLengthFromRootNode(node) / 8);
}
hashTreeRoot(value) {
const root = allocUnsafe(32);
this.hashTreeRootInto(value, root, 0);
return root;
}
hashTreeRootInto(value, output, offset) {
const byteLen = value.uint8Array.length;
const chunkCount = Math.ceil(byteLen / 32);
const blockBytes = this.getBlocksBytes(value);
merkleizeProgressiveBytes(blockBytes, chunkCount, this.mixInLengthBlockBytes, 0);
this.mixInLengthBuffer.writeUIntLE(value.bitLen, 32, 6);
merkleizeBlocksBytes(this.mixInLengthBlockBytes, 2, output, offset);
}
tree_getLeafGindices(rootGindex, rootNode) {
const byteLen = this.tree_getByteLen(rootNode);
const chunkCount = Math.ceil(byteLen / 32);
const gindices = new Array(chunkCount);
for (let i = 0; i < chunkCount; i++) {
gindices[i] = concatGindices([rootGindex, CHUNKS_GINDEX, progressiveChunkGindex(i)]);
}
gindices.push(concatGindices([rootGindex, LENGTH_GINDEX]));
return gindices;
}
}
function bitLenToSerializedLength(bitLen) {
const bytes = Math.ceil(bitLen / 8);
return bitLen % 8 === 0 ? bytes + 1 : bytes;
}
function applyPaddingBit(output, offset, bitLen) {
const byteLen = Math.ceil(bitLen / 8);
const newOffset = offset + byteLen;
if (bitLen % 8 === 0) {
output[newOffset] = 1;
return newOffset + 1;
}
output[newOffset - 1] |= 1 << (bitLen % 8);
return newOffset;
}
//# sourceMappingURL=progressiveBitList.js.map