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 { abstract Type(): ProvableSerializable; 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 = | BindingsType.Leaf | BindingsType.Object | BindingsType.Option | BindingsType.Array; function assertBindingsTypeImplementsProvable< T, B extends BindingsType & ProvableSerializable, >(_x?: B) {} assertBindingsTypeImplementsProvable>(); assertBindingsTypeImplementsProvable>(); assertBindingsTypeImplementsProvable< BindingsLeaves.AuthRequired, BindingsType >(); assertBindingsTypeImplementsProvable>(); assertBindingsTypeImplementsProvable>(); assertBindingsTypeImplementsProvable< BindingsLeaves.PublicKey, BindingsType >(); assertBindingsTypeImplementsProvable>(); assertBindingsTypeImplementsProvable< BindingsLeaves.TokenId, BindingsType >(); assertBindingsTypeImplementsProvable< BindingsLeaves.TokenSymbol, BindingsType >(); assertBindingsTypeImplementsProvable>(); assertBindingsTypeImplementsProvable>(); assertBindingsTypeImplementsProvable< BindingsLeaves.ZkappUri, BindingsType >(); assertBindingsTypeImplementsProvable<{ x: number }, BindingsType<{ x: number }>>(); assertBindingsTypeImplementsProvable>(); assertBindingsTypeImplementsProvable< BindingsLeaves.Option, BindingsType> >(); assertBindingsTypeImplementsProvable< BindingsLeaves.Option>, BindingsType>> >(); export namespace BindingsType { export class Object implements Provable { 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 } : never; constructor({ name, keys, entries, }: { name: Object['name']; keys: Object['keys']; entries: Object['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 = {}; 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 }; 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 = {}; 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 implements Provable { readonly _T!: T extends any[] ? void : never; readonly staticLength: number | null; readonly inner: T extends (infer U)[] ? BindingsType : never; constructor({ staticLength, inner, }: { staticLength: Array['staticLength']; inner: Array['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 = 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 = 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 = 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 = Option.OrUndefined | Option.Flagged | Option.ClosedInterval; export namespace Option { export class OrUndefined implements Provable { readonly _T!: T extends infer _U | undefined ? void : never; constructor(public readonly inner: T extends infer U | undefined ? BindingsType : never) {} sizeInFields(): number { return 0; } toJSON(x: T): any { // TODO: type safety const x2 = x as any | undefined; const inner = this.inner as BindingsType; 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 extends ProvableBindingsType> { readonly _T!: T extends BindingsLeaves.Option ? void : never; constructor( public readonly inner: T extends BindingsLeaves.Option ? BindingsType : never ) { super(); } Type() { return BindingsLeaves.Option(this.inner as ProvableSerializable); } } export class ClosedInterval extends ProvableBindingsType< T, BindingsLeaves.Option> > { readonly _T!: T extends BindingsLeaves.Option> ? void : never; constructor( public readonly inner: T extends BindingsLeaves.Option> ? BindingsType : never ) { super(); } Type() { return BindingsLeaves.Option(BindingsLeaves.Range(this.inner as ProvableSerializable)); } } } export type Leaf = | Leaf.Number | Leaf.String | Leaf.Actions | Leaf.AuthRequired | Leaf.Bool | Leaf.Events | Leaf.Field | Leaf.Int64 | Leaf.PublicKey | Leaf.Sign | Leaf.StateHash | Leaf.TokenId | Leaf.TokenSymbol | Leaf.UInt32 | Leaf.UInt64 | Leaf.ZkappUri; export namespace Leaf { abstract class AuxiliaryLeaf { 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 extends AuxiliaryLeaf { readonly _T!: T extends number ? void : never; readonly type: 'number' = 'number'; } export class String extends AuxiliaryLeaf { readonly _T!: T extends string ? void : never; readonly type: 'string' = 'string'; } export class Actions extends ProvableBindingsType< T, BindingsLeaves.Actions > { readonly _T!: T extends number ? void : never; readonly type: 'number' = 'number'; Type() { return BindingsLeaves.Actions; } } export class 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 extends ProvableBindingsType< T, BindingsLeaves.Bool > { readonly _T!: T extends BindingsLeaves.Bool ? void : never; readonly type: 'Bool' = 'Bool'; Type() { return BindingsLeaves.Bool; } } export class Events extends ProvableBindingsType< T, BindingsLeaves.Events > { readonly _T!: T extends number ? void : never; readonly type: 'number' = 'number'; Type() { return BindingsLeaves.Events; } } export class 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 extends ProvableBindingsType< T, BindingsLeaves.Int64 > { readonly _T!: T extends BindingsLeaves.Int64 ? void : never; readonly type: 'Int64' = 'Int64'; Type() { return BindingsLeaves.Int64; } } export class 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 extends ProvableBindingsType< T, BindingsLeaves.Sign > { readonly _T!: T extends BindingsLeaves.Sign ? void : never; readonly type: 'Sign' = 'Sign'; Type() { return BindingsLeaves.Sign; } } export class 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 implements Provable { 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 extends ProvableBindingsType< T, BindingsLeaves.TokenSymbol > { readonly _T!: T extends BindingsLeaves.TokenId ? void : never; readonly type: 'TokenId' = 'TokenId'; Type() { return BindingsLeaves.TokenSymbol; } } export class 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 extends ProvableBindingsType< T, BindingsLeaves.UInt64 > { readonly _T!: T extends BindingsLeaves.UInt64 ? void : never; readonly type: 'UInt64' = 'UInt64'; Type() { return BindingsLeaves.UInt64; } } export class ZkappUri extends ProvableBindingsType< T, BindingsLeaves.ZkappUri > { readonly _T!: T extends BindingsLeaves.ZkappUri ? void : never; readonly type: 'ZkappUri'; Type() { return BindingsLeaves.ZkappUri; } } } }