UNPKG

@chainsafe/ssz

Version:

Simple Serialize

218 lines 7.95 kB
import { allocUnsafe } from "@chainsafe/as-sha256"; import { concatGindices, getHashComputations, merkleizeBlocksBytes, zeroNode, } from "@chainsafe/persistent-merkle-tree"; import { namedClass } from "../util/named.js"; import { addLengthNode, getLengthFromRootNode } from "./arrayBasic.js"; import { CompositeType, isCompositeType } from "./composite.js"; const VALUE_GINDEX = BigInt(2); const SELECTOR_GINDEX = BigInt(3); /** * Optional: optional type containing either None or a type * - Notation: Optional[type], e.g. optional[uint64] * - merklizes as list of length 0 or 1, essentially acts like * - like Union[none,type] or * - list [], [type] */ export class OptionalType extends CompositeType { elementType; typeName; depth; maxChunkCount; fixedSize = null; minSize; maxSize; isList = true; isViewMutable = true; mixInLengthBlockBytes = new Uint8Array(64); mixInLengthBuffer = Buffer.from(this.mixInLengthBlockBytes.buffer, this.mixInLengthBlockBytes.byteOffset, this.mixInLengthBlockBytes.byteLength); constructor(elementType, opts) { super(); this.elementType = elementType; this.typeName = opts?.typeName ?? `Optional[${elementType.typeName}]`; this.maxChunkCount = 1; // Depth includes the extra level for the true/false node this.depth = elementType.depth + 1; this.minSize = 0; // Max size includes prepended 0x01 byte this.maxSize = elementType.maxSize + 1; // maxChunkCount = 1 so this.blocksBuffer.length = 32 in this case this.blocksBuffer = new Uint8Array(32); } static named(elementType, opts) { return new (namedClass(OptionalType, opts.typeName))(elementType, opts); } defaultValue() { return null; } // TODO add an OptionalView getView(tree) { return this.tree_toValue(tree.rootNode); } // TODO add an OptionalViewDU getViewDU(node) { return this.tree_toValue(node); } // TODO add an OptionalView commitView(view) { return this.value_toTree(view); } // TODO add an OptionalViewDU commitViewDU(view, hcOffset = 0, hcByLevel = null) { const node = this.value_toTree(view); if (hcByLevel !== null && node.h0 === null) { getHashComputations(node, hcOffset, hcByLevel); } return node; } // TODO add an OptionalViewDU cacheOfViewDU() { return; } value_serializedSize(value) { return value !== null ? 1 + this.elementType.value_serializedSize(value) : 0; } value_serializeToBytes(output, offset, value) { if (value !== null) { output.uint8Array[offset] = 1; return this.elementType.value_serializeToBytes(output, offset + 1, value); } return offset; } value_deserializeFromBytes(data, start, end) { if (start === end) return null; const selector = data.uint8Array[start]; if (selector !== 1) { throw new Error(`Invalid selector for Optional type: ${selector}`); } return this.elementType.value_deserializeFromBytes(data, start + 1, end); } tree_serializedSize(node) { const selector = getLengthFromRootNode(node); if (selector === 0) return 0; if (selector === 1) { return 1 + this.elementType.value_serializedSize(node.left); } throw new Error(`Invalid selector for Optional type: ${selector}`); } tree_serializeToBytes(output, offset, node) { const selector = getLengthFromRootNode(node); if (selector === 0) return offset; if (selector === 1) { output.uint8Array[offset] = 1; return this.elementType.tree_serializeToBytes(output, offset + 1, node.left); } throw new Error(`Invalid selector for Optional type: ${selector}`); } tree_deserializeFromBytes(data, start, end) { let valueNode; let selector; if (start === end) { selector = 0; valueNode = zeroNode(0); } else { selector = data.uint8Array[start]; if (selector !== 1) { throw new Error(`Invalid selector for Optional type: ${selector}`); } valueNode = this.elementType.tree_deserializeFromBytes(data, start + 1, end); } return addLengthNode(valueNode, selector); } // Merkleization hashTreeRoot(value) { const root = allocUnsafe(32); this.hashTreeRootInto(value, root, 0); return root; } hashTreeRootInto(value, output, offset) { super.hashTreeRootInto(value, this.mixInLengthBlockBytes, 0); const selector = value === null ? 0 : 1; this.mixInLengthBuffer.writeUIntLE(selector, 32, 6); // one for hashTreeRoot(value), one for selector const chunkCount = 2; merkleizeBlocksBytes(this.mixInLengthBlockBytes, chunkCount, output, offset); } getBlocksBytes(value) { if (value === null) { this.blocksBuffer.fill(0); } else { this.elementType.hashTreeRootInto(value, this.blocksBuffer, 0); } return this.blocksBuffer; } // Proofs getPropertyGindex(prop) { if (isCompositeType(this.elementType)) { const propIndex = this.elementType.getPropertyGindex(prop); return propIndex === null ? propIndex : concatGindices([VALUE_GINDEX, propIndex]); } throw new Error("not applicable for Optional basic type"); } getPropertyType(prop) { if (isCompositeType(this.elementType)) { return this.elementType.getPropertyType(prop); } throw new Error("not applicable for Optional basic type"); } getIndexProperty(index) { if (isCompositeType(this.elementType)) { return this.elementType.getIndexProperty(index); } throw new Error("not applicable for Optional basic type"); } tree_createProofGindexes(node, jsonPaths) { if (isCompositeType(this.elementType)) { return super.tree_createProofGindexes(node, jsonPaths); } throw new Error("not applicable for Optional basic type"); } tree_getLeafGindices(rootGindex, rootNode) { if (!rootNode) { throw new Error("Optional type requires rootNode argument to get leaves"); } const selector = getLengthFromRootNode(rootNode); if (isCompositeType(this.elementType) && selector === 1) { return [ // ...this.elementType.tree_getLeafGindices(concatGindices([rootGindex, VALUE_GINDEX]), rootNode.left), concatGindices([rootGindex, SELECTOR_GINDEX]), ]; } if (selector === 0 || selector === 1) { return [ // concatGindices([rootGindex, VALUE_GINDEX]), concatGindices([rootGindex, SELECTOR_GINDEX]), ]; } throw new Error(`Invalid selector for Optional type: ${selector}`); } // JSON fromJson(json) { return (json === null ? null : this.elementType.fromJson(json)); } toJson(value) { return value === null ? null : this.elementType.toJson(value); } clone(value) { return (value === null ? null : this.elementType.clone(value)); } equals(a, b) { if (a === null && b === null) return true; if (a === null || b === null) return false; return this.elementType.equals(a, b); } } export function isOptionalType(type) { return type instanceof OptionalType; } export function toNonOptionalType(type) { return (isOptionalType(type) ? type.elementType : type); } //# sourceMappingURL=optional.js.map