mina-attestations
Version:
Private Attestations on Mina
238 lines • 7.81 kB
JavaScript
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