UNPKG

o1js

Version:

TypeScript framework for zk-SNARKs and zkApps

236 lines (210 loc) 7.92 kB
import 'reflect-metadata'; import { Field } from '../wrapped.js'; import { HashInput, NonMethods } from './provable-derivers.js'; import { Provable } from '../provable.js'; import { AnyConstructor, FlexibleProvable } from './struct.js'; export { CircuitValue, prop, arrayProp }; /** * @deprecated `CircuitValue` is deprecated in favor of {@link Struct}, which features a simpler API and better typing. */ abstract class CircuitValue { constructor(...props: any[]) { // if this is called with no arguments, do nothing, to support simple super() calls if (props.length === 0) return; let fields = this.constructor.prototype._fields; if (fields === undefined) return; if (props.length !== fields.length) { throw Error( `${this.constructor.name} constructor called with ${props.length} arguments, but expected ${fields.length}` ); } for (let i = 0; i < fields.length; ++i) { let [key] = fields[i]; (this as any)[key] = props[i]; } } static fromObject<T extends AnyConstructor>( this: T, value: NonMethods<InstanceType<T>> ): InstanceType<T> { return Object.assign(Object.create(this.prototype), value); } static sizeInFields(): number { const fields: [string, any][] = (this as any).prototype._fields; return fields.reduce((acc, [_, typ]) => acc + typ.sizeInFields(), 0); } static toFields<T extends AnyConstructor>(this: T, v: InstanceType<T>): Field[] { const res: Field[] = []; const fields = this.prototype._fields; if (fields === undefined || fields === null) { return res; } for (let i = 0, n = fields.length; i < n; ++i) { const [key, propType] = fields[i]; const subElts: Field[] = propType.toFields((v as any)[key]); subElts.forEach((x) => res.push(x)); } return res; } static toAuxiliary(): [] { return []; } static toInput<T extends AnyConstructor>(this: T, v: InstanceType<T>): HashInput { let input: HashInput = { fields: [], packed: [] }; let fields = this.prototype._fields; if (fields === undefined) return input; for (let i = 0, n = fields.length; i < n; ++i) { let [key, type] = fields[i]; if ('toInput' in type) { input = HashInput.append(input, type.toInput(v[key])); continue; } // as a fallback, use toFields on the type // TODO: this is problematic -- ignores if there's a toInput on a nested type // so, remove this? should every provable define toInput? let xs: Field[] = type.toFields(v[key]); input.fields!.push(...xs); } return input; } toFields(): Field[] { return (this.constructor as any).toFields(this); } static toValue<T extends AnyConstructor>(this: T, v: InstanceType<T>) { const res: any = {}; let fields: [string, any][] = (this as any).prototype._fields ?? []; fields.forEach(([key, propType]) => { res[key] = propType.toValue((v as any)[key]); }); return res; } static fromValue<T extends AnyConstructor>(this: T, value: any): InstanceType<T> { let props: any = {}; let fields: [string, any][] = (this as any).prototype._fields ?? []; if (typeof value !== 'object' || value === null || Array.isArray(value)) { throw Error(`${this.name}.fromValue(): invalid input ${value}`); } for (let i = 0; i < fields.length; ++i) { let [key, propType] = fields[i]; if (value[key] === undefined) { throw Error(`${this.name}.fromValue(): invalid input ${value}`); } else { props[key] = propType.fromValue(value[key]); } } return Object.assign(Object.create(this.prototype), props); } toJSON(): any { return (this.constructor as any).toJSON(this); } toConstant(): this { return (this.constructor as any).toConstant(this); } equals(x: this) { return Provable.equal(this.constructor as any, this, x); } assertEquals(x: this) { Provable.assertEqual(this, x); } isConstant() { return this.toFields().every((x) => x.isConstant()); } static fromFields<T extends AnyConstructor>(this: T, xs: Field[]): InstanceType<T> { const fields: [string, any][] = (this as any).prototype._fields; if (xs.length < fields.length) { throw Error( `${this.name}.fromFields: Expected ${fields.length} field elements, got ${xs?.length}` ); } let offset = 0; const props: any = {}; for (let i = 0; i < fields.length; ++i) { const [key, propType] = fields[i]; const propSize = propType.sizeInFields(); const propVal = propType.fromFields(xs.slice(offset, offset + propSize), []); props[key] = propVal; offset += propSize; } return Object.assign(Object.create(this.prototype), props); } static check<T extends AnyConstructor>(this: T, v: InstanceType<T>) { const fields = (this as any).prototype._fields; if (fields === undefined || fields === null) { return; } for (let i = 0; i < fields.length; ++i) { const [key, propType] = fields[i]; const value = (v as any)[key]; if (propType.check === undefined) throw Error('bug: CircuitValue without .check()'); propType.check(value); } } static toCanonical<T extends AnyConstructor>(this: T, value: InstanceType<T>): InstanceType<T> { let canonical: any = {}; let fields: [string, any][] = (this as any).prototype._fields ?? []; fields.forEach(([key, type]) => { canonical[key] = Provable.toCanonical(type, value[key]); }); return canonical; } static toConstant<T extends AnyConstructor>(this: T, t: InstanceType<T>): InstanceType<T> { const xs: Field[] = (this as any).toFields(t); return (this as any).fromFields(xs.map((x) => x.toConstant())); } static toJSON<T extends AnyConstructor>(this: T, v: InstanceType<T>) { const res: any = {}; if ((this as any).prototype._fields !== undefined) { const fields: [string, any][] = (this as any).prototype._fields; fields.forEach(([key, propType]) => { res[key] = propType.toJSON((v as any)[key]); }); } return res; } static fromJSON<T extends AnyConstructor>(this: T, value: any): InstanceType<T> { let props: any = {}; let fields: [string, any][] = (this as any).prototype._fields; if (typeof value !== 'object' || value === null || Array.isArray(value)) { throw Error(`${this.name}.fromJSON(): invalid input ${value}`); } if (fields !== undefined) { for (let i = 0; i < fields.length; ++i) { let [key, propType] = fields[i]; if (value[key] === undefined) { throw Error(`${this.name}.fromJSON(): invalid input ${value}`); } else { props[key] = propType.fromJSON(value[key]); } } } return Object.assign(Object.create(this.prototype), props); } static empty<T extends AnyConstructor>(): InstanceType<T> { const fields: [string, any][] = (this as any).prototype._fields ?? []; let props: any = {}; fields.forEach(([key, propType]) => { props[key] = propType.empty(); }); return Object.assign(Object.create(this.prototype), props); } } function prop(this: any, target: any, key: string) { const fieldType = Reflect.getMetadata('design:type', target, key); if (!target.hasOwnProperty('_fields')) { target._fields = []; } if (fieldType === undefined) { } else if (fieldType.toFields && fieldType.fromFields) { target._fields.push([key, fieldType]); } else { console.log(`warning: property ${key} missing field element conversion methods`); } } function arrayProp<T>(elementType: FlexibleProvable<T>, length: number) { return function (target: any, key: string) { if (!target.hasOwnProperty('_fields')) { target._fields = []; } target._fields.push([key, Provable.Array(elementType, length)]); }; }