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