From 6af693f28a1112c44a78127eb1d5915d847dc7cc Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 17:36:47 +0100 Subject: [PATCH 01/41] add toValue impls in most places --- src/lib/bool.ts | 16 +++++++++ src/lib/circuit_value.ts | 70 ++++++++++++++++++++++++++++++++++------ src/lib/field.ts | 14 ++++++++ src/lib/provable.ts | 39 ++++++++++++++-------- src/snarky.d.ts | 42 +++++++----------------- 5 files changed, 127 insertions(+), 54 deletions(-) diff --git a/src/lib/bool.ts b/src/lib/bool.ts index 9d7fee9e4d..5f623d9b76 100644 --- a/src/lib/bool.ts +++ b/src/lib/bool.ts @@ -272,6 +272,22 @@ class Bool { return new Bool(fields[0].value); } + /** + * `Provable.toValue()` + */ + static toValue(x: Bool): 0n | 1n { + // TODO make `boolean` the value type + return x.toBoolean() ? 1n : 0n; + } + + /** + * `Provable.fromValue()` + */ + static fromValue(x: 0n | 1n) { + // TODO make `boolean` the value type + return new Bool(x === 1n); + } + /** * Serialize a {@link Bool} to a JSON string. * This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the Field. diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index d0a85e5cc9..484c5f4fc7 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -15,6 +15,7 @@ import type { IsPure, } from '../bindings/lib/provable-snarky.js'; import { Provable } from './provable.js'; +import { InferValue } from 'src/bindings/lib/provable-generic.js'; // external API export { @@ -52,18 +53,20 @@ type ProvableExtension = { empty: () => T; }; -type ProvableExtended = Provable & +type ProvableExtended = Provable & ProvableExtension; -type ProvablePureExtended = ProvablePure & +type ProvablePureExtended = ProvablePure< + T, + TValue +> & ProvableExtension; type Struct = ProvableExtended> & Constructor & { _isStruct: true }; -type StructPure = ProvablePure> & - ProvableExtension> & +type StructPure = ProvablePureExtended> & Constructor & { _isStruct: true }; -type FlexibleProvable = Provable | Struct; -type FlexibleProvablePure = ProvablePure | StructPure; +type FlexibleProvable = Provable | Struct; +type FlexibleProvablePure = ProvablePure | StructPure; type Constructor = new (...args: any) => T; type AnyConstructor = Constructor; @@ -215,6 +218,35 @@ abstract class CircuitValue { return (this as any).fromFields(xs.map((x) => x.toConstant())); } + static toValue(this: T, v: InstanceType) { + 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( + this: T, + value: any + ): InstanceType { + 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); + } + static toJSON(this: T, v: InstanceType) { const res: any = {}; if ((this as any).prototype._fields !== undefined) { @@ -371,13 +403,14 @@ function matrixProp( function Struct< A, T extends InferProvable = InferProvable, + V extends InferValue = InferValue, J extends InferJson = InferJson, Pure extends boolean = IsPure >( type: A ): (new (value: T) => T) & { _isStruct: true } & (Pure extends true - ? ProvablePure - : Provable) & { + ? ProvablePure + : Provable) & { toInput: (x: T) => { fields?: Field[] | undefined; packed?: [Field, number][] | undefined; @@ -458,6 +491,23 @@ function Struct< static check(value: T) { return this.type.check(value); } + + /** + * `Provable.toValue()` + */ + static toValue(x: T): V { + return this.type.toValue(x) as V; + } + + /** + * `Provable.fromValue()` + */ + static fromValue(v: V): T { + let value = this.type.fromValue(v); + let struct = Object.create(this.prototype); + return Object.assign(struct, value); + } + /** * This method is for internal use, you will probably not need it. * Recover a struct from its raw field elements and auxiliary data. @@ -592,7 +642,7 @@ function circuitValueEquals(a: T, b: T): boolean { } function toConstant(type: FlexibleProvable, value: T): T; -function toConstant(type: Provable, value: T): T { +function toConstant(type: Provable, value: T): T { return type.fromFields( type.toFields(value).map((x) => x.toConstant()), type.toAuxiliary(value) @@ -600,6 +650,6 @@ function toConstant(type: Provable, value: T): T { } function isConstant(type: FlexibleProvable, value: T): boolean; -function isConstant(type: Provable, value: T): boolean { +function isConstant(type: Provable, value: T): boolean { return type.toFields(value).every((x) => x.isConstant()); } diff --git a/src/lib/field.ts b/src/lib/field.ts index 6df164143b..5248024df4 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -1126,6 +1126,20 @@ class Field { */ static check() {} + /** + * `Provable.toValue()` + */ + static toValue(x: Field) { + return x.toBigInt(); + } + + /** + * `Provable.fromValue()` + */ + static fromValue(x: bigint) { + return new Field(x); + } + /** * This function is the implementation of {@link Provable.toFields} for the {@link Field} type. * diff --git a/src/lib/provable.ts b/src/lib/provable.ts index 74992a9568..6a7c59ed67 100644 --- a/src/lib/provable.ts +++ b/src/lib/provable.ts @@ -24,6 +24,7 @@ import { constraintSystem, } from './provable-context.js'; import { isBool } from './bool.js'; +import { InferValue } from 'src/bindings/lib/provable-generic.js'; // external API export { Provable }; @@ -42,7 +43,7 @@ export { * * You will find this as the required input type in a few places in o1js. One convenient way to create a `Provable` is using `Struct`. */ -type Provable = Provable_; +type Provable = Provable_; const Provable = { /** @@ -227,7 +228,7 @@ function witness = FlexibleProvable>( // rebuild the value from its fields (which are now variables) and aux data let aux = type.toAuxiliary(proverValue); - let value = (type as Provable).fromFields(fields, aux); + let value = (type as Provable).fromFields(fields, aux); // add type-specific constraints type.check(value); @@ -256,7 +257,7 @@ function assertEqualImplicit(x: T, y: T) { xs[i].assertEquals(ys[i]); } } -function assertEqualExplicit(type: Provable, x: T, y: T) { +function assertEqualExplicit(type: Provable, x: T, y: T) { let xs = type.toFields(x); let ys = type.toFields(y); for (let i = 0; i < xs.length; i++) { @@ -282,7 +283,7 @@ function equalImplicit(x: T, y: T) { checkLength('Provable.equal', xs, ys); return xs.map((x, i) => x.equals(ys[i])).reduce(Bool.and); } -function equalExplicit(type: Provable, x: T, y: T) { +function equalExplicit(type: Provable, x: T, y: T) { let xs = type.toFields(x); let ys = type.toFields(y); return xs.map((x, i) => x.equals(ys[i])).reduce(Bool.and); @@ -307,7 +308,7 @@ function ifField(b: Field, x: Field, y: Field) { return b.mul(x.sub(y)).add(y).seal(); } -function ifExplicit(condition: Bool, type: Provable, x: T, y: T): T { +function ifExplicit(condition: Bool, type: Provable, x: T, y: T): T { let xs = type.toFields(x); let ys = type.toFields(y); let b = condition.toField(); @@ -350,7 +351,7 @@ function ifImplicit(condition: Bool, x: T, y: T): T { `Provable.if(bool, MyType, x, y)` ); } - return ifExplicit(condition, type as any as Provable, x, y); + return ifExplicit(condition, type as any as Provable, x, y); } function switch_>( @@ -384,12 +385,12 @@ function switch_>( fields[j] = fields[j].add(maybeField); } } - let aux = auxiliary(type as Provable, () => { + let aux = auxiliary(type as Provable, () => { let i = mask.findIndex((b) => b.toBoolean()); if (i === -1) return undefined; return values[i]; }); - return (type as Provable).fromFields(fields, aux); + return (type as Provable).fromFields(fields, aux); } // logging in provable code @@ -427,10 +428,10 @@ function checkLength(name: string, xs: Field[], ys: Field[]) { function clone>(type: S, value: T): T { let fields = type.toFields(value); let aux = type.toAuxiliary?.(value) ?? []; - return (type as Provable).fromFields(fields, aux); + return (type as Provable).fromFields(fields, aux); } -function auxiliary(type: Provable, compute: () => T | undefined) { +function auxiliary(type: Provable, compute: () => T | undefined) { let aux; // TODO: this accepts types without .toAuxiliary(), should be changed when all snarky types are moved to TS Provable.asProver(() => { @@ -454,7 +455,7 @@ let memoizationContext = Context.create(); * for reuse by the prover. This is needed to witness non-deterministic values. */ function memoizeWitness(type: FlexibleProvable, compute: () => T) { - return Provable.witness(type as Provable, () => { + return Provable.witness(type as Provable, () => { if (!memoizationContext.has()) return compute(); let context = memoizationContext.get(); let { memoized, currentIndex } = context; @@ -467,7 +468,7 @@ function memoizeWitness(type: FlexibleProvable, compute: () => T) { memoized[currentIndex] = currentValue; } context.currentIndex += 1; - return (type as Provable).fromFields( + return (type as Provable).fromFields( currentValue.fields, currentValue.aux ); @@ -489,8 +490,9 @@ function provableArray>( length: number ): InferredProvable { type T = InferProvable; + type TValue = InferValue; type TJson = InferJson; - let type = elementType as ProvableExtended; + let type = elementType as ProvableExtended; return { /** * Returns the size of this structure in {@link Field} elements. @@ -536,6 +538,15 @@ function provableArray>( (type as any).check(array[i]); } }, + + toValue(x) { + return x.map((v) => type.toValue(v)); + }, + + fromValue(x) { + return x.map((v) => type.fromValue(v)); + }, + /** * Encodes this structure into a JSON-like object. */ @@ -573,5 +584,5 @@ function provableArray>( } return Array.from({ length }, () => type.empty()); }, - } satisfies ProvableExtended as any; + } satisfies ProvableExtended as any; } diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 69fe3bcb37..88149686f4 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -47,7 +47,7 @@ export { * * The properties and methods on the provable type exist in all base o1js types as well (aka. {@link Field}, {@link Bool}, etc.). In most cases, a zkApp developer does not need these functions to create zkApps. */ -declare interface Provable { +declare interface Provable { /** * A function that takes `value`, an element of type `T`, as argument and returns an array of {@link Field} elements that make up the provable data of `value`. * @@ -96,6 +96,16 @@ declare interface Provable { * @param value - the element of type `T` to put assertions on. */ check: (value: T) => void; + + /** + * Convert provable type to a normal JS type. + */ + toValue: (x: T) => TValue; + + /** + * Convert provable type from a normal JS type. + */ + fromValue: (x: TValue) => T; } /** @@ -106,16 +116,7 @@ declare interface Provable { * * It includes the same properties and methods as the {@link Provable} interface. */ -declare interface ProvablePure extends Provable { - /** - * A function that takes `value`, an element of type `T`, as argument and returns an array of {@link Field} elements that make up the provable data of `value`. - * - * @param value - the element of type `T` to generate the {@link Field} array from. - * - * @return A {@link Field} array describing how this `T` element is made up of {@link Field} elements. - */ - toFields: (value: T) => Field[]; - +declare interface ProvablePure extends Provable { /** * A function that takes `value` (optional), an element of type `T`, as argument and returns an array of any type that make up the "auxiliary" (non-provable) data of `value`. * As any element of the interface `ProvablePure` includes no "auxiliary" data by definition, this function always returns a default value. @@ -136,25 +137,6 @@ declare interface ProvablePure extends Provable { * @return An element of type `T` generated from the given provable data. */ fromFields: (fields: Field[]) => T; - - /** - * Return the size of the `T` type in terms of {@link Field} type, as {@link Field} is the primitive type. - * - * **Warning**: This function returns a `number`, so you cannot use it to prove something on chain. You can use it during debugging or to understand the memory complexity of some type. - * - * @return A `number` representing the size of the `T` type in terms of {@link Field} type. - */ - sizeInFields(): number; - - /** - * Add assertions to the proof to check if `value` is a valid member of type `T`. - * This function does not return anything, rather creates any number of assertions on the proof to prove `value` is a valid member of the type `T`. - * - * For instance, calling check function on the type {@link Bool} asserts that the value of the element is either 1 or 0. - * - * @param value - the element of type `T` to put assertions on. - */ - check: (value: T) => void; } type MlGroup = MlPair; From cc018b7533d6292fcc91abafd3b87552fe12762b Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 17:56:01 +0100 Subject: [PATCH 02/41] fixup events, scalar, group --- src/lib/events.ts | 38 ++++++++++++++++++++++++++++++++++---- src/lib/group.ts | 8 ++++++++ src/lib/scalar.ts | 8 ++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/lib/events.ts b/src/lib/events.ts index 70fce76400..779c6ea657 100644 --- a/src/lib/events.ts +++ b/src/lib/events.ts @@ -61,6 +61,16 @@ function createEvents({ ...Events, ...dataAsHash({ empty: Events.empty, + toValue(data: Field[][]) { + return data.map((row) => row.map((e) => BigInt(Field.toJSON(e)))); + }, + fromValue(value: bigint[][]) { + let data = value.map((row) => + row.map((e) => Field.fromJSON(e.toString())) + ); + let hash = Events.hash(data); + return { data, hash }; + }, toJSON(data: Field[][]) { return data.map((row) => row.map((e) => Field.toJSON(e))); }, @@ -104,10 +114,20 @@ function createEvents({ }, }; - const SequenceEventsProvable = { + const ActionsProvable = { ...Actions, ...dataAsHash({ empty: Actions.empty, + toValue(data: Field[][]) { + return data.map((row) => row.map((e) => BigInt(Field.toJSON(e)))); + }, + fromValue(value: bigint[][]) { + let data = value.map((row) => + row.map((e) => Field.fromJSON(e.toString())) + ); + let hash = Actions.hash(data); + return { data, hash }; + }, toJSON(data: Field[][]) { return data.map((row) => row.map((e) => Field.toJSON(e))); }, @@ -119,18 +139,22 @@ function createEvents({ }), }; - return { Events: EventsProvable, Actions: SequenceEventsProvable }; + return { Events: EventsProvable, Actions: ActionsProvable }; } -function dataAsHash({ +function dataAsHash({ empty, + toValue, + fromValue, toJSON, fromJSON, }: { empty: () => { data: T; hash: Field }; + toValue: (value: T) => V; + fromValue: (value: V) => { data: T; hash: Field }; toJSON: (value: T) => J; fromJSON: (json: J) => { data: T; hash: Field }; -}): GenericProvableExtended<{ data: T; hash: Field }, J, Field> { +}): GenericProvableExtended<{ data: T; hash: Field }, V, J, Field> { return { empty, sizeInFields() { @@ -145,6 +169,12 @@ function dataAsHash({ fromFields([hash], [data]) { return { data, hash }; }, + toValue({ data }) { + return toValue(data); + }, + fromValue(value) { + return fromValue(value); + }, toJSON({ data }) { return toJSON(data); }, diff --git a/src/lib/group.ts b/src/lib/group.ts index 89cf5bf249..2710b70456 100644 --- a/src/lib/group.ts +++ b/src/lib/group.ts @@ -409,6 +409,14 @@ class Group { return 2; } + static toValue({ x, y }: Group) { + return { x: x.toBigInt(), y: y.toBigInt() }; + } + + static fromValue(g: { x: bigint; y: bigint }) { + return new Group(g); + } + /** * Serializes a {@link Group} element to a JSON object. * diff --git a/src/lib/scalar.ts b/src/lib/scalar.ts index 176478947f..24a608672c 100644 --- a/src/lib/scalar.ts +++ b/src/lib/scalar.ts @@ -285,6 +285,14 @@ class Scalar { */ } + static toValue(x: Scalar) { + return x.toBigInt(); + } + + static fromValue(x: bigint) { + return Scalar.from(x); + } + // ProvableExtended /** From 8acbdf0706715498d3f5e2375a6cd6687719a576 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 18:04:24 +0100 Subject: [PATCH 03/41] get compilation working --- src/lib/account_update.ts | 5 +++++ src/lib/circuit.ts | 16 ++++++++++--- src/lib/hash.ts | 8 +++++++ src/lib/precondition.ts | 6 ++--- src/lib/proof_system.ts | 47 +++++++++++++++++++++------------------ src/lib/state.ts | 6 ++--- src/lib/zkapp.ts | 20 +++++++++-------- 7 files changed, 68 insertions(+), 40 deletions(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index c1938bd5e8..055b7e4721 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1285,6 +1285,11 @@ class AccountUpdate implements Types.AccountUpdate { other ); } + static toValue = Types.AccountUpdate.toValue; + static fromValue(value: TypesBigint.AccountUpdate): AccountUpdate { + let accountUpdate = Types.AccountUpdate.fromValue(value); + return new AccountUpdate(accountUpdate.body, accountUpdate.authorization); + } static witness( type: FlexibleProvable, diff --git a/src/lib/circuit.ts b/src/lib/circuit.ts index 232f3f4f0d..420f61a0b5 100644 --- a/src/lib/circuit.ts +++ b/src/lib/circuit.ts @@ -204,8 +204,8 @@ function public_(target: any, _key: string | symbol, index: number) { type CircuitData = { main(publicInput: P, privateInput: W): void; - publicInputType: ProvablePure

; - privateInputType: ProvablePure; + publicInputType: ProvablePure; + privateInputType: ProvablePure; }; function mainFromCircuitData( @@ -266,7 +266,9 @@ function circuitMain( } // TODO support auxiliary data -function provableFromTuple(typs: ProvablePure[]): ProvablePure { +function provableFromTuple( + typs: ProvablePure[] +): ProvablePure { return { sizeInFields: () => { return typs.reduce((acc, typ) => acc + typ.sizeInFields(), 0); @@ -301,5 +303,13 @@ function provableFromTuple(typs: ProvablePure[]): ProvablePure { check(xs: Array) { typs.forEach((typ, i) => (typ as any).check(xs[i])); }, + + toValue(x) { + return typs.map((typ, i) => typ.toValue(x[i])); + }, + + fromValue(x) { + return typs.map((typ, i) => typ.fromValue(x[i])); + }, }; } diff --git a/src/lib/hash.ts b/src/lib/hash.ts index c6d66d3f9d..95a218fe22 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -151,6 +151,7 @@ function packToFields({ fields = [], packed = [] }: HashInput) { const TokenSymbolPure: ProvableExtended< { symbol: string; field: Field }, + string, string > = { toFields({ field }) { @@ -169,6 +170,13 @@ const TokenSymbolPure: ProvableExtended< let actual = field.rangeCheckHelper(48); actual.assertEquals(field); }, + toValue({ symbol }) { + return symbol; + }, + fromValue(symbol: string) { + let field = prefixToField(symbol); + return { symbol, field }; + }, toJSON({ symbol }) { return symbol; }, diff --git a/src/lib/precondition.ts b/src/lib/precondition.ts index 25b10b9031..d7733568bf 100644 --- a/src/lib/precondition.ts +++ b/src/lib/precondition.ts @@ -206,7 +206,7 @@ function preconditionSubClassWithRange< >( accountUpdate: AccountUpdate, longKey: K, - fieldType: Provable, + fieldType: Provable, context: PreconditionContext ) { return { @@ -233,7 +233,7 @@ function preconditionSubclass< >( accountUpdate: AccountUpdate, longKey: K, - fieldType: Provable, + fieldType: Provable, context: PreconditionContext ) { if (fieldType === undefined) { @@ -295,7 +295,7 @@ function preconditionSubclass< function getVariable( accountUpdate: AccountUpdate, longKey: K, - fieldType: Provable + fieldType: Provable ): U { return Provable.witness(fieldType, () => { let [accountOrNetwork, ...rest] = longKey.split('.'); diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 312521374b..0d9a9e8738 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -70,12 +70,12 @@ export { }; type Undefined = undefined; -const Undefined: ProvablePureExtended = +const Undefined: ProvablePureExtended = EmptyUndefined(); type Empty = Undefined; const Empty = Undefined; type Void = undefined; -const Void: ProvablePureExtended = EmptyVoid(); +const Void: ProvablePureExtended = EmptyVoid(); class Proof { static publicInputType: FlexibleProvablePure = undefined as any; @@ -298,8 +298,8 @@ function ZkProgram< >; } { let methods = config.methods; - let publicInputType: ProvablePure = config.publicInput ?? Undefined; - let publicOutputType: ProvablePure = config.publicOutput ?? Void; + let publicInputType: ProvablePure = config.publicInput ?? Undefined; + let publicOutputType: ProvablePure = config.publicOutput ?? Void; let selfTag = { name: config.name }; type PublicInput = InferProvableOrUndefined< @@ -491,7 +491,7 @@ function sortMethodArguments( privateInputs: unknown[], selfProof: Subclass ): MethodInterface { - let witnessArgs: Provable[] = []; + let witnessArgs: Provable[] = []; let proofArgs: Subclass[] = []; let allArgs: { type: 'proof' | 'witness' | 'generic'; index: number }[] = []; let genericArgs: Subclass[] = []; @@ -541,7 +541,7 @@ function sortMethodArguments( function isAsFields( type: unknown -): type is Provable & ObjectConstructor { +): type is Provable & ObjectConstructor { return ( (typeof type === 'function' || typeof type === 'object') && type !== null && @@ -592,11 +592,11 @@ type MethodInterface = { methodName: string; // TODO: unify types of arguments // "circuit types" should be flexible enough to encompass proofs and callback arguments - witnessArgs: Provable[]; + witnessArgs: Provable[]; proofArgs: Subclass[]; genericArgs: Subclass[]; allArgs: { type: 'witness' | 'proof' | 'generic'; index: number }[]; - returnType?: Provable; + returnType?: Provable; }; // reasonable default choice for `overrideWrapDomain` @@ -613,8 +613,8 @@ async function compileProgram({ forceRecompile, overrideWrapDomain, }: { - publicInputType: ProvablePure; - publicOutputType: ProvablePure; + publicInputType: ProvablePure; + publicOutputType: ProvablePure; methodIntfs: MethodInterface[]; methods: ((...args: any) => void)[]; gates: Gate[][]; @@ -713,7 +713,7 @@ async function compileProgram({ } function analyzeMethod( - publicInputType: ProvablePure, + publicInputType: ProvablePure, methodIntf: MethodInterface, method: (...args: any) => T ) { @@ -727,8 +727,8 @@ function analyzeMethod( } function picklesRuleFromFunction( - publicInputType: ProvablePure, - publicOutputType: ProvablePure, + publicInputType: ProvablePure, + publicOutputType: ProvablePure, func: (...args: unknown[]) => any, proofSystemTag: { name: string }, { methodName, witnessArgs, proofArgs, allArgs }: MethodInterface, @@ -867,7 +867,7 @@ function methodArgumentsToConstant( let Generic = EmptyNull(); -type TypeAndValue = { type: Provable; value: T }; +type TypeAndValue = { type: Provable; value: T }; function methodArgumentTypesAndValues( { allArgs, proofArgs, witnessArgs }: MethodInterface, @@ -895,7 +895,7 @@ function methodArgumentTypesAndValues( } function emptyValue(type: FlexibleProvable): T; -function emptyValue(type: Provable) { +function emptyValue(type: Provable) { return type.fromFields( Array(type.sizeInFields()).fill(Field(0)), type.toAuxiliary() @@ -903,7 +903,7 @@ function emptyValue(type: Provable) { } function emptyWitness(type: FlexibleProvable): T; -function emptyWitness(type: Provable) { +function emptyWitness(type: Provable) { return Provable.witness(type, () => emptyValue(type)); } @@ -911,7 +911,7 @@ function getStatementType< T, O, P extends Subclass = typeof Proof ->(Proof: P): { input: ProvablePure; output: ProvablePure } { +>(Proof: P): { input: ProvablePure; output: ProvablePure } { if ( Proof.publicInputType === undefined || Proof.publicOutputType === undefined @@ -934,17 +934,20 @@ function getMaxProofsVerified(methodIntfs: MethodInterface[]) { ) as any as 0 | 1 | 2; } -function fromFieldVars(type: ProvablePure, fields: MlFieldArray) { +function fromFieldVars(type: ProvablePure, fields: MlFieldArray) { return type.fromFields(MlFieldArray.from(fields)); } -function toFieldVars(type: ProvablePure, value: T) { +function toFieldVars(type: ProvablePure, value: T) { return MlFieldArray.to(type.toFields(value)); } -function fromFieldConsts(type: ProvablePure, fields: MlFieldConstArray) { +function fromFieldConsts( + type: ProvablePure, + fields: MlFieldConstArray +) { return type.fromFields(MlFieldConstArray.from(fields)); } -function toFieldConsts(type: ProvablePure, value: T) { +function toFieldConsts(type: ProvablePure, value: T) { return MlFieldConstArray.to(type.toFields(value)); } @@ -1056,7 +1059,7 @@ type Subclass any> = (new ( [K in keyof Class]: Class[K]; } & { prototype: InstanceType }; -type PrivateInput = Provable | Subclass; +type PrivateInput = Provable | Subclass; type Method< PublicInput, diff --git a/src/lib/state.ts b/src/lib/state.ts index dc848a1996..0dbc93fa07 100644 --- a/src/lib/state.ts +++ b/src/lib/state.ts @@ -125,7 +125,7 @@ function state(stateType: FlexibleProvablePure) { if (this._?.[key]) throw Error('A @state should only be assigned once'); v._contract = { key, - stateType: stateType as ProvablePure, + stateType: stateType as ProvablePure, instance: this, class: ZkappClass, wasConstrained: false, @@ -185,7 +185,7 @@ function declareState( // metadata defined by @state, which link state to a particular SmartContract type StateAttachedContract = { key: string; - stateType: ProvablePure; + stateType: ProvablePure; instance: SmartContract; class: typeof SmartContract; wasRead: boolean; @@ -399,7 +399,7 @@ function getLayout(scClass: typeof SmartContract) { const smartContracts = new WeakMap< typeof SmartContract, { - states: [string, ProvablePure][]; + states: [string, ProvablePure][]; layout: Map | undefined; } >(); diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index a0df136baf..bff35b2877 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -96,12 +96,12 @@ function method( `@method decorator was applied to \`${methodName}\`, which is not a function.` ); } - let paramTypes: Provable[] = Reflect.getMetadata( + let paramTypes: Provable[] = Reflect.getMetadata( 'design:paramtypes', target, methodName ); - let returnType: Provable = Reflect.getMetadata( + let returnType: Provable = Reflect.getMetadata( 'design:returntype', target, methodName @@ -539,7 +539,7 @@ function computeCallData( class Callback extends GenericArgument { instance: SmartContract; - methodIntf: MethodInterface & { returnType: Provable }; + methodIntf: MethodInterface & { returnType: Provable }; args: any[]; result?: Result; @@ -1060,7 +1060,7 @@ super.init(); { type: string; event: { - data: ProvablePure; + data: ProvablePure; transactionInfo: { transactionHash: string; transactionStatus: string; @@ -1276,7 +1276,7 @@ type ReducerReturn = { */ reduce( actions: Action[][], - stateType: Provable, + stateType: Provable, reduce: (state: State, action: Action) => State, initial: { state: State; actionState: Field }, options?: { @@ -1353,7 +1353,7 @@ class ${contract.constructor.name} extends SmartContract { reduce( actionLists: A[][], - stateType: Provable, + stateType: Provable, reduce: (state: S, action: A) => S, { state, actionState }: { state: S; actionState: Field }, { @@ -1458,7 +1458,7 @@ Use the optional \`maxTransactionsWithActions\` argument to increase this number actionsForAccount = actions.map((event) => // putting our string-Fields back into the original action type event.actions.map((action) => - (reducer.actionType as ProvablePure).fromFields( + (reducer.actionType as ProvablePure).fromFields( action.map(Field) ) ) @@ -1481,7 +1481,9 @@ Use the optional \`maxTransactionsWithActions\` argument to increase this number return result.map((event) => // putting our string-Fields back into the original action type event.actions.map((action) => - (reducer.actionType as ProvablePure).fromFields(action.map(Field)) + (reducer.actionType as ProvablePure).fromFields( + action.map(Field) + ) ) ); }, @@ -1545,7 +1547,7 @@ function Account(address: PublicKey, tokenId?: Field) { */ function declareMethods( SmartContract: T, - methodArguments: Record[]> + methodArguments: Record[]> ) { for (let key in methodArguments) { let argumentTypes = methodArguments[key]; From 6a75d6f6a3c01ebdaa2c7b81b6728dfc74300922 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 18:04:28 +0100 Subject: [PATCH 04/41] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 1dd31581aa..6b09dc6c61 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 1dd31581aacb3e6d76422063f052ea88c1451e1d +Subproject commit 6b09dc6c610e10828a70268b9c3998340d694ac3 From 040098f848e50cc2768af249656ba018527731e2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 18:09:20 +0100 Subject: [PATCH 05/41] fix test compilation --- src/bindings | 2 +- src/lib/field.unit-test.ts | 4 ++-- src/lib/hash-input.unit-test.ts | 14 ++++++++------ src/lib/testing/constraint-system.ts | 14 +++++++------- src/lib/testing/equivalent.ts | 4 ++-- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/bindings b/src/bindings index 6b09dc6c61..07d117ed3c 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 6b09dc6c610e10828a70268b9c3998340d694ac3 +Subproject commit 07d117ed3c364febf417f1c42b3da6b14a985f55 diff --git a/src/lib/field.unit-test.ts b/src/lib/field.unit-test.ts index 8f7d8843f5..3d51ff2766 100644 --- a/src/lib/field.unit-test.ts +++ b/src/lib/field.unit-test.ts @@ -19,8 +19,8 @@ import { } from './testing/equivalent.js'; // types -Field satisfies Provable; -Field satisfies ProvablePure; +Field satisfies Provable; +Field satisfies ProvablePure; Field satisfies ProvableExtended; Field satisfies Binable; diff --git a/src/lib/hash-input.unit-test.ts b/src/lib/hash-input.unit-test.ts index 35b5436dd6..d4359b4a55 100644 --- a/src/lib/hash-input.unit-test.ts +++ b/src/lib/hash-input.unit-test.ts @@ -31,20 +31,22 @@ type NetworkPrecondition = Body['preconditions']['network']; // provables let bodyLayout = jsLayout.AccountUpdate.entries.body; -let Timing = provableFromLayout( +let Timing = provableFromLayout( bodyLayout.entries.update.entries.timing.inner as any ); -let Permissions_ = provableFromLayout( +let Permissions_ = provableFromLayout( bodyLayout.entries.update.entries.permissions.inner as any ); -let Update = provableFromLayout(bodyLayout.entries.update as any); -let AccountPrecondition = provableFromLayout( +let Update = provableFromLayout( + bodyLayout.entries.update as any +); +let AccountPrecondition = provableFromLayout( bodyLayout.entries.preconditions.entries.account as any ); -let NetworkPrecondition = provableFromLayout( +let NetworkPrecondition = provableFromLayout( bodyLayout.entries.preconditions.entries.network as any ); -let Body = provableFromLayout(bodyLayout as any); +let Body = provableFromLayout(bodyLayout as any); // test with random account udpates test(Random.json.accountUpdate, (accountUpdateJson) => { diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index 90f3ec1599..6deac6f828 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -314,11 +314,11 @@ function toGatess( // Random generator for arbitrary provable types -function provable(spec: CsVarSpec): Provable { +function provable(spec: CsVarSpec): Provable { return 'provable' in spec ? spec.provable : spec; } -function layout(type: Provable): Random { +function layout(type: Provable): Random { let length = type.sizeInFields(); return Random(() => { @@ -327,7 +327,7 @@ function layout(type: Provable): Random { }); } -function instantiate(type: Provable, value: T) { +function instantiate(type: Provable, value: T) { let fields = type.toFields(value).map((x) => instantiateFieldVar(x.value)); return type.fromFields(fields, type.toAuxiliary()); } @@ -385,16 +385,16 @@ function drawFieldType(): FieldType { // types -type CsVarSpec = Provable | { provable: Provable }; -type InferCsVar = T extends { provable: Provable } +type CsVarSpec = Provable | { provable: Provable }; +type InferCsVar = T extends { provable: Provable } ? U - : T extends Provable + : T extends Provable ? U : never; type CsParams>> = { [k in keyof In]: InferCsVar; }; -type TypeAndValue = { type: Provable; value: T }; +type TypeAndValue = { type: Provable; value: T }; // print a constraint system diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index ab1241b94b..4edb92513c 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -41,7 +41,7 @@ type FromSpec = { // `provable` tells us how to create witnesses, to test provable code // note: we only allow the second function to be provable; // the second because it's more natural to have non-provable types as random generator output - provable?: Provable; + provable?: Provable; }; type ToSpec = { @@ -54,7 +54,7 @@ type ToSpec = { type Spec = FromSpec & ToSpec; -type ProvableSpec = Spec & { provable: Provable }; +type ProvableSpec = Spec & { provable: Provable }; type FuncSpec, Out1, In2 extends Tuple, Out2> = { from: { From 0620f737b6934e9044b979b230a92ac4e67a5e78 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 18:13:35 +0100 Subject: [PATCH 06/41] a non-trivial value type --- src/lib/gadgets/foreign-field.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 75f41a7f82..537c9e1194 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -16,6 +16,7 @@ import { l3, compactMultiRangeCheck, } from './range-check.js'; +import { ProvablePureExtended } from '../circuit_value.js'; export { ForeignField, Field3, Sign }; @@ -347,7 +348,15 @@ const Field3 = { * Note: Witnessing this creates a plain tuple of field elements without any implicit * range checks. */ - provable: provableTuple([Field, Field, Field]), + provable: { + ...provableTuple([Field, Field, Field]), + toValue(x): bigint { + return Field3.toBigint(x); + }, + fromValue(x): Field3 { + return Field3.from(x); + }, + } satisfies ProvablePureExtended, }; type Field2 = [Field, Field]; From a00c4136de7244939dc2913215425a5c7bb0e3b0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 20:14:41 +0100 Subject: [PATCH 07/41] flexible fromValue --- src/bindings | 2 +- src/lib/bool.ts | 5 +++-- src/lib/events.ts | 8 ++++++++ src/lib/field.ts | 4 ++-- src/lib/gadgets/foreign-field.ts | 3 ++- src/lib/hash.ts | 9 ++++++--- src/snarky.d.ts | 2 +- 7 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/bindings b/src/bindings index 07d117ed3c..4fb53e4676 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 07d117ed3c364febf417f1c42b3da6b14a985f55 +Subproject commit 4fb53e4676676b0a78ac31ea0e42f05692a98095 diff --git a/src/lib/bool.ts b/src/lib/bool.ts index 5f623d9b76..0eeccb33f7 100644 --- a/src/lib/bool.ts +++ b/src/lib/bool.ts @@ -283,9 +283,10 @@ class Bool { /** * `Provable.fromValue()` */ - static fromValue(x: 0n | 1n) { + static fromValue(x: 0n | 1n | Bool) { // TODO make `boolean` the value type - return new Bool(x === 1n); + if (typeof x === 'bigint') return new Bool(x === 1n); + return x; } /** diff --git a/src/lib/events.ts b/src/lib/events.ts index 779c6ea657..90796723b2 100644 --- a/src/lib/events.ts +++ b/src/lib/events.ts @@ -173,6 +173,14 @@ function dataAsHash({ return toValue(data); }, fromValue(value) { + if ( + typeof value === 'object' && + value !== null && + 'data' in value && + 'hash' in value + ) { + return value as { data: T; hash: Field }; + } return fromValue(value); }, toJSON({ data }) { diff --git a/src/lib/field.ts b/src/lib/field.ts index 5248024df4..58c350615e 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -1136,8 +1136,8 @@ class Field { /** * `Provable.fromValue()` */ - static fromValue(x: bigint) { - return new Field(x); + static fromValue(x: bigint | Field) { + return Field.from(x); } /** diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 537c9e1194..111c017258 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -354,7 +354,8 @@ const Field3 = { return Field3.toBigint(x); }, fromValue(x): Field3 { - return Field3.from(x); + if (typeof x === 'bigint') return Field3.from(x); + return x; }, } satisfies ProvablePureExtended, }; diff --git a/src/lib/hash.ts b/src/lib/hash.ts index 95a218fe22..ddcb8560ea 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -173,9 +173,12 @@ const TokenSymbolPure: ProvableExtended< toValue({ symbol }) { return symbol; }, - fromValue(symbol: string) { - let field = prefixToField(symbol); - return { symbol, field }; + fromValue(symbol: string | TokenSymbol) { + if (typeof symbol === 'string') { + let field = prefixToField(symbol); + return { symbol, field }; + } + return symbol; }, toJSON({ symbol }) { return symbol; diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 88149686f4..ebc30be933 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -105,7 +105,7 @@ declare interface Provable { /** * Convert provable type from a normal JS type. */ - fromValue: (x: TValue) => T; + fromValue: (x: TValue | T) => T; } /** From f1d41777b7e4ece7a252ad6f570dcfd417bfb948 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 27 Nov 2023 20:32:43 +0100 Subject: [PATCH 08/41] make boolean the JS type for bool --- src/bindings | 2 +- src/lib/bool.ts | 16 ++++----- src/mina-signer/src/sign-zkapp-command.ts | 10 +++--- .../src/sign-zkapp-command.unit-test.ts | 2 +- src/provable/curve-bigint.ts | 4 +-- src/provable/field-bigint.ts | 36 +++++++++++-------- 6 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/bindings b/src/bindings index 4fb53e4676..53f9f9c532 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 4fb53e4676676b0a78ac31ea0e42f05692a98095 +Subproject commit 53f9f9c532430b6f3929e00c3ec60706329d94b8 diff --git a/src/lib/bool.ts b/src/lib/bool.ts index 0eeccb33f7..56724f3945 100644 --- a/src/lib/bool.ts +++ b/src/lib/bool.ts @@ -42,7 +42,7 @@ class Bool { this.value = x; return; } - this.value = FieldVar.constant(B(x)); + this.value = FieldVar.constant(B.toBigint(x)); } isConstant(): this is { value: ConstantBoolVar } { @@ -275,18 +275,16 @@ class Bool { /** * `Provable.toValue()` */ - static toValue(x: Bool): 0n | 1n { - // TODO make `boolean` the value type - return x.toBoolean() ? 1n : 0n; + static toValue(x: Bool): boolean { + return x.toBoolean(); } /** * `Provable.fromValue()` */ - static fromValue(x: 0n | 1n | Bool) { - // TODO make `boolean` the value type - if (typeof x === 'bigint') return new Bool(x === 1n); - return x; + static fromValue(b: boolean | Bool) { + if (typeof b === 'boolean') return new Bool(b); + return b; } /** @@ -366,7 +364,7 @@ class Bool { static #toVar(x: boolean | Bool): BoolVar { if (Bool.#isBool(x)) return x.value; - return FieldVar.constant(B(x)); + return FieldVar.constant(B.toBigint(x)); } } diff --git a/src/mina-signer/src/sign-zkapp-command.ts b/src/mina-signer/src/sign-zkapp-command.ts index 07d8a37c62..2e7148f40c 100644 --- a/src/mina-signer/src/sign-zkapp-command.ts +++ b/src/mina-signer/src/sign-zkapp-command.ts @@ -52,10 +52,10 @@ function signZkappCommand( // sign other updates with the same public key that require a signature for (let update of zkappCommand.accountUpdates) { - if (update.body.authorizationKind.isSigned === 0n) continue; + if (!update.body.authorizationKind.isSigned) continue; if (!PublicKey.equal(update.body.publicKey, publicKey)) continue; let { useFullCommitment } = update.body; - let usedCommitment = useFullCommitment === 1n ? fullCommitment : commitment; + let usedCommitment = useFullCommitment ? fullCommitment : commitment; let signature = signFieldElement(usedCommitment, privateKey, networkId); update.authorization = { signature: Signature.toBase58(signature) }; } @@ -79,10 +79,10 @@ function verifyZkappCommandSignature( // verify other signatures for the same public key for (let update of zkappCommand.accountUpdates) { - if (update.body.authorizationKind.isSigned === 0n) continue; + if (!update.body.authorizationKind.isSigned) continue; if (!PublicKey.equal(update.body.publicKey, publicKey)) continue; let { useFullCommitment } = update.body; - let usedCommitment = useFullCommitment === 1n ? fullCommitment : commitment; + let usedCommitment = useFullCommitment ? fullCommitment : commitment; if (update.authorization.signature === undefined) return false; let signature = Signature.fromBase58(update.authorization.signature); ok = verifyFieldElement(signature, usedCommitment, publicKey, networkId); @@ -100,7 +100,7 @@ function verifyAccountUpdateSignature( let { publicKey, useFullCommitment } = update.body; let { commitment, fullCommitment } = transactionCommitments; - let usedCommitment = useFullCommitment === 1n ? fullCommitment : commitment; + let usedCommitment = useFullCommitment ? fullCommitment : commitment; let signature = Signature.fromBase58(update.authorization.signature); return verifyFieldElement(signature, usedCommitment, publicKey, networkId); diff --git a/src/mina-signer/src/sign-zkapp-command.unit-test.ts b/src/mina-signer/src/sign-zkapp-command.unit-test.ts index e400f9c8d4..6780aa2f56 100644 --- a/src/mina-signer/src/sign-zkapp-command.unit-test.ts +++ b/src/mina-signer/src/sign-zkapp-command.unit-test.ts @@ -271,7 +271,7 @@ console.log('to/from json, hashes & signatures are consistent! 🎉'); function fixVerificationKey(a: AccountUpdate) { // ensure verification key is valid - if (a.body.update.verificationKey.isSome === 1n) { + if (a.body.update.verificationKey.isSome) { let [, data, hash] = Pickles.dummyVerificationKey(); a.body.update.verificationKey.value = { data, diff --git a/src/provable/curve-bigint.ts b/src/provable/curve-bigint.ts index f4d18dc4a9..41c9ec2985 100644 --- a/src/provable/curve-bigint.ts +++ b/src/provable/curve-bigint.ts @@ -96,11 +96,11 @@ const PublicKey = { if (y === undefined) { throw Error('PublicKey.toGroup: not a valid group element'); } - if (isOdd !== (y & 1n)) y = Field.negate(y); + if (isOdd !== !!(y & 1n)) y = Field.negate(y); return { x, y }; }, fromGroup({ x, y }: Group): PublicKey { - let isOdd = (y & 1n) as Bool; + let isOdd = !!(y & 1n); return { x, isOdd }; }, diff --git a/src/provable/field-bigint.ts b/src/provable/field-bigint.ts index c23e0e0bc4..6550ced2ef 100644 --- a/src/provable/field-bigint.ts +++ b/src/provable/field-bigint.ts @@ -4,13 +4,14 @@ import { BinableBigint, HashInput, ProvableBigint, + BinableBool, } from '../bindings/lib/provable-bigint.js'; export { Field, Bool, UInt32, UInt64, Sign }; export { pseudoClass, sizeInBits, checkRange, checkField }; type Field = bigint; -type Bool = 0n | 1n; +type Bool = boolean; type UInt32 = bigint; type UInt64 = bigint; @@ -24,6 +25,7 @@ type Sign = 1n | minusOne; const checkField = checkRange(0n, Fp.modulus, 'Field'); const checkBool = checkAllowList(new Set([0n, 1n]), 'Bool'); +const checkBoolBytes = checkAllowList(new Set([0, 1]), 'Bool'); const checkSign = checkAllowList(new Set([1n, minusOne]), 'Sign'); /** @@ -45,29 +47,35 @@ const Field = pseudoClass( */ const Bool = pseudoClass( function Bool(value: boolean): Bool { - return BigInt(value) as Bool; + return value; }, { - ...ProvableBigint(checkBool), - ...BinableBigint(1, checkBool), + ...BinableBool(checkBoolBytes), + fromBigint(x: Field) { + checkBool(x); + return x === 0n ? false : true; + }, + toBigint(x: Bool) { + return x ? 1n : 0n; + }, toInput(x: Bool): HashInput { - return { fields: [], packed: [[x, 1]] }; + return { fields: [], packed: [[Bool.toBigint(x), 1]] }; }, toBoolean(x: Bool) { - return !!x; + return x; }, toJSON(x: Bool) { - return !!x; + return x; }, fromJSON(b: boolean) { - let x = BigInt(b) as Bool; - checkBool(x); - return x; + return b; + }, + empty() { + return false; }, sizeInBytes: 1, fromField(x: Field) { - checkBool(x); - return x as 0n | 1n; + return Bool.fromBigint(x); }, } ); @@ -156,8 +164,8 @@ function checkRange(lower: bigint, upper: bigint, name: string) { }; } -function checkAllowList(valid: Set, name: string) { - return (x: bigint) => { +function checkAllowList(valid: Set, name: string) { + return (x: T) => { if (!valid.has(x)) { throw Error( `${name}: input must be one of ${[...valid].join(', ')}, got ${x}` From c2e34cc7e5fc4dd20185ea59be5734cffcf2e9db Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Tue, 28 Nov 2023 00:58:57 +0100 Subject: [PATCH 09/41] make conversion to mina-signer work --- src/bindings | 2 +- src/lib/account_update.ts | 8 +++-- src/lib/events.ts | 63 ++++++++++++++++-------------------- src/lib/field.ts | 7 ++++ src/lib/int.ts | 32 ++++++++++++++++++ src/provable/field-bigint.ts | 1 + 6 files changed, 74 insertions(+), 39 deletions(-) diff --git a/src/bindings b/src/bindings index 53f9f9c532..fcfb73b806 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 53f9f9c532430b6f3929e00c3ec60706329d94b8 +Subproject commit fcfb73b806f9da3f2dc5a7d0ce1885982e950816 diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 055b7e4721..fc3dfcefec 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1874,9 +1874,11 @@ function addMissingSignatures( additionalKeys = [] as PrivateKey[] ): ZkappCommandSigned { let additionalPublicKeys = additionalKeys.map((sk) => sk.toPublicKey()); - let { commitment, fullCommitment } = transactionCommitments( - TypesBigint.ZkappCommand.fromJSON(ZkappCommand.toJSON(zkappCommand)) - ); + let { commitment, fullCommitment } = transactionCommitments({ + ...Types.ZkappCommand.toValue(zkappCommand), + // TODO: represent memo in encoded form already? + memo: Memo.toBase58(Memo.fromString(zkappCommand.memo)), + }); function addFeePayerSignature(accountUpdate: FeePayerUnsigned): FeePayer { let { body, authorization, lazyAuthorization } = diff --git a/src/lib/events.ts b/src/lib/events.ts index 90796723b2..6d0e6a38a8 100644 --- a/src/lib/events.ts +++ b/src/lib/events.ts @@ -59,26 +59,23 @@ function createEvents({ }; const EventsProvable = { ...Events, - ...dataAsHash({ + ...dataAsHash({ empty: Events.empty, - toValue(data: Field[][]) { - return data.map((row) => row.map((e) => BigInt(Field.toJSON(e)))); + toValue(data) { + return data.map((row) => row.map((e) => Field.toBigint(e))); }, - fromValue(value: bigint[][]) { - let data = value.map((row) => - row.map((e) => Field.fromJSON(e.toString())) - ); - let hash = Events.hash(data); - return { data, hash }; + fromValue(value) { + return value.map((row) => row.map((e) => Field(e))); }, - toJSON(data: Field[][]) { + toJSON(data) { return data.map((row) => row.map((e) => Field.toJSON(e))); }, - fromJSON(json: string[][]) { + fromJSON(json) { let data = json.map((row) => row.map((e) => Field.fromJSON(e))); let hash = Events.hash(data); return { data, hash }; }, + Field, }), }; @@ -116,17 +113,13 @@ function createEvents({ const ActionsProvable = { ...Actions, - ...dataAsHash({ + ...dataAsHash({ empty: Actions.empty, - toValue(data: Field[][]) { - return data.map((row) => row.map((e) => BigInt(Field.toJSON(e)))); + toValue(data) { + return data.map((row) => row.map((e) => Field.toBigint(e))); }, - fromValue(value: bigint[][]) { - let data = value.map((row) => - row.map((e) => Field.fromJSON(e.toString())) - ); - let hash = Actions.hash(data); - return { data, hash }; + fromValue(value) { + return value.map((row) => row.map((e) => Field(e))); }, toJSON(data: Field[][]) { return data.map((row) => row.map((e) => Field.toJSON(e))); @@ -136,6 +129,7 @@ function createEvents({ let hash = Actions.hash(data); return { data, hash }; }, + Field, }), }; @@ -148,13 +142,20 @@ function dataAsHash({ fromValue, toJSON, fromJSON, + Field, }: { empty: () => { data: T; hash: Field }; toValue: (value: T) => V; - fromValue: (value: V) => { data: T; hash: Field }; + fromValue: (value: V | T) => T; toJSON: (value: T) => J; fromJSON: (json: J) => { data: T; hash: Field }; -}): GenericProvableExtended<{ data: T; hash: Field }, V, J, Field> { + Field: GenericSignableField; +}): GenericProvableExtended< + { data: T; hash: Field }, + { data: V; hash: bigint }, + J, + Field +> { return { empty, sizeInFields() { @@ -169,19 +170,11 @@ function dataAsHash({ fromFields([hash], [data]) { return { data, hash }; }, - toValue({ data }) { - return toValue(data); - }, - fromValue(value) { - if ( - typeof value === 'object' && - value !== null && - 'data' in value && - 'hash' in value - ) { - return value as { data: T; hash: Field }; - } - return fromValue(value); + toValue({ data, hash }) { + return { data: toValue(data), hash: Field.toBigint(hash) }; + }, + fromValue({ data, hash }) { + return { data: fromValue(data), hash: Field(hash) }; }, toJSON({ data }) { return toJSON(data); diff --git a/src/lib/field.ts b/src/lib/field.ts index 58c350615e..dea70a8da5 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -1133,6 +1133,13 @@ class Field { return x.toBigInt(); } + /** + * Convert a {@link Field} element to a bigint. + */ + static toBigint(x: Field) { + return x.toBigInt(); + } + /** * `Provable.fromValue()` */ diff --git a/src/lib/int.ts b/src/lib/int.ts index 45ca4e4011..54434fa906 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1,6 +1,7 @@ import { Field, Bool } from './core.js'; import { AnyConstructor, CircuitValue, prop } from './circuit_value.js'; import { Types } from '../bindings/mina-transaction/types.js'; +import * as TypesBigint from '../bindings/mina-transaction/transaction-leaves-bigint.js'; import { HashInput } from './hash.js'; import { Provable } from './provable.js'; @@ -371,6 +372,16 @@ class UInt64 extends CircuitValue { assertGreaterThanOrEqual(y: UInt64, message?: string) { y.assertLessThanOrEqual(this, message); } + + static toValue(x: UInt64) { + return x.value.toBigInt(); + } + + static fromValue( + x: bigint | UInt64 + ): InstanceType { + return UInt64.from(x) as any; + } } /** * A 32 bit unsigned integer with values ranging from 0 to 4,294,967,295. @@ -708,6 +719,16 @@ class UInt32 extends CircuitValue { assertGreaterThanOrEqual(y: UInt32, message?: string) { y.assertLessThanOrEqual(this, message); } + + static toValue(x: UInt32) { + return x.value.toBigInt(); + } + + static fromValue( + x: bigint | UInt32 + ): InstanceType { + return UInt32.from(x) as any; + } } class Sign extends CircuitValue { @@ -751,6 +772,17 @@ class Sign extends CircuitValue { toString() { return this.value.toString(); } + + static toValue(x: Sign) { + return x.value.toBigInt() as TypesBigint.Sign; + } + + static fromValue( + x: bigint | Sign + ): InstanceType { + if (x instanceof Sign) return x as any; + return new Sign(Field(x)) as any; + } } type BalanceChange = Types.AccountUpdate['body']['balanceChange']; diff --git a/src/provable/field-bigint.ts b/src/provable/field-bigint.ts index 6550ced2ef..2a0ee81d06 100644 --- a/src/provable/field-bigint.ts +++ b/src/provable/field-bigint.ts @@ -39,6 +39,7 @@ const Field = pseudoClass( ...ProvableBigint(checkField), ...BinableBigint(Fp.sizeInBits, checkField), ...Fp, + toBigint: (x: Field) => x, } ); From 90fbbcc95c5c79ab3cc21578701e83f6a7f1dc06 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Tue, 28 Nov 2023 01:12:39 +0100 Subject: [PATCH 10/41] private and public key explicitly --- src/lib/signature.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/lib/signature.ts b/src/lib/signature.ts index 58d68ccef0..47f78f37fc 100644 --- a/src/lib/signature.ts +++ b/src/lib/signature.ts @@ -106,6 +106,17 @@ class PrivateKey extends CircuitValue { constantScalarToBigint(privateKey.s, 'PrivateKey.toBase58') ); } + + static toValue(v: PrivateKey) { + return v.toBigInt(); + } + static fromValue( + this: T, + v: bigint | PrivateKey + ): InstanceType { + if (v instanceof PrivateKey) return v as any; + return PrivateKey.fromBigInt(v) as any; + } } // TODO: this doesn't have a non-default check method yet. does it need one? @@ -222,6 +233,16 @@ class PublicKey extends CircuitValue { static fromJSON(this: T, publicKey: string) { return PublicKey.fromBase58(publicKey) as InstanceType; } + + static toValue({ x, isOdd }: PublicKey) { + return { x: x.toBigInt(), isOdd: isOdd.toBoolean() }; + } + static fromValue( + this: T, + { x, isOdd }: PublicKey | { x: bigint; isOdd: boolean } + ): InstanceType { + return PublicKey.from({ x: Field.from(x), isOdd: Bool(isOdd) }) as any; + } } /** From fa72cf014c4dd2052a69f4edc81da9f70687e883 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Tue, 28 Nov 2023 01:46:39 +0100 Subject: [PATCH 11/41] deeply flexible input type --- src/bindings | 2 +- src/lib/circuit_value.ts | 13 ++++++++++++- src/lib/hash.ts | 27 ++++++++++----------------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/bindings b/src/bindings index fcfb73b806..f9d7a9d254 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit fcfb73b806f9da3f2dc5a7d0ce1885982e950816 +Subproject commit f9d7a9d254aad7b6de9f30f23a99905b9a8ccbac diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 484c5f4fc7..ff6e8799f6 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -15,7 +15,10 @@ import type { IsPure, } from '../bindings/lib/provable-snarky.js'; import { Provable } from './provable.js'; -import { InferValue } from 'src/bindings/lib/provable-generic.js'; +import { + DeepValueOrProvable, + InferValue, +} from 'src/bindings/lib/provable-generic.js'; // external API export { @@ -411,6 +414,7 @@ function Struct< ): (new (value: T) => T) & { _isStruct: true } & (Pure extends true ? ProvablePure : Provable) & { + from: (value: DeepValueOrProvable) => T; toInput: (x: T) => { fields?: Field[] | undefined; packed?: [Field, number][] | undefined; @@ -425,6 +429,13 @@ function Struct< constructor(value: T) { Object.assign(this, value); } + + static from(value: DeepValueOrProvable): T { + let x = this.type.fromValue(value as any); + let struct = Object.create(this.prototype); + return Object.assign(struct, x); + } + /** * This method is for internal use, you will probably not need it. * @returns the size of this struct in field elements diff --git a/src/lib/hash.ts b/src/lib/hash.ts index ddcb8560ea..3fc6706942 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -174,11 +174,14 @@ const TokenSymbolPure: ProvableExtended< return symbol; }, fromValue(symbol: string | TokenSymbol) { - if (typeof symbol === 'string') { - let field = prefixToField(symbol); - return { symbol, field }; - } - return symbol; + if (typeof symbol !== 'string') return symbol; + let bytesLength = new TextEncoder().encode(symbol).length; + if (bytesLength > 6) + throw Error( + `Token symbol ${symbol} should be a maximum of 6 bytes, but is ${bytesLength}` + ); + let field = prefixToField(symbol); + return { symbol, field }; }, toJSON({ symbol }) { return symbol; @@ -195,18 +198,8 @@ const TokenSymbolPure: ProvableExtended< }, }; class TokenSymbol extends Struct(TokenSymbolPure) { - static get empty() { - return { symbol: '', field: Field(0) }; - } - - static from(symbol: string): TokenSymbol { - let bytesLength = new TextEncoder().encode(symbol).length; - if (bytesLength > 6) - throw Error( - `Token symbol ${symbol} should be a maximum of 6 bytes, but is ${bytesLength}` - ); - let field = prefixToField(symbol); - return { symbol, field }; + static from(value: string | TokenSymbol) { + return super.from(value) as TokenSymbol; } } From 5da51ca22b62480ac6ff6913e307a8179984b3b7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 08:00:39 +0100 Subject: [PATCH 12/41] test fixup --- src/mina-signer/src/sign-zkapp-command.unit-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mina-signer/src/sign-zkapp-command.unit-test.ts b/src/mina-signer/src/sign-zkapp-command.unit-test.ts index 6780aa2f56..81a541f39b 100644 --- a/src/mina-signer/src/sign-zkapp-command.unit-test.ts +++ b/src/mina-signer/src/sign-zkapp-command.unit-test.ts @@ -57,7 +57,7 @@ test(Random.json.publicKey, (publicKeyBase58) => { let pkSnarky = PublicKeySnarky.fromBase58(publicKeyBase58); let pk = PublicKey.fromJSON(publicKeyBase58); expect(pk.x).toEqual(pkSnarky.x.toBigInt()); - expect(pk.isOdd).toEqual(pkSnarky.isOdd.toField().toBigInt()); + expect(pk.isOdd).toEqual(pkSnarky.isOdd.toBoolean()); expect(PublicKey.toJSON(pk)).toEqual(publicKeyBase58); }); From f034f19190f19f3067a0d2b7a0b848ec13baa94d Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 09:33:48 +0100 Subject: [PATCH 13/41] more flexible from for public key --- src/lib/signature.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/signature.ts b/src/lib/signature.ts index 47f78f37fc..5db7e1c990 100644 --- a/src/lib/signature.ts +++ b/src/lib/signature.ts @@ -168,8 +168,8 @@ class PublicKey extends CircuitValue { * Creates a {@link PublicKey} from a JSON structure element. * @returns a {@link PublicKey}. */ - static from(g: { x: Field; isOdd: Bool }) { - return PublicKey.fromObject(g); + static from(g: { x: Field | bigint; isOdd: Bool | boolean }) { + return PublicKey.fromObject({ x: Field.from(g.x), isOdd: Bool(g.isOdd) }); } /** @@ -177,7 +177,7 @@ class PublicKey extends CircuitValue { * @returns an empty {@link PublicKey} */ static empty(): InstanceType { - return PublicKey.from({ x: Field(0), isOdd: Bool(false) }) as any; + return PublicKey.from({ x: 0n, isOdd: false }) as any; } /** From 146b42f43c0a3352218949daa54284ed52dcfcf1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 09:34:00 +0100 Subject: [PATCH 14/41] remove asProver check in unsafe bool --- src/lib/bool.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/lib/bool.ts b/src/lib/bool.ts index 56724f3945..4389782683 100644 --- a/src/lib/bool.ts +++ b/src/lib/bool.ts @@ -349,11 +349,6 @@ class Bool { * @param x a {@link Field} */ ofField(x: Field) { - asProver(() => { - let x0 = x.toBigInt(); - if (x0 !== 0n && x0 !== 1n) - throw Error(`Bool.Unsafe.ofField(): Expected 0 or 1, got ${x0}`); - }); return new Bool(x.value); }, }; From e80baaa745662b0a3a924c18de874e5cce4794a8 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 09:35:08 +0100 Subject: [PATCH 15/41] fix accountupdate fromValue --- src/lib/account_update.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index fc3dfcefec..c82820be2b 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -33,6 +33,7 @@ import { MlArray } from './ml/base.js'; import { Signature, signFieldElement } from '../mina-signer/src/signature.js'; import { MlFieldConstArray } from './ml/fields.js'; import { transactionCommitments } from '../mina-signer/src/sign-zkapp-command.js'; +import { DeepProvableOrValue } from '../bindings/lib/provable-generic.js'; // external API export { AccountUpdate, Permissions, ZkappPublicInput }; @@ -1286,7 +1287,10 @@ class AccountUpdate implements Types.AccountUpdate { ); } static toValue = Types.AccountUpdate.toValue; - static fromValue(value: TypesBigint.AccountUpdate): AccountUpdate { + static fromValue( + value: DeepProvableOrValue | AccountUpdate + ): AccountUpdate { + if (value instanceof AccountUpdate) return value; let accountUpdate = Types.AccountUpdate.fromValue(value); return new AccountUpdate(accountUpdate.body, accountUpdate.authorization); } From 686f0e37f8aeda32d5af4846a6f68bd993e6dd1b Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 09:35:20 +0100 Subject: [PATCH 16/41] add comment --- src/lib/provable-context.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/provable-context.ts b/src/lib/provable-context.ts index 076e9f8300..f6261c81dc 100644 --- a/src/lib/provable-context.ts +++ b/src/lib/provable-context.ts @@ -58,6 +58,7 @@ function inCompileMode() { function asProver(f: () => void) { if (inCheckedComputation()) { + // TODO make this start a "witness block" context Snarky.run.asProver(f); } else { f(); @@ -106,7 +107,7 @@ function constraintSystem(f: () => T) { function gatesFromJson(cs: { gates: JsonGate[]; public_input_size: number }) { let gates: Gate[] = cs.gates.map(({ typ, wires, coeffs: hexCoeffs }) => { - let coeffs = hexCoeffs.map(hex => parseHexString(hex).toString()); + let coeffs = hexCoeffs.map((hex) => parseHexString(hex).toString()); return { type: typ, wires, coeffs }; }); return { publicInputSize: cs.public_input_size, gates }; From fb9caccaf34ea2361c48316e3e69efcff1b2c39a Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 09:35:32 +0100 Subject: [PATCH 17/41] type rename --- src/lib/circuit_value.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index ff6e8799f6..25edbeba79 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -16,7 +16,7 @@ import type { } from '../bindings/lib/provable-snarky.js'; import { Provable } from './provable.js'; import { - DeepValueOrProvable, + DeepProvableOrValue, InferValue, } from 'src/bindings/lib/provable-generic.js'; @@ -414,7 +414,7 @@ function Struct< ): (new (value: T) => T) & { _isStruct: true } & (Pure extends true ? ProvablePure : Provable) & { - from: (value: DeepValueOrProvable) => T; + from: (value: DeepProvableOrValue) => T; toInput: (x: T) => { fields?: Field[] | undefined; packed?: [Field, number][] | undefined; @@ -430,7 +430,7 @@ function Struct< Object.assign(this, value); } - static from(value: DeepValueOrProvable): T { + static from(value: DeepProvableOrValue): T { let x = this.type.fromValue(value as any); let struct = Object.create(this.prototype); return Object.assign(struct, x); From f5b6f5943cf8cd63976acae3f436f5a297775a9c Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 09:37:32 +0100 Subject: [PATCH 18/41] flexible return type in provable.witness callbacks --- src/lib/provable.ts | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/lib/provable.ts b/src/lib/provable.ts index 6a7c59ed67..041c14c0ae 100644 --- a/src/lib/provable.ts +++ b/src/lib/provable.ts @@ -24,7 +24,10 @@ import { constraintSystem, } from './provable-context.js'; import { isBool } from './bool.js'; -import { InferValue } from 'src/bindings/lib/provable-generic.js'; +import { + DeepProvableOrValue, + InferValue, +} from 'src/bindings/lib/provable-generic.js'; // external API export { Provable }; @@ -35,6 +38,7 @@ export { MemoizationContext, memoizeWitness, getBlindingValue, + from, }; // TODO move type declaration here @@ -57,7 +61,7 @@ const Provable = { * ```ts * let invX = Provable.witness(Field, () => { * // compute the inverse of `x` outside the circuit, however you like! - * return Field.inv(x)); + * return Field.inv(x); * } * // prove that `invX` is really the inverse of `x`: * invX.mul(x).assertEquals(1); @@ -190,23 +194,24 @@ const Provable = { inCheckedComputation, }; -function witness = FlexibleProvable>( - type: S, - compute: () => T -): T { +function witness< + A extends Provable, + T extends DeepProvableOrValue = DeepProvableOrValue +>(type: A, compute: () => T): InferProvable { + type S = InferProvable; let ctx = snarkContext.get(); // outside provable code, we just call the callback and return its cloned result if (!inCheckedComputation() || ctx.inWitnessBlock) { - return clone(type, compute()); + return clone(type, from(type, compute())); } - let proverValue: T | undefined = undefined; + let proverValue: S | undefined = undefined; let fields: Field[]; let id = snarkContext.enter({ ...ctx, inWitnessBlock: true }); try { let [, ...fieldVars] = Snarky.exists(type.sizeInFields(), () => { - proverValue = compute(); + proverValue = from(type, compute()); let fields = type.toFields(proverValue); let fieldConstants = fields.map((x) => x.toConstant().value[1]); @@ -228,7 +233,7 @@ function witness = FlexibleProvable>( // rebuild the value from its fields (which are now variables) and aux data let aux = type.toAuxiliary(proverValue); - let value = (type as Provable).fromFields(fields, aux); + let value = (type as Provable).fromFields(fields, aux); // add type-specific constraints type.check(value); @@ -455,7 +460,7 @@ let memoizationContext = Context.create(); * for reuse by the prover. This is needed to witness non-deterministic values. */ function memoizeWitness(type: FlexibleProvable, compute: () => T) { - return Provable.witness(type as Provable, () => { + return Provable.witness(type as Provable, () => { if (!memoizationContext.has()) return compute(); let context = memoizationContext.get(); let { memoized, currentIndex } = context; @@ -586,3 +591,12 @@ function provableArray>( }, } satisfies ProvableExtended as any; } + +// generic flexible `from()` function + +function from>( + type: A, + value: DeepProvableOrValue +): InferProvable { + return type.fromValue(value); +} From 8014954b1d411531cd65cec97cf820472238bc43 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 09:38:00 +0100 Subject: [PATCH 19/41] add test for mixed witness generation --- src/lib/circuit_value.unit-test.ts | 94 +++++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 15 deletions(-) diff --git a/src/lib/circuit_value.unit-test.ts b/src/lib/circuit_value.unit-test.ts index 6372c52a7f..cf6334729d 100644 --- a/src/lib/circuit_value.unit-test.ts +++ b/src/lib/circuit_value.unit-test.ts @@ -8,11 +8,16 @@ import { State, state } from './state.js'; import { AccountUpdate } from './account_update.js'; import { Provable } from './provable.js'; import { Field } from './core.js'; +import { Bool } from './bool.js'; +import assert from 'assert/strict'; +import { FieldType } from './field.js'; +import { DeepProvableOrValue } from '../bindings/lib/provable-generic.js'; let type = provable({ nested: { a: Number, b: Boolean }, other: String, pk: PublicKey, + bool: Bool, uint: [UInt32, UInt32], }); @@ -20,21 +25,22 @@ let value = { nested: { a: 1, b: true }, other: 'arbitrary data!!!', pk: PublicKey.empty(), + bool: new Bool(true), uint: [UInt32.one, UInt32.from(2)], }; let original = JSON.stringify(value); // sizeInFields -expect(type.sizeInFields()).toEqual(4); +expect(type.sizeInFields()).toEqual(5); // toFields // note that alphabetical order of keys determines ordering here and elsewhere let fields = type.toFields(value); -expect(fields).toEqual([Field(0), Field(0), Field(1), Field(2)]); +expect(fields).toEqual([Field(0), Field(0), Field(1), Field(1), Field(2)]); // toAuxiliary let aux = type.toAuxiliary(value); -expect(aux).toEqual([[[1], [true]], ['arbitrary data!!!'], [], [[], []]]); +expect(aux).toEqual([[[1], [true]], ['arbitrary data!!!'], [], [], [[], []]]); // toInput let input = type.toInput(value); @@ -42,6 +48,7 @@ expect(input).toEqual({ fields: [Field(0)], packed: [ [Field(0), 1], + [Field(1), 1], [Field(1), 32], [Field(2), 32], ], @@ -52,6 +59,7 @@ expect(type.toJSON(value)).toEqual({ nested: { a: 1, b: true }, other: 'arbitrary data!!!', pk: PublicKey.toBase58(PublicKey.empty()), + bool: true, uint: ['1', '2'], }); @@ -59,6 +67,18 @@ expect(type.toJSON(value)).toEqual({ let restored = type.fromFields(fields, aux); expect(JSON.stringify(restored)).toEqual(original); +// toValue, fromValue +let jsValue = type.toValue(value); +expect(jsValue).toEqual({ + nested: { a: 1, b: true }, + other: 'arbitrary data!!!', + pk: { x: 0n, isOdd: false }, + bool: true, + uint: [1n, 2n], +}); + +expect(type.fromValue(jsValue)).toEqual(value); + // check Provable.runAndCheck(() => { type.check(value); @@ -67,16 +87,12 @@ Provable.runAndCheck(() => { // should fail `check` if `check` of subfields doesn't pass expect(() => Provable.runAndCheck(() => { - let x = Provable.witness(type, () => ({ + Provable.witness(type, () => ({ ...value, - uint: [ - UInt32.zero, - // invalid Uint32 - new UInt32(Field(-1)), - ], + bool: Bool.Unsafe.ofField(Field(2)), })); }) -).toThrow(`Constraint unsatisfied`); +).toThrow('Constraint unsatisfied'); // class version of `provable` class MyStruct extends Struct({ @@ -93,12 +109,34 @@ class MyStructPure extends Struct({ uint: [UInt32, UInt32], }) {} +// Struct.from() works on both js and provable inputs + +let myStructInput = { + nested: { a: 1n, b: 2n }, + other: 3n, + pk: { x: 4n, isOdd: true }, + uint: [100n, 5n], +}; +let myStruct = MyStructPure.from(myStructInput); + +type FlexibleStruct = DeepProvableOrValue; +myStruct satisfies FlexibleStruct; +myStructInput satisfies FlexibleStruct; + +expect(myStruct).toBeInstanceOf(MyStructPure); +expect(MyStructPure.toValue(myStruct)).toEqual(myStructInput); + +let myStruct2 = MyStructPure.from(myStruct); +expect(myStruct2).toBeInstanceOf(MyStructPure); +expect(myStruct2).toEqual(myStruct); + class MyTuple extends Struct([PublicKey, String]) {} +// create a smart contract and pass auxiliary data to a method + let targetString = 'some particular string'; let gotTargetString = false; -// create a smart contract and pass auxiliary data to a method class MyContract extends SmartContract { // this is correctly rejected by the compiler -- on-chain state can't have stuff like strings in it // @state(MyStruct) y = State(); @@ -113,12 +151,38 @@ class MyContract extends SmartContract { Provable.asProver(() => { let err = 'wrong value in prover'; - if (tuple[1] !== targetString) throw Error(err); + assert.equal(tuple[1], targetString, err); // check if we can pass in account updates - if (update.lazyAuthorization?.kind !== 'lazy-signature') throw Error(err); - if (update.lazyAuthorization.privateKey?.toBase58() !== key.toBase58()) - throw Error(err); + assert.equal(update.lazyAuthorization?.kind, 'lazy-signature', err); + assert.equal( + update.lazyAuthorization.privateKey?.toBase58(), + key.toBase58(), + err + ); + }); + + // mixed witness generation + let pk = Provable.witness(PublicKey, () => ({ x: Field(5), isOdd: true })); + let struct = Provable.witness(MyStructPure, () => ({ + nested: { a: Field(0), b: 1n }, + other: 0n, + pk: PublicKey.empty(), + uint: [UInt32.zero, 1n], + })); + + if (Provable.inCheckedComputation()) { + assert(pk.x.value[0] === FieldType.Var, 'pk is a variable'); + assert(pk.isOdd.value[0] === FieldType.Var, 'pk is a variable'); + } + + Provable.asProver(() => { + assert.equal(pk.x.toBigInt(), 5n, 'pk.x'); + assert.equal(pk.isOdd.toBoolean(), true, 'pk.isOdd'); + + assert.equal(struct.nested.a.toBigInt(), 0n, 'struct.nested.a'); + assert.equal(struct.uint[0].toBigint(), 0n, 'struct.uint'); + assert.equal(struct.uint[1].toBigint(), 1n, 'struct.uint'); }); } } From d6b1374dcc6edf9c7089ede8472f2d1194dd3182 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 09:38:07 +0100 Subject: [PATCH 20/41] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index f9d7a9d254..9bcf4e3f9d 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit f9d7a9d254aad7b6de9f30f23a99905b9a8ccbac +Subproject commit 9bcf4e3f9dbaae8c7e006bd229852fc01b8afff0 From 39cf968b1dfec54159674d4af68532329bbc4bc9 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 09:41:00 +0100 Subject: [PATCH 21/41] use flexible witness in gadgets --- src/lib/gadgets/bitwise.ts | 12 +++--------- src/lib/gadgets/common.ts | 4 ++-- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index 027d996488..5c3b6da6f5 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -75,10 +75,7 @@ function xor(a: Field, b: Field, length: number) { } // calculate expected xor output - let outputXor = Provable.witness( - Field, - () => new Field(a.toBigInt() ^ b.toBigInt()) - ); + let outputXor = Provable.witness(Field, () => a.toBigInt() ^ b.toBigInt()); // builds the xor gadget chain buildXor(a, b, outputXor, padLength); @@ -181,10 +178,7 @@ function and(a: Field, b: Field, length: number) { } // calculate expect and output - let outputAnd = Provable.witness( - Field, - () => new Field(a.toBigInt() & b.toBigInt()) - ); + let outputAnd = Provable.witness(Field, () => a.toBigInt() & b.toBigInt()); // compute values for gate // explanation: https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and @@ -243,7 +237,7 @@ function rot( const rotated = shifted + excess; // Compute bound to check excess < 2^rot const bound = excess + big2Power64 - big2PowerRot; - return [rotated, excess, shifted, bound].map(Field.from); + return [rotated, excess, shifted, bound]; } ); diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 196ca64e73..56642e044f 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -75,12 +75,12 @@ function witnessSlice(f: Field, start: number, length: number) { return Provable.witness(Field, () => { let n = f.toBigInt(); - return new Field((n >> BigInt(start)) & ((1n << BigInt(length)) - 1n)); + return (n >> BigInt(start)) & ((1n << BigInt(length)) - 1n); }); } function witnessNextValue(current: Field) { - return Provable.witness(Field, () => new Field(current.toBigInt() >> 16n)); + return Provable.witness(Field, () => current.toBigInt() >> 16n); } function divideWithRemainder(numerator: bigint, denominator: bigint) { From a69de8d47098df0c46df2e68592e608a7902b41f Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 09:49:55 +0100 Subject: [PATCH 22/41] adapt group --- src/lib/group.ts | 29 +++++++++++++++++++++-------- src/lib/group.unit-test.ts | 4 ++-- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/lib/group.ts b/src/lib/group.ts index 2710b70456..526eaafe6b 100644 --- a/src/lib/group.ts +++ b/src/lib/group.ts @@ -136,8 +136,6 @@ class Group { const { x: x1, y: y1 } = this; const { x: x2, y: y2 } = g; - let zero = new Field(0); - let same_x = Provable.witness(Field, () => x1.equals(x2).toField()); let inf = Provable.witness(Bool, () => @@ -145,13 +143,13 @@ class Group { ); let inf_z = Provable.witness(Field, () => { - if (y1.equals(y2).toBoolean()) return zero; + if (y1.equals(y2).toBoolean()) return 0n; else if (x1.equals(x2).toBoolean()) return y2.sub(y1).inv(); - else return zero; + else return 0n; }); let x21_inv = Provable.witness(Field, () => { - if (x1.equals(x2).toBoolean()) return zero; + if (x1.equals(x2).toBoolean()) return 0n; else return x2.sub(x1).inv(); }); @@ -171,9 +169,9 @@ class Group { }); let [, x, y] = Snarky.gates.ecAdd( - Group.from(x1.seal(), y1.seal()).#toTuple(), - Group.from(x2.seal(), y2.seal()).#toTuple(), - Group.from(x3, y3).#toTuple(), + Group.fromCoordinates(x1.seal(), y1.seal()).#toTuple(), + Group.fromCoordinates(x2.seal(), y2.seal()).#toTuple(), + Group.fromCoordinates(x3, y3).#toTuple(), inf.toField().value, same_x.value, s.value, @@ -294,6 +292,21 @@ class Group { * Coerces two x and y coordinates into a {@link Group} element. */ static from( + g: + | { + x: FieldVar | Field | number | string | bigint; + y: FieldVar | Field | number | string | bigint; + } + | Group + ) { + if (g instanceof Group) return g; + return new Group({ x: g.x, y: g.y }); + } + + /** + * Coerces two x and y coordinates into a {@link Group} element. + */ + static fromCoordinates( x: FieldVar | Field | number | string | bigint, y: FieldVar | Field | number | string | bigint ) { diff --git a/src/lib/group.unit-test.ts b/src/lib/group.unit-test.ts index 5a7102f078..d3bc2c143a 100644 --- a/src/lib/group.unit-test.ts +++ b/src/lib/group.unit-test.ts @@ -18,8 +18,8 @@ test(Random.field, Random.field, (a, b, assert) => { } = Poseidon.hashToGroup([b])!; const zero = Group.zero; - const g1 = Group.from(x1, y1); - const g2 = Group.from(x2, y2); + const g1 = Group.fromCoordinates(x1, y1); + const g2 = Group.fromCoordinates(x2, y2); run(g1, g2, (x, y) => x.add(y), assert); run(g1.neg(), g2.neg(), (x, y) => x.add(y), assert); From 8326b164ed73da77e8e2d2e544e32fb81af35b13 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 09:58:46 +0100 Subject: [PATCH 23/41] flexible witness in random tests and examples --- src/examples/constraint_system.ts | 4 ++-- src/examples/nullifier.ts | 4 ++-- src/examples/zkapps/dex/dex-with-actions.ts | 4 ++-- src/examples/zkprogram/gadgets.ts | 12 ++++++------ src/lib/circuit_value.test.ts | 8 ++++---- src/lib/field.unit-test.ts | 6 +++--- src/lib/group.test.ts | 7 ++----- src/lib/primitives.unit-test.ts | 4 ++-- 8 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/examples/constraint_system.ts b/src/examples/constraint_system.ts index 6acb7fde53..0b8a1e8837 100644 --- a/src/examples/constraint_system.ts +++ b/src/examples/constraint_system.ts @@ -3,8 +3,8 @@ import { Field, Poseidon, Provable } from 'o1js'; let hash = Poseidon.hash([Field(1), Field(-1)]); let { rows, digest, gates, publicInputSize } = Provable.constraintSystem(() => { - let x = Provable.witness(Field, () => Field(1)); - let y = Provable.witness(Field, () => Field(-1)); + let x = Provable.witness(Field, () => 1); + let y = Provable.witness(Field, () => -1); x.add(y).assertEquals(Field(0)); let z = Poseidon.hash([x, y]); z.assertEquals(hash); diff --git a/src/examples/nullifier.ts b/src/examples/nullifier.ts index 8f3ea81001..cb93b4af7d 100644 --- a/src/examples/nullifier.ts +++ b/src/examples/nullifier.ts @@ -7,10 +7,10 @@ import { State, method, MerkleMap, - Circuit, MerkleMapWitness, Mina, AccountUpdate, + Provable, } from 'o1js'; class PayoutOnlyOnce extends SmartContract { @@ -24,7 +24,7 @@ class PayoutOnlyOnce extends SmartContract { // verify the nullifier nullifier.verify([nullifierMessage]); - let nullifierWitness = Circuit.witness(MerkleMapWitness, () => + let nullifierWitness = Provable.witness(MerkleMapWitness, () => NullifierTree.getWitness(nullifier.key()) ); diff --git a/src/examples/zkapps/dex/dex-with-actions.ts b/src/examples/zkapps/dex/dex-with-actions.ts index 34353a2c4d..46b07655f5 100644 --- a/src/examples/zkapps/dex/dex-with-actions.ts +++ b/src/examples/zkapps/dex/dex-with-actions.ts @@ -241,13 +241,13 @@ class DexTokenHolder extends SmartContract { // get total supply of liquidity tokens _before_ applying these actions // (each redeem action _decreases_ the supply, so we increase it here) - let l = Provable.witness(UInt64, (): UInt64 => { + let l = Provable.witness(UInt64, () => { let l = dex.totalSupply.get().toBigInt(); // dex.totalSupply.assertNothing(); for (let [action] of actions) { l += action.dl.toBigInt(); } - return UInt64.from(l); + return l; }); // get our token balance diff --git a/src/examples/zkprogram/gadgets.ts b/src/examples/zkprogram/gadgets.ts index 0a87b61dce..c76f93557d 100644 --- a/src/examples/zkprogram/gadgets.ts +++ b/src/examples/zkprogram/gadgets.ts @@ -1,7 +1,7 @@ import { Field, Provable, Gadgets, ZkProgram } from 'o1js'; let cs = Provable.constraintSystem(() => { - let f = Provable.witness(Field, () => Field(12)); + let f = Provable.witness(Field, () => 12); let res1 = Gadgets.rotate(f, 2, 'left'); let res2 = Gadgets.rotate(f, 2, 'right'); @@ -20,7 +20,7 @@ const BitwiseProver = ZkProgram({ rot: { privateInputs: [], method: () => { - let a = Provable.witness(Field, () => Field(48)); + let a = Provable.witness(Field, () => 48); let actualLeft = Gadgets.rotate(a, 2, 'left'); let actualRight = Gadgets.rotate(a, 2, 'right'); @@ -34,8 +34,8 @@ const BitwiseProver = ZkProgram({ xor: { privateInputs: [], method: () => { - let a = Provable.witness(Field, () => Field(5)); - let b = Provable.witness(Field, () => Field(2)); + let a = Provable.witness(Field, () => 5); + let b = Provable.witness(Field, () => 2); let actual = Gadgets.xor(a, b, 4); let expected = Field(7); actual.assertEquals(expected); @@ -44,8 +44,8 @@ const BitwiseProver = ZkProgram({ and: { privateInputs: [], method: () => { - let a = Provable.witness(Field, () => Field(3)); - let b = Provable.witness(Field, () => Field(5)); + let a = Provable.witness(Field, () => 3); + let b = Provable.witness(Field, () => 5); let actual = Gadgets.and(a, b, 4); let expected = Field(1); actual.assertEquals(expected); diff --git a/src/lib/circuit_value.test.ts b/src/lib/circuit_value.test.ts index d45c8acf8a..c32d0effbc 100644 --- a/src/lib/circuit_value.test.ts +++ b/src/lib/circuit_value.test.ts @@ -18,7 +18,7 @@ describe('circuit', () => { Provable.runAndCheck(() => { let x = Provable.witness(Int64, () => Int64.from(-1)); let y = Provable.witness(Int64, () => Int64.from(-2)); - let b = Provable.witness(Bool, () => Bool(true)); + let b = Provable.witness(Bool, () => true); let z = Provable.if(b, Int64, x, y); @@ -83,7 +83,7 @@ describe('circuit', () => { Provable.runAndCheck(() => { let x = Provable.witness(Field, () => Field(1)); - let b = Provable.witness(Bool, () => Bool(true)); + let b = Provable.witness(Bool, () => true); // positive Provable.assertEqual(b, Bool(true)); @@ -124,8 +124,8 @@ describe('circuit', () => { } Provable.runAndCheck(() => { - let x = Provable.witness(Field, () => Field(1)); - let b = Provable.witness(Bool, () => Bool(true)); + let x = Provable.witness(Field, () => 1); + let b = Provable.witness(Bool, () => true); let pk = Provable.witness(PublicKey, () => pk1); expectBoolean(Provable.equal(pk, pk1), true); diff --git a/src/lib/field.unit-test.ts b/src/lib/field.unit-test.ts index 3d51ff2766..2986c8ad89 100644 --- a/src/lib/field.unit-test.ts +++ b/src/lib/field.unit-test.ts @@ -140,7 +140,7 @@ equivalent({ from: [smallField], to: bool })( test(Random.field, (x0, assert) => { Provable.runAndCheck(() => { // Var - let x = Provable.witness(Field, () => Field(x0)); + let x = Provable.witness(Field, () => x0); assert(x.value[0] === FieldType.Var); assert(typeof x.value[1] === 'number'); throws(() => x.toConstant()); @@ -179,8 +179,8 @@ test(Random.field, (x0, assert) => { test(Random.field, Random.field, (x0, y0, assert) => { Provable.runAndCheck(() => { // equals - let x = Provable.witness(Field, () => Field(x0)); - let y = Provable.witness(Field, () => Field(y0)); + let x = Provable.witness(Field, () => x0); + let y = Provable.witness(Field, () => y0); let b = x.equals(y); b.assertEquals(x0 === y0); diff --git a/src/lib/group.test.ts b/src/lib/group.test.ts index 1a69cc6e66..06db5d56dd 100644 --- a/src/lib/group.test.ts +++ b/src/lib/group.test.ts @@ -1,17 +1,14 @@ import { Bool, Group, Scalar, Provable } from 'o1js'; describe('group', () => { - let g = Group({ - x: -1, - y: 2, - }); + let g = Group({ x: -1, y: 2 }); describe('Inside circuit', () => { describe('group membership', () => { it('valid element does not throw', () => { expect(() => { Provable.runAndCheck(() => { - Provable.witness(Group, () => g); + Provable.witness(Group, () => ({ x: -1, y: 2 })); }); }).not.toThrow(); }); diff --git a/src/lib/primitives.unit-test.ts b/src/lib/primitives.unit-test.ts index 6344ddb9f9..2db1c1053d 100644 --- a/src/lib/primitives.unit-test.ts +++ b/src/lib/primitives.unit-test.ts @@ -7,9 +7,9 @@ class Primitives extends Circuit { @circuitMain static main() { // division - let x64 = Provable.witness(UInt64, () => UInt64.from(10)); + let x64 = Provable.witness(UInt64, () => 10n); x64.div(2).assertEquals(UInt64.from(5)); - let x32 = Provable.witness(UInt32, () => UInt32.from(15)); + let x32 = Provable.witness(UInt32, () => 15n); x32.div(4).assertEquals(UInt32.from(3)); } } From 4be56551b902b83bab220d96f9bf87402260e540 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 10:36:24 +0100 Subject: [PATCH 24/41] fix: struct returns empty --- src/lib/circuit_value.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 25edbeba79..5178a0fef3 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -421,6 +421,7 @@ function Struct< }; toJSON: (x: T) => J; fromJSON: (x: J) => T; + empty: () => T; } { class Struct_ { static type = provable(type); From ba70ad1d38f787b560720d5a23f539179f78798e Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 11:45:54 +0100 Subject: [PATCH 25/41] revert to fromValue as the entry point for defining what types to accept --- src/bindings | 2 +- src/lib/account_update.ts | 4 ++-- src/lib/circuit_value.ts | 13 +++++-------- src/lib/circuit_value.unit-test.ts | 8 ++++---- src/lib/field.ts | 2 +- src/lib/group.ts | 25 ++++++------------------- src/lib/group.unit-test.ts | 4 ++-- src/lib/hash.ts | 2 +- src/lib/provable.ts | 15 ++++++--------- src/lib/signature.ts | 2 +- 10 files changed, 29 insertions(+), 48 deletions(-) diff --git a/src/bindings b/src/bindings index 9bcf4e3f9d..b330104435 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 9bcf4e3f9dbaae8c7e006bd229852fc01b8afff0 +Subproject commit b33010443544d39a8f05bee59ee9550b52708030 diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index c82820be2b..00c2f721a9 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -33,7 +33,7 @@ import { MlArray } from './ml/base.js'; import { Signature, signFieldElement } from '../mina-signer/src/signature.js'; import { MlFieldConstArray } from './ml/fields.js'; import { transactionCommitments } from '../mina-signer/src/sign-zkapp-command.js'; -import { DeepProvableOrValue } from '../bindings/lib/provable-generic.js'; +import { From } from '../bindings/lib/provable-generic.js'; // external API export { AccountUpdate, Permissions, ZkappPublicInput }; @@ -1288,7 +1288,7 @@ class AccountUpdate implements Types.AccountUpdate { } static toValue = Types.AccountUpdate.toValue; static fromValue( - value: DeepProvableOrValue | AccountUpdate + value: From | AccountUpdate ): AccountUpdate { if (value instanceof AccountUpdate) return value; let accountUpdate = Types.AccountUpdate.fromValue(value); diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 5178a0fef3..560367f4f0 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -15,10 +15,7 @@ import type { IsPure, } from '../bindings/lib/provable-snarky.js'; import { Provable } from './provable.js'; -import { - DeepProvableOrValue, - InferValue, -} from 'src/bindings/lib/provable-generic.js'; +import { From, InferValue } from 'src/bindings/lib/provable-generic.js'; // external API export { @@ -414,7 +411,7 @@ function Struct< ): (new (value: T) => T) & { _isStruct: true } & (Pure extends true ? ProvablePure : Provable) & { - from: (value: DeepProvableOrValue) => T; + fromValue: (value: From) => T; toInput: (x: T) => { fields?: Field[] | undefined; packed?: [Field, number][] | undefined; @@ -431,7 +428,7 @@ function Struct< Object.assign(this, value); } - static from(value: DeepProvableOrValue): T { + static from(value: From): T { let x = this.type.fromValue(value as any); let struct = Object.create(this.prototype); return Object.assign(struct, x); @@ -514,8 +511,8 @@ function Struct< /** * `Provable.fromValue()` */ - static fromValue(v: V): T { - let value = this.type.fromValue(v); + static fromValue(v: From): T { + let value = this.type.fromValue(v as any); let struct = Object.create(this.prototype); return Object.assign(struct, value); } diff --git a/src/lib/circuit_value.unit-test.ts b/src/lib/circuit_value.unit-test.ts index cf6334729d..a0d02d888f 100644 --- a/src/lib/circuit_value.unit-test.ts +++ b/src/lib/circuit_value.unit-test.ts @@ -11,7 +11,7 @@ import { Field } from './core.js'; import { Bool } from './bool.js'; import assert from 'assert/strict'; import { FieldType } from './field.js'; -import { DeepProvableOrValue } from '../bindings/lib/provable-generic.js'; +import { From } from '../bindings/lib/provable-generic.js'; let type = provable({ nested: { a: Number, b: Boolean }, @@ -117,16 +117,16 @@ let myStructInput = { pk: { x: 4n, isOdd: true }, uint: [100n, 5n], }; -let myStruct = MyStructPure.from(myStructInput); +let myStruct = MyStructPure.fromValue(myStructInput); -type FlexibleStruct = DeepProvableOrValue; +type FlexibleStruct = From; myStruct satisfies FlexibleStruct; myStructInput satisfies FlexibleStruct; expect(myStruct).toBeInstanceOf(MyStructPure); expect(MyStructPure.toValue(myStruct)).toEqual(myStructInput); -let myStruct2 = MyStructPure.from(myStruct); +let myStruct2 = MyStructPure.fromValue(myStruct); expect(myStruct2).toBeInstanceOf(MyStructPure); expect(myStruct2).toEqual(myStruct); diff --git a/src/lib/field.ts b/src/lib/field.ts index dea70a8da5..a233589fa2 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -1143,7 +1143,7 @@ class Field { /** * `Provable.fromValue()` */ - static fromValue(x: bigint | Field) { + static fromValue(x: Field | bigint | number | string) { return Field.from(x); } diff --git a/src/lib/group.ts b/src/lib/group.ts index 526eaafe6b..391d6a9d54 100644 --- a/src/lib/group.ts +++ b/src/lib/group.ts @@ -169,9 +169,9 @@ class Group { }); let [, x, y] = Snarky.gates.ecAdd( - Group.fromCoordinates(x1.seal(), y1.seal()).#toTuple(), - Group.fromCoordinates(x2.seal(), y2.seal()).#toTuple(), - Group.fromCoordinates(x3, y3).#toTuple(), + Group.from(x1.seal(), y1.seal()).#toTuple(), + Group.from(x2.seal(), y2.seal()).#toTuple(), + Group.from(x3, y3).#toTuple(), inf.toField().value, same_x.value, s.value, @@ -292,21 +292,6 @@ class Group { * Coerces two x and y coordinates into a {@link Group} element. */ static from( - g: - | { - x: FieldVar | Field | number | string | bigint; - y: FieldVar | Field | number | string | bigint; - } - | Group - ) { - if (g instanceof Group) return g; - return new Group({ x: g.x, y: g.y }); - } - - /** - * Coerces two x and y coordinates into a {@link Group} element. - */ - static fromCoordinates( x: FieldVar | Field | number | string | bigint, y: FieldVar | Field | number | string | bigint ) { @@ -426,7 +411,9 @@ class Group { return { x: x.toBigInt(), y: y.toBigInt() }; } - static fromValue(g: { x: bigint; y: bigint }) { + static fromValue( + g: { x: bigint | number | Field; y: bigint | number | Field } | Group + ) { return new Group(g); } diff --git a/src/lib/group.unit-test.ts b/src/lib/group.unit-test.ts index d3bc2c143a..5a7102f078 100644 --- a/src/lib/group.unit-test.ts +++ b/src/lib/group.unit-test.ts @@ -18,8 +18,8 @@ test(Random.field, Random.field, (a, b, assert) => { } = Poseidon.hashToGroup([b])!; const zero = Group.zero; - const g1 = Group.fromCoordinates(x1, y1); - const g2 = Group.fromCoordinates(x2, y2); + const g1 = Group.from(x1, y1); + const g2 = Group.from(x2, y2); run(g1, g2, (x, y) => x.add(y), assert); run(g1.neg(), g2.neg(), (x, y) => x.add(y), assert); diff --git a/src/lib/hash.ts b/src/lib/hash.ts index 3fc6706942..35df092997 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -199,7 +199,7 @@ const TokenSymbolPure: ProvableExtended< }; class TokenSymbol extends Struct(TokenSymbolPure) { static from(value: string | TokenSymbol) { - return super.from(value) as TokenSymbol; + return super.fromValue(value) as TokenSymbol; } } diff --git a/src/lib/provable.ts b/src/lib/provable.ts index 041c14c0ae..d8453be2a3 100644 --- a/src/lib/provable.ts +++ b/src/lib/provable.ts @@ -24,10 +24,7 @@ import { constraintSystem, } from './provable-context.js'; import { isBool } from './bool.js'; -import { - DeepProvableOrValue, - InferValue, -} from 'src/bindings/lib/provable-generic.js'; +import { From, InferValue } from 'src/bindings/lib/provable-generic.js'; // external API export { Provable }; @@ -194,10 +191,10 @@ const Provable = { inCheckedComputation, }; -function witness< - A extends Provable, - T extends DeepProvableOrValue = DeepProvableOrValue ->(type: A, compute: () => T): InferProvable { +function witness, T extends From = From>( + type: A, + compute: () => T +): InferProvable { type S = InferProvable; let ctx = snarkContext.get(); @@ -596,7 +593,7 @@ function provableArray>( function from>( type: A, - value: DeepProvableOrValue + value: From ): InferProvable { return type.fromValue(value); } diff --git a/src/lib/signature.ts b/src/lib/signature.ts index 5db7e1c990..99c739b06e 100644 --- a/src/lib/signature.ts +++ b/src/lib/signature.ts @@ -239,7 +239,7 @@ class PublicKey extends CircuitValue { } static fromValue( this: T, - { x, isOdd }: PublicKey | { x: bigint; isOdd: boolean } + { x, isOdd }: { x: Field | bigint; isOdd: Bool | boolean } ): InstanceType { return PublicKey.from({ x: Field.from(x), isOdd: Bool(isOdd) }) as any; } From f2d0f406e7df4b605fecab875fc904d7e8837b72 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 12:21:14 +0100 Subject: [PATCH 26/41] implement circuit string with struct --- src/lib/string.ts | 66 +++++++++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 22 deletions(-) diff --git a/src/lib/string.ts b/src/lib/string.ts index 623aa4a72e..849222cb6b 100644 --- a/src/lib/string.ts +++ b/src/lib/string.ts @@ -1,5 +1,5 @@ import { Bool, Field } from '../lib/core.js'; -import { arrayProp, CircuitValue, prop } from './circuit_value.js'; +import { provable, Struct } from './circuit_value.js'; import { Provable } from './provable.js'; import { Poseidon } from './hash.js'; @@ -7,11 +7,13 @@ export { Character, CircuitString }; const DEFAULT_STRING_LENGTH = 128; -class Character extends CircuitValue { - @prop value: Field; +class Character extends Struct({ value: Field }) { + constructor(value: Field | number) { + super({ value: Field(value) }); + } isNull(): Bool { - return this.equals(NullCharacter() as this); + return this.value.equals(NullCharacter().value); } toField(): Field { @@ -24,30 +26,47 @@ class Character extends CircuitValue { } static fromString(str: string) { - const char = Field(str.charCodeAt(0)); - return new Character(char); + return new Character(str.charCodeAt(0)); } // TODO: Add support for more character sets // right now it's 16 bits because 8 not supported :/ - static check(c: Character) { + static check(c: { value: Field }) { c.value.rangeCheckHelper(16).assertEquals(c.value); } } -class CircuitString extends CircuitValue { +// construct a provable with a `string` js type +const RawCircuitString = { + ...provable({ values: Provable.Array(Character, DEFAULT_STRING_LENGTH) }), + + toValue({ values }) { + return values + .map((x) => x.toString()) + .join('') + .replace(/[^ -~]+/g, ''); + }, + + fromValue(value) { + if (typeof value === 'object') return value; + return { + values: fillWithNull( + value.split('').map((x) => Character.fromString(x)), + DEFAULT_STRING_LENGTH + ), + }; + }, +} satisfies Provable<{ values: Character[] }, string>; + +// by using a custom provable as struct input, we override its inference of the js type +// otherwise the js type would be `{ values: { value: bigint }[] }` instead of `string` +// TODO: create an API for this that is better integrated with `Struct` +class CircuitString extends Struct(RawCircuitString) { static maxLength = DEFAULT_STRING_LENGTH; - @arrayProp(Character, DEFAULT_STRING_LENGTH) values: Character[]; - // constructor is private because - // * we do not want extra logic inside CircuitValue constructors, as a general pattern (to be able to create them generically) - // * here, not running extra logic to fill up the characters would be wrong - private constructor(values: Character[]) { - super(values); - } // this is the publicly accessible constructor static fromCharacters(chars: Character[]): CircuitString { - return new CircuitString(fillWithNull(chars, this.maxLength)); + return new CircuitString({ values: fillWithNull(chars, this.maxLength) }); } private maxLength() { @@ -81,6 +100,13 @@ class CircuitString extends CircuitValue { return (this as any)._length ?? this.computeLengthAndMask().length; } + /** + * returns true if `this` has the same value as `other` + */ + equals(other: CircuitString) { + return Provable.equal(CircuitString, this, other); + } + /** * appends another string to this one, returns the result and proves that it fits * within the `maxLength` of this string (the other string can have a different maxLength) @@ -130,18 +156,14 @@ class CircuitString extends CircuitValue { } toString(): string { - return this.values - .map((x) => x.toString()) - .join('') - .replace(/[^ -~]+/g, ''); + return CircuitString.toValue(this); } static fromString(str: string): CircuitString { if (str.length > this.maxLength) { throw Error('CircuitString.fromString: input string exceeds max length!'); } - let characters = str.split('').map((x) => Character.fromString(x)); - return CircuitString.fromCharacters(characters); + return new CircuitString(CircuitString.fromValue(str)); } } From cf057201eb5db887ac9007b5eef6c49dcf3878f4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 12:22:17 +0100 Subject: [PATCH 27/41] move string test to a node script --- .../{string.test.ts => string.unit-test.ts} | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) rename src/lib/{string.test.ts => string.unit-test.ts} (93%) diff --git a/src/lib/string.test.ts b/src/lib/string.unit-test.ts similarity index 93% rename from src/lib/string.test.ts rename to src/lib/string.unit-test.ts index a1c11c1869..5e2ffb0e9f 100644 --- a/src/lib/string.test.ts +++ b/src/lib/string.unit-test.ts @@ -1,17 +1,8 @@ -import { - Bool, - Character, - Provable, - CircuitString, - Field, - shutdown, - isReady, -} from 'o1js'; +import { Bool, Character, Provable, CircuitString, Field } from 'o1js'; +import { describe, test } from 'node:test'; +import { expect } from 'expect'; describe('Circuit String', () => { - beforeEach(() => isReady); - afterAll(() => setTimeout(shutdown, 0)); - describe('#equals', () => { test('returns true when values are equal', () => { const str = CircuitString.fromString( @@ -36,15 +27,12 @@ describe('Circuit String', () => { test('returns false when values are not equal', () => { const str = CircuitString.fromString('Your size'); const not_same_str = CircuitString.fromString('size'); + expect(str.equals(not_same_str)).toEqual(Bool(false)); Provable.runAndCheck(() => { - const str = Provable.witness(CircuitString, () => { - return CircuitString.fromString('Your size'); - }); - const not_same_str = Provable.witness(CircuitString, () => { - return CircuitString.fromString('size'); - }); + const str = Provable.witness(CircuitString, () => 'Your size'); + const not_same_str = Provable.witness(CircuitString, () => 'size'); Provable.asProver(() => { expect(str.equals(not_same_str).toBoolean()).toEqual(false); }); @@ -174,8 +162,8 @@ describe('Circuit String', () => { }); }); */ - describe('with invalid input', () => { - test.skip('cannot use a character out of range', () => { + describe.skip('with invalid input', () => { + test('cannot use a character out of range', () => { expect(() => { Provable.runAndCheck(() => { const str = Provable.witness(CircuitString, () => { From 5e464b67e9d42d81a541eabbe508a009a9c370fb Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 12:47:35 +0100 Subject: [PATCH 28/41] demonstrate mixing of types --- src/lib/circuit_value.unit-test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/circuit_value.unit-test.ts b/src/lib/circuit_value.unit-test.ts index a0d02d888f..709eb91cc0 100644 --- a/src/lib/circuit_value.unit-test.ts +++ b/src/lib/circuit_value.unit-test.ts @@ -112,10 +112,10 @@ class MyStructPure extends Struct({ // Struct.from() works on both js and provable inputs let myStructInput = { - nested: { a: 1n, b: 2n }, + nested: { a: Field(1), b: 2n }, other: 3n, pk: { x: 4n, isOdd: true }, - uint: [100n, 5n], + uint: [100n, UInt32.zero], }; let myStruct = MyStructPure.fromValue(myStructInput); From dff87697c1ba5515ebd2edd6bd27f815f6560431 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 13:13:22 +0100 Subject: [PATCH 29/41] fixup test --- src/lib/circuit_value.unit-test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/circuit_value.unit-test.ts b/src/lib/circuit_value.unit-test.ts index 709eb91cc0..7847994cce 100644 --- a/src/lib/circuit_value.unit-test.ts +++ b/src/lib/circuit_value.unit-test.ts @@ -124,7 +124,12 @@ myStruct satisfies FlexibleStruct; myStructInput satisfies FlexibleStruct; expect(myStruct).toBeInstanceOf(MyStructPure); -expect(MyStructPure.toValue(myStruct)).toEqual(myStructInput); +expect(MyStructPure.toValue(myStruct)).toEqual({ + nested: { a: 1n, b: 2n }, + other: 3n, + pk: { x: 4n, isOdd: true }, + uint: [100n, 0n], +}); let myStruct2 = MyStructPure.fromValue(myStruct); expect(myStruct2).toBeInstanceOf(MyStructPure); From fde5ddf67ea4439eea25da2eec2c7c9a94450750 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 14:08:01 +0100 Subject: [PATCH 30/41] fixup (don't ask me what is happening here) --- src/lib/hash.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/hash.ts b/src/lib/hash.ts index 35df092997..932d5b96fe 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -199,7 +199,7 @@ const TokenSymbolPure: ProvableExtended< }; class TokenSymbol extends Struct(TokenSymbolPure) { static from(value: string | TokenSymbol) { - return super.fromValue(value) as TokenSymbol; + return TokenSymbol.fromValue(value) as TokenSymbol; } } From 12dd7a195555e93940e07c6b966349dc4064611c Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 21:40:10 +0100 Subject: [PATCH 31/41] fix, simplification --- src/lib/circuit_value.ts | 2 +- src/lib/provable.ts | 14 ++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 560367f4f0..788ef42139 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -15,7 +15,7 @@ import type { IsPure, } from '../bindings/lib/provable-snarky.js'; import { Provable } from './provable.js'; -import { From, InferValue } from 'src/bindings/lib/provable-generic.js'; +import { From, InferValue } from '../bindings/lib/provable-generic.js'; // external API export { diff --git a/src/lib/provable.ts b/src/lib/provable.ts index d8453be2a3..98a3ce2d6b 100644 --- a/src/lib/provable.ts +++ b/src/lib/provable.ts @@ -35,7 +35,6 @@ export { MemoizationContext, memoizeWitness, getBlindingValue, - from, }; // TODO move type declaration here @@ -200,7 +199,7 @@ function witness, T extends From = From>( // outside provable code, we just call the callback and return its cloned result if (!inCheckedComputation() || ctx.inWitnessBlock) { - return clone(type, from(type, compute())); + return clone(type, type.fromValue(compute())); } let proverValue: S | undefined = undefined; let fields: Field[]; @@ -208,7 +207,7 @@ function witness, T extends From = From>( let id = snarkContext.enter({ ...ctx, inWitnessBlock: true }); try { let [, ...fieldVars] = Snarky.exists(type.sizeInFields(), () => { - proverValue = from(type, compute()); + proverValue = type.fromValue(compute()); let fields = type.toFields(proverValue); let fieldConstants = fields.map((x) => x.toConstant().value[1]); @@ -588,12 +587,3 @@ function provableArray>( }, } satisfies ProvableExtended as any; } - -// generic flexible `from()` function - -function from>( - type: A, - value: From -): InferProvable { - return type.fromValue(value); -} From ed07f64caff406cf32e864a2a22a5eb50474c37d Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 28 Nov 2023 21:50:27 +0100 Subject: [PATCH 32/41] make value type argument optional & revert `any` explosion --- src/lib/circuit.ts | 8 +++----- src/lib/circuit_value.ts | 15 ++++---------- src/lib/precondition.ts | 6 +++--- src/lib/proof_system.ts | 43 +++++++++++++++++++--------------------- src/lib/provable.ts | 26 ++++++++++++------------ src/lib/state.ts | 6 +++--- src/lib/zkapp.ts | 20 +++++++++---------- src/snarky.d.ts | 4 ++-- 8 files changed, 57 insertions(+), 71 deletions(-) diff --git a/src/lib/circuit.ts b/src/lib/circuit.ts index 420f61a0b5..2094f5e91f 100644 --- a/src/lib/circuit.ts +++ b/src/lib/circuit.ts @@ -204,8 +204,8 @@ function public_(target: any, _key: string | symbol, index: number) { type CircuitData = { main(publicInput: P, privateInput: W): void; - publicInputType: ProvablePure; - privateInputType: ProvablePure; + publicInputType: ProvablePure

; + privateInputType: ProvablePure; }; function mainFromCircuitData( @@ -266,9 +266,7 @@ function circuitMain( } // TODO support auxiliary data -function provableFromTuple( - typs: ProvablePure[] -): ProvablePure { +function provableFromTuple(typs: ProvablePure[]): ProvablePure { return { sizeInFields: () => { return typs.reduce((acc, typ) => acc + typ.sizeInFields(), 0); diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 788ef42139..7a8dcdbf33 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -65,8 +65,8 @@ type Struct = ProvableExtended> & Constructor & { _isStruct: true }; type StructPure = ProvablePureExtended> & Constructor & { _isStruct: true }; -type FlexibleProvable = Provable | Struct; -type FlexibleProvablePure = ProvablePure | StructPure; +type FlexibleProvable = Provable | Struct; +type FlexibleProvablePure = ProvablePure | StructPure; type Constructor = new (...args: any) => T; type AnyConstructor = Constructor; @@ -427,13 +427,6 @@ function Struct< constructor(value: T) { Object.assign(this, value); } - - static from(value: From): T { - let x = this.type.fromValue(value as any); - let struct = Object.create(this.prototype); - return Object.assign(struct, x); - } - /** * This method is for internal use, you will probably not need it. * @returns the size of this struct in field elements @@ -651,7 +644,7 @@ function circuitValueEquals(a: T, b: T): boolean { } function toConstant(type: FlexibleProvable, value: T): T; -function toConstant(type: Provable, value: T): T { +function toConstant(type: Provable, value: T): T { return type.fromFields( type.toFields(value).map((x) => x.toConstant()), type.toAuxiliary(value) @@ -659,6 +652,6 @@ function toConstant(type: Provable, value: T): T { } function isConstant(type: FlexibleProvable, value: T): boolean; -function isConstant(type: Provable, value: T): boolean { +function isConstant(type: Provable, value: T): boolean { return type.toFields(value).every((x) => x.isConstant()); } diff --git a/src/lib/precondition.ts b/src/lib/precondition.ts index d7733568bf..25b10b9031 100644 --- a/src/lib/precondition.ts +++ b/src/lib/precondition.ts @@ -206,7 +206,7 @@ function preconditionSubClassWithRange< >( accountUpdate: AccountUpdate, longKey: K, - fieldType: Provable, + fieldType: Provable, context: PreconditionContext ) { return { @@ -233,7 +233,7 @@ function preconditionSubclass< >( accountUpdate: AccountUpdate, longKey: K, - fieldType: Provable, + fieldType: Provable, context: PreconditionContext ) { if (fieldType === undefined) { @@ -295,7 +295,7 @@ function preconditionSubclass< function getVariable( accountUpdate: AccountUpdate, longKey: K, - fieldType: Provable + fieldType: Provable ): U { return Provable.witness(fieldType, () => { let [accountOrNetwork, ...rest] = longKey.split('.'); diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 0d9a9e8738..8e1dc280a6 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -298,8 +298,8 @@ function ZkProgram< >; } { let methods = config.methods; - let publicInputType: ProvablePure = config.publicInput ?? Undefined; - let publicOutputType: ProvablePure = config.publicOutput ?? Void; + let publicInputType: ProvablePure = config.publicInput ?? Undefined; + let publicOutputType: ProvablePure = config.publicOutput ?? Void; let selfTag = { name: config.name }; type PublicInput = InferProvableOrUndefined< @@ -491,7 +491,7 @@ function sortMethodArguments( privateInputs: unknown[], selfProof: Subclass ): MethodInterface { - let witnessArgs: Provable[] = []; + let witnessArgs: Provable[] = []; let proofArgs: Subclass[] = []; let allArgs: { type: 'proof' | 'witness' | 'generic'; index: number }[] = []; let genericArgs: Subclass[] = []; @@ -541,7 +541,7 @@ function sortMethodArguments( function isAsFields( type: unknown -): type is Provable & ObjectConstructor { +): type is Provable & ObjectConstructor { return ( (typeof type === 'function' || typeof type === 'object') && type !== null && @@ -592,11 +592,11 @@ type MethodInterface = { methodName: string; // TODO: unify types of arguments // "circuit types" should be flexible enough to encompass proofs and callback arguments - witnessArgs: Provable[]; + witnessArgs: Provable[]; proofArgs: Subclass[]; genericArgs: Subclass[]; allArgs: { type: 'witness' | 'proof' | 'generic'; index: number }[]; - returnType?: Provable; + returnType?: Provable; }; // reasonable default choice for `overrideWrapDomain` @@ -613,8 +613,8 @@ async function compileProgram({ forceRecompile, overrideWrapDomain, }: { - publicInputType: ProvablePure; - publicOutputType: ProvablePure; + publicInputType: ProvablePure; + publicOutputType: ProvablePure; methodIntfs: MethodInterface[]; methods: ((...args: any) => void)[]; gates: Gate[][]; @@ -713,7 +713,7 @@ async function compileProgram({ } function analyzeMethod( - publicInputType: ProvablePure, + publicInputType: ProvablePure, methodIntf: MethodInterface, method: (...args: any) => T ) { @@ -727,8 +727,8 @@ function analyzeMethod( } function picklesRuleFromFunction( - publicInputType: ProvablePure, - publicOutputType: ProvablePure, + publicInputType: ProvablePure, + publicOutputType: ProvablePure, func: (...args: unknown[]) => any, proofSystemTag: { name: string }, { methodName, witnessArgs, proofArgs, allArgs }: MethodInterface, @@ -867,7 +867,7 @@ function methodArgumentsToConstant( let Generic = EmptyNull(); -type TypeAndValue = { type: Provable; value: T }; +type TypeAndValue = { type: Provable; value: T }; function methodArgumentTypesAndValues( { allArgs, proofArgs, witnessArgs }: MethodInterface, @@ -895,7 +895,7 @@ function methodArgumentTypesAndValues( } function emptyValue(type: FlexibleProvable): T; -function emptyValue(type: Provable) { +function emptyValue(type: Provable) { return type.fromFields( Array(type.sizeInFields()).fill(Field(0)), type.toAuxiliary() @@ -903,7 +903,7 @@ function emptyValue(type: Provable) { } function emptyWitness(type: FlexibleProvable): T; -function emptyWitness(type: Provable) { +function emptyWitness(type: Provable) { return Provable.witness(type, () => emptyValue(type)); } @@ -911,7 +911,7 @@ function getStatementType< T, O, P extends Subclass = typeof Proof ->(Proof: P): { input: ProvablePure; output: ProvablePure } { +>(Proof: P): { input: ProvablePure; output: ProvablePure } { if ( Proof.publicInputType === undefined || Proof.publicOutputType === undefined @@ -934,20 +934,17 @@ function getMaxProofsVerified(methodIntfs: MethodInterface[]) { ) as any as 0 | 1 | 2; } -function fromFieldVars(type: ProvablePure, fields: MlFieldArray) { +function fromFieldVars(type: ProvablePure, fields: MlFieldArray) { return type.fromFields(MlFieldArray.from(fields)); } -function toFieldVars(type: ProvablePure, value: T) { +function toFieldVars(type: ProvablePure, value: T) { return MlFieldArray.to(type.toFields(value)); } -function fromFieldConsts( - type: ProvablePure, - fields: MlFieldConstArray -) { +function fromFieldConsts(type: ProvablePure, fields: MlFieldConstArray) { return type.fromFields(MlFieldConstArray.from(fields)); } -function toFieldConsts(type: ProvablePure, value: T) { +function toFieldConsts(type: ProvablePure, value: T) { return MlFieldConstArray.to(type.toFields(value)); } @@ -1059,7 +1056,7 @@ type Subclass any> = (new ( [K in keyof Class]: Class[K]; } & { prototype: InstanceType }; -type PrivateInput = Provable | Subclass; +type PrivateInput = Provable | Subclass; type Method< PublicInput, diff --git a/src/lib/provable.ts b/src/lib/provable.ts index 98a3ce2d6b..ab9b2b8269 100644 --- a/src/lib/provable.ts +++ b/src/lib/provable.ts @@ -24,7 +24,7 @@ import { constraintSystem, } from './provable-context.js'; import { isBool } from './bool.js'; -import { From, InferValue } from 'src/bindings/lib/provable-generic.js'; +import { From, InferValue } from '../bindings/lib/provable-generic.js'; // external API export { Provable }; @@ -43,7 +43,7 @@ export { * * You will find this as the required input type in a few places in o1js. One convenient way to create a `Provable` is using `Struct`. */ -type Provable = Provable_; +type Provable = Provable_; const Provable = { /** @@ -229,7 +229,7 @@ function witness, T extends From = From>( // rebuild the value from its fields (which are now variables) and aux data let aux = type.toAuxiliary(proverValue); - let value = (type as Provable).fromFields(fields, aux); + let value = (type as Provable).fromFields(fields, aux); // add type-specific constraints type.check(value); @@ -258,7 +258,7 @@ function assertEqualImplicit(x: T, y: T) { xs[i].assertEquals(ys[i]); } } -function assertEqualExplicit(type: Provable, x: T, y: T) { +function assertEqualExplicit(type: Provable, x: T, y: T) { let xs = type.toFields(x); let ys = type.toFields(y); for (let i = 0; i < xs.length; i++) { @@ -284,7 +284,7 @@ function equalImplicit(x: T, y: T) { checkLength('Provable.equal', xs, ys); return xs.map((x, i) => x.equals(ys[i])).reduce(Bool.and); } -function equalExplicit(type: Provable, x: T, y: T) { +function equalExplicit(type: Provable, x: T, y: T) { let xs = type.toFields(x); let ys = type.toFields(y); return xs.map((x, i) => x.equals(ys[i])).reduce(Bool.and); @@ -309,7 +309,7 @@ function ifField(b: Field, x: Field, y: Field) { return b.mul(x.sub(y)).add(y).seal(); } -function ifExplicit(condition: Bool, type: Provable, x: T, y: T): T { +function ifExplicit(condition: Bool, type: Provable, x: T, y: T): T { let xs = type.toFields(x); let ys = type.toFields(y); let b = condition.toField(); @@ -352,7 +352,7 @@ function ifImplicit(condition: Bool, x: T, y: T): T { `Provable.if(bool, MyType, x, y)` ); } - return ifExplicit(condition, type as any as Provable, x, y); + return ifExplicit(condition, type as any as Provable, x, y); } function switch_>( @@ -386,12 +386,12 @@ function switch_>( fields[j] = fields[j].add(maybeField); } } - let aux = auxiliary(type as Provable, () => { + let aux = auxiliary(type as Provable, () => { let i = mask.findIndex((b) => b.toBoolean()); if (i === -1) return undefined; return values[i]; }); - return (type as Provable).fromFields(fields, aux); + return (type as Provable).fromFields(fields, aux); } // logging in provable code @@ -429,10 +429,10 @@ function checkLength(name: string, xs: Field[], ys: Field[]) { function clone>(type: S, value: T): T { let fields = type.toFields(value); let aux = type.toAuxiliary?.(value) ?? []; - return (type as Provable).fromFields(fields, aux); + return (type as Provable).fromFields(fields, aux); } -function auxiliary(type: Provable, compute: () => T | undefined) { +function auxiliary(type: Provable, compute: () => T | undefined) { let aux; // TODO: this accepts types without .toAuxiliary(), should be changed when all snarky types are moved to TS Provable.asProver(() => { @@ -456,7 +456,7 @@ let memoizationContext = Context.create(); * for reuse by the prover. This is needed to witness non-deterministic values. */ function memoizeWitness(type: FlexibleProvable, compute: () => T) { - return Provable.witness(type as Provable, () => { + return Provable.witness(type as Provable, () => { if (!memoizationContext.has()) return compute(); let context = memoizationContext.get(); let { memoized, currentIndex } = context; @@ -469,7 +469,7 @@ function memoizeWitness(type: FlexibleProvable, compute: () => T) { memoized[currentIndex] = currentValue; } context.currentIndex += 1; - return (type as Provable).fromFields( + return (type as Provable).fromFields( currentValue.fields, currentValue.aux ); diff --git a/src/lib/state.ts b/src/lib/state.ts index 0dbc93fa07..dc848a1996 100644 --- a/src/lib/state.ts +++ b/src/lib/state.ts @@ -125,7 +125,7 @@ function state(stateType: FlexibleProvablePure) { if (this._?.[key]) throw Error('A @state should only be assigned once'); v._contract = { key, - stateType: stateType as ProvablePure, + stateType: stateType as ProvablePure, instance: this, class: ZkappClass, wasConstrained: false, @@ -185,7 +185,7 @@ function declareState( // metadata defined by @state, which link state to a particular SmartContract type StateAttachedContract = { key: string; - stateType: ProvablePure; + stateType: ProvablePure; instance: SmartContract; class: typeof SmartContract; wasRead: boolean; @@ -399,7 +399,7 @@ function getLayout(scClass: typeof SmartContract) { const smartContracts = new WeakMap< typeof SmartContract, { - states: [string, ProvablePure][]; + states: [string, ProvablePure][]; layout: Map | undefined; } >(); diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index bff35b2877..a0df136baf 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -96,12 +96,12 @@ function method( `@method decorator was applied to \`${methodName}\`, which is not a function.` ); } - let paramTypes: Provable[] = Reflect.getMetadata( + let paramTypes: Provable[] = Reflect.getMetadata( 'design:paramtypes', target, methodName ); - let returnType: Provable = Reflect.getMetadata( + let returnType: Provable = Reflect.getMetadata( 'design:returntype', target, methodName @@ -539,7 +539,7 @@ function computeCallData( class Callback extends GenericArgument { instance: SmartContract; - methodIntf: MethodInterface & { returnType: Provable }; + methodIntf: MethodInterface & { returnType: Provable }; args: any[]; result?: Result; @@ -1060,7 +1060,7 @@ super.init(); { type: string; event: { - data: ProvablePure; + data: ProvablePure; transactionInfo: { transactionHash: string; transactionStatus: string; @@ -1276,7 +1276,7 @@ type ReducerReturn = { */ reduce( actions: Action[][], - stateType: Provable, + stateType: Provable, reduce: (state: State, action: Action) => State, initial: { state: State; actionState: Field }, options?: { @@ -1353,7 +1353,7 @@ class ${contract.constructor.name} extends SmartContract { reduce( actionLists: A[][], - stateType: Provable, + stateType: Provable, reduce: (state: S, action: A) => S, { state, actionState }: { state: S; actionState: Field }, { @@ -1458,7 +1458,7 @@ Use the optional \`maxTransactionsWithActions\` argument to increase this number actionsForAccount = actions.map((event) => // putting our string-Fields back into the original action type event.actions.map((action) => - (reducer.actionType as ProvablePure).fromFields( + (reducer.actionType as ProvablePure).fromFields( action.map(Field) ) ) @@ -1481,9 +1481,7 @@ Use the optional \`maxTransactionsWithActions\` argument to increase this number return result.map((event) => // putting our string-Fields back into the original action type event.actions.map((action) => - (reducer.actionType as ProvablePure).fromFields( - action.map(Field) - ) + (reducer.actionType as ProvablePure).fromFields(action.map(Field)) ) ); }, @@ -1547,7 +1545,7 @@ function Account(address: PublicKey, tokenId?: Field) { */ function declareMethods( SmartContract: T, - methodArguments: Record[]> + methodArguments: Record[]> ) { for (let key in methodArguments) { let argumentTypes = methodArguments[key]; diff --git a/src/snarky.d.ts b/src/snarky.d.ts index ebc30be933..76570cde00 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -47,7 +47,7 @@ export { * * The properties and methods on the provable type exist in all base o1js types as well (aka. {@link Field}, {@link Bool}, etc.). In most cases, a zkApp developer does not need these functions to create zkApps. */ -declare interface Provable { +declare interface Provable { /** * A function that takes `value`, an element of type `T`, as argument and returns an array of {@link Field} elements that make up the provable data of `value`. * @@ -116,7 +116,7 @@ declare interface Provable { * * It includes the same properties and methods as the {@link Provable} interface. */ -declare interface ProvablePure extends Provable { +declare interface ProvablePure extends Provable { /** * A function that takes `value` (optional), an element of type `T`, as argument and returns an array of any type that make up the "auxiliary" (non-provable) data of `value`. * As any element of the interface `ProvablePure` includes no "auxiliary" data by definition, this function always returns a default value. From 60acef028238b5dcf0c9c72d8be4f69ac81b3ae5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 17 Apr 2024 16:43:38 +0200 Subject: [PATCH 33/41] fixup: lost witness() changes --- src/lib/provable/types/witness.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/lib/provable/types/witness.ts b/src/lib/provable/types/witness.ts index c432eac1b3..da219279ab 100644 --- a/src/lib/provable/types/witness.ts +++ b/src/lib/provable/types/witness.ts @@ -1,31 +1,33 @@ import type { Field } from '../field.js'; -import type { FlexibleProvable } from './struct.js'; +import type { FlexibleProvable, InferProvable } from './struct.js'; import type { Provable } from './provable-intf.js'; import { inCheckedComputation, snarkContext, } from '../core/provable-context.js'; import { exists, existsAsync } from '../core/exists.js'; +import { From } from '../../../bindings/lib/provable-generic.js'; export { witness, witnessAsync }; -function witness = FlexibleProvable>( - type: S, +function witness, T extends From = From>( + type: A, compute: () => T -): T { +): InferProvable { + type S = InferProvable; let ctx = snarkContext.get(); // outside provable code, we just call the callback and return its cloned result if (!inCheckedComputation() || ctx.inWitnessBlock) { - return clone(type, compute()); + return clone(type, type.fromValue(compute())); } - let proverValue: T | undefined = undefined; + let proverValue: S | undefined = undefined; let fields: Field[]; let id = snarkContext.enter({ ...ctx, inWitnessBlock: true }); try { fields = exists(type.sizeInFields(), () => { - proverValue = compute(); + proverValue = type.fromValue(compute()); let fields = type.toFields(proverValue); return fields.map((x) => x.toBigInt()); }); @@ -35,7 +37,7 @@ function witness = FlexibleProvable>( // rebuild the value from its fields (which are now variables) and aux data let aux = type.toAuxiliary(proverValue); - let value = (type as Provable).fromFields(fields, aux); + let value = (type as Provable).fromFields(fields, aux); // add type-specific constraints type.check(value); From e3a97af5813397dbbf80dbd08d46ba2cfc5ae441 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 17 Apr 2024 16:53:39 +0200 Subject: [PATCH 34/41] fix build --- src/bindings | 2 +- src/lib/mina/token/forest-iterator.unit-test.ts | 4 ++-- src/lib/proof-system/circuit.ts | 4 ++-- src/lib/provable/crypto/signature.ts | 2 +- src/lib/provable/foreign-field.ts | 14 ++++++++++---- src/lib/provable/gadgets/arithmetic.ts | 3 ++- src/lib/provable/provable.ts | 4 ++-- src/lib/provable/scalar.ts | 2 +- src/lib/provable/test/foreign-field.unit-test.ts | 6 +++--- src/lib/provable/types/auxiliary.ts | 2 ++ src/lib/provable/types/fields.ts | 10 ++++++++-- src/lib/provable/types/provable-derivers.ts | 7 ++++++- src/lib/provable/types/unconstrained.ts | 6 +++++- 13 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/bindings b/src/bindings index d1def14af0..d7e194f29f 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit d1def14af069d6afc1acdc49dd5bd73feff7c531 +Subproject commit d7e194f29f720f62f70b5ca9d6d449fa0cd28947 diff --git a/src/lib/mina/token/forest-iterator.unit-test.ts b/src/lib/mina/token/forest-iterator.unit-test.ts index 3a1ded5ea4..a41a5edbcb 100644 --- a/src/lib/mina/token/forest-iterator.unit-test.ts +++ b/src/lib/mina/token/forest-iterator.unit-test.ts @@ -33,8 +33,8 @@ const accountUpdateBigint = Random.map( // ensure that, by default, all account updates are token-accessible a.body.mayUseToken = a.body.callDepth === 0 - ? { parentsOwnToken: 1n, inheritFromParent: 0n } - : { parentsOwnToken: 0n, inheritFromParent: 1n }; + ? { parentsOwnToken: true, inheritFromParent: false } + : { parentsOwnToken: false, inheritFromParent: true }; return a; } ); diff --git a/src/lib/proof-system/circuit.ts b/src/lib/proof-system/circuit.ts index 927e345d0f..631dd99625 100644 --- a/src/lib/proof-system/circuit.ts +++ b/src/lib/proof-system/circuit.ts @@ -270,11 +270,11 @@ function provableFromTuple( }, toValue(x) { - return typs.map((typ, i) => typ.toValue(x[i])); + return types.map((typ, i) => typ.toValue(x[i])); }, fromValue(x) { - return typs.map((typ, i) => typ.fromValue(x[i])); + return types.map((typ, i) => typ.fromValue(x[i])); }, }; } diff --git a/src/lib/provable/crypto/signature.ts b/src/lib/provable/crypto/signature.ts index 1d63a649e3..d1f0cced8a 100644 --- a/src/lib/provable/crypto/signature.ts +++ b/src/lib/provable/crypto/signature.ts @@ -220,7 +220,7 @@ class PublicKey extends CircuitValue { x = toConstantField(x, 'toBase58', 'pk', 'public key'); return PublicKeyBigint.toBase58({ x: x.toBigInt(), - isOdd: isOdd.toBoolean() ? 1n : 0n, + isOdd: isOdd.toBoolean(), }); } diff --git a/src/lib/provable/foreign-field.ts b/src/lib/provable/foreign-field.ts index e9f658682b..c37297d43d 100644 --- a/src/lib/provable/foreign-field.ts +++ b/src/lib/provable/foreign-field.ts @@ -454,7 +454,7 @@ class UnreducedForeignField extends ForeignField { type: 'Unreduced' | 'AlmostReduced' | 'FullyReduced' = 'Unreduced'; static _provable: - | ProvablePureExtended + | ProvablePureExtended | undefined = undefined; static get provable() { assert(this._provable !== undefined, 'ForeignField class not initialized.'); @@ -474,7 +474,7 @@ class AlmostForeignField extends ForeignFieldWithMul { } static _provable: - | ProvablePureExtended + | ProvablePureExtended | undefined = undefined; static get provable() { assert(this._provable !== undefined, 'ForeignField class not initialized.'); @@ -516,7 +516,7 @@ class CanonicalForeignField extends ForeignFieldWithMul { } static _provable: - | ProvablePureExtended + | ProvablePureExtended | undefined = undefined; static get provable() { assert(this._provable !== undefined, 'ForeignField class not initialized.'); @@ -696,7 +696,7 @@ type Constructor = new (...args: any[]) => T; function provable( Class: Constructor & { check(x: ForeignField): void } -): ProvablePureExtended { +): ProvablePureExtended { return { toFields(x) { return x.value; @@ -714,6 +714,12 @@ function provable( check(x: ForeignField) { Class.check(x); }, + toValue(x) { + return x.toBigInt(); + }, + fromValue(x) { + return new Class(x); + }, // ugh toJSON(x: ForeignField) { return x.toBigInt().toString(); diff --git a/src/lib/provable/gadgets/arithmetic.ts b/src/lib/provable/gadgets/arithmetic.ts index c5d1bc75f3..3f49fb610b 100644 --- a/src/lib/provable/gadgets/arithmetic.ts +++ b/src/lib/provable/gadgets/arithmetic.ts @@ -28,7 +28,8 @@ function divMod32(n: Field, quotientBits = 32) { let nBigInt = n.toBigInt(); let q = nBigInt >> 32n; let r = nBigInt - (q << 32n); - return [new Field(q), new Field(r)]; + // why do we have to do this? + return [q, r] satisfies [bigint, bigint]; } ); diff --git a/src/lib/provable/provable.ts b/src/lib/provable/provable.ts index cc23f64fb7..3993f08a24 100644 --- a/src/lib/provable/provable.ts +++ b/src/lib/provable/provable.ts @@ -22,7 +22,7 @@ import { generateWitness, } from './core/provable-context.js'; import { witness, witnessAsync } from './types/witness.js'; -import { From, InferValue } from '../../bindings/lib/provable-generic.js'; +import { InferValue } from '../../bindings/lib/provable-generic.js'; // external API export { Provable }; @@ -447,7 +447,7 @@ let memoizationContext = Context.create(); * for reuse by the prover. This is needed to witness non-deterministic values. */ function memoizeWitness(type: FlexibleProvable, compute: () => T) { - return Provable.witness(type as Provable, () => { + return Provable.witness(type as Provable, () => { if (!memoizationContext.has()) return compute(); let context = memoizationContext.get(); let { memoized, currentIndex } = context; diff --git a/src/lib/provable/scalar.ts b/src/lib/provable/scalar.ts index 61eec7c037..2277b304d3 100644 --- a/src/lib/provable/scalar.ts +++ b/src/lib/provable/scalar.ts @@ -76,7 +76,7 @@ class Scalar implements ShiftedScalar { * * See {@link FieldVar} for an explanation of constants vs. variables. */ - toConstant() { + toConstant(): Scalar { if (this.isConstant()) return this; return Provable.toConstant(Scalar, this); } diff --git a/src/lib/provable/test/foreign-field.unit-test.ts b/src/lib/provable/test/foreign-field.unit-test.ts index fb8872ab13..6d31f3a638 100644 --- a/src/lib/provable/test/foreign-field.unit-test.ts +++ b/src/lib/provable/test/foreign-field.unit-test.ts @@ -1,5 +1,5 @@ import { Field } from '../wrapped.js'; -import { createForeignField } from '../foreign-field.js'; +import { AlmostForeignField, createForeignField } from '../foreign-field.js'; import { Fq } from '../../../bindings/crypto/finite-field.js'; import { expect } from 'expect'; import { @@ -55,13 +55,13 @@ test(Random.scalar, (x0, assert) => { let f = spec({ rng: Random.scalar, there: ForeignScalar.from, - back: (x) => x.toBigInt(), + back: (x: AlmostForeignField) => x.toBigInt(), provable: ForeignScalar.AlmostReduced.provable, }); let u264 = spec({ rng: Random.bignat(1n << 264n), there: ForeignScalar.from, - back: (x) => x.toBigInt(), + back: (x: ForeignScalar) => x.toBigInt(), provable: ForeignScalar.Unreduced.provable, }); diff --git a/src/lib/provable/types/auxiliary.ts b/src/lib/provable/types/auxiliary.ts index d9c1c76dd2..99799373e7 100644 --- a/src/lib/provable/types/auxiliary.ts +++ b/src/lib/provable/types/auxiliary.ts @@ -8,6 +8,8 @@ const RandomId: ProvableHashable = { toAuxiliary: (v = Math.random()) => [v], fromFields: (_, [v]) => v, check: () => {}, + toValue: (x) => x, + fromValue: (x) => x, toInput: () => ({}), empty: () => Math.random(), }; diff --git a/src/lib/provable/types/fields.ts b/src/lib/provable/types/fields.ts index d020ac6fb3..6c214f3914 100644 --- a/src/lib/provable/types/fields.ts +++ b/src/lib/provable/types/fields.ts @@ -6,12 +6,14 @@ export { modifiedField, fields }; // provable for a single field element -const ProvableField: ProvablePureExtended = { +const ProvableField: ProvablePureExtended = { sizeInFields: () => 1, toFields: (x) => [x], toAuxiliary: () => [], fromFields: ([x]) => x, check: () => {}, + toValue: (x) => x.toBigInt(), + fromValue: (x) => createField(x), toInput: (x) => ({ fields: [x] }), toJSON: (x) => getField().toJSON(x), fromJSON: (x) => getField().fromJSON(x), @@ -28,13 +30,17 @@ function modifiedField( let id = (t: T) => t; -function fields(length: number): ProvablePureExtended { +function fields( + length: number +): ProvablePureExtended { return { sizeInFields: () => length, toFields: id, toAuxiliary: () => [], fromFields: id, check: () => {}, + toValue: (x) => x.map((y) => y.toBigInt()), + fromValue: (x) => x.map(createField), toInput: (x) => ({ fields: x }), toJSON: (x) => x.map(getField().toJSON), fromJSON: (x) => x.map(getField().fromJSON), diff --git a/src/lib/provable/types/provable-derivers.ts b/src/lib/provable/types/provable-derivers.ts index 2fd4dd144e..7126b11ec1 100644 --- a/src/lib/provable/types/provable-derivers.ts +++ b/src/lib/provable/types/provable-derivers.ts @@ -9,6 +9,7 @@ import { IsPure as GenericIsPure, createHashInput, Constructor, + InferValue, } from '../../../bindings/lib/provable-generic.js'; import { Tuple } from '../../util/types.js'; import { GenericHashInput } from '../../../bindings/lib/generic.js'; @@ -86,6 +87,10 @@ function provableFromClass>( raw.check(value); } }, + toValue: raw.toValue, + fromValue(x) { + return construct(Class, raw.fromValue(x)); + }, toInput: raw.toInput, toJSON: raw.toJSON, fromJSON(x) { @@ -96,7 +101,7 @@ function provableFromClass>( ? Class.empty() : construct(Class, raw.empty()); }, - } satisfies ProvableExtended> as any; + } satisfies ProvableExtended, InferJson> as any; } function construct(Class: Constructor, value: Raw): T { diff --git a/src/lib/provable/types/unconstrained.ts b/src/lib/provable/types/unconstrained.ts index a29531a1a6..517941d2cc 100644 --- a/src/lib/provable/types/unconstrained.ts +++ b/src/lib/provable/types/unconstrained.ts @@ -110,7 +110,7 @@ and Provable.asProver() blocks, which execute outside the proof. }); } - static provable: Provable> & { + static provable: UnconstrainedProvable & { toInput: (x: Unconstrained) => { fields?: Field[]; packed?: [Field, number][]; @@ -121,6 +121,10 @@ and Provable.asProver() blocks, which execute outside the proof. toAuxiliary: (t?: any) => [t ?? new Unconstrained(false)], fromFields: (_, [t]) => t, check: () => {}, + toValue: (t) => t.get(), + fromValue: (t) => Unconstrained.from(t), toInput: () => ({}), }; } + +type UnconstrainedProvable = Provable, T>; From 0ace1967bf68bcd3a3013dad978ef65a4729c089 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 17 Apr 2024 17:42:56 +0200 Subject: [PATCH 35/41] simplify unconstrained for now until better understood --- src/lib/provable/types/unconstrained.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/provable/types/unconstrained.ts b/src/lib/provable/types/unconstrained.ts index 517941d2cc..011556a47d 100644 --- a/src/lib/provable/types/unconstrained.ts +++ b/src/lib/provable/types/unconstrained.ts @@ -121,8 +121,8 @@ and Provable.asProver() blocks, which execute outside the proof. toAuxiliary: (t?: any) => [t ?? new Unconstrained(false)], fromFields: (_, [t]) => t, check: () => {}, - toValue: (t) => t.get(), - fromValue: (t) => Unconstrained.from(t), + toValue: (t) => t, + fromValue: (t) => t, toInput: () => ({}), }; } From 9f0fe16fb345157d1d00817a19e83d8e8fc4077c Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 17 Apr 2024 17:44:17 +0200 Subject: [PATCH 36/41] fix type --- src/lib/provable/types/unconstrained.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/provable/types/unconstrained.ts b/src/lib/provable/types/unconstrained.ts index 011556a47d..89fb648b8d 100644 --- a/src/lib/provable/types/unconstrained.ts +++ b/src/lib/provable/types/unconstrained.ts @@ -110,7 +110,7 @@ and Provable.asProver() blocks, which execute outside the proof. }); } - static provable: UnconstrainedProvable & { + static provable: Provable> & { toInput: (x: Unconstrained) => { fields?: Field[]; packed?: [Field, number][]; @@ -126,5 +126,3 @@ and Provable.asProver() blocks, which execute outside the proof. toInput: () => ({}), }; } - -type UnconstrainedProvable = Provable, T>; From 65f75e49484aed7f373562e44a215c1c768060de Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 18 Apr 2024 12:03:42 +0200 Subject: [PATCH 37/41] wip debugging --- src/mina-signer/src/sign-zkapp-command.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/mina-signer/src/sign-zkapp-command.ts b/src/mina-signer/src/sign-zkapp-command.ts index 344ba8b48b..f16b857777 100644 --- a/src/mina-signer/src/sign-zkapp-command.ts +++ b/src/mina-signer/src/sign-zkapp-command.ts @@ -101,6 +101,7 @@ function verifyAccountUpdateSignature( transactionCommitments: { commitment: bigint; fullCommitment: bigint }, networkId: NetworkId ) { + console.log('verifyAccountUpdateSignature', update.authorization.signature); if (update.authorization.signature === undefined) return false; let { publicKey, useFullCommitment } = update.body; @@ -108,7 +109,12 @@ function verifyAccountUpdateSignature( let usedCommitment = useFullCommitment ? fullCommitment : commitment; let signature = Signature.fromBase58(update.authorization.signature); - return verifyFieldElement(signature, usedCommitment, publicKey, networkId); + let ok = verifyFieldElement(signature, usedCommitment, publicKey, networkId); + console.dir( + { signature, usedCommitment, publicKey, networkId, ok }, + { depth: null } + ); + return ok; } function transactionCommitments( From fc4556cdeeaa2361dd4e474228e5d9af5d67b897 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 19 Apr 2024 00:29:40 +0200 Subject: [PATCH 38/41] fix requireNothing --- src/lib/mina/account-update.ts | 2 -- src/lib/mina/precondition.ts | 3 ++- src/mina-signer/src/sign-zkapp-command.ts | 8 +------- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/lib/mina/account-update.ts b/src/lib/mina/account-update.ts index 83fd1de649..cfb21532da 100644 --- a/src/lib/mina/account-update.ts +++ b/src/lib/mina/account-update.ts @@ -15,7 +15,6 @@ import { Pickles, Test } from '../../snarky.js'; import { jsLayout } from '../../bindings/mina-transaction/gen/js-layout.js'; import { Types, - TypesBigint, toJSONEssential, } from '../../bindings/mina-transaction/types.js'; import { PrivateKey, PublicKey } from '../provable/crypto/signature.js'; @@ -82,7 +81,6 @@ import { } from './smart-contract-context.js'; import { assert } from '../util/assert.js'; import { RandomId } from '../provable/types/auxiliary.js'; -import { NetworkId } from '../../mina-signer/src/types.js'; import { From } from '../../bindings/lib/provable-generic.js'; // external API diff --git a/src/lib/mina/precondition.ts b/src/lib/mina/precondition.ts index 3d3942b66f..1cc03dfc25 100644 --- a/src/lib/mina/precondition.ts +++ b/src/lib/mina/precondition.ts @@ -331,7 +331,7 @@ function preconditionSubclass< >( accountUpdate: AccountUpdate, longKey: K, - fieldType: Provable, + fieldType: Provable & { empty(): U }, context: PreconditionContext ) { if (fieldType === undefined) { @@ -381,6 +381,7 @@ function preconditionSubclass< ) as AnyCondition; if ('isSome' in property) { property.isSome = Bool(false); + property.value = fieldType.empty(); } context.constrained.add(longKey); }, diff --git a/src/mina-signer/src/sign-zkapp-command.ts b/src/mina-signer/src/sign-zkapp-command.ts index f16b857777..344ba8b48b 100644 --- a/src/mina-signer/src/sign-zkapp-command.ts +++ b/src/mina-signer/src/sign-zkapp-command.ts @@ -101,7 +101,6 @@ function verifyAccountUpdateSignature( transactionCommitments: { commitment: bigint; fullCommitment: bigint }, networkId: NetworkId ) { - console.log('verifyAccountUpdateSignature', update.authorization.signature); if (update.authorization.signature === undefined) return false; let { publicKey, useFullCommitment } = update.body; @@ -109,12 +108,7 @@ function verifyAccountUpdateSignature( let usedCommitment = useFullCommitment ? fullCommitment : commitment; let signature = Signature.fromBase58(update.authorization.signature); - let ok = verifyFieldElement(signature, usedCommitment, publicKey, networkId); - console.dir( - { signature, usedCommitment, publicKey, networkId, ok }, - { depth: null } - ); - return ok; + return verifyFieldElement(signature, usedCommitment, publicKey, networkId); } function transactionCommitments( From 257501d92da3eaf8e9a1c1ada570ee1eaaad7383 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 19 Apr 2024 01:43:25 +0200 Subject: [PATCH 39/41] automate gen/transaction changes --- src/build/js-layout-to-types.mjs | 75 +++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/src/build/js-layout-to-types.mjs b/src/build/js-layout-to-types.mjs index 70c983d0f0..107a34c4e7 100644 --- a/src/build/js-layout-to-types.mjs +++ b/src/build/js-layout-to-types.mjs @@ -20,14 +20,15 @@ let builtinLeafTypes = new Set([ ]); let indent = ''; -function writeType(typeData, isJson, withTypeMap) { +function writeType(typeData, isValue, isJson, withTypeMap) { let converters = {}; - if (!isJson && typeData.checkedType) { + if (!(isJson || isValue) && typeData.checkedType) { let name = typeData.checkedTypeName; converters[name] = { typeName: name, - type: writeType(typeData.checkedType, false, withTypeMap).output, - jsonType: writeType(typeData, true, true).output, + type: writeType(typeData.checkedType, false, false, withTypeMap).output, + valueType: writeType(typeData, true, false, true).output, + jsonType: writeType(typeData, false, true, true).output, }; typeData = typeData.checkedType; } @@ -37,7 +38,7 @@ function writeType(typeData, isJson, withTypeMap) { output, dependencies, converters: j, - } = writeType(inner, isJson, withTypeMap); + } = writeType(inner, isValue, isJson, withTypeMap); mergeObject(converters, j); return { output: `${output}[]`, @@ -50,7 +51,7 @@ function writeType(typeData, isJson, withTypeMap) { output, dependencies, converters: j, - } = writeType(inner, isJson, withTypeMap); + } = writeType(inner, isValue, isJson, withTypeMap); if (optionType === 'flaggedOption' || optionType === 'closedInterval') { dependencies ??= new Set(); dependencies.add('Bool'); @@ -84,7 +85,7 @@ function writeType(typeData, isJson, withTypeMap) { value = value.inner; questionMark = '?'; } - let inner = writeType(value, isJson, withTypeMap); + let inner = writeType(value, isValue, isJson, withTypeMap); mergeSet(dependencies, inner.dependencies); mergeObject(converters, inner.converters); output += indent + `${key}${questionMark}: ${inner.output};\n`; @@ -93,8 +94,8 @@ function writeType(typeData, isJson, withTypeMap) { output += indent + '}'; return { output, dependencies, converters }; } - if (withTypeMap & !builtinLeafTypes.has(type)) { - type = `${isJson ? 'Json.' : ''}TypeMap["${type}"]`; + if (withTypeMap && !builtinLeafTypes.has(type)) { + type = `${isJson ? 'Json.' : isValue ? 'Value.' : ''}TypeMap["${type}"]`; } // built in type if (builtinLeafTypes.has(type)) return { output: type, converters }; @@ -119,17 +120,25 @@ function writeTsContent({ let fromLayout = isProvable ? 'provableFromLayout' : 'signableFromLayout'; let FromLayout = isProvable ? 'ProvableFromLayout' : 'SignableFromLayout'; + let Type = (Name, T, TValue, ...args) => + `${Name}<${(isProvable ? [T, TValue, ...args] : [T, ...args]).join(', ')}>`; + let GenericType = isProvable ? 'GenericProvableExtended' : 'GenericSignable'; let GeneratedType = isProvable ? 'ProvableExtended' : 'Signable'; - for (let [Type, value] of Object.entries(types)) { - let inner = writeType(value, isJson); - exports.add(Type); + for (let [T, value] of Object.entries(types)) { + let inner = writeType(value, false, isJson); + exports.add(T); mergeSet(dependencies, inner.dependencies); mergeObject(converters, inner.converters); - output += `type ${Type} = ${inner.output};\n\n`; + output += `type ${T} = ${inner.output};\n\n`; if (!isJson) { - output += `let ${Type} = ${fromLayout}<${Type}, Json.${Type}>(jsLayout.${Type} as any);\n\n`; + output += `let ${T} = ${Type( + fromLayout, + T, + `Value.${T}`, + `Json.${T}` + )}(jsLayout.${T} as any);\n\n`; } } @@ -149,6 +158,9 @@ ${ ? `import { ${GenericType} } from '../../lib/generic.js';\n` + `import { ${FromLayout}, GenericLayout } from '../../lib/from-layout.js';\n` + "import * as Json from './transaction-json.js';\n" + + (isProvable + ? "import * as Value from './transaction-bigint.js';\n" + : '') + "import { jsLayout } from './js-layout.js';\n" : '' } @@ -169,7 +181,12 @@ ${ (!isJson || '') && ` const TypeMap: { - [K in keyof TypeMap]: ${GeneratedType}; + [K in keyof TypeMap]: ${Type( + GeneratedType, + 'TypeMap[K]', + 'Value.TypeMap[K]', + 'Json.TypeMap[K]' + )}; } = { ${[...typeMapKeys].join(', ')} } @@ -179,17 +196,33 @@ const TypeMap: { ${ (!isJson || '') && ` -type ${GeneratedType} = ${GenericType}; +type ${Type(GeneratedType, 'T', 'TValue', 'TJson')} = ${Type( + GenericType, + 'T', + 'TValue', + 'TJson', + 'Field' + )}; type Layout = GenericLayout; type CustomTypes = { ${customTypes - .map((c) => `${c.typeName}: ${GeneratedType}<${c.type}, ${c.jsonType}>;`) + .map( + (c) => + `${c.typeName}: ${Type( + GeneratedType, + c.type, + c.valueType, + c.jsonType + )};` + ) .join(' ')} } let customTypes: CustomTypes = { ${customTypeNames.join(', ')} }; -let { ${fromLayout}, toJSONEssential, empty } = ${FromLayout}< - TypeMap, - Json.TypeMap ->(TypeMap, customTypes); +let { ${fromLayout}, toJSONEssential, empty } = ${Type( + FromLayout, + 'TypeMap', + 'Value.TypeMap', + 'Json.TypeMap' + )}(TypeMap, customTypes); ` } From 0dd9f7fe03554f66f52fd7cbfb09dbe468e137dd Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 19 Apr 2024 08:16:02 +0200 Subject: [PATCH 40/41] fix tx type regeneration --- src/bindings | 2 +- src/build/js-layout-to-types.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bindings b/src/bindings index d7e194f29f..d8c860a164 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit d7e194f29f720f62f70b5ca9d6d449fa0cd28947 +Subproject commit d8c860a164b2a594823e28ee42a3e1ad9e9fb732 diff --git a/src/build/js-layout-to-types.mjs b/src/build/js-layout-to-types.mjs index 107a34c4e7..206bd40cdd 100644 --- a/src/build/js-layout-to-types.mjs +++ b/src/build/js-layout-to-types.mjs @@ -27,7 +27,7 @@ function writeType(typeData, isValue, isJson, withTypeMap) { converters[name] = { typeName: name, type: writeType(typeData.checkedType, false, false, withTypeMap).output, - valueType: writeType(typeData, true, false, true).output, + valueType: writeType(typeData.checkedType, true, false, true).output, jsonType: writeType(typeData, false, true, true).output, }; typeData = typeData.checkedType; From 5fce3552220bf6e3bde75e05d1591f99fc27a703 Mon Sep 17 00:00:00 2001 From: Gregor Mitscha-Baude Date: Fri, 19 Apr 2024 08:44:21 +0200 Subject: [PATCH 41/41] fix requireNothing and add tests --- src/lib/mina/precondition.test.ts | 46 ++++++++++++++++++++++++++++++- src/lib/mina/precondition.ts | 12 +++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/lib/mina/precondition.test.ts b/src/lib/mina/precondition.test.ts index ae6fbbce11..202d351504 100644 --- a/src/lib/mina/precondition.test.ts +++ b/src/lib/mina/precondition.test.ts @@ -208,6 +208,50 @@ describe('preconditions', () => { await expect(tx.sign([feePayer.key]).send()).rejects.toThrow(/unsatisfied/); }); + it('unsatisfied requireEquals should be overridden by requireNothing', async () => { + let nonce = contract.account.nonce.get(); + let publicKey = PublicKey.from({ x: Field(-1), isOdd: Bool(false) }); + + await Mina.transaction(feePayer, async () => { + for (let precondition of implementedNumber) { + let p = precondition().get(); + precondition().requireEquals(p.add(1) as any); + precondition().requireNothing(); + } + for (let precondition of implementedBool) { + let p = precondition().get(); + precondition().requireEquals(p.not()); + precondition().requireNothing(); + } + contract.account.delegate.requireEquals(publicKey); + contract.account.delegate.requireNothing(); + contract.requireSignature(); + AccountUpdate.attachToTransaction(contract.self); + }) + .sign([feePayer.key, contractAccount.key]) + .send(); + // check that tx was applied, by checking nonce was incremented + expect(contract.account.nonce.get()).toEqual(nonce.add(1)); + }); + + it('unsatisfied requireBetween should be overridden by requireNothing', async () => { + let nonce = contract.account.nonce.get(); + + await Mina.transaction(feePayer, async () => { + for (let precondition of implementedWithRange) { + let p: any = precondition().get(); + precondition().requireBetween(p.add(20), p.add(30)); + precondition().requireNothing(); + } + contract.requireSignature(); + AccountUpdate.attachToTransaction(contract.self); + }) + .sign([feePayer.key, contractAccount.key]) + .send(); + // check that tx was applied, by checking nonce was incremented + expect(contract.account.nonce.get()).toEqual(nonce.add(1)); + }); + // TODO: is this a gotcha that should be addressed? // the test below fails, so it seems that nonce is applied successfully with a WRONG precondition.. // however, this is just because `zkapp.requireSignature()` overwrites the nonce precondition with one that is satisfied @@ -227,7 +271,6 @@ let implementedNumber = [ () => contract.account.receiptChainHash, () => contract.network.blockchainLength, () => contract.network.globalSlotSinceGenesis, - () => contract.network.timestamp, () => contract.network.minWindowDensity, () => contract.network.totalCurrency, () => contract.network.stakingEpochData.epochLength, @@ -251,6 +294,7 @@ let implementedBool = [ let implemented = [ ...implementedNumber, ...implementedBool, + () => contract.network.timestamp, () => contract.account.delegate, ]; let implementedWithRange = [ diff --git a/src/lib/mina/precondition.ts b/src/lib/mina/precondition.ts index 1cc03dfc25..87cf5e0dfc 100644 --- a/src/lib/mina/precondition.ts +++ b/src/lib/mina/precondition.ts @@ -381,7 +381,17 @@ function preconditionSubclass< ) as AnyCondition; if ('isSome' in property) { property.isSome = Bool(false); - property.value = fieldType.empty(); + if ('lower' in property.value && 'upper' in property.value) { + if (fieldType === UInt64) { + property.value.lower = UInt64.zero as U; + property.value.upper = UInt64.MAXINT() as U; + } else if (fieldType === UInt32) { + property.value.lower = UInt32.zero as U; + property.value.upper = UInt32.MAXINT() as U; + } + } else { + property.value = fieldType.empty(); + } } context.constrained.add(longKey); },