@chainsafe/ssz
Version:
Simple Serialize
221 lines • 8.13 kB
JavaScript
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