UNPKG

mina-attestations

Version:
238 lines 7.81 kB
import { Option, Provable, UInt32, Gadgets, } from 'o1js'; import { assert, assertHasProperty, chunk, zip } from "../util.js"; import { ProvableType } from "../o1js-missing.js"; import { assertLessThan16, lessThan16 } from "./gadgets.js"; import { TypeBuilder } from "../provable-type-builder.js"; export { StaticArray }; function StaticArray(type, length) { let innerType = ProvableType.get(type); // assert length bounds assert(length >= 0, 'length must be >= 0'); assert(length < 2 ** 16, 'length must be < 2^16'); const provableArray = TypeBuilder.shape({ array: Provable.Array(innerType, length), }) .forConstructor(({ array }) => new StaticArray_(array)) // convert to/from plain array .mapValue({ there: ({ array }) => array, back: (array) => ({ array }), distinguish: (s) => s instanceof StaticArrayBase, }) .build(); class StaticArray_ extends StaticArrayBase { get innerType() { return innerType; } static get length() { return length; } static from(input) { return provableArray.fromValue(input); } static provable = provableArray; } return StaticArray_; } StaticArray.from = function (type, array) { return StaticArray(type, array.length).from(array); }; class StaticArrayBase { /** * The plain array */ array; // props to override get innerType() { throw Error('Inner type must be defined in a subclass.'); } static get length() { throw Error('Length must be defined in a subclass.'); } // derived prop get length() { return this.constructor.length; } /** * The `length` of the array. For compatibility with `DynamicArray`, we also provide it under `maxLength`. */ get maxLength() { return this.length; } constructor(array) { assert(array.length === this.length, 'input has to match length'); this.array = array; } *[Symbol.iterator]() { for (let a of this.array) yield a; } /** * Asserts that 0 <= i < this.length, using a cached check that's not duplicated when doing it on the same variable multiple times. * * Handles constants without creating constraints. * * Cost: 1.5 */ assertIndexInRange(i) { i = UInt32.from(i); if (!this._indicesInRange.has(i.value)) { assertLessThan16(i, this.length); this._indicesInRange.add(i.value); } } /** * Gets value at index i, and proves that the index is in the array. * * Handles constant indices without creating constraints. * * Cost: TN + 1.5 */ get(i) { i = UInt32.from(i); this.assertIndexInRange(i); return this.getOrUnconstrained(i.value); } /** * Gets a value at index i, as an option that is None if the index is not in the array. * * Note: The correct type for `i` is actually UInt16 which doesn't exist. The method is not complete (but sound) for i >= 2^16. * * Cost: TN + 2.5 */ getOption(i) { i = UInt32.from(i); let type = this.innerType; let isContained = lessThan16(i.value, this.length); let value = this.getOrUnconstrained(i.value); const OptionT = Option(type); return OptionT.fromValue({ isSome: isContained, value }); } /** * Gets a value at index i, ASSUMING that the index is in the array. * * If the index is in fact not in the array, the return value is completely unconstrained. * * **Warning**: Only use this if you already know/proved by other means that the index is within bounds. * * Cost: T*N where T = size of the type */ getOrUnconstrained(i) { let NULL = ProvableType.synthesize(this.innerType); if (i.isConstant()) return this.array[Number(i)] ?? NULL; let type = this.innerType; let ai = Provable.witness(type, () => this.array[Number(i)] ?? NULL); let aiFields = type.toFields(ai); // assert a is correct on every field column with arrayGet() let fields = this.array.map((t) => type.toFields(t)); for (let j = 0; j < type.sizeInFields(); j++) { let column = fields.map((x) => x[j]); Gadgets.arrayGet(column, i).assertEquals(aiFields[j]); } return ai; } /** * Sets a value at index i and proves that the index is in the array. * * Cost: 1.5(T + 1)N + 1.5 */ set(i, value) { i = UInt32.from(i); this.assertIndexInRange(i); this.setOrDoNothing(i.value, value); } /** * Sets a value at index i, or does nothing if the index is not in the array * * Cost: 1.5(T + 1)N */ setOrDoNothing(i, value) { if (i.isConstant()) { let i0 = i.toBigInt(); if (i0 < this.length) this.array[Number(i0)] = value; return; } zip(this.array, this._indexMask(i)).forEach(([t, equalsIJ], j) => { this.array[j] = Provable.if(equalsIJ, this.innerType, value, t); }); } /** * Map every element of the array to a new value. */ map(type, f) { let NewArray = StaticArray(type, this.length); let array = this.array.map(f); let newArray = new NewArray(array); // new array has same length, so it can use the same cached masks newArray._indexMasks = this._indexMasks; newArray._indicesInRange = this._indicesInRange; return newArray; } /** * Iterate over all elements of the array. */ forEach(f) { this.array.forEach(f); } /** * Reduce the array to a single value. */ reduce(state, f) { this.forEach((t) => { state = f(state, t); }); return state; } /** * Split into a static number of fixed-size chunks. * Requires that the length is a multiple of the chunk size. */ chunk(chunkSize) { let chunked = chunk(this.array, chunkSize); let newLength = this.length / chunkSize; const Chunk = StaticArray(this.innerType, chunkSize); const Chunked = StaticArray(Chunk, newLength); return new Chunked(chunked.map(Chunk.from)); } /** * Reverse the array. * * Returns a copy and does not modify the original array. */ toReversed() { return new this.constructor(this.array.toReversed()); } slice(start, end) { assert(start >= 0, 'start must be >= 0'); assert(end <= this.length, 'end must be <= length'); const Array = StaticArray(this.innerType, end - start); return new Array(this.array.slice(start, end)); } // cached variables to not duplicate constraints if we do something like array.get(i), array.set(i, ..) on the same index _indexMasks = new Map(); _indicesInRange = new Set(); /** * Compute i.equals(j) for all indices j in the static-size array. * * Costs: 1.5N * * TODO: equals() could be optimized to just 1 double generic because j is constant, o1js doesn't do that */ _indexMask(i) { let mask = this._indexMasks.get(i); mask ??= this.array.map((_, j) => i.equals(j)); this._indexMasks.set(i, mask); return mask; } toValue() { assertHasProperty(this.constructor, 'provable', 'Need subclass'); return this.constructor.provable.toValue(this); } } /** * Base class of all StaticArray subclasses */ StaticArray.Base = StaticArrayBase; //# sourceMappingURL=static-array.js.map