o1js
Version:
TypeScript framework for zk-SNARKs and zkApps
697 lines (592 loc) • 21.9 kB
text/typescript
import {
GenericHashInput,
GenericProvable,
GenericProvablePure,
GenericProvableExtended,
GenericProvableExtendedPure,
GenericSignable,
} from './generic.js';
export {
createDerivers,
createHashInput,
ProvableConstructor,
SignableConstructor,
NonMethods,
InferProvable,
InferJson,
InferValue,
InferredProvable,
IsPure,
From,
Constructor,
NestedProvable,
InferProvableNested,
InferJsonNested,
InferValueNested,
};
type ProvableConstructor<Field> = <A>(
typeObj: A,
/**
* @deprecated
*/
options?: { isPure?: boolean }
) => InferredProvable<A, Field>;
type SignableConstructor<Field> = <A>(typeObj: A) => InferredSignable<A, Field>;
let complexTypes = new Set(['object', 'function']);
let primitives = new Set([Number, String, Boolean, BigInt, null, undefined]);
function createDerivers<Field>(): {
provable: ProvableConstructor<Field>;
signable: SignableConstructor<Field>;
} {
type Signable<T, TJson = JSONValue> = GenericSignable<T, TJson, Field>;
type ProvableExtended<T, TValue = any, TJson = JSONValue> = GenericProvableExtended<
T,
TValue,
TJson,
Field
>;
type HashInput = GenericHashInput<Field>;
const HashInput = createHashInput<Field>();
/**
* A function that gives us a hint that the input type is a `Provable` and we shouldn't continue
* recursing into its properties, when computing methods that aren't required by the `Provable` interface.
*/
function isProvable(typeObj: object): typeObj is GenericProvable<any, any, Field> {
return (
'sizeInFields' in typeObj &&
'toFields' in typeObj &&
'fromFields' in typeObj &&
'check' in typeObj &&
'toValue' in typeObj &&
'fromValue' in typeObj &&
'toAuxiliary' in typeObj
);
}
/**
* Accepts objects of the form { provable: Provable }
*/
function hasProvable(typeObj: object): typeObj is { provable: GenericProvable<any, any, Field> } {
return (
'provable' in typeObj &&
(typeof typeObj.provable === 'object' || typeof typeObj.provable === 'function') &&
typeObj.provable !== null &&
isProvable(typeObj.provable)
);
}
function provable<A>(typeObj: A): InferredProvable<A, Field> {
type T = InferProvable<A, Field>;
type V = InferValue<A>;
type J = InferJson<A>;
if (!isPrimitive(typeObj) && !complexTypes.has(typeof typeObj)) {
throw Error(`provable: unsupported type "${typeObj}"`);
}
function sizeInFields(typeObj: NestedProvable<Field>): number {
if (isPrimitive(typeObj)) return 0;
if (!complexTypes.has(typeof typeObj)) throw Error(`provable: unsupported type "${typeObj}"`);
if (hasProvable(typeObj)) return typeObj.provable.sizeInFields();
if (Array.isArray(typeObj)) return typeObj.map(sizeInFields).reduce((a, b) => a + b, 0);
if (isProvable(typeObj)) return typeObj.sizeInFields();
return Object.values(typeObj)
.map(sizeInFields)
.reduce((a, b) => a + b, 0);
}
function toFields(typeObj: NestedProvable<Field>, obj: any): Field[] {
if (isPrimitive(typeObj)) return [];
if (!complexTypes.has(typeof typeObj)) throw Error(`provable: unsupported type "${typeObj}"`);
if (hasProvable(typeObj)) return typeObj.provable.toFields(obj);
if (Array.isArray(typeObj)) {
if (!Array.isArray(obj)) {
if (typeof obj === 'object') {
return typeObj.map((t, i) => toFields(t, obj[i])).flat();
}
throw Error(`Expected an array for type, but got ${typeof obj}`);
}
if (typeObj.length !== obj.length) {
throw Error(`Expected array length ${typeObj.length}, but got ${obj.length}`);
}
return typeObj.map((t, i) => toFields(t, obj[i])).flat();
}
if (isProvable(typeObj)) return typeObj.toFields(obj);
return Object.keys(typeObj)
.map((k) => toFields(typeObj[k], obj[k]))
.flat();
}
function toAuxiliary(typeObj: NestedProvable<Field>, obj?: any): any[] {
if (typeObj === Number) return [obj ?? 0];
if (typeObj === String) return [obj ?? ''];
if (typeObj === Boolean) return [obj ?? false];
if (typeObj === BigInt) return [obj ?? 0n];
if (typeObj === undefined || typeObj === null) return [];
if (isPrimitive(typeObj) || !complexTypes.has(typeof typeObj))
throw Error(`provable: unsupported type "${typeObj}"`);
if (hasProvable(typeObj)) return typeObj.provable.toAuxiliary(obj);
if (Array.isArray(typeObj)) return typeObj.map((t, i) => toAuxiliary(t, obj?.[i]));
if (isProvable(typeObj)) return typeObj.toAuxiliary(obj);
return Object.keys(typeObj).map((k) => toAuxiliary(typeObj[k], obj?.[k]));
}
function fromFields(typeObj: NestedProvable<Field>, fields: Field[], aux: any[] = []): any {
if (typeObj === Number || typeObj === String || typeObj === Boolean || typeObj === BigInt)
return aux[0];
if (typeObj === undefined || typeObj === null) return typeObj;
if (isPrimitive(typeObj) || !complexTypes.has(typeof typeObj))
throw Error(`provable: unsupported type "${typeObj}"`);
if (hasProvable(typeObj)) return typeObj.provable.fromFields(fields, aux);
if (Array.isArray(typeObj)) {
let array: any[] = [];
let i = 0;
let offset = 0;
for (let subObj of typeObj) {
let size = sizeInFields(subObj);
array.push(fromFields(subObj, fields.slice(offset, offset + size), aux[i]));
offset += size;
i++;
}
return array;
}
if (isProvable(typeObj)) return typeObj.fromFields(fields, aux);
let keys = Object.keys(typeObj);
let values = fromFields(
keys.map((k) => typeObj[k]),
fields,
aux
);
return Object.fromEntries(keys.map((k, i) => [k, values[i]]));
}
function check(typeObj: NestedProvable<Field>, obj: any): void {
if (isPrimitive(typeObj)) return;
if (!complexTypes.has(typeof typeObj)) throw Error(`provable: unsupported type "${typeObj}"`);
if (hasProvable(typeObj)) return typeObj.provable.check(obj);
if (Array.isArray(typeObj)) return typeObj.forEach((t, i) => check(t, obj[i]));
if (isProvable(typeObj)) return typeObj.check(obj);
if (display(typeObj) === 'Struct') {
throw new Error(
`provable: cannot run check() on 'Struct' type. ` +
`Instead of using 'Struct' directly, extend 'Struct' to create a specific type.\n\n` +
`Example:\n` +
`// Incorrect Usage:\n` +
`class MyStruct extends Struct({\n` +
` fieldA: Struct, // This is incorrect\n` +
`}) {}\n\n` +
`// Correct Usage:\n` +
`class MyStruct extends Struct({\n` +
` fieldA: MySpecificStruct, // Use the specific struct type\n` +
`}) {}\n`
);
}
if (typeof typeObj === 'function') {
throw new Error(
`provable: invalid type detected. Functions are not supported as types. ` +
`Ensure you are passing an instance of a supported type or an anonymous object.\n`
);
}
// Only recurse into the object if it's an object and not a function
return Object.keys(typeObj).forEach((k) => check(typeObj[k], obj[k]));
}
function toCanonical(typeObj: NestedProvable<Field>, value: any): any {
if (isPrimitive(typeObj)) return value;
if (!complexTypes.has(typeof typeObj)) throw Error(`provable: unsupported type "${typeObj}"`);
if (hasProvable(typeObj)) return typeObj.provable.toCanonical?.(value) ?? value;
if (Array.isArray(typeObj)) {
return typeObj.forEach((t, i) => toCanonical(t, value[i]));
}
if (isProvable(typeObj)) return typeObj.toCanonical?.(value) ?? value;
return Object.fromEntries(
Object.keys(typeObj).map((k) => {
return [k, toCanonical(typeObj[k], value[k])];
})
);
}
const toValue = createMap('toValue');
const fromValue = createMap('fromValue');
let { empty, fromJSON, toJSON, toInput } = signable(
typeObj,
// if one of these is true, we don't want to continue searching for 'signable' methods
(obj) => isProvable(obj) || hasProvable(obj)
);
type S = InferSignable<A, Field>;
const type = typeObj as NestedProvable<Field>;
return {
sizeInFields: () => sizeInFields(type),
toFields: (obj: T) => toFields(type, obj),
toAuxiliary: (obj?: T) => toAuxiliary(type, obj),
fromFields: (fields: Field[], aux: any[]) => fromFields(type, fields, aux) as T,
check: (obj: T) => check(type, obj),
toValue(x) {
return toValue(type, x);
},
fromValue(v) {
return fromValue(type, v);
},
toCanonical(x) {
return toCanonical(type, x);
},
toInput: (obj: T) => toInput(obj as S),
toJSON: (obj: T) => toJSON(obj as S) satisfies J,
fromJSON: (json: J) => fromJSON(json) as T,
empty: () => empty() as T,
} satisfies ProvableExtended<T, V, J> as InferredProvable<A, Field>;
}
function signable<A>(
typeObj: A,
shouldTerminate?: (typeObj: object) => boolean
): InferredSignable<A, Field> {
type T = InferSignable<A, Field>;
type J = InferJson<A>;
let objectKeys = typeof typeObj === 'object' && typeObj !== null ? Object.keys(typeObj) : [];
let primitives = new Set([Number, String, Boolean, BigInt, null, undefined]);
if (!primitives.has(typeObj as any) && !complexTypes.has(typeof typeObj)) {
throw Error(`provable: unsupported type "${typeObj}"`);
}
function toInput(typeObj: any, obj: any): HashInput {
if (primitives.has(typeObj)) return {};
if (!complexTypes.has(typeof typeObj)) throw Error(`provable: unsupported type "${typeObj}"`);
if ('provable' in typeObj) return toInput(typeObj.provable, obj);
if (Array.isArray(typeObj)) {
return typeObj.map((t, i) => toInput(t, obj[i])).reduce(HashInput.append, HashInput.empty);
}
if ('toInput' in typeObj) return typeObj.toInput(obj) as HashInput;
if ('toFields' in typeObj) {
return { fields: typeObj.toFields(obj) };
}
return Object.keys(typeObj)
.map((k) => toInput(typeObj[k], obj[k]))
.reduce(HashInput.append, HashInput.empty);
}
function toJSON(typeObj: any, obj: any): JSONValue {
if (typeObj === BigInt) return obj.toString();
if (typeObj === String || typeObj === Number || typeObj === Boolean) return obj;
if (typeObj === undefined || typeObj === null) return null;
if (!complexTypes.has(typeof typeObj)) throw Error(`provable: unsupported type "${typeObj}"`);
if ('provable' in typeObj) return toJSON(typeObj.provable, obj);
if (Array.isArray(typeObj)) return typeObj.map((t, i) => toJSON(t, obj[i]));
if ('toJSON' in typeObj) return typeObj.toJSON(obj);
if (shouldTerminate?.(typeObj) === true) {
throw Error(`Expected \`toJSON()\` method on ${display(typeObj)}`);
}
return Object.fromEntries(Object.keys(typeObj).map((k) => [k, toJSON(typeObj[k], obj[k])]));
}
function fromJSON(typeObj: any, json: any): any {
if (typeObj === BigInt) return BigInt(json as string);
if (typeObj === String || typeObj === Number || typeObj === Boolean) return json;
if (typeObj === null || typeObj === undefined) return undefined;
if (!complexTypes.has(typeof typeObj)) throw Error(`provable: unsupported type "${typeObj}"`);
if ('provable' in typeObj) return fromJSON(typeObj.provable, json);
if (Array.isArray(typeObj)) return typeObj.map((t, i) => fromJSON(t, json[i]));
if ('fromJSON' in typeObj) return typeObj.fromJSON(json);
if (shouldTerminate?.(typeObj) === true) {
throw Error(`Expected \`fromJSON()\` method on ${display(typeObj)}`);
}
let keys = Object.keys(typeObj);
let values = fromJSON(
keys.map((k) => typeObj[k]),
keys.map((k) => json[k])
);
return Object.fromEntries(keys.map((k, i) => [k, values[i]]));
}
function empty(typeObj: any): any {
if (typeObj === Number) return 0;
if (typeObj === String) return '';
if (typeObj === Boolean) return false;
if (typeObj === BigInt) return 0n;
if (typeObj === null || typeObj === undefined) return typeObj;
if (!complexTypes.has(typeof typeObj)) throw Error(`provable: unsupported type "${typeObj}"`);
if ('provable' in typeObj) return empty(typeObj.provable);
if (Array.isArray(typeObj)) return typeObj.map(empty);
if ('empty' in typeObj) return typeObj.empty();
if (shouldTerminate?.(typeObj) === true) {
throw Error(`Expected \`empty()\` method on ${display(typeObj)}`);
}
return Object.fromEntries(Object.keys(typeObj).map((k) => [k, empty(typeObj[k])]));
}
return {
toInput: (obj: T) => toInput(typeObj, obj),
toJSON: (obj: T) => toJSON(typeObj, obj) as J,
fromJSON: (json: J) => fromJSON(typeObj, json),
empty: () => empty(typeObj) as T,
} satisfies Signable<T, J> as InferredSignable<A, Field>;
}
function display(typeObj: object) {
if ('name' in typeObj) return typeObj.name;
return 'anonymous type object';
}
function createMap<S extends string>(name: S) {
function map(typeObj: any, obj: any): any {
if (primitives.has(typeObj)) return obj;
if (!complexTypes.has(typeof typeObj)) throw Error(`provable: unsupported type "${typeObj}"`);
if (hasProvable(typeObj) && name in typeObj.provable)
return (typeObj.provable as any)[name](obj);
if (Array.isArray(typeObj)) return typeObj.map((t, i) => map(t, obj[i]));
if (name in typeObj) return typeObj[name](obj);
return Object.fromEntries(Object.keys(typeObj).map((k) => [k, map(typeObj[k], obj[k])]));
}
return map;
}
return { provable, signable };
}
function isPrimitive(typeObj: any): typeObj is Primitive {
return primitives.has(typeObj);
}
function createHashInput<Field>() {
type HashInput = GenericHashInput<Field>;
return {
get empty() {
return {};
},
append(input1: HashInput, input2: HashInput): HashInput {
return {
fields: (input1.fields ?? []).concat(input2.fields ?? []),
packed: (input1.packed ?? []).concat(input2.packed ?? []),
};
},
};
}
// some type inference helpers
type JSONValue = number | string | boolean | null | Array<JSONValue> | { [key: string]: JSONValue };
type Struct<T, Field> = GenericProvableExtended<NonMethods<T>, any, any, Field> &
Constructor<T> & { _isStruct: true };
type NonMethodKeys<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
type NonMethods<T> = Pick<T, NonMethodKeys<T>>;
type Constructor<T> = new (...args: any) => T;
type Tuple<T> = [T, ...T[]] | [];
type Primitive = typeof String | typeof Number | typeof Boolean | typeof BigInt | null | undefined;
type InferPrimitive<P extends Primitive> = P extends typeof String
? string
: P extends typeof Number
? number
: P extends typeof Boolean
? boolean
: P extends typeof BigInt
? bigint
: P extends null
? null
: P extends undefined
? undefined
: any;
type InferPrimitiveValue<P extends Primitive> = P extends typeof String
? string
: P extends typeof Number
? number
: P extends typeof Boolean
? boolean
: P extends typeof BigInt
? bigint
: P extends null
? null
: P extends undefined
? undefined
: any;
type InferPrimitiveJson<P extends Primitive> = P extends typeof String
? string
: P extends typeof Number
? number
: P extends typeof Boolean
? boolean
: P extends typeof BigInt
? string
: P extends null
? null
: P extends undefined
? null
: any;
type NestedProvable<Field> =
| Primitive
| { provable: GenericProvable<any, any, Field> }
| GenericProvable<any, any, Field>
| [NestedProvable<Field>, ...NestedProvable<Field>[]]
| NestedProvable<Field>[]
| { [key: string]: NestedProvable<Field> };
type InferProvable<A, Field> = A extends { provable: Constructor<infer U> }
? A extends { provable: GenericProvable<U, any, Field> }
? U
: A extends { provable: Struct<U, Field> }
? U
: InferProvableBase<A, Field>
: A extends Constructor<infer U>
? A extends GenericProvable<U, any, Field>
? U
: A extends Struct<U, Field>
? U
: InferProvableBase<A, Field>
: InferProvableBase<A, Field>;
type InferProvableBase<A, Field> = A extends {
provable: GenericProvable<infer U, any, Field>;
}
? U
: A extends GenericProvable<infer U, any, Field>
? U
: A extends Primitive
? InferPrimitive<A>
: A extends Tuple<any>
? {
[I in keyof A]: InferProvable<A[I], Field>;
}
: A extends (infer U)[]
? InferProvable<U, Field>[]
: A extends Record<any, any>
? {
[K in keyof A]: InferProvable<A[K], Field>;
}
: never;
type InferValue<A> = A extends { provable: GenericProvable<any, infer U, any> }
? U
: A extends GenericProvable<any, infer U, any>
? U
: A extends Primitive
? InferPrimitiveValue<A>
: A extends Tuple<any>
? {
[I in keyof A]: InferValue<A[I]>;
}
: A extends (infer U)[]
? InferValue<U>[]
: A extends Record<any, any>
? {
[K in keyof A]: InferValue<A[K]>;
}
: never;
type WithJson<J> = { toJSON: (x: any) => J };
type InferJson<A> = A extends { provable: WithJson<infer J> }
? J
: A extends WithJson<infer J>
? J
: A extends Primitive
? InferPrimitiveJson<A>
: A extends Tuple<any>
? {
[I in keyof A]: InferJson<A[I]>;
}
: A extends (infer U)[]
? InferJson<U>[]
: A extends Record<any, any>
? {
[K in keyof A]: InferJson<A[K]>;
}
: JSONValue;
type IsPure<A, Field> = IsPureBase<A, Field> extends true ? true : false;
type IsPureBase<A, Field> = A extends {
provable: GenericProvablePure<any, any, Field>;
}
? true
: A extends GenericProvablePure<any, any, Field>
? true
: A extends { provable: GenericProvable<any, any, Field> }
? false
: A extends GenericProvable<any, any, Field>
? false
: A extends Primitive
? false
: A extends (infer U)[]
? IsPure<U, Field>
: A extends Record<any, any>
? {
[K in keyof A]: IsPure<A[K], Field>;
}[keyof A]
: false;
type InferredProvable<A, Field> = IsPure<A, Field> extends true
? GenericProvableExtendedPure<InferProvable<A, Field>, InferValue<A>, InferJson<A>, Field>
: GenericProvableExtended<InferProvable<A, Field>, InferValue<A>, InferJson<A>, Field>;
// signable
type InferSignable<A, Field> = A extends {
provable: GenericSignable<infer U, any, Field>;
}
? U
: A extends GenericSignable<infer U, any, Field>
? U
: A extends Primitive
? InferPrimitive<A>
: A extends Tuple<any>
? {
[I in keyof A]: InferSignable<A[I], Field>;
}
: A extends (infer U)[]
? InferSignable<U, Field>[]
: A extends Record<any, any>
? {
[K in keyof A]: InferSignable<A[K], Field>;
}
: never;
type InferredSignable<A, Field> = GenericSignable<InferSignable<A, Field>, InferJson<A>, Field>;
// deep union type for flexible fromValue
type From<A> = A extends {
provable: {
fromValue: (x: infer U) => any;
} & GenericProvable<any, any, any>;
}
? U | InferProvable<A, any>
: A extends {
fromValue: (x: infer U) => any;
} & GenericProvable<any, any, any>
? U | InferProvable<A, any>
: A extends GenericProvable<any, any, any>
? InferProvable<A, any> | InferValue<A>
: A extends Primitive
? InferPrimitiveValue<A>
: A extends Tuple<any>
? {
[I in keyof A]: From<A[I]>;
}
: A extends (infer U)[]
? From<U>[]
: A extends Record<any, any>
? {
[K in keyof A]: From<A[K]>;
}
: never;
// nested
type InferProvableNested<Field, A extends NestedProvable<Field>> = A extends Primitive
? InferPrimitive<A>
: A extends { provable: GenericProvable<infer P, any, any> }
? P
: A extends GenericProvable<infer P, any, any>
? P
: A extends [NestedProvable<Field>, ...NestedProvable<Field>[]]
? {
[I in keyof A & number]: InferProvableNested<Field, A[I]>;
}
: A extends (infer U extends NestedProvable<Field>)[]
? InferProvableNested<Field, U>[]
: A extends Record<string, NestedProvable<Field>>
? {
[K in keyof A]: InferProvableNested<Field, A[K]>;
}
: never;
type InferValueNested<Field, A extends NestedProvable<Field>> = A extends Primitive
? InferPrimitiveValue<A>
: A extends { provable: GenericProvable<any, infer U, any> }
? U
: A extends GenericProvable<any, infer U, any>
? U
: A extends [NestedProvable<Field>, ...NestedProvable<Field>[]]
? {
[I in keyof A & number]: InferValueNested<Field, A[I]>;
}
: A extends (infer U extends NestedProvable<Field>)[]
? InferValueNested<Field, U>[]
: A extends Record<string, NestedProvable<Field>>
? {
[K in keyof A]: InferValueNested<Field, A[K]>;
}
: never;
type InferJsonNested<Field, A extends NestedProvable<Field>> = A extends Primitive
? InferPrimitiveJson<A>
: A extends { provable: GenericProvable<any, any, Field> }
? A['provable'] extends WithJson<infer J>
? J
: never
: A extends GenericProvable<any, any, Field>
? A extends WithJson<infer J>
? J
: never
: A extends [NestedProvable<Field>, ...NestedProvable<Field>[]]
? {
[I in keyof A & number]: InferJsonNested<Field, A[I]>;
}
: A extends (infer U extends NestedProvable<Field>)[]
? InferJsonNested<Field, U>[]
: A extends Record<string, NestedProvable<Field>>
? {
[K in keyof A]: InferJsonNested<Field, A[K]>;
}
: never;