From 57df94e5efee1ba92c1055916ae1b4e7f52f196e Mon Sep 17 00:00:00 2001 From: Gregor Date: Sat, 30 Jul 2022 15:20:32 +0200 Subject: [PATCH 01/12] implement primitive types as AsFieldsAndAux --- src/index.ts | 7 ++-- src/lib/circuit_value.ts | 72 ++++++++++++++++++++++++++++++++++- src/lib/hash.ts | 17 +++++++-- src/snarky/parties-helpers.ts | 26 ++++++------- src/snarky/parties-leaves.ts | 59 +++++++++++++++++++++++----- src/snarky/types.ts | 1 - 6 files changed, 148 insertions(+), 34 deletions(-) diff --git a/src/index.ts b/src/index.ts index 73860647a5..6a10e53f48 100644 --- a/src/index.ts +++ b/src/index.ts @@ -58,12 +58,11 @@ export { Character, CircuitString } from './lib/string'; // experimental APIs import { Reducer } from './lib/zkapp'; import { createChildParty } from './lib/party'; -import { memoizeWitness } from './lib/circuit_value'; import { - jsLayout, - asFieldsAndAux, + memoizeWitness, AsFieldsAndAux as AsFieldsAndAux_, -} from './snarky/types'; +} from './lib/circuit_value'; +import { jsLayout, asFieldsAndAux } from './snarky/types'; import { packToFields } from './lib/hash'; export { Experimental }; diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 0f766bd6fd..dedc5c69df 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -1,6 +1,7 @@ import 'reflect-metadata'; import { Circuit, Field, Bool, JSONValue, AsFieldElements } from '../snarky'; import { Context } from './global-context'; +import { HashInput } from './hash'; import { snarkContext } from './proof_system'; // external API @@ -17,6 +18,7 @@ export { // internal API export { + AsFieldsAndAux, cloneCircuitValue, circuitValueEquals, circuitArray, @@ -57,11 +59,10 @@ abstract class CircuitValue { v: InstanceType ): Field[] { const res: Field[] = []; - const fields = (this as any).prototype._fields; + const fields = this.prototype._fields; if (fields === undefined || fields === null) { return res; } - for (let i = 0, n = fields.length; i < n; ++i) { const [key, propType] = fields[i]; const subElts: Field[] = propType.toFields((v as any)[key]); @@ -70,6 +71,28 @@ abstract class CircuitValue { return res; } + static toInput( + this: T, + v: InstanceType + ): HashInput { + let input: HashInput = { fields: [], packed: [] }; + let fields = this.prototype._fields; + if (fields === undefined) return input; + for (let i = 0, n = fields.length; i < n; ++i) { + let [key, type] = fields[i]; + if ('toInput' in type) { + HashInput.append(input, type.toInput(v[key])); + continue; + } + // as a fallback, use toFields on the type + // TODO: this is problematic -- ignores if there's a toInput on a nested type + // so, remove this? should every circuit value define toInput? + let xs: Field[] = type.toFields(v[key]); + input.fields!.push(...xs); + } + return input; + } + toFields(): Field[] { return (this.constructor as any).toFields(this); } @@ -581,3 +604,48 @@ function getBlindingValue() { } return context.blindingValue; } + +// "complex" circuit values which have auxiliary data, and have to be hashed + +type AsFieldsAndAux = { + sizeInFields(): number; + toFields(value: T): Field[]; + toAuxiliary(value?: T): any[]; + fromFields(fields: Field[], aux: any[]): T; + toJson(value: T): TJson; + check(value: T): void; + toInput(value: T): HashInput; +}; + +// convert from circuit values +function fromCircuitValue( + type: T +): AsFieldsAndAux, JSONValue> { + return { + sizeInFields() { + return type.sizeInFields(); + }, + toFields(value) { + return type.toFields(value); + }, + toAuxiliary(_) { + return []; + }, + fromFields(fields) { + return type.ofFields(fields); + }, + check(value) { + type.check(value); + }, + toInput(value) { + return type.toInput(value); + }, + toJson(value) { + return type.toJSON(value); + }, + }; +} + +const AsFieldsAndAux = { + fromCircuitValue, +}; diff --git a/src/lib/hash.ts b/src/lib/hash.ts index f8dc6bad9f..83541a89b8 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -1,4 +1,4 @@ -import { AsFieldsAndAux } from '../snarky/parties-helpers'; +import { AsFieldsAndAux } from './circuit_value'; import { Poseidon as Poseidon_, Field } from '../snarky'; import { inCheckedComputation } from './proof_system'; @@ -7,6 +7,7 @@ export { Poseidon }; // internal API export { + HashInput, prefixes, emptyHashWithPrefix, hashWithPrefix, @@ -91,7 +92,7 @@ function prefixToField(prefix: string) { * Convert the {fields, packed} hash input representation to a list of field elements * Random_oracle_input.Chunked.pack_to_fields */ -function packToFields({ fields = [], packed = [] }: Input) { +function packToFields({ fields = [], packed = [] }: HashInput) { if (packed.length === 0) return fields; let packedBits = []; let currentPackedField = Field.zero; @@ -112,7 +113,17 @@ function packToFields({ fields = [], packed = [] }: Input) { return fields.concat(packedBits); } -type Input = { fields?: Field[]; packed?: [Field, number][] }; +type HashInput = { fields?: Field[]; packed?: [Field, number][] }; +const HashInput = { + append(input1: HashInput, input2: HashInput) { + if (input2.fields !== undefined) { + (input1.fields ??= []).push(...input2.fields); + } + if (input2.packed !== undefined) { + (input1.packed ??= []).push(...input2.packed); + } + }, +}; type TokenSymbol = { symbol: string; field: Field }; diff --git a/src/snarky/parties-helpers.ts b/src/snarky/parties-helpers.ts index 2bcb77cca0..f66b691132 100644 --- a/src/snarky/parties-helpers.ts +++ b/src/snarky/parties-helpers.ts @@ -1,18 +1,10 @@ import * as Leaves from './parties-leaves'; import { Field, Bool, Circuit } from '../snarky'; -import { circuitArray } from '../lib/circuit_value'; +import { circuitArray, AsFieldsAndAux } from '../lib/circuit_value'; +import { HashInput } from 'lib/hash'; export { asFieldsAndAux, Layout, AsFieldsAndAux }; -type AsFieldsAndAux = { - sizeInFields(): number; - toFields(value: T): Field[]; - toAuxiliary(value?: T): any[]; - fromFields(fields: Field[], aux: any[]): T; - toJson(value: T): TJson; - check(value: T): void; - toInput(value: T): Leaves.Input; -}; type CustomTypes = Record>; function asFieldsAndAux(typeData: Layout, customTypes: CustomTypes) { @@ -35,7 +27,7 @@ function asFieldsAndAux(typeData: Layout, customTypes: CustomTypes) { check(value: T): void { check(typeData, value, customTypes); }, - toInput(value: T): Leaves.Input { + toInput(value: T): HashInput { return toInput(typeData, value, customTypes); }, witness(f: () => T): T { @@ -226,6 +218,9 @@ function fromFieldsReversed( } return values; } + if (Leaves.isFullType(typeData.type)) { + return Leaves.FullTypes[typeData.type].fromFields(fields, aux); + } return Leaves.fromFields(typeData.type, fields, aux); } @@ -250,7 +245,7 @@ function check(typeData: Layout, value: any, customTypes: CustomTypes) { } function toInput(typeData: Layout, value: any, customTypes: CustomTypes) { - return mapReduce( + return mapReduce( { map(type, value) { return Leaves.toInput(type, value); @@ -259,7 +254,7 @@ function toInput(typeData: Layout, value: any, customTypes: CustomTypes) { return type.toInput(value); }, reduceArray(array) { - let acc: Leaves.Input = { fields: [], packed: [] }; + let acc: HashInput = { fields: [], packed: [] }; for (let { fields, packed } of array) { if (fields) acc.fields!.push(...fields); if (packed) acc.packed!.push(...packed); @@ -267,7 +262,7 @@ function toInput(typeData: Layout, value: any, customTypes: CustomTypes) { return acc; }, reduceObject(keys, object) { - let acc: Leaves.Input = { fields: [], packed: [] }; + let acc: HashInput = { fields: [], packed: [] }; for (let key of keys) { let { fields, packed } = object[key]; if (fields) acc.fields!.push(...fields); @@ -344,6 +339,9 @@ function mapReduce( }); return spec.reduceObject(keys, object); } + if (Leaves.isFullType(typeData.type)) { + return spec.mapCustom(Leaves.FullTypes[typeData.type], value); + } return spec.map(typeData.type, value); } diff --git a/src/snarky/parties-leaves.ts b/src/snarky/parties-leaves.ts index daf821da79..1b412d8235 100644 --- a/src/snarky/parties-leaves.ts +++ b/src/snarky/parties-leaves.ts @@ -1,9 +1,9 @@ import { Field, Bool, Group, Ledger, Circuit } from '../snarky'; import * as Json from './gen/parties-json'; import { UInt32, UInt64, Sign } from '../lib/int'; -import { TokenSymbol } from '../lib/hash'; +import { TokenSymbol, HashInput } from '../lib/hash'; import { PublicKey } from '../lib/signature'; -import { AsFieldsAndAux } from './parties-helpers'; +import { AsFieldsAndAux } from '../lib/circuit_value'; export { PublicKey, Field, Bool, AuthRequired, UInt64, UInt32, Sign, TokenId }; @@ -18,7 +18,8 @@ export { check, toInput, TypeMap, - Input, + isFullType, + FullTypes, }; type AuthRequired = { @@ -45,6 +46,49 @@ type TypeMap = { string: string; }; +// types that implement AsFieldAndAux, and so can be left out of the conversion maps below +// sort of a "transposed" representation +type FullTypesKey = 'number' | 'null' | 'undefined' | 'string'; +type OtherTypesKey = Exclude; +type FullTypes = { + [K in FullTypesKey]: AsFieldsAndAux; +}; + +let emptyType = { + sizeInFields: () => 0, + toFields: () => [], + toAuxiliary: () => [], + fromFields: () => null, + check: () => {}, + toInput: () => ({}), + toJson: () => null, +}; + +const FullTypes: FullTypes = { + // implementations for primitive JS types + number: { + ...emptyType, + toAuxiliary: (value = 0) => [value], + toJson: (value) => value, + fromFields: (_, aux) => aux.pop()!, + }, + string: { + ...emptyType, + toAuxiliary: (value = '') => [value], + toJson: (value) => value, + fromFields: (_, aux) => aux.pop()!, + }, + null: emptyType, + undefined: { + ...emptyType, + fromFields: () => undefined, + }, +}; + +function isFullType(type: keyof TypeMap): type is FullTypesKey { + return type in FullTypes; +} + // json conversion function identity(x: any) { @@ -316,16 +360,11 @@ function check(typeName: K, value: TypeMap[K]) { // to input -type Input = { - fields?: Field[]; - packed?: [Field, number][]; -}; - type ToInput = { - [K in keyof TypeMap]: (x: TypeMap[K]) => Input; + [K in keyof TypeMap]: (x: TypeMap[K]) => HashInput; }; -function emptyInput(_: any): Input { +function emptyInput(_: any): HashInput { return {}; } diff --git a/src/snarky/types.ts b/src/snarky/types.ts index b5b0d2a6b4..38cc873485 100644 --- a/src/snarky/types.ts +++ b/src/snarky/types.ts @@ -3,7 +3,6 @@ import { customTypes } from './gen/parties'; export * as Types from './gen/parties'; export { jsLayout } from './gen/js-layout'; -export { AsFieldsAndAux } from './parties-helpers'; export { asFieldsAndAux }; From 21600705275f4009e2de4cc7b98c8dfecded5d7d Mon Sep 17 00:00:00 2001 From: Gregor Date: Sat, 30 Jul 2022 15:25:46 +0200 Subject: [PATCH 02/12] remove obsolete code for primitives --- src/snarky/parties-helpers.ts | 2 +- src/snarky/parties-leaves.ts | 79 +++++++---------------------------- 2 files changed, 16 insertions(+), 65 deletions(-) diff --git a/src/snarky/parties-helpers.ts b/src/snarky/parties-helpers.ts index f66b691132..8ed7eff250 100644 --- a/src/snarky/parties-helpers.ts +++ b/src/snarky/parties-helpers.ts @@ -288,7 +288,7 @@ function toInput(typeData: Layout, value: any, customTypes: CustomTypes) { type MapReduceSpec = { customTypes: CustomTypes; - map: (type: keyof Leaves.TypeMap, value?: T) => R; + map: (type: Leaves.OtherTypesKey, value?: T) => R; mapCustom: (type: AsFieldsAndAux, value?: T) => R; reduceArray: (array: R[], typeData: ArrayLayout) => R; reduceObject: (keys: string[], record: Record) => R; diff --git a/src/snarky/parties-leaves.ts b/src/snarky/parties-leaves.ts index 1b412d8235..c23f62b6f7 100644 --- a/src/snarky/parties-leaves.ts +++ b/src/snarky/parties-leaves.ts @@ -18,6 +18,7 @@ export { check, toInput, TypeMap, + OtherTypesKey, isFullType, FullTypes, }; @@ -91,15 +92,12 @@ function isFullType(type: keyof TypeMap): type is FullTypesKey { // json conversion -function identity(x: any) { - return x; -} function asString(x: Field | bigint) { return x.toString(); } type ToJson = { - [K in keyof TypeMap]: (x: TypeMap[K]) => Json.TypeMap[K]; + [K in OtherTypesKey]: (x: TypeMap[K]) => Json.TypeMap[K]; }; let ToJson: ToJson = { @@ -138,16 +136,9 @@ let ToJson: ToJson = { if (x.neg().toString() === '1') return 'Negative'; throw Error(`Invalid Sign: ${x}`); }, - // builtin - number: identity, - null: identity, - undefined(_: undefined) { - return null; - }, - string: identity, }; -function toJson(typeName: K, value: TypeMap[K]) { +function toJson(typeName: K, value: TypeMap[K]) { if (!(typeName in ToJson)) throw Error(`toJson: unsupported type "${typeName}"`); return ToJson[typeName](value); @@ -155,7 +146,7 @@ function toJson(typeName: K, value: TypeMap[K]) { // to fields -type ToFields = { [K in keyof TypeMap]: (x: TypeMap[K]) => Field[] }; +type ToFields = { [K in OtherTypesKey]: (x: TypeMap[K]) => Field[] }; function asFields(x: any): Field[] { return x.toFields(); @@ -182,14 +173,9 @@ let ToFields: ToFields = { UInt64: asFields, TokenId: asFields, Sign: asFields, - // builtin - number: empty, - null: empty, - undefined: empty, - string: empty, }; -function toFields(typeName: K, value: TypeMap[K]) { +function toFields(typeName: K, value: TypeMap[K]) { if (!(typeName in ToFields)) throw Error(`toFields: unsupported type "${typeName}"`); return ToFields[typeName](value); @@ -198,7 +184,7 @@ function toFields(typeName: K, value: TypeMap[K]) { // to auxiliary type ToAuxiliary = { - [K in keyof TypeMap]: + [K in OtherTypesKey]: | ((x: TypeMap[K] | undefined) => []) | ((x: TypeMap[K] | undefined) => [TypeMap[K]]); }; @@ -212,14 +198,9 @@ let ToAuxiliary: ToAuxiliary = { UInt64: empty, TokenId: empty, Sign: empty, - // builtin - number: (x = 0) => [x], - null: empty, - undefined: empty, - string: (x = '') => [x], }; -function toAuxiliary( +function toAuxiliary( typeName: K, value: TypeMap[K] | undefined ) { @@ -230,7 +211,7 @@ function toAuxiliary( // size in fields -type SizeInFields = { [K in keyof TypeMap]: number }; +type SizeInFields = { [K in OtherTypesKey]: number }; let SizeInFields: SizeInFields = { PublicKey: 2, Field: 1, @@ -240,14 +221,9 @@ let SizeInFields: SizeInFields = { UInt64: 1, TokenId: 1, Sign: 1, - // builtin - number: 0, - null: 0, - undefined: 0, - string: 0, }; -function sizeInFields(typeName: K) { +function sizeInFields(typeName: K) { if (!(typeName in ToFields)) throw Error(`sizeInFields: unsupported type "${typeName}"`); return SizeInFields[typeName]; @@ -257,13 +233,9 @@ function sizeInFields(typeName: K) { // these functions get the reversed output of `toFields` and `toAuxiliary` and pop the values they need from those arrays type FromFields = { - [K in keyof TypeMap]: (fields: Field[], aux: any[]) => TypeMap[K]; + [K in OtherTypesKey]: (fields: Field[], aux: any[]) => TypeMap[K]; }; -function takeOneAux(_: Field[], aux: any[]) { - return aux.pop()!; -} - let FromFields: FromFields = { PublicKey(fields: Field[]) { let x = fields.pop()!; @@ -309,14 +281,9 @@ let FromFields: FromFields = { Sign(fields: Field[]) { return new Sign(fields.pop()!); }, - // builtin - number: takeOneAux, - null: () => null, - undefined: () => undefined, - string: takeOneAux, }; -function fromFields( +function fromFields( typeName: K, fields: Field[], aux: any[] @@ -328,9 +295,7 @@ function fromFields( // check -type Check = { [K in keyof TypeMap]: (x: TypeMap[K]) => void }; - -function none() {} +type Check = { [K in OtherTypesKey]: (x: TypeMap[K]) => void }; let Check: Check = { PublicKey: (v) => PublicKey.check(v), @@ -345,14 +310,9 @@ let Check: Check = { UInt64: (v) => UInt64.check(v), TokenId: (v) => Field.check(v), Sign: (v) => Sign.check(v), - // builtin - number: none, - null: none, - undefined: none, - string: none, }; -function check(typeName: K, value: TypeMap[K]) { +function check(typeName: K, value: TypeMap[K]) { if (!(typeName in ToFields)) throw Error(`check: unsupported type "${typeName}"`); Check[typeName](value); @@ -361,13 +321,9 @@ function check(typeName: K, value: TypeMap[K]) { // to input type ToInput = { - [K in keyof TypeMap]: (x: TypeMap[K]) => HashInput; + [K in OtherTypesKey]: (x: TypeMap[K]) => HashInput; }; -function emptyInput(_: any): HashInput { - return {}; -} - let ToInput: ToInput = { PublicKey(pk) { let [x, isOdd] = ToFields.PublicKey(pk); @@ -403,14 +359,9 @@ let ToInput: ToInput = { UInt64(x) { return { packed: [[x.value, 64]] }; }, - // builtin - number: emptyInput, - null: emptyInput, - undefined: emptyInput, - string: emptyInput, }; -function toInput(typeName: K, value: TypeMap[K]) { +function toInput(typeName: K, value: TypeMap[K]) { if (!(typeName in ToFields)) throw Error(`toInput: unsupported type "${typeName}"`); return ToInput[typeName](value); From 95819aa1e9461dac818ba52007a2f76267805eb0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Sat, 30 Jul 2022 16:06:29 +0200 Subject: [PATCH 03/12] transpose UInt32, UInt64 --- src/lib/circuit_value.ts | 16 ++++++++++----- src/lib/int.ts | 13 ++++++++++++ src/snarky/parties-leaves.ts | 38 ++++++++++-------------------------- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index dedc5c69df..e6e6ac55e5 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -618,9 +618,10 @@ type AsFieldsAndAux = { }; // convert from circuit values -function fromCircuitValue( - type: T -): AsFieldsAndAux, JSONValue> { +function fromCircuitValue< + T extends AnyConstructor & typeof CircuitValue, + TJson = JSONValue +>(type: T): AsFieldsAndAux, TJson> { return { sizeInFields() { return type.sizeInFields(); @@ -632,7 +633,12 @@ function fromCircuitValue( return []; }, fromFields(fields) { - return type.ofFields(fields); + let myFields: Field[] = []; + let size = type.sizeInFields(); + for (let i = 0; i < size; i++) { + myFields.push(fields.pop()!); + } + return type.ofFields(myFields); }, check(value) { type.check(value); @@ -641,7 +647,7 @@ function fromCircuitValue( return type.toInput(value); }, toJson(value) { - return type.toJSON(value); + return type.toJSON(value) as any; }, }; } diff --git a/src/lib/int.ts b/src/lib/int.ts index bc3517a610..5b11be50b4 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1,6 +1,7 @@ import { Circuit, Field } from '../snarky'; import { CircuitValue, prop } from './circuit_value'; import { Types } from '../snarky/types'; +import { HashInput } from './hash'; // external API export { UInt32, UInt64, Int64, Sign }; @@ -25,6 +26,12 @@ class UInt64 extends CircuitValue { let actual = x.value.rangeCheckHelper(64); actual.assertEquals(x.value); } + static toInput(x: UInt64): HashInput { + return { packed: [[x.value, 64]] }; + } + static toJSON(x: UInt64) { + return x.value.toString(); + } private static checkConstant(x: Field) { if (!x.isConstant()) return x; @@ -198,6 +205,12 @@ class UInt32 extends CircuitValue { let actual = x.value.rangeCheckHelper(32); actual.assertEquals(x.value); } + static toInput(x: UInt32): HashInput { + return { packed: [[x.value, 32]] }; + } + static toJSON(x: UInt32) { + return x.value.toString(); + } private static checkConstant(x: Field) { if (!x.isConstant()) return x; diff --git a/src/snarky/parties-leaves.ts b/src/snarky/parties-leaves.ts index c23f62b6f7..b658264b6d 100644 --- a/src/snarky/parties-leaves.ts +++ b/src/snarky/parties-leaves.ts @@ -49,7 +49,13 @@ type TypeMap = { // types that implement AsFieldAndAux, and so can be left out of the conversion maps below // sort of a "transposed" representation -type FullTypesKey = 'number' | 'null' | 'undefined' | 'string'; +type FullTypesKey = + | 'number' + | 'null' + | 'undefined' + | 'string' + | 'UInt32' + | 'UInt64'; type OtherTypesKey = Exclude; type FullTypes = { [K in FullTypesKey]: AsFieldsAndAux; @@ -66,7 +72,9 @@ let emptyType = { }; const FullTypes: FullTypes = { - // implementations for primitive JS types + UInt32: AsFieldsAndAux.fromCircuitValue(UInt32), + UInt64: AsFieldsAndAux.fromCircuitValue(UInt64), + // primitive JS types number: { ...emptyType, toAuxiliary: (value = 0) => [value], @@ -122,12 +130,6 @@ let ToJson: ToJson = { default: throw Error('Unexpected permission'); } }, - UInt32(x: UInt32) { - return x.value.toString(); - }, - UInt64(x: UInt64) { - return x.value.toString(); - }, TokenId(x: TokenId) { return Ledger.fieldToBase58(x); }, @@ -169,8 +171,6 @@ let ToFields: ToFields = { .map(asFields) .flat(); }, - UInt32: asFields, - UInt64: asFields, TokenId: asFields, Sign: asFields, }; @@ -194,8 +194,6 @@ let ToAuxiliary: ToAuxiliary = { Field: empty, Bool: empty, AuthRequired: empty, - UInt32: empty, - UInt64: empty, TokenId: empty, Sign: empty, }; @@ -217,8 +215,6 @@ let SizeInFields: SizeInFields = { Field: 1, Bool: 1, AuthRequired: 3, - UInt32: 1, - UInt64: 1, TokenId: 1, Sign: 1, }; @@ -269,12 +265,6 @@ let FromFields: FromFields = { let signatureSufficient = FromFields.Bool(fields, _); return { constant, signatureNecessary, signatureSufficient }; }, - UInt32(fields: Field[]) { - return new UInt32(fields.pop()!); - }, - UInt64(fields: Field[]) { - return new UInt64(fields.pop()!); - }, TokenId(fields: Field[]) { return fields.pop()!; }, @@ -306,8 +296,6 @@ let Check: Check = { Bool.check(x.signatureNecessary); Bool.check(x.signatureSufficient); }, - UInt32: (v) => UInt32.check(v), - UInt64: (v) => UInt64.check(v), TokenId: (v) => Field.check(v), Sign: (v) => Sign.check(v), }; @@ -353,12 +341,6 @@ let ToInput: ToInput = { Sign(x) { return { packed: [[x.isPositive().toField(), 1]] }; }, - UInt32(x) { - return { packed: [[x.value, 32]] }; - }, - UInt64(x) { - return { packed: [[x.value, 64]] }; - }, }; function toInput(typeName: K, value: TypeMap[K]) { From 5af1ef61e2f50de1ac9beef05878736fa16ba807 Mon Sep 17 00:00:00 2001 From: Gregor Date: Sat, 30 Jul 2022 16:13:11 +0200 Subject: [PATCH 04/12] toJson -> toJSON --- src/examples/party-witness.ts | 2 +- src/examples/to-hash-input.ts | 2 +- src/lib/circuit_value.ts | 4 ++-- src/lib/hash.ts | 2 +- src/lib/party.ts | 6 +++--- src/snarky/parties-helpers.ts | 10 +++++----- src/snarky/parties-leaves.ts | 18 +++++++++--------- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/examples/party-witness.ts b/src/examples/party-witness.ts index 12fd007b29..367e100ff5 100644 --- a/src/examples/party-witness.ts +++ b/src/examples/party-witness.ts @@ -13,7 +13,7 @@ let fields = Types.Party.toFields(party); let aux = Types.Party.toAuxiliary(party); let partyRaw = Types.Party.fromFields(fields, aux); -let json = Types.Party.toJson(partyRaw); +let json = Types.Party.toJSON(partyRaw); if (address.toBase58() !== json.body.publicKey) throw Error('fail'); diff --git a/src/examples/to-hash-input.ts b/src/examples/to-hash-input.ts index 4b0b3c7455..7480fcca56 100644 --- a/src/examples/to-hash-input.ts +++ b/src/examples/to-hash-input.ts @@ -129,7 +129,7 @@ function testInput( toInputOcaml: (json: string) => InputOcaml, value: T ) { - let json = Module.toJson(value); + let json = Module.toJSON(value); // console.log(json); let input1 = inputFromOcaml(toInputOcaml(JSON.stringify(json))); let input2 = Module.toInput(value); diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index e6e6ac55e5..e6395a7336 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -612,7 +612,7 @@ type AsFieldsAndAux = { toFields(value: T): Field[]; toAuxiliary(value?: T): any[]; fromFields(fields: Field[], aux: any[]): T; - toJson(value: T): TJson; + toJSON(value: T): TJson; check(value: T): void; toInput(value: T): HashInput; }; @@ -646,7 +646,7 @@ function fromCircuitValue< toInput(value) { return type.toInput(value); }, - toJson(value) { + toJSON(value) { return type.toJSON(value) as any; }, }; diff --git a/src/lib/hash.ts b/src/lib/hash.ts index 83541a89b8..0ae3df1bd2 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -146,7 +146,7 @@ const TokenSymbolPure: AsFieldsAndAux = { let actual = field.rangeCheckHelper(48); actual.assertEquals(field); }, - toJson({ symbol }) { + toJSON({ symbol }) { return symbol; }, toInput({ field }) { diff --git a/src/lib/party.ts b/src/lib/party.ts index d7235079aa..551d796165 100644 --- a/src/lib/party.ts +++ b/src/lib/party.ts @@ -807,7 +807,7 @@ class Party implements Types.Party { } toJSON() { - return Types.Party.toJson(this); + return Types.Party.toJSON(this); } hash() { @@ -821,7 +821,7 @@ class Party implements Types.Party { let input = Types.Party.toInput(this); return hashWithPrefix(prefixes.body, packToFields(input)); } else { - let json = Types.Party.toJson(this); + let json = Types.Party.toJSON(this); return Ledger.hashPartyFromJson(JSON.stringify(json)); } } @@ -1034,7 +1034,7 @@ type PartiesProved = { function partiesToJson({ feePayer, otherParties, memo }: Parties) { memo = Ledger.memoToBase58(memo); - return Types.Parties.toJson({ feePayer, otherParties, memo }); + return Types.Parties.toJSON({ feePayer, otherParties, memo }); } const Authorization = { diff --git a/src/snarky/parties-helpers.ts b/src/snarky/parties-helpers.ts index 8ed7eff250..4d3315d46f 100644 --- a/src/snarky/parties-helpers.ts +++ b/src/snarky/parties-helpers.ts @@ -21,8 +21,8 @@ function asFieldsAndAux(typeData: Layout, customTypes: CustomTypes) { fromFields(fields: Field[], aux: any[]): T { return fromFields(typeData, fields, aux, customTypes); }, - toJson(value: T): TJson { - return toJson(typeData, value, customTypes); + toJSON(value: T): TJson { + return toJSON(typeData, value, customTypes); }, check(value: T): void { check(typeData, value, customTypes); @@ -48,14 +48,14 @@ function asFieldsAndAux(typeData: Layout, customTypes: CustomTypes) { }; } -function toJson(typeData: Layout, value: any, customTypes: CustomTypes) { +function toJSON(typeData: Layout, value: any, customTypes: CustomTypes) { return mapReduce( { map(type, value) { - return Leaves.toJson(type, value); + return Leaves.toJSON(type, value); }, mapCustom(type, value) { - return type.toJson(value); + return type.toJSON(value); }, reduceArray(array) { return array; diff --git a/src/snarky/parties-leaves.ts b/src/snarky/parties-leaves.ts index b658264b6d..36b251e595 100644 --- a/src/snarky/parties-leaves.ts +++ b/src/snarky/parties-leaves.ts @@ -10,7 +10,7 @@ export { PublicKey, Field, Bool, AuthRequired, UInt64, UInt32, Sign, TokenId }; export { Events, StringWithHash, TokenSymbol }; export { - toJson, + toJSON, toFields, toAuxiliary, sizeInFields, @@ -68,7 +68,7 @@ let emptyType = { fromFields: () => null, check: () => {}, toInput: () => ({}), - toJson: () => null, + toJSON: () => null, }; const FullTypes: FullTypes = { @@ -78,13 +78,13 @@ const FullTypes: FullTypes = { number: { ...emptyType, toAuxiliary: (value = 0) => [value], - toJson: (value) => value, + toJSON: (value) => value, fromFields: (_, aux) => aux.pop()!, }, string: { ...emptyType, toAuxiliary: (value = '') => [value], - toJson: (value) => value, + toJSON: (value) => value, fromFields: (_, aux) => aux.pop()!, }, null: emptyType, @@ -140,9 +140,9 @@ let ToJson: ToJson = { }, }; -function toJson(typeName: K, value: TypeMap[K]) { +function toJSON(typeName: K, value: TypeMap[K]) { if (!(typeName in ToJson)) - throw Error(`toJson: unsupported type "${typeName}"`); + throw Error(`toJSON: unsupported type "${typeName}"`); return ToJson[typeName](value); } @@ -368,8 +368,8 @@ const Events: AsFieldsAndAux, string[][]> = { let data = aux.pop()!; return { data, hash }; }, - toJson({ data }) { - return data.map((row) => row.map((e) => toJson('Field', e))); + toJSON({ data }) { + return data.map((row) => row.map((e) => toJSON('Field', e))); }, check() {}, toInput({ hash }) { @@ -392,7 +392,7 @@ const StringWithHash: AsFieldsAndAux, string> = { let data = aux.pop()!; return { data, hash }; }, - toJson({ data }) { + toJSON({ data }) { return data; }, check() {}, From c2235ee8241bb9114d283b6f280a97ab1f3f2ebc Mon Sep 17 00:00:00 2001 From: Gregor Date: Sat, 30 Jul 2022 16:40:39 +0200 Subject: [PATCH 05/12] transpose Sign --- src/lib/int.ts | 8 ++++++++ src/snarky/parties-leaves.ts | 19 +++---------------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/lib/int.ts b/src/lib/int.ts index 5b11be50b4..fdd36a7e3c 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -350,6 +350,14 @@ class Sign extends CircuitValue { // x^2 == 1 <=> x == 1 or x == -1 x.value.square().assertEquals(Field.one); } + static toInput(x: Sign): HashInput { + return { packed: [[x.isPositive().toField(), 1]] }; + } + static toJSON(x: Sign) { + if (x.toString() === '1') return 'Positive'; + if (x.neg().toString() === '1') return 'Negative'; + throw Error(`Invalid Sign: ${x}`); + } neg() { return new Sign(this.value.neg()); } diff --git a/src/snarky/parties-leaves.ts b/src/snarky/parties-leaves.ts index 36b251e595..d3feadd2c3 100644 --- a/src/snarky/parties-leaves.ts +++ b/src/snarky/parties-leaves.ts @@ -55,7 +55,8 @@ type FullTypesKey = | 'undefined' | 'string' | 'UInt32' - | 'UInt64'; + | 'UInt64' + | 'Sign'; type OtherTypesKey = Exclude; type FullTypes = { [K in FullTypesKey]: AsFieldsAndAux; @@ -74,6 +75,7 @@ let emptyType = { const FullTypes: FullTypes = { UInt32: AsFieldsAndAux.fromCircuitValue(UInt32), UInt64: AsFieldsAndAux.fromCircuitValue(UInt64), + Sign: AsFieldsAndAux.fromCircuitValue(Sign), // primitive JS types number: { ...emptyType, @@ -133,11 +135,6 @@ let ToJson: ToJson = { TokenId(x: TokenId) { return Ledger.fieldToBase58(x); }, - Sign(x: Sign) { - if (x.toString() === '1') return 'Positive'; - if (x.neg().toString() === '1') return 'Negative'; - throw Error(`Invalid Sign: ${x}`); - }, }; function toJSON(typeName: K, value: TypeMap[K]) { @@ -172,7 +169,6 @@ let ToFields: ToFields = { .flat(); }, TokenId: asFields, - Sign: asFields, }; function toFields(typeName: K, value: TypeMap[K]) { @@ -195,7 +191,6 @@ let ToAuxiliary: ToAuxiliary = { Bool: empty, AuthRequired: empty, TokenId: empty, - Sign: empty, }; function toAuxiliary( @@ -216,7 +211,6 @@ let SizeInFields: SizeInFields = { Bool: 1, AuthRequired: 3, TokenId: 1, - Sign: 1, }; function sizeInFields(typeName: K) { @@ -268,9 +262,6 @@ let FromFields: FromFields = { TokenId(fields: Field[]) { return fields.pop()!; }, - Sign(fields: Field[]) { - return new Sign(fields.pop()!); - }, }; function fromFields( @@ -297,7 +288,6 @@ let Check: Check = { Bool.check(x.signatureSufficient); }, TokenId: (v) => Field.check(v), - Sign: (v) => Sign.check(v), }; function check(typeName: K, value: TypeMap[K]) { @@ -338,9 +328,6 @@ let ToInput: ToInput = { TokenId(x) { return { fields: [x] }; }, - Sign(x) { - return { packed: [[x.isPositive().toField(), 1]] }; - }, }; function toInput(typeName: K, value: TypeMap[K]) { From 5dd6321245bd7ee6ba6f9302650952e3d869c786 Mon Sep 17 00:00:00 2001 From: Gregor Date: Sat, 30 Jul 2022 17:09:54 +0200 Subject: [PATCH 06/12] enriich circuitValue & use to transpose TokenId --- src/lib/circuit_value.ts | 41 +++++++++++++++++++++++++++++++----- src/lib/hash.ts | 1 + src/lib/zkapp.ts | 2 +- src/snarky/parties-leaves.ts | 30 +++++++++++++------------- 4 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index e6395a7336..71b914e703 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -18,6 +18,7 @@ export { // internal API export { + AsFieldsExtended, AsFieldsAndAux, cloneCircuitValue, circuitValueEquals, @@ -362,13 +363,18 @@ function circuitMain( let primitives = new Set(['Field', 'Bool', 'Scalar', 'Group']); let complexTypes = new Set(['object', 'function']); +type AsFieldsExtended = AsFieldElements & { + toInput: (x: T) => { fields?: Field[]; packed?: [Field, number][] }; + toJSON: (x: T) => JSONValue; +}; + // TODO properly type this at the interface // create recursive type that describes JSON-like structures of circuit types // TODO unit-test this function circuitValue( typeObj: any, options?: { customObjectKeys: string[] } -): AsFieldElements { +): AsFieldsExtended { let objectKeys = typeof typeObj === 'object' && typeObj !== null ? options?.customObjectKeys ?? Object.keys(typeObj).sort() @@ -390,6 +396,30 @@ function circuitValue( if ('toFields' in typeObj) return typeObj.toFields(obj); return objectKeys.map((k) => toFields(typeObj[k], obj[k])).flat(); } + function toInput(typeObj: any, obj: any): HashInput { + if (!complexTypes.has(typeof typeObj) || typeObj === null) return {}; + if (Array.isArray(typeObj)) { + return typeObj + .map((t, i) => toInput(t, obj[i])) + .reduce(HashInput.append, {}); + } + if ('toInput' in typeObj) return typeObj.toInput(obj) as HashInput; + if ('toFields' in typeObj) { + return { fields: typeObj.toFields(obj) }; + } + return objectKeys + .map((k) => toInput(typeObj[k], obj[k])) + .reduce(HashInput.append, {}); + } + function toJSON(typeObj: any, obj: any): JSONValue { + if (!complexTypes.has(typeof typeObj) || typeObj === null) + return obj ?? null; + if (Array.isArray(typeObj)) return typeObj.map((t, i) => toJSON(t, obj[i])); + if ('toJSON' in typeObj) return typeObj.toJSON(obj); + return Object.fromEntries( + objectKeys.map((k) => [k, toJSON(typeObj[k], obj[k])]) + ); + } function ofFields(typeObj: any, fields: Field[]): any { if (!complexTypes.has(typeof typeObj) || typeObj === null) return null; if (Array.isArray(typeObj)) { @@ -419,6 +449,8 @@ function circuitValue( return { sizeInFields: () => sizeInFields(typeObj), toFields: (obj: T) => toFields(typeObj, obj), + toInput: (obj: T) => toInput(typeObj, obj), + toJSON: (obj: T) => toJSON(typeObj, obj), ofFields: (fields: Field[]) => ofFields(typeObj, fields) as T, check: (obj: T) => check(typeObj, obj), }; @@ -618,10 +650,9 @@ type AsFieldsAndAux = { }; // convert from circuit values -function fromCircuitValue< - T extends AnyConstructor & typeof CircuitValue, - TJson = JSONValue ->(type: T): AsFieldsAndAux, TJson> { +function fromCircuitValue, TJson = JSONValue>( + type: A +): AsFieldsAndAux { return { sizeInFields() { return type.sizeInFields(); diff --git a/src/lib/hash.ts b/src/lib/hash.ts index 0ae3df1bd2..0d7bbe4e70 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -122,6 +122,7 @@ const HashInput = { if (input2.packed !== undefined) { (input1.packed ??= []).push(...input2.packed); } + return input1; }, }; diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index d21f2b345f..a4af42acef 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -348,7 +348,7 @@ function wrapMethod( // then we're already in a witness block, and shouldn't open another one let { party, result } = methodCallDepth === 0 - ? Party.witness( + ? Party.witness( returnType ?? circuitValue(null), runCalledContract, true diff --git a/src/snarky/parties-leaves.ts b/src/snarky/parties-leaves.ts index d3feadd2c3..8dcf88d62c 100644 --- a/src/snarky/parties-leaves.ts +++ b/src/snarky/parties-leaves.ts @@ -3,7 +3,11 @@ import * as Json from './gen/parties-json'; import { UInt32, UInt64, Sign } from '../lib/int'; import { TokenSymbol, HashInput } from '../lib/hash'; import { PublicKey } from '../lib/signature'; -import { AsFieldsAndAux } from '../lib/circuit_value'; +import { + AsFieldsAndAux, + AsFieldsExtended, + circuitValue, +} from '../lib/circuit_value'; export { PublicKey, Field, Bool, AuthRequired, UInt64, UInt32, Sign, TokenId }; @@ -56,7 +60,8 @@ type FullTypesKey = | 'string' | 'UInt32' | 'UInt64' - | 'Sign'; + | 'Sign' + | 'TokenId'; type OtherTypesKey = Exclude; type FullTypes = { [K in FullTypesKey]: AsFieldsAndAux; @@ -72,10 +77,18 @@ let emptyType = { toJSON: () => null, }; +const TokenId: AsFieldsExtended = { + ...circuitValue(Field), + toJSON(x: TokenId) { + return Ledger.fieldToBase58(x); + }, +}; + const FullTypes: FullTypes = { UInt32: AsFieldsAndAux.fromCircuitValue(UInt32), UInt64: AsFieldsAndAux.fromCircuitValue(UInt64), Sign: AsFieldsAndAux.fromCircuitValue(Sign), + TokenId: AsFieldsAndAux.fromCircuitValue(TokenId), // primitive JS types number: { ...emptyType, @@ -132,9 +145,6 @@ let ToJson: ToJson = { default: throw Error('Unexpected permission'); } }, - TokenId(x: TokenId) { - return Ledger.fieldToBase58(x); - }, }; function toJSON(typeName: K, value: TypeMap[K]) { @@ -168,7 +178,6 @@ let ToFields: ToFields = { .map(asFields) .flat(); }, - TokenId: asFields, }; function toFields(typeName: K, value: TypeMap[K]) { @@ -190,7 +199,6 @@ let ToAuxiliary: ToAuxiliary = { Field: empty, Bool: empty, AuthRequired: empty, - TokenId: empty, }; function toAuxiliary( @@ -210,7 +218,6 @@ let SizeInFields: SizeInFields = { Field: 1, Bool: 1, AuthRequired: 3, - TokenId: 1, }; function sizeInFields(typeName: K) { @@ -259,9 +266,6 @@ let FromFields: FromFields = { let signatureSufficient = FromFields.Bool(fields, _); return { constant, signatureNecessary, signatureSufficient }; }, - TokenId(fields: Field[]) { - return fields.pop()!; - }, }; function fromFields( @@ -287,7 +291,6 @@ let Check: Check = { Bool.check(x.signatureNecessary); Bool.check(x.signatureSufficient); }, - TokenId: (v) => Field.check(v), }; function check(typeName: K, value: TypeMap[K]) { @@ -325,9 +328,6 @@ let ToInput: ToInput = { ], }; }, - TokenId(x) { - return { fields: [x] }; - }, }; function toInput(typeName: K, value: TypeMap[K]) { From 23b5b11b2115f5ee4938cf4117696bf50c549f29 Mon Sep 17 00:00:00 2001 From: Gregor Date: Sat, 30 Jul 2022 17:36:40 +0200 Subject: [PATCH 07/12] fix circuitValue.check --- src/lib/circuit_value.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 71b914e703..20598fe158 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -140,7 +140,6 @@ abstract class CircuitValue { if (fields === undefined || fields === null) { return; } - for (let i = 0; i < fields.length; ++i) { const [key, propType] = fields[i]; const value = (v as any)[key]; @@ -440,7 +439,7 @@ function circuitValue( return Object.fromEntries(objectKeys.map((k, i) => [k, values[i]])); } function check(typeObj: any, obj: any): void { - if (typeof typeObj !== 'object' || typeObj === null) return; + if (!complexTypes.has(typeof typeObj) || typeObj === null) return; if (Array.isArray(typeObj)) return typeObj.forEach((t, i) => check(t, obj[i])); if ('check' in typeObj) return typeObj.check(obj); From 82cd65ba8346eca966f7e1ef8ceb6d7844aee5ae Mon Sep 17 00:00:00 2001 From: Gregor Date: Sat, 30 Jul 2022 17:36:50 +0200 Subject: [PATCH 08/12] transpose AuthRequired --- src/snarky/parties-leaves.ts | 81 +++++++++++++++++------------------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/src/snarky/parties-leaves.ts b/src/snarky/parties-leaves.ts index 8dcf88d62c..0430d59ce1 100644 --- a/src/snarky/parties-leaves.ts +++ b/src/snarky/parties-leaves.ts @@ -61,7 +61,8 @@ type FullTypesKey = | 'UInt32' | 'UInt64' | 'Sign' - | 'TokenId'; + | 'TokenId' + | 'AuthRequired'; type OtherTypesKey = Exclude; type FullTypes = { [K in FullTypesKey]: AsFieldsAndAux; @@ -83,12 +84,49 @@ const TokenId: AsFieldsExtended = { return Ledger.fieldToBase58(x); }, }; +let AuthRequired: AsFieldsExtended = { + ...circuitValue( + { constant: Bool, signatureNecessary: Bool, signatureSufficient: Bool }, + { + customObjectKeys: [ + 'constant', + 'signatureNecessary', + 'signatureSufficient', + ], + } + ), + toJSON(x) { + let c = Number(x.constant.toBoolean()); + let n = Number(x.signatureNecessary.toBoolean()); + let s = Number(x.signatureSufficient.toBoolean()); + // prettier-ignore + switch (`${c}${n}${s}`) { + case '110': return 'Impossible'; + case '101': return 'None'; + case '000': return 'Proof'; + case '011': return 'Signature'; + case '001': return 'Either'; + default: throw Error('Unexpected permission'); + } + }, + // TODO: this should be made automatic by putting toInput on Bool + toInput({ constant, signatureNecessary, signatureSufficient }) { + return { + packed: [ + [constant.toField(), 1], + [signatureNecessary.toField(), 1], + [signatureSufficient.toField(), 1], + ], + }; + }, +}; const FullTypes: FullTypes = { UInt32: AsFieldsAndAux.fromCircuitValue(UInt32), UInt64: AsFieldsAndAux.fromCircuitValue(UInt64), Sign: AsFieldsAndAux.fromCircuitValue(Sign), TokenId: AsFieldsAndAux.fromCircuitValue(TokenId), + AuthRequired: AsFieldsAndAux.fromCircuitValue(AuthRequired), // primitive JS types number: { ...emptyType, @@ -131,20 +169,6 @@ let ToJson: ToJson = { Bool(x: Bool) { return x.toBoolean(); }, - AuthRequired(x: AuthRequired) { - let c = Number(x.constant.toBoolean()); - let n = Number(x.signatureNecessary.toBoolean()); - let s = Number(x.signatureSufficient.toBoolean()); - // prettier-ignore - switch (`${c}${n}${s}`) { - case '110': return 'Impossible'; - case '101': return 'None'; - case '000': return 'Proof'; - case '011': return 'Signature'; - case '001': return 'Either'; - default: throw Error('Unexpected permission'); - } - }, }; function toJSON(typeName: K, value: TypeMap[K]) { @@ -173,11 +197,6 @@ let ToFields: ToFields = { }, Field: asFields, Bool: asFields, - AuthRequired(x: AuthRequired) { - return [x.constant, x.signatureNecessary, x.signatureSufficient] - .map(asFields) - .flat(); - }, }; function toFields(typeName: K, value: TypeMap[K]) { @@ -198,7 +217,6 @@ let ToAuxiliary: ToAuxiliary = { PublicKey: empty, Field: empty, Bool: empty, - AuthRequired: empty, }; function toAuxiliary( @@ -217,7 +235,6 @@ let SizeInFields: SizeInFields = { PublicKey: 2, Field: 1, Bool: 1, - AuthRequired: 3, }; function sizeInFields(typeName: K) { @@ -260,12 +277,6 @@ let FromFields: FromFields = { Bool(fields: Field[]) { return Bool.Unsafe.ofField(fields.pop()!); }, - AuthRequired(fields: Field[], _) { - let constant = FromFields.Bool(fields, _); - let signatureNecessary = FromFields.Bool(fields, _); - let signatureSufficient = FromFields.Bool(fields, _); - return { constant, signatureNecessary, signatureSufficient }; - }, }; function fromFields( @@ -286,11 +297,6 @@ let Check: Check = { PublicKey: (v) => PublicKey.check(v), Field: (v) => Field.check(v), Bool: (v) => Bool.check(v), - AuthRequired(x: AuthRequired) { - Bool.check(x.constant); - Bool.check(x.signatureNecessary); - Bool.check(x.signatureSufficient); - }, }; function check(typeName: K, value: TypeMap[K]) { @@ -319,15 +325,6 @@ let ToInput: ToInput = { Bool(x) { return { packed: [[x.toField(), 1]] }; }, - AuthRequired({ constant, signatureNecessary, signatureSufficient }) { - return { - packed: [ - [constant.toField(), 1], - [signatureNecessary.toField(), 1], - [signatureSufficient.toField(), 1], - ], - }; - }, }; function toInput(typeName: K, value: TypeMap[K]) { From b10646f1515a697a10c5e7b8d24dc1b8c1887eb7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Sat, 30 Jul 2022 17:45:20 +0200 Subject: [PATCH 09/12] transpose PublicKey --- src/snarky/parties-leaves.ts | 88 ++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 43 deletions(-) diff --git a/src/snarky/parties-leaves.ts b/src/snarky/parties-leaves.ts index 0430d59ce1..fde96f14b1 100644 --- a/src/snarky/parties-leaves.ts +++ b/src/snarky/parties-leaves.ts @@ -62,7 +62,8 @@ type FullTypesKey = | 'UInt64' | 'Sign' | 'TokenId' - | 'AuthRequired'; + | 'AuthRequired' + | 'PublicKey'; type OtherTypesKey = Exclude; type FullTypes = { [K in FullTypesKey]: AsFieldsAndAux; @@ -80,11 +81,12 @@ let emptyType = { const TokenId: AsFieldsExtended = { ...circuitValue(Field), - toJSON(x: TokenId) { + toJSON(x: TokenId): Json.TokenId { return Ledger.fieldToBase58(x); }, }; -let AuthRequired: AsFieldsExtended = { + +const AuthRequired: AsFieldsExtended = { ...circuitValue( { constant: Bool, signatureNecessary: Bool, signatureSufficient: Bool }, { @@ -95,7 +97,7 @@ let AuthRequired: AsFieldsExtended = { ], } ), - toJSON(x) { + toJSON(x): Json.AuthRequired { let c = Number(x.constant.toBoolean()); let n = Number(x.signatureNecessary.toBoolean()); let s = Number(x.signatureSufficient.toBoolean()); @@ -121,12 +123,51 @@ let AuthRequired: AsFieldsExtended = { }, }; +const PublicKeyCompressed: AsFieldsExtended = { + ...circuitValue(PublicKey), + toJSON(x): Json.PublicKey { + return Ledger.publicKeyToString(x); + }, + toFields({ g }: PublicKey) { + let { x, y } = g; + // TODO inefficient! in-snark public key should be uncompressed + let isOdd = y.toBits()[0]; + return [x, isOdd.toField()]; + }, + ofFields([x, isOdd]) { + // compute y from elliptic curve equation y^2 = x^3 + 5 + // TODO: this is used in-snark, so we should improve constraint efficiency + let ySquared = x.mul(x).mul(x).add(5); + let someY: Field; + if (ySquared.isConstant()) { + someY = ySquared.sqrt(); + } else { + someY = Circuit.witness(Field, () => ySquared.toConstant().sqrt()); + someY.square().equals(ySquared).or(x.equals(Field.zero)).assertTrue(); + } + let isTheRightY = isOdd.equals(someY.toBits()[0].toField()); + let y = isTheRightY + .toField() + .mul(someY) + .add(isTheRightY.not().toField().mul(someY.neg())); + return new PublicKey(new Group(x, y)); + }, + toInput(pk) { + let [x, isOdd] = this.toFields(pk); + return { + fields: [x], + packed: [[isOdd, 1]], + }; + }, +}; + const FullTypes: FullTypes = { UInt32: AsFieldsAndAux.fromCircuitValue(UInt32), UInt64: AsFieldsAndAux.fromCircuitValue(UInt64), Sign: AsFieldsAndAux.fromCircuitValue(Sign), TokenId: AsFieldsAndAux.fromCircuitValue(TokenId), AuthRequired: AsFieldsAndAux.fromCircuitValue(AuthRequired), + PublicKey: AsFieldsAndAux.fromCircuitValue(PublicKeyCompressed), // primitive JS types number: { ...emptyType, @@ -162,9 +203,6 @@ type ToJson = { }; let ToJson: ToJson = { - PublicKey(x: PublicKey): Json.PublicKey { - return Ledger.publicKeyToString(x); - }, Field: asString, Bool(x: Bool) { return x.toBoolean(); @@ -189,12 +227,6 @@ function empty(_: any): [] { } let ToFields: ToFields = { - PublicKey({ g }: PublicKey) { - let { x, y } = g; - // TODO inefficient! in-snark public key should be uncompressed - let isOdd = y.toBits()[0]; - return [x, isOdd.toField()]; - }, Field: asFields, Bool: asFields, }; @@ -214,7 +246,6 @@ type ToAuxiliary = { }; let ToAuxiliary: ToAuxiliary = { - PublicKey: empty, Field: empty, Bool: empty, }; @@ -232,7 +263,6 @@ function toAuxiliary( type SizeInFields = { [K in OtherTypesKey]: number }; let SizeInFields: SizeInFields = { - PublicKey: 2, Field: 1, Bool: 1, }; @@ -251,26 +281,6 @@ type FromFields = { }; let FromFields: FromFields = { - PublicKey(fields: Field[]) { - let x = fields.pop()!; - let isOdd = fields.pop()!; - // compute y from elliptic curve equation y^2 = x^3 + 5 - // TODO: this is used in-snark, so we should improve constraint efficiency - let ySquared = x.mul(x).mul(x).add(5); - let someY: Field; - if (ySquared.isConstant()) { - someY = ySquared.sqrt(); - } else { - someY = Circuit.witness(Field, () => ySquared.toConstant().sqrt()); - someY.square().equals(ySquared).or(x.equals(Field.zero)).assertTrue(); - } - let isTheRightY = isOdd.equals(someY.toBits()[0].toField()); - let y = isTheRightY - .toField() - .mul(someY) - .add(isTheRightY.not().toField().mul(someY.neg())); - return new PublicKey(new Group(x, y)); - }, Field(fields: Field[]) { return fields.pop()!; }, @@ -294,7 +304,6 @@ function fromFields( type Check = { [K in OtherTypesKey]: (x: TypeMap[K]) => void }; let Check: Check = { - PublicKey: (v) => PublicKey.check(v), Field: (v) => Field.check(v), Bool: (v) => Bool.check(v), }; @@ -312,13 +321,6 @@ type ToInput = { }; let ToInput: ToInput = { - PublicKey(pk) { - let [x, isOdd] = ToFields.PublicKey(pk); - return { - fields: [x], - packed: [[isOdd, 1]], - }; - }, Field(x) { return { fields: [x] }; }, From 5c2bd2ab4bc28437bce194fcb3c2aaba5d8422a5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Sat, 30 Jul 2022 18:17:32 +0200 Subject: [PATCH 10/12] transpose Bool and Field --- src/snarky/parties-helpers.ts | 10 ++--- src/snarky/parties-leaves.ts | 83 ++++++++++++----------------------- 2 files changed, 32 insertions(+), 61 deletions(-) diff --git a/src/snarky/parties-helpers.ts b/src/snarky/parties-helpers.ts index 4d3315d46f..8355f15200 100644 --- a/src/snarky/parties-helpers.ts +++ b/src/snarky/parties-helpers.ts @@ -52,7 +52,7 @@ function toJSON(typeData: Layout, value: any, customTypes: CustomTypes) { return mapReduce( { map(type, value) { - return Leaves.toJSON(type, value); + return Leaves.toJSON(type, value as never); }, mapCustom(type, value) { return type.toJSON(value); @@ -80,7 +80,7 @@ function toFields(typeData: Layout, value: any, customTypes: CustomTypes) { return mapReduce( { map(type, value) { - return Leaves.toFields(type, value); + return Leaves.toFields(type, value as never); }, mapCustom(type, value) { return type.toFields(value); @@ -228,7 +228,7 @@ function check(typeData: Layout, value: any, customTypes: CustomTypes) { return mapReduce( { map(type, value) { - return Leaves.check(type, value); + return Leaves.check(type, value as never); }, mapCustom(type, value) { return type.check(value); @@ -248,7 +248,7 @@ function toInput(typeData: Layout, value: any, customTypes: CustomTypes) { return mapReduce( { map(type, value) { - return Leaves.toInput(type, value); + return Leaves.toInput(type, value as never); }, mapCustom(type, value) { return type.toInput(value); @@ -319,7 +319,7 @@ function mapReduce( case 'flaggedOption': let v: { isSome: T; value: T } | undefined = value as any; return spec.reduceFlaggedOption({ - isSome: spec.map('Bool', v?.isSome), + isSome: spec.mapCustom(Leaves.FullTypes.Bool, v?.isSome), value: mapReduce(spec, inner, v?.value), }); case 'orUndefined': diff --git a/src/snarky/parties-leaves.ts b/src/snarky/parties-leaves.ts index fde96f14b1..befdc72088 100644 --- a/src/snarky/parties-leaves.ts +++ b/src/snarky/parties-leaves.ts @@ -58,6 +58,8 @@ type FullTypesKey = | 'null' | 'undefined' | 'string' + | 'Field' + | 'Bool' | 'UInt32' | 'UInt64' | 'Sign' @@ -79,6 +81,13 @@ let emptyType = { toJSON: () => null, }; +const Bool_: AsFieldsExtended = { + ...circuitValue(Bool), + toInput(x) { + return { packed: [[x.toField(), 1]] }; + }, +}; + const TokenId: AsFieldsExtended = { ...circuitValue(Field), toJSON(x: TokenId): Json.TokenId { @@ -161,13 +170,17 @@ const PublicKeyCompressed: AsFieldsExtended = { }, }; +let { fromCircuitValue } = AsFieldsAndAux; + const FullTypes: FullTypes = { - UInt32: AsFieldsAndAux.fromCircuitValue(UInt32), - UInt64: AsFieldsAndAux.fromCircuitValue(UInt64), - Sign: AsFieldsAndAux.fromCircuitValue(Sign), - TokenId: AsFieldsAndAux.fromCircuitValue(TokenId), - AuthRequired: AsFieldsAndAux.fromCircuitValue(AuthRequired), - PublicKey: AsFieldsAndAux.fromCircuitValue(PublicKeyCompressed), + Field: fromCircuitValue(circuitValue(Field)), + Bool: fromCircuitValue(Bool_), + UInt32: fromCircuitValue(UInt32), + UInt64: fromCircuitValue(UInt64), + Sign: fromCircuitValue(Sign), + TokenId: fromCircuitValue(TokenId), + AuthRequired: fromCircuitValue(AuthRequired), + PublicKey: fromCircuitValue(PublicKeyCompressed), // primitive JS types number: { ...emptyType, @@ -194,20 +207,11 @@ function isFullType(type: keyof TypeMap): type is FullTypesKey { // json conversion -function asString(x: Field | bigint) { - return x.toString(); -} - type ToJson = { [K in OtherTypesKey]: (x: TypeMap[K]) => Json.TypeMap[K]; }; -let ToJson: ToJson = { - Field: asString, - Bool(x: Bool) { - return x.toBoolean(); - }, -}; +let ToJson: ToJson = {}; function toJSON(typeName: K, value: TypeMap[K]) { if (!(typeName in ToJson)) @@ -219,17 +223,7 @@ function toJSON(typeName: K, value: TypeMap[K]) { type ToFields = { [K in OtherTypesKey]: (x: TypeMap[K]) => Field[] }; -function asFields(x: any): Field[] { - return x.toFields(); -} -function empty(_: any): [] { - return []; -} - -let ToFields: ToFields = { - Field: asFields, - Bool: asFields, -}; +let ToFields: ToFields = {}; function toFields(typeName: K, value: TypeMap[K]) { if (!(typeName in ToFields)) @@ -245,10 +239,7 @@ type ToAuxiliary = { | ((x: TypeMap[K] | undefined) => [TypeMap[K]]); }; -let ToAuxiliary: ToAuxiliary = { - Field: empty, - Bool: empty, -}; +let ToAuxiliary: ToAuxiliary = {}; function toAuxiliary( typeName: K, @@ -262,10 +253,7 @@ function toAuxiliary( // size in fields type SizeInFields = { [K in OtherTypesKey]: number }; -let SizeInFields: SizeInFields = { - Field: 1, - Bool: 1, -}; +let SizeInFields: SizeInFields = {}; function sizeInFields(typeName: K) { if (!(typeName in ToFields)) @@ -280,14 +268,7 @@ type FromFields = { [K in OtherTypesKey]: (fields: Field[], aux: any[]) => TypeMap[K]; }; -let FromFields: FromFields = { - Field(fields: Field[]) { - return fields.pop()!; - }, - Bool(fields: Field[]) { - return Bool.Unsafe.ofField(fields.pop()!); - }, -}; +let FromFields: FromFields = {}; function fromFields( typeName: K, @@ -303,10 +284,7 @@ function fromFields( type Check = { [K in OtherTypesKey]: (x: TypeMap[K]) => void }; -let Check: Check = { - Field: (v) => Field.check(v), - Bool: (v) => Bool.check(v), -}; +let Check: Check = {}; function check(typeName: K, value: TypeMap[K]) { if (!(typeName in ToFields)) @@ -320,14 +298,7 @@ type ToInput = { [K in OtherTypesKey]: (x: TypeMap[K]) => HashInput; }; -let ToInput: ToInput = { - Field(x) { - return { fields: [x] }; - }, - Bool(x) { - return { packed: [[x.toField(), 1]] }; - }, -}; +let ToInput: ToInput = {}; function toInput(typeName: K, value: TypeMap[K]) { if (!(typeName in ToFields)) @@ -355,7 +326,7 @@ const Events: AsFieldsAndAux, string[][]> = { return { data, hash }; }, toJSON({ data }) { - return data.map((row) => row.map((e) => toJSON('Field', e))); + return data.map((row) => row.map((e) => e.toString())); }, check() {}, toInput({ hash }) { From 7a150fe3e37796ff08d2620ada70378baeaa9cfc Mon Sep 17 00:00:00 2001 From: Gregor Date: Sat, 30 Jul 2022 18:35:24 +0200 Subject: [PATCH 11/12] remove non-transposed logic --- src/snarky/parties-helpers.ts | 39 ++-------- src/snarky/parties-leaves.ts | 142 ++-------------------------------- 2 files changed, 12 insertions(+), 169 deletions(-) diff --git a/src/snarky/parties-helpers.ts b/src/snarky/parties-helpers.ts index 8355f15200..1c56bc9adc 100644 --- a/src/snarky/parties-helpers.ts +++ b/src/snarky/parties-helpers.ts @@ -1,4 +1,4 @@ -import * as Leaves from './parties-leaves'; +import { TypeMap } from './parties-leaves'; import { Field, Bool, Circuit } from '../snarky'; import { circuitArray, AsFieldsAndAux } from '../lib/circuit_value'; import { HashInput } from 'lib/hash'; @@ -52,9 +52,6 @@ function toJSON(typeData: Layout, value: any, customTypes: CustomTypes) { return mapReduce( { map(type, value) { - return Leaves.toJSON(type, value as never); - }, - mapCustom(type, value) { return type.toJSON(value); }, reduceArray(array) { @@ -80,9 +77,6 @@ function toFields(typeData: Layout, value: any, customTypes: CustomTypes) { return mapReduce( { map(type, value) { - return Leaves.toFields(type, value as never); - }, - mapCustom(type, value) { return type.toFields(value); }, reduceArray(array) { @@ -108,9 +102,6 @@ function toAuxiliary(typeData: Layout, value: any, customTypes: CustomTypes) { return mapReduce( { map(type, value) { - return Leaves.toAuxiliary(type, value); - }, - mapCustom(type, value) { return type.toAuxiliary(value); }, reduceArray(array, { staticLength }) { @@ -136,9 +127,6 @@ function toAuxiliary(typeData: Layout, value: any, customTypes: CustomTypes) { function sizeInFields(typeData: Layout, customTypes: CustomTypes) { let spec: MapReduceSpec = { map(type) { - return Leaves.sizeInFields(type); - }, - mapCustom(type) { return type.sizeInFields(); }, reduceArray(_, { inner, staticLength }): number { @@ -218,19 +206,13 @@ function fromFieldsReversed( } return values; } - if (Leaves.isFullType(typeData.type)) { - return Leaves.FullTypes[typeData.type].fromFields(fields, aux); - } - return Leaves.fromFields(typeData.type, fields, aux); + return TypeMap[typeData.type].fromFields(fields, aux); } function check(typeData: Layout, value: any, customTypes: CustomTypes) { return mapReduce( { map(type, value) { - return Leaves.check(type, value as never); - }, - mapCustom(type, value) { return type.check(value); }, reduceArray() {}, @@ -248,9 +230,6 @@ function toInput(typeData: Layout, value: any, customTypes: CustomTypes) { return mapReduce( { map(type, value) { - return Leaves.toInput(type, value as never); - }, - mapCustom(type, value) { return type.toInput(value); }, reduceArray(array) { @@ -288,8 +267,7 @@ function toInput(typeData: Layout, value: any, customTypes: CustomTypes) { type MapReduceSpec = { customTypes: CustomTypes; - map: (type: Leaves.OtherTypesKey, value?: T) => R; - mapCustom: (type: AsFieldsAndAux, value?: T) => R; + map: (type: AsFieldsAndAux, value?: T) => R; reduceArray: (array: R[], typeData: ArrayLayout) => R; reduceObject: (keys: string[], record: Record) => R; reduceFlaggedOption: (option: { isSome: R; value: R }) => R; @@ -304,7 +282,7 @@ function mapReduce( let { checkedTypeName } = typeData; if (checkedTypeName) { // there's a custom type! - return spec.mapCustom(spec.customTypes[checkedTypeName], value); + return spec.map(spec.customTypes[checkedTypeName], value); } if (typeData.type === 'array') { let v: T[] | undefined = value as any; @@ -319,7 +297,7 @@ function mapReduce( case 'flaggedOption': let v: { isSome: T; value: T } | undefined = value as any; return spec.reduceFlaggedOption({ - isSome: spec.mapCustom(Leaves.FullTypes.Bool, v?.isSome), + isSome: spec.map(TypeMap.Bool, v?.isSome), value: mapReduce(spec, inner, v?.value), }); case 'orUndefined': @@ -339,17 +317,14 @@ function mapReduce( }); return spec.reduceObject(keys, object); } - if (Leaves.isFullType(typeData.type)) { - return spec.mapCustom(Leaves.FullTypes[typeData.type], value); - } - return spec.map(typeData.type, value); + return spec.map(TypeMap[typeData.type], value); } // types type WithChecked = { checkedType?: Layout; checkedTypeName?: string }; -type BaseLayout = { type: keyof Leaves.TypeMap } & WithChecked; +type BaseLayout = { type: keyof TypeMap } & WithChecked; type RangeLayout = { type: 'object'; diff --git a/src/snarky/parties-leaves.ts b/src/snarky/parties-leaves.ts index befdc72088..5638c63706 100644 --- a/src/snarky/parties-leaves.ts +++ b/src/snarky/parties-leaves.ts @@ -13,19 +13,7 @@ export { PublicKey, Field, Bool, AuthRequired, UInt64, UInt32, Sign, TokenId }; export { Events, StringWithHash, TokenSymbol }; -export { - toJSON, - toFields, - toAuxiliary, - sizeInFields, - fromFields, - check, - toInput, - TypeMap, - OtherTypesKey, - isFullType, - FullTypes, -}; +export { TypeMap }; type AuthRequired = { constant: Bool; @@ -53,23 +41,6 @@ type TypeMap = { // types that implement AsFieldAndAux, and so can be left out of the conversion maps below // sort of a "transposed" representation -type FullTypesKey = - | 'number' - | 'null' - | 'undefined' - | 'string' - | 'Field' - | 'Bool' - | 'UInt32' - | 'UInt64' - | 'Sign' - | 'TokenId' - | 'AuthRequired' - | 'PublicKey'; -type OtherTypesKey = Exclude; -type FullTypes = { - [K in FullTypesKey]: AsFieldsAndAux; -}; let emptyType = { sizeInFields: () => 0, @@ -172,7 +143,9 @@ const PublicKeyCompressed: AsFieldsExtended = { let { fromCircuitValue } = AsFieldsAndAux; -const FullTypes: FullTypes = { +const TypeMap: { + [K in keyof TypeMap]: AsFieldsAndAux; +} = { Field: fromCircuitValue(circuitValue(Field)), Bool: fromCircuitValue(Bool_), UInt32: fromCircuitValue(UInt32), @@ -201,112 +174,7 @@ const FullTypes: FullTypes = { }, }; -function isFullType(type: keyof TypeMap): type is FullTypesKey { - return type in FullTypes; -} - -// json conversion - -type ToJson = { - [K in OtherTypesKey]: (x: TypeMap[K]) => Json.TypeMap[K]; -}; - -let ToJson: ToJson = {}; - -function toJSON(typeName: K, value: TypeMap[K]) { - if (!(typeName in ToJson)) - throw Error(`toJSON: unsupported type "${typeName}"`); - return ToJson[typeName](value); -} - -// to fields - -type ToFields = { [K in OtherTypesKey]: (x: TypeMap[K]) => Field[] }; - -let ToFields: ToFields = {}; - -function toFields(typeName: K, value: TypeMap[K]) { - if (!(typeName in ToFields)) - throw Error(`toFields: unsupported type "${typeName}"`); - return ToFields[typeName](value); -} - -// to auxiliary - -type ToAuxiliary = { - [K in OtherTypesKey]: - | ((x: TypeMap[K] | undefined) => []) - | ((x: TypeMap[K] | undefined) => [TypeMap[K]]); -}; - -let ToAuxiliary: ToAuxiliary = {}; - -function toAuxiliary( - typeName: K, - value: TypeMap[K] | undefined -) { - if (!(typeName in ToFields)) - throw Error(`toAuxiliary: unsupported type "${typeName}"`); - return ToAuxiliary[typeName](value); -} - -// size in fields - -type SizeInFields = { [K in OtherTypesKey]: number }; -let SizeInFields: SizeInFields = {}; - -function sizeInFields(typeName: K) { - if (!(typeName in ToFields)) - throw Error(`sizeInFields: unsupported type "${typeName}"`); - return SizeInFields[typeName]; -} - -// from fields & aux -// these functions get the reversed output of `toFields` and `toAuxiliary` and pop the values they need from those arrays - -type FromFields = { - [K in OtherTypesKey]: (fields: Field[], aux: any[]) => TypeMap[K]; -}; - -let FromFields: FromFields = {}; - -function fromFields( - typeName: K, - fields: Field[], - aux: any[] -): TypeMap[K] { - if (!(typeName in ToFields)) - throw Error(`fromFields: unsupported type "${typeName}"`); - return FromFields[typeName](fields, aux); -} - -// check - -type Check = { [K in OtherTypesKey]: (x: TypeMap[K]) => void }; - -let Check: Check = {}; - -function check(typeName: K, value: TypeMap[K]) { - if (!(typeName in ToFields)) - throw Error(`check: unsupported type "${typeName}"`); - Check[typeName](value); -} - -// to input - -type ToInput = { - [K in OtherTypesKey]: (x: TypeMap[K]) => HashInput; -}; - -let ToInput: ToInput = {}; - -function toInput(typeName: K, value: TypeMap[K]) { - if (!(typeName in ToFields)) - throw Error(`toInput: unsupported type "${typeName}"`); - return ToInput[typeName](value); -} - -// converters for types which got an annotation about its circuit type in Ocaml +// types which got an annotation about its circuit type in Ocaml type DataAsHash = { data: T; hash: Field }; From 43aece6f38847a42b920f4dbad0bc71afc298e28 Mon Sep 17 00:00:00 2001 From: Gregor Date: Sat, 30 Jul 2022 19:54:56 +0200 Subject: [PATCH 12/12] clean up by putting toInput on Field, Bool --- src/index.ts | 3 +-- src/lib/circuit_value.ts | 3 ++- src/lib/core.ts | 11 +++++++++++ src/snarky.d.ts | 9 +++++++-- src/snarky/parties-leaves.ts | 26 +++++--------------------- 5 files changed, 26 insertions(+), 26 deletions(-) create mode 100644 src/lib/core.ts diff --git a/src/index.ts b/src/index.ts index 6a10e53f48..dbacc9f8c0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,4 @@ export { - Field, - Bool, Group, Scalar, AsFieldElements, @@ -8,6 +6,7 @@ export { isReady, shutdown, } from './snarky'; +export { Field, Bool } from './lib/core'; export type { VerificationKey, Keypair } from './snarky'; export * from './snarky/addons'; export { Poseidon } from './lib/hash'; diff --git a/src/lib/circuit_value.ts b/src/lib/circuit_value.ts index 20598fe158..800873df27 100644 --- a/src/lib/circuit_value.ts +++ b/src/lib/circuit_value.ts @@ -1,5 +1,6 @@ import 'reflect-metadata'; -import { Circuit, Field, Bool, JSONValue, AsFieldElements } from '../snarky'; +import { Circuit, JSONValue, AsFieldElements } from '../snarky'; +import { Field, Bool } from './core'; import { Context } from './global-context'; import { HashInput } from './hash'; import { snarkContext } from './proof_system'; diff --git a/src/lib/core.ts b/src/lib/core.ts new file mode 100644 index 0000000000..09c9c51ed9 --- /dev/null +++ b/src/lib/core.ts @@ -0,0 +1,11 @@ +import { Bool, Field } from '../snarky'; + +export { Field, Bool }; + +Field.toInput = function (x) { + return { fields: [x] }; +}; + +Bool.toInput = function (x) { + return { packed: [[x.toField(), 1] as [Field, number]] }; +}; diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 219b2b1e10..0a38821a32 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -334,8 +334,7 @@ declare class Field { ): Bool; */ - // static toJSON(x: Field): JSONValue; - + static toJSON(x: Field): JSONValue; static fromJSON(x: JSONValue): Field | null; static fromString(x: string): Field; @@ -343,6 +342,9 @@ declare class Field { static fromBigInt(x: bigint): Field; static check(x: Field): void; + + // monkey-patched in JS + static toInput(x: Field): { fields: Field[] }; } /** @@ -465,6 +467,9 @@ declare class Bool { static toJSON(x: Bool): JSONValue; static fromJSON(x: JSONValue): Bool | null; static check(x: Bool): void; + + // monkey-patched in JS + static toInput(x: Bool): { packed: [Field, number][] }; } declare interface AsFieldElements { diff --git a/src/snarky/parties-leaves.ts b/src/snarky/parties-leaves.ts index 5638c63706..79087e80e4 100644 --- a/src/snarky/parties-leaves.ts +++ b/src/snarky/parties-leaves.ts @@ -1,7 +1,8 @@ -import { Field, Bool, Group, Ledger, Circuit } from '../snarky'; +import { Group, Ledger, Circuit } from '../snarky'; +import { Field, Bool } from '../lib/core'; import * as Json from './gen/parties-json'; import { UInt32, UInt64, Sign } from '../lib/int'; -import { TokenSymbol, HashInput } from '../lib/hash'; +import { TokenSymbol } from '../lib/hash'; import { PublicKey } from '../lib/signature'; import { AsFieldsAndAux, @@ -52,13 +53,6 @@ let emptyType = { toJSON: () => null, }; -const Bool_: AsFieldsExtended = { - ...circuitValue(Bool), - toInput(x) { - return { packed: [[x.toField(), 1]] }; - }, -}; - const TokenId: AsFieldsExtended = { ...circuitValue(Field), toJSON(x: TokenId): Json.TokenId { @@ -91,16 +85,6 @@ const AuthRequired: AsFieldsExtended = { default: throw Error('Unexpected permission'); } }, - // TODO: this should be made automatic by putting toInput on Bool - toInput({ constant, signatureNecessary, signatureSufficient }) { - return { - packed: [ - [constant.toField(), 1], - [signatureNecessary.toField(), 1], - [signatureSufficient.toField(), 1], - ], - }; - }, }; const PublicKeyCompressed: AsFieldsExtended = { @@ -146,8 +130,8 @@ let { fromCircuitValue } = AsFieldsAndAux; const TypeMap: { [K in keyof TypeMap]: AsFieldsAndAux; } = { - Field: fromCircuitValue(circuitValue(Field)), - Bool: fromCircuitValue(Bool_), + Field: fromCircuitValue(Field), + Bool: fromCircuitValue(Bool), UInt32: fromCircuitValue(UInt32), UInt64: fromCircuitValue(UInt64), Sign: fromCircuitValue(Sign),