o1js
Version:
TypeScript framework for zk-SNARKs and zkApps
610 lines (532 loc) • 17.4 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,
};
type ProvableConstructor<Field> = <A>(
typeObj: A,
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
);
}
function provable<A>(
typeObj: A,
options?: { isPure?: boolean } // TODO: remove this option, it has no effect
): 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 (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 (Array.isArray(typeObj))
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 (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 (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 (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]));
}
const toValue = createMap('toValue');
const fromValue = createMap('fromValue');
let { empty, fromJSON, toJSON, toInput } = signable(typeObj, isProvable);
type S = InferSignable<A, Field>;
return {
sizeInFields: () => sizeInFields(typeObj as NestedProvable<Field>),
toFields: (obj: T) => toFields(typeObj as NestedProvable<Field>, obj),
toAuxiliary: (obj?: T) =>
toAuxiliary(typeObj as NestedProvable<Field>, obj),
fromFields: (fields: Field[], aux: any[]) =>
fromFields(typeObj as NestedProvable<Field>, fields, aux) as T,
check: (obj: T) => check(typeObj as NestedProvable<Field>, obj),
toValue(x) {
return toValue(typeObj, x);
},
fromValue(v) {
return fromValue(typeObj, v);
},
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, isToplevel = false): HashInput {
if (primitives.has(typeObj)) return {};
if (!complexTypes.has(typeof typeObj))
throw Error(`provable: unsupported type "${typeObj}"`);
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 (isToplevel ? objectKeys : Object.keys(typeObj))
.map((k) => toInput(typeObj[k], obj[k]))
.reduce(HashInput.append, HashInput.empty);
}
function toJSON(typeObj: any, obj: any, isToplevel = false): 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 (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(
(isToplevel ? objectKeys : Object.keys(typeObj)).map((k) => [
k,
toJSON(typeObj[k], obj[k]),
])
);
}
function fromJSON(typeObj: any, json: any, isToplevel = false): 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 (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 = isToplevel ? objectKeys : 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 (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, true),
toJSON: (obj: T) => toJSON(typeObj, obj, true) as J,
fromJSON: (json: J) => fromJSON(typeObj, json, true),
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';
}
return { provable, signable };
}
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 (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;
}
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
: JSONValue;
type NestedProvable<Field> =
| Primitive
| GenericProvable<any, any, Field>
| [NestedProvable<Field>, ...NestedProvable<Field>[]]
| NestedProvable<Field>[]
| { [key: string]: NestedProvable<Field> };
type InferProvable<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 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 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 WithJson<infer J>
? J
: A extends Primitive
? InferPrimitiveJson<A>
: A extends Tuple<any>
? {
[I in keyof A]: InferJson<A[I]>;
}
: A extends WithJson<infer U>[]
? 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 GenericProvablePure<any, any, Field>
? true
: 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 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 {
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;