UNPKG

@chainsafe/ssz

Version:
221 lines 8.13 kB
import { allocUnsafe } from "@chainsafe/as-sha256"; import { ProofType, Tree, concatGindices, createProof, getNode, merkleizeBlocksBytes, } from "@chainsafe/persistent-merkle-tree"; import { byteArrayEquals } from "../util/byteArray.js"; import { cacheRoot, symbolCachedPermanentRoot } from "../util/merkleize.js"; import { treePostProcessFromProofNode } from "../util/proof/treePostProcessFromProofNode.js"; import { Type } from "./abstract.js"; export const LENGTH_GINDEX = BigInt(3); /** * Represents a composite type as defined in the spec: * https://github.com/ethereum/consensus-specs/blob/v1.6.1/ssz/simple-serialize.md#composite-types */ export class CompositeType extends Type { cachePermanentRootStruct; isBasic = false; blocksBuffer = new Uint8Array(0); constructor( /** * Caches `hashTreeRoot()` result for struct values. * * WARNING: Must only be used for immutable values. The cached root is never discarded */ cachePermanentRootStruct) { super(); this.cachePermanentRootStruct = cachePermanentRootStruct; } /** New instance of a recursive zero'ed value converted to Tree View */ defaultView() { return this.toView(this.defaultValue()); } /** New instance of a recursive zero'ed value converted to Deferred Update Tree View */ defaultViewDU() { return this.toViewDU(this.defaultValue()); } /** * Deserialize binary data to a Tree View. * @see {@link CompositeType.getView} */ deserializeToView(data) { const dataView = new DataView(data.buffer, data.byteOffset, data.byteLength); const node = this.tree_deserializeFromBytes({ uint8Array: data, dataView }, 0, data.length); return this.getView(new Tree(node)); } /** * Deserialize binary data to a Deferred Update Tree View. * @see {@link CompositeType.getViewDU} */ deserializeToViewDU(data) { const dataView = new DataView(data.buffer, data.byteOffset, data.byteLength); const node = this.tree_deserializeFromBytes({ uint8Array: data, dataView }, 0, data.length); return this.getViewDU(node); } /** * Transform value to a View. * @see {@link CompositeType.getView} */ toView(value) { const node = this.value_toTree(value); return this.getView(new Tree(node)); } /** * Transform value to a ViewDU. * @see {@link CompositeType.getViewDU} */ toViewDU(value) { const node = this.value_toTree(value); return this.getViewDU(node); } /** * Transform value to a View. * @see {@link CompositeType.getView} */ toValueFromView(view) { const node = this.commitView(view); return this.tree_toValue(node); } /** * Transform value to a ViewDU. * @see {@link CompositeType.getViewDU} */ toValueFromViewDU(view) { const node = this.commitViewDU(view); return this.tree_toValue(node); } /** * Transform a ViewDU to a View. * @see {@link CompositeType.getView} and {@link CompositeType.getViewDU} */ toViewFromViewDU(view) { const node = this.commitViewDU(view); return this.getView(new Tree(node)); } /** * Transform a View to a ViewDU. * @see {@link CompositeType.getView} and {@link CompositeType.getViewDU} */ toViewDUFromView(view) { const node = this.commitView(view); return this.getViewDU(node); } // Merkleize API hashTreeRoot(value) { // Return cached mutable root if any if (this.cachePermanentRootStruct) { const cachedRoot = value[symbolCachedPermanentRoot]; if (cachedRoot) { return cachedRoot; } } const root = allocUnsafe(32); const safeCache = true; this.hashTreeRootInto(value, root, 0, safeCache); // hashTreeRootInto will cache the root if cachePermanentRootStruct is true return root; } hashTreeRootInto(value, output, offset, safeCache = false) { // Return cached mutable root if any if (this.cachePermanentRootStruct) { const cachedRoot = value[symbolCachedPermanentRoot]; if (cachedRoot) { output.set(cachedRoot, offset); return; } } const blocksBuffer = this.getBlocksBytes(value); merkleizeBlocksBytes(blocksBuffer, this.maxChunkCount, output, offset); if (this.cachePermanentRootStruct) { cacheRoot(value, output, offset, safeCache); } } // For debugging and testing this feature getCachedPermanentRoot(value) { return value[symbolCachedPermanentRoot]; } // Proofs API /** * Create a Tree View from a Proof. Verifies that the Proof is correct against `root`. * @see {@link CompositeType.getView} */ createFromProof(proof, root) { const rootNodeFromProof = Tree.createFromProof(proof).rootNode; const rootNode = treePostProcessFromProofNode(rootNodeFromProof, this); if (root !== undefined && !byteArrayEquals(rootNode.root, root)) { throw new Error("Proof does not match trusted root"); } return this.getView(new Tree(rootNode)); } /** INTERNAL METHOD: For view's API, create proof from a tree */ tree_createProof(node, jsonPaths) { const gindexes = this.tree_createProofGindexes(node, jsonPaths); return createProof(node, { type: ProofType.treeOffset, gindices: gindexes, }); } /** INTERNAL METHOD: For view's API, create proof from a tree */ tree_createProofGindexes(node, jsonPaths) { const gindexes = []; for (const jsonPath of jsonPaths) { gindexes.push(...this.tree_createProofGindexesForPath(node, jsonPath)); } return gindexes; } tree_createProofGindexesForPath(node, jsonPath) { const [prop, ...remainingPath] = jsonPath; if (prop === undefined) { return this.tree_getLeafGindices(BigInt(1), node); } const childGindex = this.getPropertyGindex(prop); if (childGindex === null) { return this.tree_getLeafGindices(BigInt(1), node); } const childType = this.getPropertyType(prop); if (!isCompositeType(childType)) { if (remainingPath.length > 0) { throw new Error("Invalid path: cannot navigate beyond a basic type"); } return [childGindex]; } const childNode = getNode(node, childGindex); if (remainingPath.length === 0) { return childType.tree_getLeafGindices(childGindex, childNode); } return childType .tree_createProofGindexes(childNode, [remainingPath]) .map((gindex) => concatGindices([childGindex, gindex])); } /** * Navigate to a subtype & gindex using a path */ getPathInfo(path) { const gindices = []; let type = this; for (const prop of path) { if (type.isBasic) { throw new Error("Invalid path: cannot navigate beyond a basic type"); } const gindex = type.getPropertyGindex(prop); // else stop navigating if (gindex !== null) { gindices.push(gindex); type = type.getPropertyType(prop); } } return { type, gindex: concatGindices(gindices), }; } /** * INTERNAL METHOD: post process `Ǹode` instance created from a proof and return either the same node, * and a new node representing the same data is a different `Node` instance. Currently used exclusively * by ContainerNodeStruct to convert `BranchNode` into `BranchNodeStruct`. */ tree_fromProofNode(node) { return { node, done: false }; } } export function isCompositeType(type) { return !type.isBasic; } //# sourceMappingURL=composite.js.map