o1js
Version:
TypeScript framework for zk-SNARKs and zkApps
650 lines (649 loc) • 20.7 kB
TypeScript
import { Bool, Field } from './wrapped.js';
import { Option } from './option.js';
import { From, InferValue } from '../../bindings/lib/provable-generic.js';
import { Unconstrained } from './types/unconstrained.js';
import { Provable } from './provable.js';
export { IndexedMerkleMap, IndexedMerkleMapBase };
export { Leaf };
/**
* Class factory for an Indexed Merkle Map with a given height.
*
* ```ts
* class MerkleMap extends IndexedMerkleMap(33) {}
*
* let map = new MerkleMap();
*
* map.insert(2n, 14n);
* map.insert(1n, 13n);
*
* let x = map.get(2n); // 14
* ```
*
* Indexed Merkle maps can be used directly in provable code:
*
* ```ts
* ZkProgram({
* methods: {
* test: {
* privateInputs: [MerkleMap, Field],
*
* method(map: MerkleMap, key: Field) {
* // get the value associated with `key`
* let value = map.getOption(key).orElse(0n);
*
* // increment the value by 1
* map.set(key, value.add(1));
* }
* }
* }
* })
* ```
*
* Initially, every `IndexedMerkleMap` is populated by a single key-value pair: `(0, 0)`. The value for key `0` can be updated like any other.
* When keys and values are hash outputs, `(0, 0)` can serve as a convenient way to represent a dummy update to the tree, since 0 is not
* efficiently computable as a hash image, and this update doesn't affect the Merkle root.
*/
declare function IndexedMerkleMap(height: number): typeof IndexedMerkleMapBase;
declare const provableBase: {
root: typeof import("./field.js").Field & ((x: string | number | bigint | import("./core/fieldvar.js").FieldConst | import("./core/fieldvar.js").FieldVar | import("./field.js").Field) => import("./field.js").Field);
length: typeof import("./field.js").Field & ((x: string | number | bigint | import("./core/fieldvar.js").FieldConst | import("./core/fieldvar.js").FieldVar | import("./field.js").Field) => import("./field.js").Field);
data: Provable<Unconstrained<{
nodes: (bigint | undefined)[][];
sortedLeaves: StoredLeaf[];
}>, {
nodes: (bigint | undefined)[][];
sortedLeaves: StoredLeaf[];
}> & {
toInput: (x: Unconstrained<{
nodes: (bigint | undefined)[][];
sortedLeaves: StoredLeaf[];
}>) => {
fields?: import("./field.js").Field[] | undefined;
packed?: [import("./field.js").Field, number][] | undefined;
};
empty: () => Unconstrained<{
nodes: (bigint | undefined)[][];
sortedLeaves: StoredLeaf[];
}>;
};
};
declare class IndexedMerkleMapBase {
root: Field;
length: Field;
get height(): number;
readonly data: Unconstrained<{
readonly nodes: (bigint | undefined)[][];
readonly sortedLeaves: StoredLeaf[];
}>;
static provable: Provable<IndexedMerkleMapBase, InferValue<typeof provableBase>>;
/**
* Creates a new, empty Indexed Merkle Map.
*/
constructor();
static _firstLeaf: {
key: bigint;
value: bigint;
nextKey: bigint;
index: number;
};
/**
* Clone the entire Merkle map.
*
* This method is provable.
*/
clone(): IndexedMerkleMapBase;
/**
* Overwrite the entire Merkle map with another one.
*
* This method is provable.
*/
overwrite(other: IndexedMerkleMapBase): void;
/**
* Overwrite the entire Merkle map with another one, if the condition is true.
*
* This method is provable.
*/
overwriteIf(condition: Bool | boolean, other: IndexedMerkleMapBase): void;
/**
* Insert a new leaf `(key, value)`.
*
* Proves that `key` doesn't exist yet.
*/
insert(key: Field | bigint, value: Field | bigint): void;
/**
* Update an existing leaf `(key, value)`.
*
* Proves that the `key` exists.
*
* Returns the previous value.
*/
update(key: Field | bigint, value: Field | bigint): Field;
/**
* Perform _either_ an insertion or update, depending on whether the key exists.
*
* Note: This method is handling both the `insert()` and `update()` case at the same time, so you
* can use it if you don't know whether the key exists or not.
*
* However, this comes at an efficiency cost, so prefer to use `insert()` or `update()` if you know whether the key exists.
*
* Returns the previous value, as an option (which is `None` if the key didn't exist before).
*/
set(key: Field | bigint, value: Field | bigint): Option<Field, bigint>;
/**
* Perform an insertion or update, if the enabling condition is true.
*
* If the condition is false, we instead set the 0 key to the value 0.
* This is the initial value and for typical uses of `IndexedMerkleMap`, it is guaranteed to be a no-op because the 0 key is never used.
*
* **Warning**: Only use this method if you are sure that the 0 key is not used in your application.
* Otherwise, you might accidentally overwrite a valid key-value pair.
*/
setIf(condition: Bool | boolean, key: Field | bigint, value: Field | bigint): Option<import("./field.js").Field, bigint>;
/**
* Get a value from a key.
*
* Proves that the key already exists in the map yet and fails otherwise.
*/
get(key: Field | bigint): Field;
/**
* Get a value from a key.
*
* Returns an option which is `None` if the key doesn't exist. (In that case, the option's value is unconstrained.)
*
* Note that this is more flexible than `get()` and allows you to handle the case where the key doesn't exist.
* However, it uses about twice as many constraints for that reason.
*/
getOption(key: Field | bigint): Option<Field, bigint>;
/**
* Prove that the given key exists in the map.
*/
assertIncluded(key: Field | bigint, message?: string): void;
/**
* Prove that the given key does not exist in the map.
*/
assertNotIncluded(key: Field | bigint, message?: string): void;
/**
* Check whether the given key exists in the map.
*/
isIncluded(key: Field | bigint): Bool;
/**
* Helper method to prove inclusion of a leaf in the tree.
*/
_proveInclusion(leaf: Leaf, message?: string): {
witness: import("./field.js").Field[];
index: import("./bool.js").Bool[];
};
/**
* Helper method to conditionally prove inclusion of a leaf in the tree.
*/
_proveInclusionIf(condition: Bool, leaf: Leaf, message?: string): void;
/**
* Helper method to prove inclusion of an empty leaf in the tree.
*
* This validates the path against the current root, so that we can use it to insert a new leaf.
*/
_proveEmpty(index: Bool[]): {
witness: import("./field.js").Field[];
index: import("./bool.js").Bool[];
};
/**
* Helper method to conditionally prove inclusion of a leaf in the tree.
*
* If the condition is false, we prove that the tree contains an empty leaf instead.
*/
_proveInclusionOrEmpty(condition: Bool, index: Bool[], leaf: BaseLeaf, message?: string): {
witness: import("./field.js").Field[];
index: import("./bool.js").Bool[];
};
/**
* Helper method to update the root against a previously validated path.
*
* Returns the new root.
*/
_proveUpdate(leaf: BaseLeaf, path: {
index: Bool[];
witness: Field[];
}): import("./field.js").Field;
/**
* Helper method to compute the root given a leaf node and its index.
*
* The index can be given as a `Field` or as an array of bits.
*/
_computeRoot(node: Field, index: Unconstrained<number> | Bool[], witness?: Field[]): {
root: import("./field.js").Field;
path: {
witness: import("./field.js").Field[];
index: import("./bool.js").Bool[];
};
};
/**
* Given a key, returns both the low node and the leaf that contains the key.
*
* If the key does not exist, a dummy value is returned for the leaf.
*
* Can only be called outside provable code.
*/
_findLeaf(key_: Field | bigint): InferValue<typeof LeafPair>;
/**
* Update or append a leaf in our internal data structures
*/
_setLeafUnconstrained(leafExists: Bool | boolean, leaf: Leaf): void;
}
declare const BaseLeaf_base: (new (value: {
key: import("./field.js").Field;
value: import("./field.js").Field;
nextKey: import("./field.js").Field;
}) => {
key: import("./field.js").Field;
value: import("./field.js").Field;
nextKey: import("./field.js").Field;
}) & {
_isStruct: true; /**
* Overwrite the entire Merkle map with another one, if the condition is true.
*
* This method is provable.
*/
} & Omit<import("./types/provable-intf.js").Provable<{
key: import("./field.js").Field;
value: import("./field.js").Field;
nextKey: import("./field.js").Field;
}, {
key: bigint;
value: bigint;
nextKey: bigint;
}>, "fromFields"> & {
fromFields: (fields: import("./field.js").Field[]) => {
key: import("./field.js").Field;
value: import("./field.js").Field;
nextKey: import("./field.js").Field;
};
} & {
fromValue: (value: {
key: string | number | bigint | import("./field.js").Field;
value: string | number | bigint | import("./field.js").Field;
nextKey: string | number | bigint | import("./field.js").Field;
}) => {
key: import("./field.js").Field;
value: import("./field.js").Field;
nextKey: import("./field.js").Field;
};
toInput: (x: {
key: import("./field.js").Field;
value: import("./field.js").Field;
nextKey: import("./field.js").Field;
}) => {
fields?: import("./field.js").Field[] | undefined;
packed?: [import("./field.js").Field, number][] | undefined;
};
toJSON: (x: {
key: import("./field.js").Field;
value: import("./field.js").Field;
nextKey: import("./field.js").Field;
}) => {
key: string;
value: string;
nextKey: string;
};
fromJSON: (x: {
key: string;
value: string;
nextKey: string;
}) => {
key: import("./field.js").Field;
value: import("./field.js").Field;
nextKey: import("./field.js").Field;
};
empty: () => {
key: import("./field.js").Field;
value: import("./field.js").Field;
nextKey: import("./field.js").Field;
};
};
declare class BaseLeaf extends BaseLeaf_base {
}
declare const Leaf_base: (new (value: {
value: import("./field.js").Field;
key: import("./field.js").Field;
nextKey: import("./field.js").Field;
index: Unconstrained<number>;
sortedIndex: Unconstrained<number>;
}) => {
value: import("./field.js").Field;
key: import("./field.js").Field;
nextKey: import("./field.js").Field;
index: Unconstrained<number>;
sortedIndex: Unconstrained<number>;
}) & {
_isStruct: true; /**
* Overwrite the entire Merkle map with another one, if the condition is true.
*
* This method is provable.
*/
} & Provable<{
value: import("./field.js").Field;
key: import("./field.js").Field;
nextKey: import("./field.js").Field;
index: Unconstrained<number>;
sortedIndex: Unconstrained<number>;
}, {
value: bigint;
key: bigint;
nextKey: bigint;
index: number;
sortedIndex: number;
}> & {
fromValue: (value: {
value: string | number | bigint | import("./field.js").Field;
key: string | number | bigint | import("./field.js").Field;
nextKey: string | number | bigint | import("./field.js").Field;
index: number | Unconstrained<number>;
sortedIndex: number | Unconstrained<number>;
}) => {
value: import("./field.js").Field;
key: import("./field.js").Field;
nextKey: import("./field.js").Field;
index: Unconstrained<number>;
sortedIndex: Unconstrained<number>;
};
toInput: (x: {
value: import("./field.js").Field;
key: import("./field.js").Field;
nextKey: import("./field.js").Field;
index: Unconstrained<number>;
sortedIndex: Unconstrained<number>;
}) => {
fields?: import("./field.js").Field[] | undefined;
packed?: [import("./field.js").Field, number][] | undefined;
};
toJSON: (x: {
value: import("./field.js").Field;
key: import("./field.js").Field;
nextKey: import("./field.js").Field;
index: Unconstrained<number>;
sortedIndex: Unconstrained<number>;
}) => {
value: string;
key: string;
nextKey: string;
index: {
toFields: {};
toAuxiliary: {};
fromFields: {};
sizeInFields: {};
check: {};
toValue: {};
fromValue: {};
toCanonical?: {} | null | undefined;
toInput: {};
empty: {};
};
sortedIndex: {
toFields: {};
toAuxiliary: {};
fromFields: {};
sizeInFields: {};
check: {};
toValue: {};
fromValue: {};
toCanonical?: {} | null | undefined;
toInput: {};
empty: {};
};
};
fromJSON: (x: {
value: string;
key: string;
nextKey: string;
index: {
toFields: {};
toAuxiliary: {};
fromFields: {};
sizeInFields: {};
check: {};
toValue: {};
fromValue: {};
toCanonical?: {} | null | undefined;
toInput: {};
empty: {};
};
sortedIndex: {
toFields: {};
toAuxiliary: {};
fromFields: {};
sizeInFields: {};
check: {};
toValue: {};
fromValue: {};
toCanonical?: {} | null | undefined;
toInput: {};
empty: {};
};
}) => {
value: import("./field.js").Field;
key: import("./field.js").Field;
nextKey: import("./field.js").Field;
index: Unconstrained<number>;
sortedIndex: Unconstrained<number>;
};
empty: () => {
value: import("./field.js").Field;
key: import("./field.js").Field;
nextKey: import("./field.js").Field;
index: Unconstrained<number>;
sortedIndex: Unconstrained<number>;
};
};
declare class Leaf extends Leaf_base {
/**
* Compute a leaf node: the hash of a leaf that becomes part of the Merkle tree.
*/
static hashNode(leaf: From<typeof BaseLeaf>): import("./field.js").Field;
/**
* Create a new leaf, given its low node and index.
*/
static nextAfter(low: Leaf, index: Field, leaf: BaseLeaf): Leaf;
static toStored(leaf: Leaf): StoredLeaf;
static fromStored(leaf: StoredLeaf, sortedIndex: number): {
sortedIndex: number;
value: bigint;
key: bigint;
nextKey: bigint;
index: number;
};
}
type StoredLeaf = {
readonly value: bigint;
readonly key: bigint;
readonly nextKey: bigint;
readonly index: number;
};
declare const LeafPair_base: (new (value: {
low: Leaf;
self: Leaf;
}) => {
low: Leaf;
self: Leaf;
}) & {
_isStruct: true; /**
* Overwrite the entire Merkle map with another one, if the condition is true.
*
* This method is provable.
*/
} & Provable<{
low: Leaf;
self: Leaf;
}, {
low: {
value: bigint;
key: bigint;
nextKey: bigint;
index: number;
sortedIndex: number;
};
self: {
value: bigint;
key: bigint;
nextKey: bigint;
index: number;
sortedIndex: number;
};
}> & {
fromValue: (value: {
low: Leaf | {
value: string | number | bigint | import("./field.js").Field;
key: string | number | bigint | import("./field.js").Field;
nextKey: string | number | bigint | import("./field.js").Field;
index: number | Unconstrained<number>;
sortedIndex: number | Unconstrained<number>;
};
self: Leaf | {
value: string | number | bigint | import("./field.js").Field;
key: string | number | bigint | import("./field.js").Field;
nextKey: string | number | bigint | import("./field.js").Field;
index: number | Unconstrained<number>;
sortedIndex: number | Unconstrained<number>;
};
}) => {
low: Leaf;
self: Leaf;
};
toInput: (x: {
low: Leaf;
self: Leaf;
}) => {
fields?: import("./field.js").Field[] | undefined;
packed?: [import("./field.js").Field, number][] | undefined;
};
toJSON: (x: {
low: Leaf;
self: Leaf;
}) => {
low: {
value: string;
key: string;
nextKey: string;
index: {
toFields: {};
toAuxiliary: {};
fromFields: {};
sizeInFields: {};
check: {};
toValue: {};
fromValue: {};
toCanonical?: {} | null | undefined;
toInput: {};
empty: {};
};
sortedIndex: {
toFields: {};
toAuxiliary: {};
fromFields: {};
sizeInFields: {};
check: {};
toValue: {};
fromValue: {};
toCanonical?: {} | null | undefined;
toInput: {};
empty: {};
};
};
self: {
value: string;
key: string;
nextKey: string;
index: {
toFields: {};
toAuxiliary: {};
fromFields: {};
sizeInFields: {};
check: {};
toValue: {};
fromValue: {};
toCanonical?: {} | null | undefined;
toInput: {};
empty: {};
};
sortedIndex: {
toFields: {};
toAuxiliary: {};
fromFields: {};
sizeInFields: {};
check: {};
toValue: {};
fromValue: {};
toCanonical?: {} | null | undefined;
toInput: {};
empty: {};
};
};
};
fromJSON: (x: {
low: {
value: string;
key: string;
nextKey: string;
index: {
toFields: {};
toAuxiliary: {};
fromFields: {};
sizeInFields: {};
check: {};
toValue: {};
fromValue: {};
toCanonical?: {} | null | undefined;
toInput: {};
empty: {};
};
sortedIndex: {
toFields: {};
toAuxiliary: {};
fromFields: {};
sizeInFields: {};
check: {};
toValue: {};
fromValue: {};
toCanonical?: {} | null | undefined;
toInput: {};
empty: {};
};
};
self: {
value: string;
key: string;
nextKey: string;
index: {
toFields: {};
toAuxiliary: {};
fromFields: {};
sizeInFields: {};
check: {};
toValue: {};
fromValue: {};
toCanonical?: {} | null | undefined;
toInput: {};
empty: {};
};
sortedIndex: {
toFields: {};
toAuxiliary: {};
fromFields: {};
sizeInFields: {};
check: {};
toValue: {};
fromValue: {};
toCanonical?: {} | null | undefined;
toInput: {};
empty: {};
};
};
}) => {
low: Leaf;
self: Leaf;
};
empty: () => {
low: Leaf;
self: Leaf;
};
};
declare class LeafPair extends LeafPair_base {
}