UNPKG

@chainsafe/ssz

Version:

Simple Serialize

211 lines 7.48 kB
import { allocUnsafe } from "@chainsafe/as-sha256"; import { concatGindices, getHashComputations, getNode, merkleizeBlocksBytes, } from "@chainsafe/persistent-merkle-tree"; import { namedClass } from "../util/named.js"; import { addLengthNode, getLengthFromRootNode } from "./arrayBasic.js"; import { CompositeType, isCompositeType } from "./composite.js"; import { NoneType } from "./none.js"; const VALUE_GINDEX = BigInt(2); const SELECTOR_GINDEX = BigInt(3); /** * Union: union type containing one of the given subtypes * - Notation: Union[type_0, type_1, ...], e.g. union[None, uint64, uint32] */ export class UnionType extends CompositeType { types; typeName; depth = 1; maxChunkCount = 1; fixedSize = null; minSize; maxSize; isList = true; isViewMutable = true; mixInLengthBlockBytes = new Uint8Array(64); mixInLengthBuffer = Buffer.from(this.mixInLengthBlockBytes.buffer, this.mixInLengthBlockBytes.byteOffset, this.mixInLengthBlockBytes.byteLength); maxSelector; constructor(types, opts) { super(); this.types = types; if (types.length >= 128) { throw Error("Must have less than 128 types"); } if (types.length === 0) { throw Error("Must have at least 1 type option"); } if (types[0] instanceof NoneType && types.length < 2) { throw Error("Must have at least 2 type options if the first is None"); } for (let i = 1; i < types.length; i++) { if (types[i] instanceof NoneType) { throw Error("None may only be the first option"); } } this.typeName = opts?.typeName ?? `Union[${types.map((t) => t.typeName).join(",")}]`; const minLens = []; const maxLens = []; for (const _type of types) { minLens.push(_type.minSize); maxLens.push(_type.maxSize); } this.minSize = 1 + Math.min(...minLens); this.maxSize = 1 + Math.max(...maxLens); this.maxSelector = this.types.length - 1; // maxChunkCount = 1 so this.blocksBuffer.length = 32 in this case this.blocksBuffer = new Uint8Array(32); } static named(types, opts) { return new (namedClass(UnionType, opts.typeName))(types, opts); } defaultValue() { return { selector: 0, value: this.types[0].defaultValue(), }; } getView(tree) { return this.tree_toValue(tree.rootNode); } getViewDU(node) { return this.tree_toValue(node); } cacheOfViewDU() { return; } commitView(view) { return this.value_toTree(view); } commitViewDU(view, hcOffset = 0, hcByLevel = null) { const node = this.value_toTree(view); if (hcByLevel !== null && node.h0 === null) { getHashComputations(node, hcOffset, hcByLevel); } return node; } value_serializedSize(value) { return 1 + this.types[value.selector].value_serializedSize(value.value); } value_serializeToBytes(output, offset, value) { output.uint8Array[offset] = value.selector; return this.types[value.selector].value_serializeToBytes(output, offset + 1, value.value); } value_deserializeFromBytes(data, start, end) { const selector = data.uint8Array[start]; if (selector > this.maxSelector) { throw Error(`Invalid selector ${selector}`); } return { selector, value: this.types[selector].value_deserializeFromBytes(data, start + 1, end), }; } tree_serializedSize(node) { const selector = getLengthFromRootNode(node); const valueNode = node.left; return 1 + this.types[selector].value_serializedSize(valueNode); } tree_serializeToBytes(output, offset, node) { const selector = getLengthFromRootNode(node); const valueNode = node.left; output.uint8Array[offset] = selector; return this.types[selector].tree_serializeToBytes(output, offset + 1, valueNode); } tree_deserializeFromBytes(data, start, end) { const selector = data.uint8Array[start]; if (selector > this.maxSelector) { throw Error(`Invalid selector ${selector}`); } const valueNode = this.types[selector].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); this.mixInLengthBuffer.writeUIntLE(value.selector, 32, 6); const chunkCount = 2; merkleizeBlocksBytes(this.mixInLengthBlockBytes, chunkCount, output, offset); } getBlocksBytes(value) { this.types[value.selector].hashTreeRootInto(value.value, this.blocksBuffer, 0); return this.blocksBuffer; } // Proofs getPropertyGindex(prop) { switch (prop) { case "value": return VALUE_GINDEX; case "selector": return SELECTOR_GINDEX; default: throw new Error(`Invalid Union type property ${prop}`); } } getPropertyType() { // a Union has multiple types throw new Error("Not applicable for Union type"); } getIndexProperty(index) { if (index === 0) return "value"; if (index === 1) return "selector"; throw Error("Union index of out bounds"); } tree_getLeafGindices(rootGindex, rootNode) { if (!rootNode) { throw Error("rootNode required"); } const gindices = [concatGindices([rootGindex, SELECTOR_GINDEX])]; const selector = getLengthFromRootNode(rootNode); const type = this.types[selector]; const extendedFieldGindex = concatGindices([rootGindex, VALUE_GINDEX]); if (isCompositeType(type)) { gindices.push(...type.tree_getLeafGindices(extendedFieldGindex, getNode(rootNode, VALUE_GINDEX))); } else { gindices.push(extendedFieldGindex); } return gindices; } // JSON fromJson(json) { if (typeof json !== "object") { throw new Error("JSON must be of type object"); } const union = json; if (typeof union.selector !== "number") { throw new Error("Invalid JSON Union selector must be number"); } const type = this.types[union.selector]; if (!type) { throw new Error("Invalid JSON Union selector out of range"); } return { selector: union.selector, value: type.toJson(union.value), }; } toJson(value) { return { selector: value.selector, value: this.types[value.selector].toJson(value.value), }; } clone(value) { return { selector: value.selector, value: this.types[value.selector].clone(value.value), }; } equals(a, b) { if (a.selector !== b.selector) { return false; } return this.types[a.selector].equals(a.value, b.value); } } //# sourceMappingURL=union.js.map