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 = ( typeObj: A, /** * @deprecated */ options?: { isPure?: boolean } ) => InferredProvable; type SignableConstructor = (typeObj: A) => InferredSignable; let complexTypes = new Set(['object', 'function']); let primitives = new Set([Number, String, Boolean, BigInt, null, undefined]); function createDerivers(): { provable: ProvableConstructor; signable: SignableConstructor; } { type Signable = GenericSignable; type ProvableExtended = GenericProvableExtended< T, TValue, TJson, Field >; type HashInput = GenericHashInput; const HashInput = createHashInput(); /** * 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 { 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 } { return ( 'provable' in typeObj && (typeof typeObj.provable === 'object' || typeof typeObj.provable === 'function') && typeObj.provable !== null && isProvable(typeObj.provable) ); } function provable(typeObj: A): InferredProvable { type T = InferProvable; type V = InferValue; type J = InferJson; if (!isPrimitive(typeObj) && !complexTypes.has(typeof typeObj)) { throw Error(`provable: unsupported type "${typeObj}"`); } function sizeInFields(typeObj: NestedProvable): 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, 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, 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, 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, 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, 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; const type = typeObj as NestedProvable; 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 as InferredProvable; } function signable( typeObj: A, shouldTerminate?: (typeObj: object) => boolean ): InferredSignable { type T = InferSignable; type J = InferJson; 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 as InferredSignable; } function display(typeObj: object) { if ('name' in typeObj) return typeObj.name; return 'anonymous type object'; } function createMap(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() { type HashInput = GenericHashInput; 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 | { [key: string]: JSONValue }; type Struct = GenericProvableExtended, any, any, Field> & Constructor & { _isStruct: true }; type NonMethodKeys = { [K in keyof T]: T[K] extends Function ? never : K; }[keyof T]; type NonMethods = Pick>; type Constructor = new (...args: any) => T; type Tuple = [T, ...T[]] | []; type Primitive = typeof String | typeof Number | typeof Boolean | typeof BigInt | null | undefined; type InferPrimitive

= 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 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 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 = | Primitive | { provable: GenericProvable } | GenericProvable | [NestedProvable, ...NestedProvable[]] | NestedProvable[] | { [key: string]: NestedProvable }; type InferProvable = A extends { provable: Constructor } ? A extends { provable: GenericProvable } ? U : A extends { provable: Struct } ? U : InferProvableBase : A extends Constructor ? A extends GenericProvable ? U : A extends Struct ? U : InferProvableBase : InferProvableBase; type InferProvableBase = A extends { provable: GenericProvable; } ? U : A extends GenericProvable ? U : A extends Primitive ? InferPrimitive : A extends Tuple ? { [I in keyof A]: InferProvable; } : A extends (infer U)[] ? InferProvable[] : A extends Record ? { [K in keyof A]: InferProvable; } : never; type InferValue = A extends { provable: GenericProvable } ? U : A extends GenericProvable ? U : A extends Primitive ? InferPrimitiveValue : A extends Tuple ? { [I in keyof A]: InferValue; } : A extends (infer U)[] ? InferValue[] : A extends Record ? { [K in keyof A]: InferValue; } : never; type WithJson = { toJSON: (x: any) => J }; type InferJson = A extends { provable: WithJson } ? J : A extends WithJson ? J : A extends Primitive ? InferPrimitiveJson : A extends Tuple ? { [I in keyof A]: InferJson; } : A extends (infer U)[] ? InferJson[] : A extends Record ? { [K in keyof A]: InferJson; } : JSONValue; type IsPure = IsPureBase extends true ? true : false; type IsPureBase = A extends { provable: GenericProvablePure; } ? true : A extends GenericProvablePure ? true : A extends { provable: GenericProvable } ? false : A extends GenericProvable ? false : A extends Primitive ? false : A extends (infer U)[] ? IsPure : A extends Record ? { [K in keyof A]: IsPure; }[keyof A] : false; type InferredProvable = IsPure extends true ? GenericProvableExtendedPure, InferValue, InferJson, Field> : GenericProvableExtended, InferValue, InferJson, Field>; // signable type InferSignable = A extends { provable: GenericSignable; } ? U : A extends GenericSignable ? U : A extends Primitive ? InferPrimitive : A extends Tuple ? { [I in keyof A]: InferSignable; } : A extends (infer U)[] ? InferSignable[] : A extends Record ? { [K in keyof A]: InferSignable; } : never; type InferredSignable = GenericSignable, InferJson, Field>; // deep union type for flexible fromValue type From = A extends { provable: { fromValue: (x: infer U) => any; } & GenericProvable; } ? U | InferProvable : A extends { fromValue: (x: infer U) => any; } & GenericProvable ? U | InferProvable : A extends GenericProvable ? InferProvable | InferValue : A extends Primitive ? InferPrimitiveValue : A extends Tuple ? { [I in keyof A]: From; } : A extends (infer U)[] ? From[] : A extends Record ? { [K in keyof A]: From; } : never; // nested type InferProvableNested> = A extends Primitive ? InferPrimitive : A extends { provable: GenericProvable } ? P : A extends GenericProvable ? P : A extends [NestedProvable, ...NestedProvable[]] ? { [I in keyof A & number]: InferProvableNested; } : A extends (infer U extends NestedProvable)[] ? InferProvableNested[] : A extends Record> ? { [K in keyof A]: InferProvableNested; } : never; type InferValueNested> = A extends Primitive ? InferPrimitiveValue : A extends { provable: GenericProvable } ? U : A extends GenericProvable ? U : A extends [NestedProvable, ...NestedProvable[]] ? { [I in keyof A & number]: InferValueNested; } : A extends (infer U extends NestedProvable)[] ? InferValueNested[] : A extends Record> ? { [K in keyof A]: InferValueNested; } : never; type InferJsonNested> = A extends Primitive ? InferPrimitiveJson : A extends { provable: GenericProvable } ? A['provable'] extends WithJson ? J : never : A extends GenericProvable ? A extends WithJson ? J : never : A extends [NestedProvable, ...NestedProvable[]] ? { [I in keyof A & number]: InferJsonNested; } : A extends (infer U extends NestedProvable)[] ? InferJsonNested[] : A extends Record> ? { [K in keyof A]: InferJsonNested; } : never;