UNPKG

o1js

Version:

TypeScript framework for zk-SNARKs and zkApps

655 lines (546 loc) 17.4 kB
import * as BindingsLeaves from './leaves.js'; import { FieldsDecoder, ProvableSerializable } from './util.js'; import { versionBytes } from '../../crypto/constants.js'; import { Provable } from '../../../lib/provable/provable.js'; import { HashInput } from '../../../lib/provable/types/provable-derivers.js'; import { toBase58Check } from '../../../lib/util/base58.js'; const JsArray = Array; abstract class ProvableBindingsType<T, Actual> { abstract Type(): ProvableSerializable<Actual>; sizeInFields(): number { return this.Type().sizeInFields(); } toJSON(x: T): any { return this.Type().toJSON(x as never as Actual); } toInput(x: T): HashInput { return this.Type().toInput(x as never as Actual); } toFields(x: T): BindingsLeaves.Field[] { return this.Type().toFields(x as never as Actual); } toAuxiliary(x?: T): any[] { return this.Type().toAuxiliary(x as never as Actual); } fromFields(fields: BindingsLeaves.Field[], aux: any[]): T { return this.Type().fromFields(fields, aux) as never as T; } toValue(x: T): T { return x; } fromValue(x: T): T { return x; } check(x: T) { return this.Type().check(x as never as Actual); } } export type BindingsType<T> = | BindingsType.Leaf<T> | BindingsType.Object<T> | BindingsType.Option<T> | BindingsType.Array<T>; function assertBindingsTypeImplementsProvable< T, B extends BindingsType<T> & ProvableSerializable<T>, >(_x?: B) {} assertBindingsTypeImplementsProvable<number, BindingsType<number>>(); assertBindingsTypeImplementsProvable<string, BindingsType<string>>(); assertBindingsTypeImplementsProvable< BindingsLeaves.AuthRequired, BindingsType<BindingsLeaves.AuthRequired> >(); assertBindingsTypeImplementsProvable<BindingsLeaves.Bool, BindingsType<BindingsLeaves.Bool>>(); assertBindingsTypeImplementsProvable<BindingsLeaves.Field, BindingsType<BindingsLeaves.Field>>(); assertBindingsTypeImplementsProvable< BindingsLeaves.PublicKey, BindingsType<BindingsLeaves.PublicKey> >(); assertBindingsTypeImplementsProvable<BindingsLeaves.Sign, BindingsType<BindingsLeaves.Sign>>(); assertBindingsTypeImplementsProvable< BindingsLeaves.TokenId, BindingsType<BindingsLeaves.TokenId> >(); assertBindingsTypeImplementsProvable< BindingsLeaves.TokenSymbol, BindingsType<BindingsLeaves.TokenSymbol> >(); assertBindingsTypeImplementsProvable<BindingsLeaves.UInt32, BindingsType<BindingsLeaves.UInt32>>(); assertBindingsTypeImplementsProvable<BindingsLeaves.UInt64, BindingsType<BindingsLeaves.UInt64>>(); assertBindingsTypeImplementsProvable< BindingsLeaves.ZkappUri, BindingsType<BindingsLeaves.ZkappUri> >(); assertBindingsTypeImplementsProvable<{ x: number }, BindingsType<{ x: number }>>(); assertBindingsTypeImplementsProvable<number[], BindingsType<number[]>>(); assertBindingsTypeImplementsProvable< BindingsLeaves.Option<number>, BindingsType<BindingsLeaves.Option<number>> >(); assertBindingsTypeImplementsProvable< BindingsLeaves.Option<BindingsLeaves.Range<number>>, BindingsType<BindingsLeaves.Option<BindingsLeaves.Range<number>>> >(); export namespace BindingsType { export class Object<T> implements Provable<T> { readonly _T!: T extends { [key: string]: any } ? void : never; readonly name: string; readonly keys: (keyof T)[]; readonly entries: T extends { [key: string]: any } ? { [key in keyof T]: BindingsType<T[key]> } : never; constructor({ name, keys, entries, }: { name: Object<T>['name']; keys: Object<T>['keys']; entries: Object<T>['entries']; }) { this.name = name; this.keys = keys; this.entries = entries; } sizeInFields(): number { let sum = 0; for (const key of this.keys) { sum += this.entries[key].sizeInFields(); } return sum; } toJSON(x: T): any { // TODO: type safety const x2 = x as { [key in keyof T]: any }; const json: Partial<T> = {}; for (const key of this.keys) { json[key] = this.entries[key].toJSON(x2[key]); } return json; } toInput(x: T): HashInput { // TODO: type safety const x2 = x as { [key in keyof T]: any }; const acc: HashInput = { fields: [], packed: [] }; for (const key of this.keys) { // surely there is an optimization here to avoid allocating so many temporary arrays const { fields, packed } = this.entries[key].toInput(x2[key]); acc.fields!.push(...(fields ?? [])); acc.packed!.push(...(packed ?? [])); } return acc; } toFields(x: T): BindingsLeaves.Field[] { // TODO: type safety const x2 = x as { [key in keyof T]: any }; return this.keys.map((key) => this.entries[key].toFields(x2[key])).flat(); } toAuxiliary(x?: T): any[] { // TODO: type safety const x2 = x as { [key in keyof T]: any } | undefined; const entries2 = this.entries as { [key in keyof T]: BindingsType<any> }; return this.keys.map((key) => entries2[key].toAuxiliary(x2 !== undefined ? x2[key] : undefined) ); } fromFields(fields: BindingsLeaves.Field[], aux: any[]): T { const decoder = new FieldsDecoder(fields); // TODO: make this type-safe // const obj: Partial<T> = {}; const obj: any = {}; for (const i in this.keys) { const key = this.keys[i]; const entryType = this.entries[key]; const entryAux = aux[i]; // console.log(`${this.name}[${JSON.stringify(key)}] :: aux = ${JSON.stringify(entryAux)}`); obj[key] = decoder.decode(entryType.sizeInFields(), (entryFields) => entryType.fromFields(entryFields, entryAux) ); } return obj; } toValue(x: T): T { return x; } fromValue(x: T): T { return x; } check(_x: T) { throw new Error('TODO'); } } export class Array<T> implements Provable<T> { readonly _T!: T extends any[] ? void : never; readonly staticLength: number | null; readonly inner: T extends (infer U)[] ? BindingsType<U> : never; constructor({ staticLength, inner, }: { staticLength: Array<T>['staticLength']; inner: Array<T>['inner']; }) { this.staticLength = staticLength; this.inner = inner; } sizeInFields(): number { if (this.staticLength !== null) { return this.staticLength * this.inner.sizeInFields(); } else { return 0; } } toJSON(x: T extends any[] ? T : never): any { // TODO: type safety const inner: BindingsType<any> = this.inner; return x.map((el) => inner.toJSON(el)); } toInput(x: T): HashInput { if (!(x instanceof JsArray)) throw new Error('impossible'); // TODO: type safety const inner: BindingsType<any> = this.inner; const acc: HashInput = { fields: [], packed: [] }; x.forEach((el) => { const { fields, packed } = inner.toInput(el); acc.fields!.push(...(fields ?? [])); acc.packed!.push(...(packed ?? [])); }); return acc; } toFields(x: T): BindingsLeaves.Field[] { if (!(x instanceof JsArray)) throw new Error('impossible'); // TODO: type safety const inner: BindingsType<any> = this.inner; return x.map((el) => inner.toFields(el)).flat(); } toAuxiliary(x?: T): any[] { if (this.staticLength !== null) { if (x !== undefined) { // TODO: type safety const x2 = x as any[]; if (x2.length !== this.staticLength) throw new Error('invalid array length'); return x2.map((v) => this.inner.toAuxiliary(v)); } else { return new JsArray(this.staticLength).fill(this.inner.toAuxiliary()); } } else { // TODO: type safety return x as any[]; } } fromFields(fields: BindingsLeaves.Field[], aux: any[]): T { if (this.staticLength !== null) { const decoder = new FieldsDecoder(fields); const x = new JsArray(); for (let i = 0; i < this.staticLength; i++) x[i] = decoder.decode(this.inner.sizeInFields(), (f) => this.inner.fromFields(f, aux[i])); // TODO: type safety return x as T; } else { // TODO: type safety return aux as T; } } toValue(x: T): T { return x; } fromValue(x: T): T { return x; } check(_x: T) { throw new Error('TODO'); } } export type Option<T> = Option.OrUndefined<T> | Option.Flagged<T> | Option.ClosedInterval<T>; export namespace Option { export class OrUndefined<T> implements Provable<T> { readonly _T!: T extends infer _U | undefined ? void : never; constructor(public readonly inner: T extends infer U | undefined ? BindingsType<U> : never) {} sizeInFields(): number { return 0; } toJSON(x: T): any { // TODO: type safety const x2 = x as any | undefined; const inner = this.inner as BindingsType<any>; return x2 !== undefined ? inner.toJSON(x2) : null; } toInput(_x: T): any { return {}; } toFields(_x: T): BindingsLeaves.Field[] { return []; } toAuxiliary(x?: T): any[] { return x === undefined ? [false] : [true, this.inner.toAuxiliary(x)]; } fromFields(fields: BindingsLeaves.Field[], aux: any[]): T { // TODO: type safety return (aux[0] ? this.inner.fromFields(fields, aux[1]) : undefined) as T; } toValue(x: T): T { return x; } fromValue(x: T): T { return x; } check(_x: T) { throw new Error('TODO'); } } export class Flagged<T> extends ProvableBindingsType<T, BindingsLeaves.Option<any>> { readonly _T!: T extends BindingsLeaves.Option<any> ? void : never; constructor( public readonly inner: T extends BindingsLeaves.Option<infer U> ? BindingsType<U> : never ) { super(); } Type() { return BindingsLeaves.Option(this.inner as ProvableSerializable<any>); } } export class ClosedInterval<T> extends ProvableBindingsType< T, BindingsLeaves.Option<BindingsLeaves.Range<any>> > { readonly _T!: T extends BindingsLeaves.Option<BindingsLeaves.Range<any>> ? void : never; constructor( public readonly inner: T extends BindingsLeaves.Option<BindingsLeaves.Range<infer U>> ? BindingsType<U> : never ) { super(); } Type() { return BindingsLeaves.Option(BindingsLeaves.Range(this.inner as ProvableSerializable<any>)); } } } export type Leaf<T> = | Leaf.Number<T> | Leaf.String<T> | Leaf.Actions<T> | Leaf.AuthRequired<T> | Leaf.Bool<T> | Leaf.Events<T> | Leaf.Field<T> | Leaf.Int64<T> | Leaf.PublicKey<T> | Leaf.Sign<T> | Leaf.StateHash<T> | Leaf.TokenId<T> | Leaf.TokenSymbol<T> | Leaf.UInt32<T> | Leaf.UInt64<T> | Leaf.ZkappUri<T>; export namespace Leaf { abstract class AuxiliaryLeaf<T> { constructor() {} sizeInFields(): number { return 0; } toJSON(x: T): any { return x; } toInput(_x: T): HashInput { return {}; } toFields(_x: T): BindingsLeaves.Field[] { return []; } toAuxiliary(x?: T): any[] { return [x]; } fromFields(_fields: BindingsLeaves.Field[], aux: any[]): T { return aux[0]; } toValue(x: T): T { return x; } fromValue(x: T): T { return x; } check(_x: T) { throw new Error('TODO'); } } export class Number<T = number> extends AuxiliaryLeaf<T> { readonly _T!: T extends number ? void : never; readonly type: 'number' = 'number'; } export class String<T = string> extends AuxiliaryLeaf<T> { readonly _T!: T extends string ? void : never; readonly type: 'string' = 'string'; } export class Actions<T = BindingsLeaves.Actions> extends ProvableBindingsType< T, BindingsLeaves.Actions > { readonly _T!: T extends number ? void : never; readonly type: 'number' = 'number'; Type() { return BindingsLeaves.Actions; } } export class AuthRequired<T = BindingsLeaves.AuthRequired> extends ProvableBindingsType< T, BindingsLeaves.AuthRequired > { readonly _T!: T extends BindingsLeaves.AuthRequired ? void : never; readonly type: 'AuthRequired' = 'AuthRequired'; Type() { return BindingsLeaves.AuthRequired; } } export class Bool<T = BindingsLeaves.Bool> extends ProvableBindingsType< T, BindingsLeaves.Bool > { readonly _T!: T extends BindingsLeaves.Bool ? void : never; readonly type: 'Bool' = 'Bool'; Type() { return BindingsLeaves.Bool; } } export class Events<T = BindingsLeaves.Events> extends ProvableBindingsType< T, BindingsLeaves.Events > { readonly _T!: T extends number ? void : never; readonly type: 'number' = 'number'; Type() { return BindingsLeaves.Events; } } export class Field<T = BindingsLeaves.Field> extends ProvableBindingsType< T, BindingsLeaves.Field > { readonly _T!: T extends BindingsLeaves.Field ? void : never; readonly type: 'Field' = 'Field'; Type() { return BindingsLeaves.Field; } } export class Int64<T = BindingsLeaves.Int64> extends ProvableBindingsType< T, BindingsLeaves.Int64 > { readonly _T!: T extends BindingsLeaves.Int64 ? void : never; readonly type: 'Int64' = 'Int64'; Type() { return BindingsLeaves.Int64; } } export class PublicKey<T = BindingsLeaves.PublicKey> extends ProvableBindingsType< T, BindingsLeaves.PublicKey > { readonly _T!: T extends BindingsLeaves.PublicKey ? void : never; readonly type: 'PublicKey' = 'PublicKey'; Type() { return BindingsLeaves.PublicKey; } } export class Sign<T = BindingsLeaves.Sign> extends ProvableBindingsType< T, BindingsLeaves.Sign > { readonly _T!: T extends BindingsLeaves.Sign ? void : never; readonly type: 'Sign' = 'Sign'; Type() { return BindingsLeaves.Sign; } } export class StateHash<T = BindingsLeaves.StateHash> extends ProvableBindingsType< T, BindingsLeaves.StateHash > { readonly _T!: T extends BindingsLeaves.StateHash ? void : never; readonly type: 'StateHash' = 'StateHash'; Type() { return BindingsLeaves.StateHash; } } // TODO NOW export class TokenId<T = BindingsLeaves.TokenId> implements Provable<T> { readonly _T!: T extends BindingsLeaves.TokenId ? void : never; readonly type: 'TokenId' = 'TokenId'; constructor() {} sizeInFields(): number { return BindingsLeaves.Field.sizeInFields(); } toJSON(x: T): any { // TODO: type safety return toBase58Check( BindingsLeaves.Field.toBytes(x as BindingsLeaves.Field), versionBytes.tokenIdKey ); } toInput(x: T): HashInput { // TODO: type safety return BindingsLeaves.Field.toInput(x as BindingsLeaves.Field); } toFields(x: T): BindingsLeaves.Field[] { // TODO: type safety return BindingsLeaves.Field.toFields(x as BindingsLeaves.Field); } toAuxiliary(_x?: T): any[] { return []; } fromFields(fields: BindingsLeaves.Field[], _aux: any[]): T { // TODO: type safety return BindingsLeaves.Field.fromFields(fields) as T; } toValue(x: T): T { return x; } fromValue(x: T): T { return x; } check(_x: T) { throw new Error('TODO'); } } export class TokenSymbol<T = BindingsLeaves.TokenSymbol> extends ProvableBindingsType< T, BindingsLeaves.TokenSymbol > { readonly _T!: T extends BindingsLeaves.TokenId ? void : never; readonly type: 'TokenId' = 'TokenId'; Type() { return BindingsLeaves.TokenSymbol; } } export class UInt32<T = BindingsLeaves.UInt32> extends ProvableBindingsType< T, BindingsLeaves.UInt32 > { readonly _T!: T extends BindingsLeaves.UInt32 ? void : never; readonly type: 'UInt32' = 'UInt32'; Type() { return BindingsLeaves.UInt32; } } export class UInt64<T = BindingsLeaves.UInt64> extends ProvableBindingsType< T, BindingsLeaves.UInt64 > { readonly _T!: T extends BindingsLeaves.UInt64 ? void : never; readonly type: 'UInt64' = 'UInt64'; Type() { return BindingsLeaves.UInt64; } } export class ZkappUri<T = BindingsLeaves.ZkappUri> extends ProvableBindingsType< T, BindingsLeaves.ZkappUri > { readonly _T!: T extends BindingsLeaves.ZkappUri ? void : never; readonly type: 'ZkappUri'; Type() { return BindingsLeaves.ZkappUri; } } } }