UNPKG

@chainsafe/ssz

Version:
117 lines 5.33 kB
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