diff --git a/CHANGELOG.md b/CHANGELOG.md index 2686d8e543..e691085dbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Change `{SmartContract,ZkProgram}.analyzeMethods()` to be async https://github.com/o1-labs/o1js/pull/1450 - `Provable.runAndCheck()`, `Provable.constraintSystem()` and `{SmartContract,ZkProgram}.digest()` are also async now - `Provable.runAndCheckSync()` added and immediately deprecated for a smoother upgrade path for tests +- **Remove deprecated APIs** + - Remove `CircuitValue`, `prop`, `arrayProp` and `matrixProp` https://github.com/o1-labs/o1js/pull/1507 - Remove `this.sender` which unintuitively did not prove that its value was the actual sender of the transaction https://github.com/o1-labs/o1js/pull/1464 [@julio4](https://github.com/julio4) Replaced by more explicit APIs: - `this.sender.getUnconstrained()` which has the old behavior of `this.sender`, and returns an unconstrained value (which means that the prover can set it to any value they want) diff --git a/src/bindings b/src/bindings index 06030b02d1..b5422da399 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 06030b02d1d04abc534d12645272e2924afbefaa +Subproject commit b5422da399520e7acd529648eeeadf30e1be2c79 diff --git a/src/examples/zkapps/voting/election-preconditions.ts b/src/examples/zkapps/voting/election-preconditions.ts index c574321e8b..131fc69012 100644 --- a/src/examples/zkapps/voting/election-preconditions.ts +++ b/src/examples/zkapps/voting/election-preconditions.ts @@ -1,12 +1,6 @@ -import { CircuitValue, prop, UInt32 } from 'o1js'; +import { Struct, UInt32 } from 'o1js'; -export default class ElectionPreconditions extends CircuitValue { - @prop startElection: UInt32; - @prop endElection: UInt32; - - constructor(startElection: UInt32, endElection: UInt32) { - super(); - this.startElection = startElection; - this.endElection = endElection; - } -} +export default class ElectionPreconditions extends Struct({ + startElection: UInt32, + endElection: UInt32, +}) {} diff --git a/src/examples/zkapps/voting/factory.ts b/src/examples/zkapps/voting/factory.ts index f9162d61d7..21156d2bf9 100644 --- a/src/examples/zkapps/voting/factory.ts +++ b/src/examples/zkapps/voting/factory.ts @@ -11,7 +11,9 @@ import { } from './preconditions.js'; import { Voting, Voting_ } from './voting.js'; -export interface VotingAppParams { +export { VotingAppParams }; + +type VotingAppParams = { candidatePreconditions: ParticipantPreconditions; voterPreconditions: ParticipantPreconditions; electionPreconditions: ElectionPreconditions; @@ -19,7 +21,7 @@ export interface VotingAppParams { candidateKey: PrivateKey; votingKey: PrivateKey; doProofs: boolean; -} +}; function defaultParams(): VotingAppParams { return { diff --git a/src/examples/zkapps/voting/member.ts b/src/examples/zkapps/voting/member.ts index ef0a8b6050..fe9d189128 100644 --- a/src/examples/zkapps/voting/member.ts +++ b/src/examples/zkapps/voting/member.ts @@ -1,11 +1,10 @@ import { - CircuitValue, Field, - prop, PublicKey, UInt64, Poseidon, MerkleWitness, + Struct, } from 'o1js'; export class MyMerkleWitness extends MerkleWitness(3) {} @@ -17,24 +16,24 @@ let dummyWitness = Array.from(Array(MyMerkleWitness.height - 1).keys()).map( () => w ); -export class Member extends CircuitValue { - @prop publicKey: PublicKey; - @prop balance: UInt64; +export class Member extends Struct({ + publicKey: PublicKey, + balance: UInt64, - // will need this to keep track of votes for candidates - @prop votes: Field; - - @prop witness: MyMerkleWitness; - @prop votesWitness: MyMerkleWitness; + // need this to keep track of votes for candidates + votes: Field, + witness: MyMerkleWitness, + votesWitness: MyMerkleWitness, +}) { constructor(publicKey: PublicKey, balance: UInt64) { - super(); - this.publicKey = publicKey; - this.balance = balance; - this.votes = Field(0); - - this.witness = new MyMerkleWitness(dummyWitness); - this.votesWitness = new MyMerkleWitness(dummyWitness); + super({ + publicKey, + balance, + votes: Field(0), + witness: new MyMerkleWitness(dummyWitness), + votesWitness: new MyMerkleWitness(dummyWitness), + }); } getHash(): Field { diff --git a/src/examples/zkapps/voting/membership.ts b/src/examples/zkapps/voting/membership.ts index 841f7b8707..5bed4e3012 100644 --- a/src/examples/zkapps/voting/membership.ts +++ b/src/examples/zkapps/voting/membership.ts @@ -19,11 +19,13 @@ import { ParticipantPreconditions } from './preconditions.js'; let participantPreconditions = ParticipantPreconditions.default; -interface MembershipParams { +Provable; + +type MembershipParams = { participantPreconditions: ParticipantPreconditions; contractAddress: PublicKey; doProofs: boolean; -} +}; /** * Returns a new contract instance that based on a set of preconditions. @@ -124,7 +126,7 @@ export class Membership_ extends SmartContract { }), Bool, (state: Bool, action: Member) => { - return action.equals(member).or(state); + return Provable.equal(Member, action, member).or(state); }, // initial state { state: Bool(false), actionState: accumulatedMembers } diff --git a/src/examples/zkapps/voting/participant-preconditions.ts b/src/examples/zkapps/voting/participant-preconditions.ts deleted file mode 100644 index 4101d4846f..0000000000 --- a/src/examples/zkapps/voting/participant-preconditions.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CircuitValue, prop, UInt64 } from 'o1js'; - -export default class ParticipantPreconditions extends CircuitValue { - @prop minMinaVote: UInt64; - @prop minMinaCandidate: UInt64; - @prop maxMinaCandidate: UInt64; - - constructor( - minMinaVote: UInt64, - minMinaCandidate: UInt64, - maxMinaCandidate: UInt64 - ) { - super(); - this.minMinaVote = minMinaVote; - this.minMinaCandidate = minMinaCandidate; - this.maxMinaCandidate = maxMinaCandidate; - } -} diff --git a/src/examples/zkapps/voting/voting.ts b/src/examples/zkapps/voting/voting.ts index 7ae346b873..87d2bc9c9c 100644 --- a/src/examples/zkapps/voting/voting.ts +++ b/src/examples/zkapps/voting/voting.ts @@ -47,7 +47,7 @@ let voterPreconditions = ParticipantPreconditions.default; */ let electionPreconditions = ElectionPreconditions.default; -interface VotingParams { +type VotingParams = { electionPreconditions: ElectionPreconditions; voterPreconditions: ParticipantPreconditions; candidatePreconditions: ParticipantPreconditions; @@ -55,7 +55,7 @@ interface VotingParams { voterAddress: PublicKey; contractAddress: PublicKey; doProofs: boolean; -} +}; /** * Returns a new contract instance that based on a set of preconditions. diff --git a/src/index.ts b/src/index.ts index aff4570bca..0c23cb7404 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export type { ProvablePure } from './snarky.js'; +export type { ProvablePure } from './lib/provable-types/provable-intf.js'; export { Ledger } from './snarky.js'; export { Field, Bool, Group, Scalar } from './lib/core.js'; export { @@ -21,17 +21,9 @@ export type { FlexibleProvable, FlexibleProvablePure, InferProvable, -} from './lib/circuit-value.js'; -export { - CircuitValue, - prop, - arrayProp, - matrixProp, - provable, - provablePure, - Struct, - Unconstrained, -} from './lib/circuit-value.js'; +} from './lib/provable-types/struct.js'; +export { provable, provablePure, Struct } from './lib/provable-types/struct.js'; +export { Unconstrained } from './lib/provable-types/unconstrained.js'; export { Provable } from './lib/provable.js'; export { Circuit, Keypair, public_, circuitMain } from './lib/circuit.js'; export { UInt32, UInt64, Int64, Sign, UInt8 } from './lib/int.js'; @@ -104,9 +96,12 @@ export { } from './lib/fetch.js'; export * as Encryption from './lib/encryption.js'; export * as Encoding from './bindings/lib/encoding.js'; -export { Character, CircuitString } from './lib/string.js'; -export { MerkleTree, MerkleWitness } from './lib/merkle-tree.js'; -export { MerkleMap, MerkleMapWitness } from './lib/merkle-map.js'; +export { Character, CircuitString } from './lib/provable-types/string.js'; +export { MerkleTree, MerkleWitness } from './lib/provable-types/merkle-tree.js'; +export { + MerkleMap, + MerkleMapWitness, +} from './lib/provable-types/merkle-map.js'; export { Nullifier } from './lib/nullifier.js'; diff --git a/src/lib/account-update.ts b/src/lib/account-update.ts index ebf6f6ebc6..7477c85a4b 100644 --- a/src/lib/account-update.ts +++ b/src/lib/account-update.ts @@ -4,7 +4,7 @@ import { provable, provablePure, StructNoJson, -} from './circuit-value.js'; +} from './provable-types/struct.js'; import { memoizationContext, memoizeWitness, Provable } from './provable.js'; import { Field, Bool } from './core.js'; import { Pickles, Test } from '../snarky.js'; diff --git a/src/lib/bool.ts b/src/lib/bool.ts index 22cf60d4f7..164d1e4c32 100644 --- a/src/lib/bool.ts +++ b/src/lib/bool.ts @@ -1,18 +1,12 @@ import { Snarky } from '../snarky.js'; -import { - Field, - FieldConst, - FieldType, - FieldVar, - readVarMessage, - withMessage, -} from './field.js'; -import { Bool as B } from '../provable/field-bigint.js'; +import { Field, readVarMessage, withMessage } from './field.js'; +import { FieldVar, FieldConst, FieldType } from './provable-core/fieldvar.js'; import { defineBinable } from '../bindings/lib/binable.js'; import { NonNegativeInteger } from '../bindings/crypto/non-negative.js'; import { asProver } from './provable-context.js'; -import { existsOne } from './gadgets/common.js'; +import { existsOne } from './provable-core/exists.js'; import { assertMul } from './gadgets/compatible.js'; +import { setBoolConstructor } from './provable-core/field-constructor.js'; export { BoolVar, Bool }; @@ -45,7 +39,7 @@ class Bool { this.value = x; return; } - this.value = FieldVar.constant(B(x)); + this.value = FieldVar.constant(BigInt(x)); } isConstant(): this is { value: ConstantBoolVar } { @@ -200,7 +194,7 @@ class Bool { } /** - * This converts the {@link Bool} to a javascript [[boolean]]. + * This converts the {@link Bool} to a JS `boolean`. * This can only be called on non-witness values. */ toBoolean(): boolean { @@ -365,6 +359,7 @@ class Bool { }, }; } +setBoolConstructor(Bool); const BoolBinable = defineBinable({ toBytes(b: Bool) { @@ -394,5 +389,5 @@ function toBoolean(x: boolean | Bool): boolean { function toFieldVar(x: boolean | Bool): BoolVar { if (x instanceof Bool) return x.value; - return FieldVar.constant(B(x)); + return FieldVar.constant(BigInt(x)); } diff --git a/src/lib/circuit.ts b/src/lib/circuit.ts index d15ad87717..3b209aec57 100644 --- a/src/lib/circuit.ts +++ b/src/lib/circuit.ts @@ -1,10 +1,11 @@ import 'reflect-metadata'; -import { ProvablePure, Snarky } from '../snarky.js'; +import { Snarky } from '../snarky.js'; import { MlFieldArray, MlFieldConstArray } from './ml/fields.js'; import { withThreadPool } from '../snarky.js'; import { Provable } from './provable.js'; import { snarkContext, gatesFromJson } from './provable-context.js'; import { prettifyStacktrace, prettifyStacktracePromise } from './errors.js'; +import { ProvablePure } from './provable-types/provable-intf.js'; // external API export { public_, circuitMain, Circuit, Keypair, Proof, VerificationKey }; diff --git a/src/lib/field.ts b/src/lib/field.ts index e5767a89b0..54e9e56459 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -1,5 +1,6 @@ import { Snarky } from '../snarky.js'; -import { Field as Fp } from '../provable/field-bigint.js'; +import { Fp } from '../bindings/crypto/finite-field.js'; +import { BinableFp, SignableFp } from '../mina-signer/src/field-bigint.js'; import { defineBinable } from '../bindings/lib/binable.js'; import type { NonNegativeInteger } from '../bindings/crypto/non-negative.js'; import { asProver, inCheckedComputation } from './provable-context.js'; @@ -13,18 +14,23 @@ import { assertBoolean, } from './gadgets/compatible.js'; import { toLinearCombination } from './gadgets/basic.js'; +import { + FieldType, + FieldVar, + FieldConst, + VarFieldVar, + ConstantFieldVar, +} from './provable-core/fieldvar.js'; +import { exists, existsOne } from './provable-core/exists.js'; +import { setFieldConstructor } from './provable-core/field-constructor.js'; // external API export { Field }; // internal API export { - FieldType, - FieldVar, - FieldConst, ConstantField, VarField, - VarFieldVar, withMessage, readVarMessage, toConstantField, @@ -32,118 +38,6 @@ export { checkBitLength, }; -type FieldConst = [0, bigint]; - -function constToBigint(x: FieldConst): Fp { - return x[1]; -} -function constFromBigint(x: Fp): FieldConst { - return [0, Fp(x)]; -} - -const FieldConst = { - fromBigint: constFromBigint, - toBigint: constToBigint, - equal(x: FieldConst, y: FieldConst) { - return x[1] === y[1]; - }, - [0]: constFromBigint(0n), - [1]: constFromBigint(1n), - [-1]: constFromBigint(-1n), -}; - -enum FieldType { - Constant, - Var, - Add, - Scale, -} - -/** - * `FieldVar` is the core data type in snarky. It is eqivalent to `Cvar.t` in OCaml. - * It represents a field element that is part of provable code - either a constant or a variable. - * - * **Variables** end up filling the witness columns of a constraint system. - * Think of a variable as a value that has to be provided by the prover, and that has to satisfy all the - * constraints it is involved in. - * - * **Constants** end up being hard-coded into the constraint system as gate coefficients. - * Think of a constant as a value that is known publicly, at compile time, and that defines the constraint system. - * - * Both constants and variables can be combined into an AST using the Add and Scale combinators. - */ -type FieldVar = - | [FieldType.Constant, FieldConst] - | [FieldType.Var, number] - | [FieldType.Add, FieldVar, FieldVar] - | [FieldType.Scale, FieldConst, FieldVar]; - -type ConstantFieldVar = [FieldType.Constant, FieldConst]; -type VarFieldVar = [FieldType.Var, number]; - -const FieldVar = { - // constructors - Constant(x: FieldConst): ConstantFieldVar { - return [FieldType.Constant, x]; - }, - Var(x: number): VarFieldVar { - return [FieldType.Var, x]; - }, - Add(x: FieldVar, y: FieldVar): [FieldType.Add, FieldVar, FieldVar] { - return [FieldType.Add, x, y]; - }, - Scale(c: FieldConst, x: FieldVar): [FieldType.Scale, FieldConst, FieldVar] { - return [FieldType.Scale, c, x]; - }, - - constant(x: bigint | FieldConst): ConstantFieldVar { - let x0 = typeof x === 'bigint' ? FieldConst.fromBigint(x) : x; - return [FieldType.Constant, x0]; - }, - add(x: FieldVar, y: FieldVar): FieldVar { - if (FieldVar.isConstant(x) && x[1][1] === 0n) return y; - if (FieldVar.isConstant(y) && y[1][1] === 0n) return x; - if (FieldVar.isConstant(x) && FieldVar.isConstant(y)) { - return FieldVar.constant(Fp.add(x[1][1], y[1][1])); - } - return [FieldType.Add, x, y]; - }, - scale(c: bigint | FieldConst, x: FieldVar): FieldVar { - let c0 = typeof c === 'bigint' ? FieldConst.fromBigint(c) : c; - if (c0[1] === 0n) return FieldVar.constant(0n); - if (c0[1] === 1n) return x; - if (FieldVar.isConstant(x)) { - return FieldVar.constant(Fp.mul(c0[1], x[1][1])); - } - if (FieldVar.isScale(x)) { - return [ - FieldType.Scale, - FieldConst.fromBigint(Fp.mul(c0[1], x[1][1])), - x[2], - ]; - } - return [FieldType.Scale, c0, x]; - }, - - // type guards - isConstant(x: FieldVar): x is ConstantFieldVar { - return x[0] === FieldType.Constant; - }, - isVar(x: FieldVar): x is VarFieldVar { - return x[0] === FieldType.Var; - }, - isAdd(x: FieldVar): x is [FieldType.Add, FieldVar, FieldVar] { - return x[0] === FieldType.Add; - }, - isScale(x: FieldVar): x is [FieldType.Scale, FieldConst, FieldVar] { - return x[0] === FieldType.Scale; - }, - - [0]: [FieldType.Constant, FieldConst[0]] satisfies ConstantFieldVar, - [1]: [FieldType.Constant, FieldConst[1]] satisfies ConstantFieldVar, - [-1]: [FieldType.Constant, FieldConst[-1]] satisfies ConstantFieldVar, -}; - type ConstantField = Field & { value: ConstantFieldVar }; type VarField = Field & { value: VarFieldVar }; @@ -209,7 +103,7 @@ class Field { } } // TODO this should handle common values efficiently by reading from a lookup table - this.value = FieldVar.constant(Fp(x)); + this.value = FieldVar.constant(Fp.mod(BigInt(x))); } // helpers @@ -430,30 +324,25 @@ class Field { isEven() { if (this.isConstant()) return new Bool(this.toBigInt() % 2n === 0n); - let [, isOddVar, xDiv2Var] = Snarky.run.exists(2, () => { - let bits = Fp.toBits(this.toBigInt()); + let [isOdd, xDiv2] = exists(2, () => { + let bits = BinableFp.toBits(this.toBigInt()); let isOdd = bits.shift()! ? 1n : 0n; - - return [ - 0, - FieldConst.fromBigint(isOdd), - FieldConst.fromBigint(Fp.fromBits(bits)), - ]; + return [isOdd, BinableFp.fromBits(bits)]; }); - let isOdd = new Field(isOddVar); - let xDiv2 = new Field(xDiv2Var); - // range check for 253 bits // WARNING: this makes use of a special property of the Pasta curves, // namely that a random field element is < 2^254 with overwhelming probability // TODO use 88-bit RCs to make this more efficient xDiv2.toBits(253); + // boolean check + assertBoolean(isOdd); + // check composition xDiv2.mul(2).add(isOdd).assertEquals(this); - return new Bool(isOddVar).not(); + return Bool.Unsafe.fromField(isOdd).not(); } /** @@ -485,12 +374,11 @@ class Field { return new Field(z); } // create a new witness for z = x*y - let z = Snarky.run.existsOne(() => - FieldConst.fromBigint(Fp.mul(this.toBigInt(), toFp(y))) - ); + let z = existsOne(() => Fp.mul(this.toBigInt(), toFp(y))); + // add a multiplication constraint assertMul(this, y, z); - return new Field(z); + return z; } /** @@ -517,13 +405,11 @@ class Field { return new Field(z); } // create a witness for z = x^(-1) - let z = Snarky.run.existsOne(() => { - let z = Fp.inverse(this.toBigInt()) ?? 0n; - return FieldConst.fromBigint(z); - }); + let z = existsOne(() => Fp.inverse(this.toBigInt()) ?? 0n); + // constrain x * z === 1 assertMul(this, z, FieldVar[1]); - return new Field(z); + return z; } /** @@ -584,10 +470,8 @@ class Field { return new Field(Fp.square(this.toBigInt())); } // create a new witness for z = x^2 - let z_ = Snarky.run.existsOne(() => - FieldConst.fromBigint(Fp.square(this.toBigInt())) - ); - let z = new Field(z_); + let z = existsOne(() => Fp.square(this.toBigInt())); + // add a squaring constraint assertSquare(this, z); return z; @@ -620,11 +504,8 @@ class Field { return new Field(z); } // create a witness for sqrt(x) - let z_ = Snarky.run.existsOne(() => { - let z = Fp.sqrt(this.toBigInt()) ?? 0n; - return FieldConst.fromBigint(z); - }); - let z = new Field(z_); + let z = existsOne(() => Fp.sqrt(this.toBigInt()) ?? 0n); + // constrain z * z === x assertSquare(z, this); return z; @@ -639,25 +520,21 @@ class Field { } // create witnesses z = 1/x, or z=0 if x=0, // and b = 1 - zx - let [, b, z] = Snarky.run.exists(2, () => { + let [b, z] = exists(2, () => { let x = this.toBigInt(); let z = Fp.inverse(x) ?? 0n; let b = Fp.sub(1n, Fp.mul(z, x)); - return [0, FieldConst.fromBigint(b), FieldConst.fromBigint(z)]; + return [b, z]; }); // add constraints // b * x === 0 assertMul(b, this, FieldVar[0]); // z * x === 1 - b - assertMul( - z, - this, - FieldVar.add(FieldVar[1], FieldVar.scale(FieldConst[-1], b)) - ); + assertMul(z, this, new Field(1).sub(b)); // ^^^ these prove that b = Bool(x === 0): // if x = 0, the 2nd equation implies b = 1 // if x != 0, the 1st implies b = 0 - return new Bool(b); + return Bool.Unsafe.fromField(b); } /** @@ -680,11 +557,9 @@ class Field { return this.sub(y).isZero(); } // if both are variables, we create one new variable for x-y so that `isZero` doesn't create two - let xMinusY = Snarky.run.existsOne(() => - FieldConst.fromBigint(Fp.sub(this.toBigInt(), toFp(y))) - ); - let z = new Field(xMinusY); + let z = existsOne(() => Fp.sub(this.toBigInt(), toFp(y))); this.sub(y).assertEquals(z); + return z.isZero(); } @@ -959,7 +834,7 @@ class Field { toBits(length: number = 254) { checkBitLength('Field.toBits()', length, 254); if (this.isConstant()) { - let bits = Fp.toBits(this.toBigInt()); + let bits = BinableFp.toBits(this.toBigInt()); if (bits.slice(length).some((bit) => bit)) throw Error(`Field.toBits(): ${this} does not fit in ${length} bits`); return bits.slice(0, length).map((b) => new Bool(b)); @@ -996,7 +871,7 @@ class Field { let bits_ = bits .map((b) => (typeof b === 'boolean' ? b : b.toBoolean())) .concat(Array(Fp.sizeInBits - length).fill(false)); - return new Field(Fp.fromBits(bits_)); + return new Field(BinableFp.fromBits(bits_)); } return bits .map((b) => new Bool(b)) @@ -1025,9 +900,9 @@ class Field { let [c, x] = terms[0]; if (c === 1n) return new Field(x); } - let x = Snarky.run.existsOne(() => Snarky.field.readVar(this.value)); - this.assertEquals(new Field(x)); - return VarField(x); + let x = existsOne(() => this.toBigInt()); + this.assertEquals(x); + return x; } /** @@ -1186,7 +1061,7 @@ class Field { * @return A {@link Field} coerced from the given JSON string. */ static fromJSON(json: string) { - return new Field(Fp.fromJSON(json)); + return new Field(SignableFp.fromJSON(json)); } /** @@ -1247,23 +1122,24 @@ class Field { /** * The size of a {@link Field} element in bytes - 32. */ - static sizeInBytes = Fp.sizeInBytes; + static sizeInBytes = BinableFp.sizeInBytes; /** * The size of a {@link Field} element in bits - 255. */ static sizeInBits = Fp.sizeInBits; } +setFieldConstructor(Field); const FieldBinable = defineBinable({ toBytes(t: Field) { let t0 = toConstantField(t, 'toBytes').toBigInt(); - return Fp.toBytes(t0); + return BinableFp.toBytes(t0); }, readBytes(bytes, offset) { let uint8array = new Uint8Array(32); uint8array.set(bytes.slice(offset, offset + 32)); - let x = Fp.fromBytes([...uint8array]); + let x = BinableFp.fromBytes([...uint8array]); return [new Field(x), offset + 32]; }, }); @@ -1280,22 +1156,22 @@ function isConstant( return (x as Field).isConstant(); } -function toFp(x: bigint | number | string | Field): Fp { +function toFp(x: bigint | number | string | Field): bigint { let type = typeof x; if (type === 'bigint' || type === 'number' || type === 'string') { - return Fp(x as bigint | number | string); + return Fp.mod(BigInt(x as bigint | number | string)); } return (x as Field).toBigInt(); } function toFieldConst(x: bigint | number | string | ConstantField): FieldConst { if (x instanceof Field) return x.value[1]; - return FieldConst.fromBigint(Fp(x)); + return FieldConst.fromBigint(Fp.mod(BigInt(x))); } function toFieldVar(x: bigint | number | string | Field): FieldVar { if (x instanceof Field) return x.value; - return FieldVar.constant(Fp(x)); + return FieldVar.constant(Fp.mod(BigInt(x))); } function withMessage(error: unknown, message?: string) { diff --git a/src/lib/field.unit-test.ts b/src/lib/field.unit-test.ts index 16a384b256..8297d2565c 100644 --- a/src/lib/field.unit-test.ts +++ b/src/lib/field.unit-test.ts @@ -1,12 +1,12 @@ -import { ProvablePure } from '../snarky.js'; import { Field } from './core.js'; -import { Field as Fp } from '../provable/field-bigint.js'; +import { Fp } from '../bindings/crypto/finite-field.js'; +import { BinableFp } from '../mina-signer/src/field-bigint.js'; import { test, Random } from './testing/property.js'; import { deepEqual, throws } from 'node:assert/strict'; import { Provable } from './provable.js'; import { Binable } from '../bindings/lib/binable.js'; -import { ProvableExtended } from './circuit-value.js'; -import { FieldType } from './field.js'; +import { ProvableExtended } from './provable-types/struct.js'; +import { FieldType } from './provable-core/fieldvar.js'; import { equivalentProvable as equivalent, oneOf, @@ -18,6 +18,7 @@ import { Spec, } from './testing/equivalent.js'; import { runAndCheckSync } from './provable-context.js'; +import { ProvablePure } from './provable-types/provable-intf.js'; // types Field satisfies Provable; @@ -203,9 +204,9 @@ test(Random.field, Random.field, (x0, y0, assert) => { Provable.asProver(() => assert(z.toBigInt() === Fp.mul(x0, y0))); // toBits / fromBits - // Fp.toBits() returns 255 bits, but our new to/from impl only accepts <=254 + // BinableFp.toBits() returns 255 bits, but our new to/from impl only accepts <=254 // https://github.com/o1-labs/o1js/pull/1461 - let bits = Fp.toBits(x0).slice(0, -1); + let bits = BinableFp.toBits(x0).slice(0, -1); let x1 = Provable.witness(Field, () => Field.fromBits(bits)); let bitsVars = x1.toBits(); Provable.asProver(() => diff --git a/src/lib/foreign-curve.ts b/src/lib/foreign-curve.ts index 8e02fb8dc7..cfaf5756f3 100644 --- a/src/lib/foreign-curve.ts +++ b/src/lib/foreign-curve.ts @@ -4,13 +4,13 @@ import { createCurveAffine, } from '../bindings/crypto/elliptic-curve.js'; import type { Group } from './group.js'; -import { ProvablePureExtended } from './circuit-value.js'; +import { ProvablePureExtended } from './provable-types/struct.js'; import { AlmostForeignField, createForeignField } from './foreign-field.js'; import { EllipticCurve, Point } from './gadgets/elliptic-curve.js'; import { Field3 } from './gadgets/foreign-field.js'; import { assert } from './gadgets/common.js'; import { Provable } from './provable.js'; -import { provableFromClass } from '../bindings/lib/provable-snarky.js'; +import { provableFromClass } from './provable-types/provable-derivers.js'; // external API export { createForeignCurve, ForeignCurve }; diff --git a/src/lib/foreign-ecdsa.ts b/src/lib/foreign-ecdsa.ts index b29966fccc..6c829daac9 100644 --- a/src/lib/foreign-ecdsa.ts +++ b/src/lib/foreign-ecdsa.ts @@ -1,6 +1,6 @@ -import { provableFromClass } from '../bindings/lib/provable-snarky.js'; +import { provableFromClass } from './provable-types/provable-derivers.js'; import { CurveParams } from '../bindings/crypto/elliptic-curve.js'; -import { ProvablePureExtended } from './circuit-value.js'; +import { ProvablePureExtended } from './provable-types/struct.js'; import { FlexiblePoint, ForeignCurve, diff --git a/src/lib/foreign-field.ts b/src/lib/foreign-field.ts index 80db014c84..b6d83f3ccf 100644 --- a/src/lib/foreign-field.ts +++ b/src/lib/foreign-field.ts @@ -4,7 +4,7 @@ import { FiniteField, createField, } from '../bindings/crypto/finite-field.js'; -import { Field, FieldVar, checkBitLength, withMessage } from './field.js'; +import { Field, checkBitLength, withMessage } from './field.js'; import { Provable } from './provable.js'; import { Bool } from './bool.js'; import { Tuple, TupleMap, TupleN } from './util/types.js'; @@ -13,7 +13,7 @@ import { Gadgets } from './gadgets/gadgets.js'; import { ForeignField as FF } from './gadgets/foreign-field.js'; import { assert } from './gadgets/common.js'; import { l3, l } from './gadgets/range-check.js'; -import { ProvablePureExtended } from './circuit-value.js'; +import { ProvablePureExtended } from './provable-types/struct.js'; // external API export { createForeignField }; diff --git a/src/lib/foreign-field.unit-test.ts b/src/lib/foreign-field.unit-test.ts index 9c823e2a00..3d8da573ca 100644 --- a/src/lib/foreign-field.unit-test.ts +++ b/src/lib/foreign-field.unit-test.ts @@ -1,7 +1,7 @@ -import { ProvablePure } from '../snarky.js'; import { Field, Group } from './core.js'; import { ForeignField, createForeignField } from './foreign-field.js'; -import { Scalar as Fq, Group as G } from '../provable/curve-bigint.js'; +import { Fq } from '../bindings/crypto/finite-field.js'; +import { Pallas } from '../bindings/crypto/elliptic-curve.js'; import { expect } from 'expect'; import { bool, @@ -18,6 +18,7 @@ import { Circuit, circuitMain } from './circuit.js'; import { Scalar } from './scalar.js'; import { l } from './gadgets/range-check.js'; import { assert } from './gadgets/common.js'; +import { ProvablePure } from './provable-types/provable-intf.js'; // toy example - F_17 @@ -112,7 +113,7 @@ equivalent({ from: [f], to: f })( // scalar shift in foreign field arithmetic vs in the exponent -let scalarShift = Fq(1n + 2n ** 255n); +let scalarShift = Fq.mod(1n + 2n ** 255n); let oneHalf = Fq.inverse(2n)!; function unshift(s: ForeignField) { @@ -125,7 +126,7 @@ function scaleShifted(point: Group, shiftedScalar: Scalar) { } let scalarBigint = Fq.random(); -let pointBigint = G.scale(G.generatorMina, scalarBigint); +let pointBigint = Pallas.toAffine(Pallas.scale(Pallas.one, scalarBigint)); // perform a "scalar unshift" in foreign field arithmetic, // then convert to scalar from bits (which shifts it back) and scale a point by the scalar diff --git a/src/lib/gadgets/arithmetic.ts b/src/lib/gadgets/arithmetic.ts index 9d7cac4f6c..09b7278815 100644 --- a/src/lib/gadgets/arithmetic.ts +++ b/src/lib/gadgets/arithmetic.ts @@ -1,4 +1,4 @@ -import { provableTuple } from '../circuit-value.js'; +import { provableTuple } from '../provable-types/struct.js'; import { Field } from '../core.js'; import { assert } from '../errors.js'; import { Provable } from '../provable.js'; diff --git a/src/lib/gadgets/arithmetic.unit-test.ts b/src/lib/gadgets/arithmetic.unit-test.ts index d03f4e0934..9c6c7a4322 100644 --- a/src/lib/gadgets/arithmetic.unit-test.ts +++ b/src/lib/gadgets/arithmetic.unit-test.ts @@ -7,7 +7,7 @@ import { } from '../testing/equivalent.js'; import { Field } from '../core.js'; import { Gadgets } from './gadgets.js'; -import { provable } from '../circuit-value.js'; +import { provable } from '../provable-types/struct.js'; import { assert } from './common.js'; let Arithmetic = ZkProgram({ diff --git a/src/lib/gadgets/basic.ts b/src/lib/gadgets/basic.ts index ac07907300..8e9945dd9f 100644 --- a/src/lib/gadgets/basic.ts +++ b/src/lib/gadgets/basic.ts @@ -2,17 +2,17 @@ * Basic gadgets that only use generic gates */ import { Fp } from '../../bindings/crypto/finite-field.js'; +import { Field, VarField } from '../field.js'; import { - Field, - FieldConst, FieldType, FieldVar, - VarField, + FieldConst, VarFieldVar, -} from '../field.js'; -import { existsOne, toVar } from './common.js'; +} from '../provable-core/fieldvar.js'; +import { toVar } from './common.js'; import { Gates, fieldVar } from '../gates.js'; import { TupleN } from '../util/types.js'; +import { existsOne } from '../provable-core/exists.js'; export { assertMul, arrayGet, assertOneOf }; diff --git a/src/lib/gadgets/bit-slices.ts b/src/lib/gadgets/bit-slices.ts index a83882e040..4d359ad92b 100644 --- a/src/lib/gadgets/bit-slices.ts +++ b/src/lib/gadgets/bit-slices.ts @@ -2,12 +2,12 @@ * Gadgets for converting between field elements and bit slices of various lengths */ import { bigIntToBits } from '../../bindings/crypto/bigint-helpers.js'; -import { Bool } from '../bool.js'; import { Field } from '../field.js'; import { UInt8 } from '../int.js'; +import { exists } from '../provable-core/exists.js'; import { Provable } from '../provable.js'; import { chunk } from '../util/arrays.js'; -import { assert, exists } from './common.js'; +import { assert } from './common.js'; import type { Field3 } from './foreign-field.js'; import { l } from './range-check.js'; diff --git a/src/lib/gadgets/bitwise.ts b/src/lib/gadgets/bitwise.ts index d0c741fbb1..c1bfab2b6e 100644 --- a/src/lib/gadgets/bitwise.ts +++ b/src/lib/gadgets/bitwise.ts @@ -1,16 +1,11 @@ import { Provable } from '../provable.js'; -import { Field as Fp } from '../../provable/field-bigint.js'; +import { Fp } from '../../bindings/crypto/finite-field.js'; import { Field } from '../field.js'; import { Gates } from '../gates.js'; -import { - assert, - divideWithRemainder, - toVar, - exists, - bitSlice, -} from './common.js'; +import { assert, divideWithRemainder, toVar, bitSlice } from './common.js'; import { rangeCheck32, rangeCheck64 } from './range-check.js'; import { divMod32 } from './arithmetic.js'; +import { exists } from '../provable-core/exists.js'; export { xor, diff --git a/src/lib/gadgets/common.ts b/src/lib/gadgets/common.ts index 72084a08e5..bbfae9cf47 100644 --- a/src/lib/gadgets/common.ts +++ b/src/lib/gadgets/common.ts @@ -1,42 +1,11 @@ -import { - Field, - FieldConst, - FieldVar, - VarField, - VarFieldVar, -} from '../field.js'; -import { Tuple, TupleN } from '../util/types.js'; -import { Snarky } from '../../snarky.js'; -import { MlArray } from '../ml/base.js'; +import { Field, VarField } from '../field.js'; +import { FieldVar, VarFieldVar } from '../provable-core/fieldvar.js'; +import { Tuple } from '../util/types.js'; import type { Bool } from '../bool.js'; import { fieldVar } from '../gates.js'; +import { existsOne } from '../provable-core/exists.js'; -export { - exists, - existsOne, - toVars, - toVar, - isVar, - assert, - bitSlice, - divideWithRemainder, -}; - -function existsOne(compute: () => bigint) { - let varMl = Snarky.run.existsOne(() => FieldConst.fromBigint(compute())); - return VarField(varMl); -} - -function exists TupleN>( - n: N, - compute: C -) { - let varsMl = Snarky.run.exists(n, () => - MlArray.mapTo(compute(), FieldConst.fromBigint) - ); - let vars = MlArray.mapFrom(varsMl, VarField); - return TupleN.fromArray(n, vars); -} +export { toVars, toVar, isVar, assert, bitSlice, divideWithRemainder }; /** * Given a Field, collapse its AST to a pure Var. See {@link FieldVar}. diff --git a/src/lib/gadgets/compatible.ts b/src/lib/gadgets/compatible.ts index 262dbb3585..7c12c21d18 100644 --- a/src/lib/gadgets/compatible.ts +++ b/src/lib/gadgets/compatible.ts @@ -3,7 +3,8 @@ * `plonk_constraint_system.ml` / R1CS_constraint_system. */ import { Fp } from '../../bindings/crypto/finite-field.js'; -import { Field, FieldVar } from '../field.js'; +import { Field } from '../field.js'; +import { FieldVar } from '../provable-core/fieldvar.js'; import { assert } from './common.js'; import { Gates } from '../gates.js'; import { ScaledVar, emptyCell, reduceToScaledVar } from './basic.js'; diff --git a/src/lib/gadgets/elliptic-curve.ts b/src/lib/gadgets/elliptic-curve.ts index 3db54dcc11..a1cb212671 100644 --- a/src/lib/gadgets/elliptic-curve.ts +++ b/src/lib/gadgets/elliptic-curve.ts @@ -1,7 +1,7 @@ import { inverse, mod } from '../../bindings/crypto/finite-field.js'; import { Field } from '../field.js'; import { Provable } from '../provable.js'; -import { assert, exists } from './common.js'; +import { assert } from './common.js'; import { Field3, ForeignField, split, weakBound } from './foreign-field.js'; import { l2, multiRangeCheck } from './range-check.js'; import { sha256 } from 'js-sha256'; @@ -15,11 +15,12 @@ import { affineDouble, } from '../../bindings/crypto/elliptic-curve.js'; import { Bool } from '../bool.js'; -import { provable } from '../circuit-value.js'; +import { provable } from '../provable-types/struct.js'; import { assertPositiveInteger } from '../../bindings/crypto/non-negative.js'; import { arrayGet } from './basic.js'; import { sliceField3 } from './bit-slices.js'; import { Hashed } from '../provable-types/packed.js'; +import { exists } from '../provable-core/exists.js'; // external API export { EllipticCurve, Point, Ecdsa }; diff --git a/src/lib/gadgets/foreign-field.ts b/src/lib/gadgets/foreign-field.ts index 8990a2667b..4299dbcc38 100644 --- a/src/lib/gadgets/foreign-field.ts +++ b/src/lib/gadgets/foreign-field.ts @@ -5,15 +5,16 @@ import { inverse as modInverse, mod, } from '../../bindings/crypto/finite-field.js'; -import { provableTuple } from '../../bindings/lib/provable-snarky.js'; +import { provableTuple } from '../provable-types/provable-derivers.js'; import { Bool } from '../bool.js'; -import { Unconstrained } from '../circuit-value.js'; +import { Unconstrained } from '../provable-types/unconstrained.js'; import { Field } from '../field.js'; import { Gates, foreignFieldAdd } from '../gates.js'; +import { exists } from '../provable-core/exists.js'; import { modifiedField } from '../provable-types/fields.js'; import { Tuple, TupleN } from '../util/types.js'; import { assertOneOf } from './basic.js'; -import { assert, bitSlice, exists, toVar, toVars } from './common.js'; +import { assert, bitSlice, toVar, toVars } from './common.js'; import { l, lMask, diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index d110a6349b..b47a406f58 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -1,10 +1,11 @@ import { Snarky } from '../../snarky.js'; import { Fp } from '../../bindings/crypto/finite-field.js'; -import { Field as FieldProvable } from '../../provable/field-bigint.js'; +import { BinableFp } from '../../mina-signer/src/field-bigint.js'; import { Field } from '../field.js'; import { Gates } from '../gates.js'; -import { assert, bitSlice, exists, toVar, toVars } from './common.js'; +import { assert, bitSlice, toVar, toVars } from './common.js'; import { Bool } from '../bool.js'; +import { exists } from '../provable-core/exists.js'; export { rangeCheck64, @@ -255,10 +256,10 @@ function rangeCheckHelper(length: number, x: Field) { let lengthDiv16 = length / 16; if (x.isConstant()) { - let bits = FieldProvable.toBits(x.toBigInt()) + let bits = BinableFp.toBits(x.toBigInt()) .slice(0, length) .concat(Array(Fp.sizeInBits - length).fill(false)); - return new Field(FieldProvable.fromBits(bits)); + return new Field(BinableFp.fromBits(bits)); } let y = Snarky.field.truncateToBits16(lengthDiv16, x.value); return new Field(y); diff --git a/src/lib/gadgets/sha256.ts b/src/lib/gadgets/sha256.ts index a7e201814f..7b65172052 100644 --- a/src/lib/gadgets/sha256.ts +++ b/src/lib/gadgets/sha256.ts @@ -2,13 +2,14 @@ import { mod } from '../../bindings/crypto/finite-field.js'; import { Field } from '../core.js'; import { UInt32, UInt8 } from '../int.js'; +import { exists } from '../provable-core/exists.js'; import { FlexibleBytes } from '../provable-types/bytes.js'; import { Bytes } from '../provable-types/provable-types.js'; import { chunk } from '../util/arrays.js'; import { TupleN } from '../util/types.js'; import { divMod32 } from './arithmetic.js'; import { bytesToWord, wordToBytes } from './bit-slices.js'; -import { bitSlice, exists } from './common.js'; +import { bitSlice } from './common.js'; import { rangeCheck16 } from './range-check.js'; export { SHA256 }; @@ -70,7 +71,9 @@ function padding(data: FlexibleBytes): UInt32[][] { // chunk 4 bytes into one UInt32, as expected by SHA256 // bytesToWord expects little endian, so we reverse the bytes chunks.push( - UInt32.Unsafe.fromField(bytesToWord(paddedMessage.slice(i, i + 4).reverse())) + UInt32.Unsafe.fromField( + bytesToWord(paddedMessage.slice(i, i + 4).reverse()) + ) ); } @@ -133,11 +136,15 @@ const SHA256 = { h = g; g = f; f = e; - e = UInt32.Unsafe.fromField(divMod32(d.value.add(unreducedT1), 16).remainder); // mod 32bit the unreduced field element + e = UInt32.Unsafe.fromField( + divMod32(d.value.add(unreducedT1), 16).remainder + ); // mod 32bit the unreduced field element d = c; c = b; b = a; - a = UInt32.Unsafe.fromField(divMod32(unreducedT2.add(unreducedT1), 16).remainder); // mod 32bit + a = UInt32.Unsafe.fromField( + divMod32(unreducedT2.add(unreducedT1), 16).remainder + ); // mod 32bit } // new intermediate hash value @@ -276,5 +283,7 @@ function sigma(u: UInt32, bits: TupleN, firstShifted = false) { // since xor() is implicitly range-checking both of its inputs, this provides the missing // proof that xRotR0, xRotR1, xRotR2 < 2^32, which implies x0 < 2^d0, x1 < 2^d1, x2 < 2^d2 - return UInt32.Unsafe.fromField(xRotR0).xor(UInt32.Unsafe.fromField(xRotR1)).xor(UInt32.Unsafe.fromField(xRotR2)); + return UInt32.Unsafe.fromField(xRotR0) + .xor(UInt32.Unsafe.fromField(xRotR1)) + .xor(UInt32.Unsafe.fromField(xRotR2)); } diff --git a/src/lib/gates.ts b/src/lib/gates.ts index b34ce670e0..6389273224 100644 --- a/src/lib/gates.ts +++ b/src/lib/gates.ts @@ -1,7 +1,8 @@ import { Snarky } from '../snarky.js'; -import { FieldConst, type Field, FieldVar } from './field.js'; -import { exists } from './gadgets/common.js'; +import type { Field } from './field.js'; +import { FieldVar, FieldConst } from './provable-core/fieldvar.js'; import { MlArray, MlTuple } from './ml/base.js'; +import { exists } from './provable-core/exists.js'; import { TupleN } from './util/types.js'; export { diff --git a/src/lib/group.ts b/src/lib/group.ts index ad9d7efec6..f80782070a 100644 --- a/src/lib/group.ts +++ b/src/lib/group.ts @@ -1,7 +1,8 @@ -import { Field, FieldVar } from './field.js'; +import { Field } from './field.js'; +import { FieldVar } from './provable-core/fieldvar.js'; import { Scalar } from './scalar.js'; import { Snarky } from '../snarky.js'; -import { Field as Fp } from '../provable/field-bigint.js'; +import { Fp } from '../bindings/crypto/finite-field.js'; import { GroupAffine, Pallas } from '../bindings/crypto/elliptic-curve.js'; import { Provable } from './provable.js'; import { Bool } from './bool.js'; diff --git a/src/lib/group.unit-test.ts b/src/lib/group.unit-test.ts index a302814db8..e70a292baf 100644 --- a/src/lib/group.unit-test.ts +++ b/src/lib/group.unit-test.ts @@ -1,7 +1,7 @@ import { Group } from './core.js'; import { test, Random } from './testing/property.js'; import { Provable } from './provable.js'; -import { Poseidon } from '../provable/poseidon-bigint.js'; +import { Poseidon } from '../mina-signer/src/poseidon-bigint.js'; console.log('group consistency tests'); diff --git a/src/lib/hash.ts b/src/lib/hash.ts index cef4f2bbcd..cf2c068a5f 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -1,4 +1,8 @@ -import { HashInput, ProvableExtended, Struct } from './circuit-value.js'; +import { + HashInput, + ProvableExtended, + Struct, +} from './provable-types/struct.js'; import { Snarky } from '../snarky.js'; import { Field } from './core.js'; import { createHashHelpers } from './hash-generic.js'; diff --git a/src/lib/int.ts b/src/lib/int.ts index e12a8aec53..eb9ad9d547 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1,5 +1,5 @@ import { Field, Bool } from './core.js'; -import { AnyConstructor, CircuitValue, Struct, prop } from './circuit-value.js'; +import { AnyConstructor, Struct } from './provable-types/struct.js'; import { Types } from '../bindings/mina-transaction/types.js'; import { HashInput } from './hash.js'; import { Provable } from './provable.js'; @@ -7,7 +7,9 @@ import * as RangeCheck from './gadgets/range-check.js'; import * as Bitwise from './gadgets/bitwise.js'; import { addMod32 } from './gadgets/arithmetic.js'; import type { Gadgets } from './gadgets/gadgets.js'; -import { FieldVar, withMessage } from './field.js'; +import { withMessage } from './field.js'; +import { FieldVar } from './provable-core/fieldvar.js'; +import { CircuitValue, prop } from './provable-types/circuit-value.js'; // external API export { UInt8, UInt32, UInt64, Int64, Sign }; diff --git a/src/lib/mina/account.ts b/src/lib/mina/account.ts index ea99bb52b1..525bff7e5d 100644 --- a/src/lib/mina/account.ts +++ b/src/lib/mina/account.ts @@ -10,7 +10,7 @@ import { TypeMap, } from '../../bindings/mina-transaction/gen/transaction.js'; import { jsLayout } from '../../bindings/mina-transaction/gen/js-layout.js'; -import { ProvableExtended } from '../circuit-value.js'; +import { ProvableExtended } from '../provable-types/struct.js'; import { FetchedAccount } from './graphql.js'; export { Account, PartialAccount }; diff --git a/src/lib/mina/mina-instance.ts b/src/lib/mina/mina-instance.ts index cd04034b8b..b713a2b99f 100644 --- a/src/lib/mina/mina-instance.ts +++ b/src/lib/mina/mina-instance.ts @@ -94,7 +94,7 @@ type NetworkConstants = { accountCreationFee: UInt64; }; -interface Mina { +type Mina = { transaction( sender: DeprecatedFeePayerSpec, f: () => Promise @@ -126,7 +126,7 @@ interface Mina { ) => { hash: string; actions: string[][] }[]; proofsEnabled: boolean; getNetworkId(): NetworkId; -} +}; let activeInstance: Mina = { accountCreationFee: () => defaultNetworkConstants.accountCreationFee, diff --git a/src/lib/mina/token/forest-iterator.ts b/src/lib/mina/token/forest-iterator.ts index 0bb673ff89..25529472c6 100644 --- a/src/lib/mina/token/forest-iterator.ts +++ b/src/lib/mina/token/forest-iterator.ts @@ -6,7 +6,7 @@ import { } from '../../account-update.js'; import { Field } from '../../core.js'; import { Provable } from '../../provable.js'; -import { Struct } from '../../circuit-value.js'; +import { Struct } from '../../provable-types/struct.js'; import { assert } from '../../gadgets/common.js'; import { MerkleListIterator, diff --git a/src/lib/mina/token/forest-iterator.unit-test.ts b/src/lib/mina/token/forest-iterator.unit-test.ts index d93158b9ca..4d03d158de 100644 --- a/src/lib/mina/token/forest-iterator.unit-test.ts +++ b/src/lib/mina/token/forest-iterator.unit-test.ts @@ -15,7 +15,6 @@ import { } from '../../../mina-signer/src/sign-zkapp-command.js'; import assert from 'assert'; import { Field, Bool } from '../../core.js'; -import { Bool as BoolB } from '../../../provable/field-bigint.js'; import { PublicKey } from '../../signature.js'; // RANDOM NUMBER GENERATORS for account updates @@ -33,8 +32,8 @@ const accountUpdateBigint = Random.map( // ensure that, by default, all account updates are token-accessible a.body.mayUseToken = a.body.callDepth === 0 - ? { parentsOwnToken: BoolB(true), inheritFromParent: BoolB(false) } - : { parentsOwnToken: BoolB(false), inheritFromParent: BoolB(true) }; + ? { parentsOwnToken: 1n, inheritFromParent: 0n } + : { parentsOwnToken: 0n, inheritFromParent: 1n }; return a; } ); diff --git a/src/lib/mina/transaction-validation.ts b/src/lib/mina/transaction-validation.ts index afe67d0451..10d39c7f82 100644 --- a/src/lib/mina/transaction-validation.ts +++ b/src/lib/mina/transaction-validation.ts @@ -15,7 +15,7 @@ import { PublicKey } from '../signature.js'; import { JsonProof, verify } from '../proof-system.js'; import { verifyAccountUpdateSignature } from '../../mina-signer/src/sign-zkapp-command.js'; import { TransactionCost, TransactionLimits } from './constants.js'; -import { cloneCircuitValue } from '../circuit-value.js'; +import { cloneCircuitValue } from '../provable-types/struct.js'; import { assert } from '../gadgets/common.js'; import { Types, TypesBigint } from '../../bindings/mina-transaction/types.js'; import type { NetworkId } from '../../mina-signer/src/types.js'; diff --git a/src/lib/ml/consistency.unit-test.ts b/src/lib/ml/consistency.unit-test.ts index 1cf5f60617..821c53e047 100644 --- a/src/lib/ml/consistency.unit-test.ts +++ b/src/lib/ml/consistency.unit-test.ts @@ -1,11 +1,11 @@ -import { Ledger, Test } from '../../snarky.js'; +import { Test } from '../../snarky.js'; import { Random, test } from '../testing/property.js'; import { Field, Bool } from '../core.js'; import { PrivateKey, PublicKey } from '../signature.js'; import { TokenId, dummySignature } from '../account-update.js'; import { Ml } from './conversion.js'; import { expect } from 'expect'; -import { FieldConst } from '../field.js'; +import { FieldConst } from '../provable-core/fieldvar.js'; import { Provable } from '../provable.js'; // PrivateKey.toBase58, fromBase58 diff --git a/src/lib/ml/conversion.ts b/src/lib/ml/conversion.ts index 60748b2118..f97db2ace8 100644 --- a/src/lib/ml/conversion.ts +++ b/src/lib/ml/conversion.ts @@ -3,9 +3,9 @@ */ import type { MlPublicKey, MlPublicKeyVar } from '../../snarky.js'; -import { HashInput } from '../circuit-value.js'; +import { HashInput } from '../provable-types/struct.js'; import { Bool, Field } from '../core.js'; -import { FieldConst, FieldVar } from '../field.js'; +import { FieldVar, FieldConst } from '../provable-core/fieldvar.js'; import { Scalar, ScalarConst } from '../scalar.js'; import { PrivateKey, PublicKey } from '../signature.js'; import { MlPair, MlBool, MlArray } from './base.js'; diff --git a/src/lib/ml/fields.ts b/src/lib/ml/fields.ts index 4921e9272a..2766e33a8b 100644 --- a/src/lib/ml/fields.ts +++ b/src/lib/ml/fields.ts @@ -1,4 +1,5 @@ -import { ConstantField, Field, FieldConst, FieldVar } from '../field.js'; +import { ConstantField, Field } from '../field.js'; +import { FieldVar, FieldConst } from '../provable-core/fieldvar.js'; import { MlArray } from './base.js'; export { MlFieldArray, MlFieldConstArray }; diff --git a/src/lib/nullifier.ts b/src/lib/nullifier.ts index c8e84984ef..3604cb8962 100644 --- a/src/lib/nullifier.ts +++ b/src/lib/nullifier.ts @@ -1,8 +1,8 @@ import type { Nullifier as JsonNullifier } from '../mina-signer/src/types.js'; -import { Struct } from './circuit-value.js'; +import { Struct } from './provable-types/struct.js'; import { Field, Group, Scalar } from './core.js'; import { Poseidon } from './hash.js'; -import { MerkleMapWitness } from './merkle-map.js'; +import { MerkleMapWitness } from './provable-types/merkle-map.js'; import { PrivateKey, PublicKey, scaleShifted } from './signature.js'; import { Provable } from './provable.js'; diff --git a/src/lib/precondition.ts b/src/lib/precondition.ts index 8800d5b344..0b685a5490 100644 --- a/src/lib/precondition.ts +++ b/src/lib/precondition.ts @@ -1,5 +1,8 @@ import { Bool, Field } from './core.js'; -import { circuitValueEquals, cloneCircuitValue } from './circuit-value.js'; +import { + circuitValueEquals, + cloneCircuitValue, +} from './provable-types/struct.js'; import { Provable } from './provable.js'; import { activeInstance as Mina } from './mina/mina-instance.js'; import type { AccountUpdate } from './account-update.js'; diff --git a/src/lib/proof-system.ts b/src/lib/proof-system.ts index 3a025dc2c4..2df2431edf 100644 --- a/src/lib/proof-system.ts +++ b/src/lib/proof-system.ts @@ -5,7 +5,6 @@ import { } from '../bindings/lib/generic.js'; import { withThreadPool } from '../snarky.js'; import { - ProvablePure, Pickles, FeatureFlags, MlFeatureFlags, @@ -21,15 +20,14 @@ import { Struct, provable, provablePure, - toConstant, -} from './circuit-value.js'; +} from './provable-types/struct.js'; import { Provable } from './provable.js'; import { assert, prettifyStacktracePromise } from './errors.js'; import { snarkContext } from './provable-context.js'; import { hashConstant } from './hash.js'; import { MlArray, MlBool, MlResult, MlPair } from './ml/base.js'; import { MlFieldArray, MlFieldConstArray } from './ml/fields.js'; -import { FieldConst, FieldVar } from './field.js'; +import { FieldVar, FieldConst } from './provable-core/fieldvar.js'; import { Cache, readCache, writeCache } from './proof-system/cache.js'; import { decodeProverKey, @@ -37,6 +35,7 @@ import { parseHeader, } from './proof-system/prover-keys.js'; import { setSrsCache, unsetSrsCache } from '../bindings/crypto/bindings/srs.js'; +import { ProvablePure } from './provable-types/provable-intf.js'; // public API export { @@ -847,12 +846,12 @@ function methodArgumentsToConstant( let arg = args[i]; let { type, index } = allArgs[i]; if (type === 'witness') { - constArgs.push(toConstant(witnessArgs[index], arg)); + constArgs.push(Provable.toConstant(witnessArgs[index], arg)); } else if (type === 'proof') { let Proof = proofArgs[index]; let type = getStatementType(Proof); - let publicInput = toConstant(type.input, arg.publicInput); - let publicOutput = toConstant(type.output, arg.publicOutput); + let publicInput = Provable.toConstant(type.input, arg.publicInput); + let publicOutput = Provable.toConstant(type.output, arg.publicOutput); constArgs.push( new Proof({ publicInput, publicOutput, proof: arg.proof }) ); diff --git a/src/lib/proof-system.unit-test.ts b/src/lib/proof-system.unit-test.ts index 5f85d65048..d8882adc00 100644 --- a/src/lib/proof-system.unit-test.ts +++ b/src/lib/proof-system.unit-test.ts @@ -1,5 +1,5 @@ import { Field, Bool } from './core.js'; -import { Struct } from './circuit-value.js'; +import { Struct } from './provable-types/struct.js'; import { UInt64 } from './int.js'; import { CompiledTag, @@ -10,13 +10,14 @@ import { sortMethodArguments, } from './proof-system.js'; import { expect } from 'expect'; -import { Pickles, ProvablePure, Snarky } from '../snarky.js'; +import { Pickles, Snarky } from '../snarky.js'; import { AnyFunction } from './util/types.js'; import { snarkContext } from './provable-context.js'; import { it } from 'node:test'; import { Provable } from './provable.js'; import { bool, equivalentAsync, field, record } from './testing/equivalent.js'; -import { FieldConst, FieldVar } from './field.js'; +import { FieldVar, FieldConst } from './provable-core/fieldvar.js'; +import { ProvablePure } from './provable-types/provable-intf.js'; const EmptyProgram = ZkProgram({ name: 'empty', diff --git a/src/lib/provable-core/exists.ts b/src/lib/provable-core/exists.ts index 863d197d84..30ae28bbe5 100644 --- a/src/lib/provable-core/exists.ts +++ b/src/lib/provable-core/exists.ts @@ -1,22 +1,70 @@ import { Snarky } from '../../snarky.js'; -import { FieldConst, type VarFieldVar } from '../field.js'; +import { FieldConst, VarFieldVar } from '../provable-core/fieldvar.js'; +import type { VarField } from '../field.js'; import { MlArray, MlOption } from '../ml/base.js'; +import { createField } from './field-constructor.js'; +import { TupleN } from '../util/types.js'; -export { existsAsync }; +export { createVarField, exists, existsAsync, existsOne }; -async function existsAsync( - size: number, - compute: (() => Promise) | (() => bigint[]) -): Promise { +/** + * Witness `size` field element variables by passing a callback that returns `size` bigints. + * + * Note: this is called "exists" because in a proof, you use it like this: + * > "I prove that there exists x, such that (some statement)" + */ +function exists TupleN>( + size: N, + compute: C +): TupleN { // enter prover block let finish = Snarky.run.enterAsProver(size); if (!Snarky.run.inProver()) { - return MlArray.from(finish(MlOption())); + // step outside prover block and create vars: compile case + let vars = MlArray.mapFrom(finish(MlOption()), createVarField); + return TupleN.fromArray(size, vars); } - // TODO would be nice to be able to step outside the as_prover block - // with a try-catch if the callback throws an error + // run the callback to get values to witness + let values = compute(); + if (values.length !== size) + throw Error( + `Expected witnessed values of length ${size}, got ${values.length}.` + ); + + // note: here, we deliberately reduce the bigint values modulo the field size + // this makes it easier to use normal arithmetic in low-level gadgets, + // i.e. you can just witness x - y and it will be a field subtraction + let inputValues = MlArray.mapTo(values, FieldConst.fromBigint); + + // step outside prover block and create vars: prover case + let fieldVars = finish(MlOption(inputValues)); + let vars = MlArray.mapFrom(fieldVars, createVarField); + return TupleN.fromArray(size, vars); +} + +/** + * Variant of {@link exists} that witnesses 1 field element. + */ +function existsOne(compute: () => bigint): VarField { + return exists(1, () => [compute()])[0]; +} + +/** + * Async variant of {@link exists}, which allows an async callback. + */ +async function existsAsync< + N extends number, + C extends () => Promise> +>(size: N, compute: C): Promise> { + // enter prover block + let finish = Snarky.run.enterAsProver(size); + + if (!Snarky.run.inProver()) { + let vars = MlArray.mapFrom(finish(MlOption()), createVarField); + return TupleN.fromArray(size, vars); + } // run the async callback to get values to witness let values = await compute(); @@ -25,7 +73,18 @@ async function existsAsync( `Expected witnessed values of length ${size}, got ${values.length}.` ); + // note: here, we deliberately reduce the bigint values modulo the field size + // this makes it easier to use normal arithmetic in low-level gadgets, + // i.e. you can just witness x - y and it will be a field subtraction let inputValues = MlArray.mapTo(values, FieldConst.fromBigint); - let vars = finish(MlOption(inputValues)); - return MlArray.from(vars); + + let fieldVars = finish(MlOption(inputValues)); + let vars = MlArray.mapFrom(fieldVars, createVarField); + return TupleN.fromArray(size, vars); +} + +// helpers for varfields + +function createVarField(x: VarFieldVar): VarField { + return createField(x) as VarField; } diff --git a/src/lib/provable-core/field-constructor.ts b/src/lib/provable-core/field-constructor.ts new file mode 100644 index 0000000000..3bf4781c93 --- /dev/null +++ b/src/lib/provable-core/field-constructor.ts @@ -0,0 +1,57 @@ +/** + * Stub module to break dependency cycle between Field and Bool classes and + * core gadgets which they depend on but which need to create Fields and Bools, + * or check if a value is a Field or a Bool. + */ +import type { Field } from '../field.js'; +import type { Bool } from '../bool.js'; +import type { FieldVar, FieldConst } from './fieldvar.js'; + +export { + createField, + createBool, + isField, + isBool, + setFieldConstructor, + setBoolConstructor, +}; + +let fieldConstructor: typeof Field | undefined; +let boolConstructor: typeof Bool | undefined; + +function setFieldConstructor(constructor: typeof Field) { + fieldConstructor = constructor; +} +function setBoolConstructor(constructor: typeof Bool) { + boolConstructor = constructor; +} + +function createField( + value: string | number | bigint | Field | FieldVar | FieldConst +): Field { + if (fieldConstructor === undefined) + throw Error('Cannot construct a Field before the class was defined.'); + return new fieldConstructor(value); +} + +function createBool(value: boolean | Bool | FieldVar): Bool { + if (boolConstructor === undefined) + throw Error('Cannot construct a Bool before the class was defined.'); + return new boolConstructor(value); +} + +function isField(x: unknown): x is Field { + if (fieldConstructor === undefined) + throw Error( + 'Cannot check for instance of Field before the class was defined.' + ); + return x instanceof fieldConstructor; +} + +function isBool(x: unknown): x is Bool { + if (boolConstructor === undefined) + throw Error( + 'Cannot check for instance of Bool before the class was defined.' + ); + return x instanceof boolConstructor; +} diff --git a/src/lib/provable-core/fieldvar.ts b/src/lib/provable-core/fieldvar.ts new file mode 100644 index 0000000000..363e2dc5d1 --- /dev/null +++ b/src/lib/provable-core/fieldvar.ts @@ -0,0 +1,116 @@ +import { Fp } from '../../bindings/crypto/finite-field.js'; + +// internal API +export { FieldType, FieldVar, FieldConst, ConstantFieldVar, VarFieldVar }; + +type FieldConst = [0, bigint]; + +function constToBigint(x: FieldConst): bigint { + return x[1]; +} +function constFromBigint(x: bigint): FieldConst { + return [0, Fp.mod(x)]; +} + +const FieldConst = { + fromBigint: constFromBigint, + toBigint: constToBigint, + equal(x: FieldConst, y: FieldConst) { + return x[1] === y[1]; + }, + [0]: constFromBigint(0n), + [1]: constFromBigint(1n), + [-1]: constFromBigint(-1n), +}; + +enum FieldType { + Constant, + Var, + Add, + Scale, +} + +/** + * `FieldVar` is the core data type in snarky. It is eqivalent to `Cvar.t` in OCaml. + * It represents a field element that is part of provable code - either a constant or a variable. + * + * **Variables** end up filling the witness columns of a constraint system. + * Think of a variable as a value that has to be provided by the prover, and that has to satisfy all the + * constraints it is involved in. + * + * **Constants** end up being hard-coded into the constraint system as gate coefficients. + * Think of a constant as a value that is known publicly, at compile time, and that defines the constraint system. + * + * Both constants and variables can be combined into an AST using the Add and Scale combinators. + */ +type FieldVar = + | [FieldType.Constant, FieldConst] + | [FieldType.Var, number] + | [FieldType.Add, FieldVar, FieldVar] + | [FieldType.Scale, FieldConst, FieldVar]; + +type ConstantFieldVar = [FieldType.Constant, FieldConst]; +type VarFieldVar = [FieldType.Var, number]; + +const FieldVar = { + // constructors + Constant(x: FieldConst): ConstantFieldVar { + return [FieldType.Constant, x]; + }, + Var(x: number): VarFieldVar { + return [FieldType.Var, x]; + }, + Add(x: FieldVar, y: FieldVar): [FieldType.Add, FieldVar, FieldVar] { + return [FieldType.Add, x, y]; + }, + Scale(c: FieldConst, x: FieldVar): [FieldType.Scale, FieldConst, FieldVar] { + return [FieldType.Scale, c, x]; + }, + + constant(x: bigint | FieldConst): ConstantFieldVar { + let x0 = typeof x === 'bigint' ? FieldConst.fromBigint(x) : x; + return [FieldType.Constant, x0]; + }, + add(x: FieldVar, y: FieldVar): FieldVar { + if (FieldVar.isConstant(x) && x[1][1] === 0n) return y; + if (FieldVar.isConstant(y) && y[1][1] === 0n) return x; + if (FieldVar.isConstant(x) && FieldVar.isConstant(y)) { + return FieldVar.constant(Fp.add(x[1][1], y[1][1])); + } + return [FieldType.Add, x, y]; + }, + scale(c: bigint | FieldConst, x: FieldVar): FieldVar { + let c0 = typeof c === 'bigint' ? FieldConst.fromBigint(c) : c; + if (c0[1] === 0n) return FieldVar.constant(0n); + if (c0[1] === 1n) return x; + if (FieldVar.isConstant(x)) { + return FieldVar.constant(Fp.mul(c0[1], x[1][1])); + } + if (FieldVar.isScale(x)) { + return [ + FieldType.Scale, + FieldConst.fromBigint(Fp.mul(c0[1], x[1][1])), + x[2], + ]; + } + return [FieldType.Scale, c0, x]; + }, + + // type guards + isConstant(x: FieldVar): x is ConstantFieldVar { + return x[0] === FieldType.Constant; + }, + isVar(x: FieldVar): x is VarFieldVar { + return x[0] === FieldType.Var; + }, + isAdd(x: FieldVar): x is [FieldType.Add, FieldVar, FieldVar] { + return x[0] === FieldType.Add; + }, + isScale(x: FieldVar): x is [FieldType.Scale, FieldConst, FieldVar] { + return x[0] === FieldType.Scale; + }, + + [0]: [FieldType.Constant, FieldConst[0]] satisfies ConstantFieldVar, + [1]: [FieldType.Constant, FieldConst[1]] satisfies ConstantFieldVar, + [-1]: [FieldType.Constant, FieldConst[-1]] satisfies ConstantFieldVar, +}; diff --git a/src/lib/provable-types/bytes.ts b/src/lib/provable-types/bytes.ts index b8a443371e..078d936890 100644 --- a/src/lib/provable-types/bytes.ts +++ b/src/lib/provable-types/bytes.ts @@ -1,5 +1,5 @@ -import { provableFromClass } from '../../bindings/lib/provable-snarky.js'; -import type { ProvablePureExtended } from '../circuit-value.js'; +import { provableFromClass } from './provable-derivers.js'; +import type { ProvablePureExtended } from './struct.js'; import { assert } from '../gadgets/common.js'; import { chunkString } from '../util/arrays.js'; import { Provable } from '../provable.js'; diff --git a/src/lib/provable-types/circuit-value.ts b/src/lib/provable-types/circuit-value.ts new file mode 100644 index 0000000000..3b54f501b8 --- /dev/null +++ b/src/lib/provable-types/circuit-value.ts @@ -0,0 +1,221 @@ +import 'reflect-metadata'; +import { Field } from '../core.js'; +import { HashInput, NonMethods } from './provable-derivers.js'; +import { Provable } from '../provable.js'; +import { AnyConstructor, FlexibleProvable } from './struct.js'; + +export { CircuitValue, prop, arrayProp }; + +/** + * @deprecated `CircuitValue` is deprecated in favor of {@link Struct}, which features a simpler API and better typing. + */ +abstract class CircuitValue { + constructor(...props: any[]) { + // if this is called with no arguments, do nothing, to support simple super() calls + if (props.length === 0) return; + + let fields = this.constructor.prototype._fields; + if (fields === undefined) return; + if (props.length !== fields.length) { + throw Error( + `${this.constructor.name} constructor called with ${props.length} arguments, but expected ${fields.length}` + ); + } + for (let i = 0; i < fields.length; ++i) { + let [key] = fields[i]; + (this as any)[key] = props[i]; + } + } + + static fromObject( + this: T, + value: NonMethods> + ): InstanceType { + return Object.assign(Object.create(this.prototype), value); + } + + static sizeInFields(): number { + const fields: [string, any][] = (this as any).prototype._fields; + return fields.reduce((acc, [_, typ]) => acc + typ.sizeInFields(), 0); + } + + static toFields( + this: T, + v: InstanceType + ): Field[] { + const res: Field[] = []; + const fields = this.prototype._fields; + if (fields === undefined || fields === null) { + return res; + } + for (let i = 0, n = fields.length; i < n; ++i) { + const [key, propType] = fields[i]; + const subElts: Field[] = propType.toFields((v as any)[key]); + subElts.forEach((x) => res.push(x)); + } + return res; + } + + static toAuxiliary(): [] { + return []; + } + + static toInput( + 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) { + input = HashInput.append(input, type.toInput(v[key])); + continue; + } + // as a fallback, use toFields on the type + // TODO: this is problematic -- ignores if there's a toInput on a nested type + // so, remove this? should every provable define toInput? + let xs: Field[] = type.toFields(v[key]); + input.fields!.push(...xs); + } + return input; + } + + toFields(): Field[] { + return (this.constructor as any).toFields(this); + } + + toJSON(): any { + return (this.constructor as any).toJSON(this); + } + + toConstant(): this { + return (this.constructor as any).toConstant(this); + } + + equals(x: this) { + return Provable.equal(this, x); + } + + assertEquals(x: this) { + Provable.assertEqual(this, x); + } + + isConstant() { + return this.toFields().every((x) => x.isConstant()); + } + + static fromFields( + this: T, + xs: Field[] + ): InstanceType { + const fields: [string, any][] = (this as any).prototype._fields; + if (xs.length < fields.length) { + throw Error( + `${this.name}.fromFields: Expected ${fields.length} field elements, got ${xs?.length}` + ); + } + let offset = 0; + const props: any = {}; + for (let i = 0; i < fields.length; ++i) { + const [key, propType] = fields[i]; + const propSize = propType.sizeInFields(); + const propVal = propType.fromFields( + xs.slice(offset, offset + propSize), + [] + ); + props[key] = propVal; + offset += propSize; + } + return Object.assign(Object.create(this.prototype), props); + } + + static check(this: T, v: InstanceType) { + const fields = (this as any).prototype._fields; + if (fields === undefined || fields === null) { + return; + } + for (let i = 0; i < fields.length; ++i) { + const [key, propType] = fields[i]; + const value = (v as any)[key]; + if (propType.check === undefined) + throw Error('bug: CircuitValue without .check()'); + propType.check(value); + } + } + + static toConstant( + this: T, + t: InstanceType + ): InstanceType { + const xs: Field[] = (this as any).toFields(t); + return (this as any).fromFields(xs.map((x) => x.toConstant())); + } + + static toJSON(this: T, v: InstanceType) { + const res: any = {}; + if ((this as any).prototype._fields !== undefined) { + const fields: [string, any][] = (this as any).prototype._fields; + fields.forEach(([key, propType]) => { + res[key] = propType.toJSON((v as any)[key]); + }); + } + return res; + } + + static fromJSON( + 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}.fromJSON(): invalid input ${value}`); + } + if (fields !== undefined) { + for (let i = 0; i < fields.length; ++i) { + let [key, propType] = fields[i]; + if (value[key] === undefined) { + throw Error(`${this.name}.fromJSON(): invalid input ${value}`); + } else { + props[key] = propType.fromJSON(value[key]); + } + } + } + return Object.assign(Object.create(this.prototype), props); + } + + static empty(): InstanceType { + const fields: [string, any][] = (this as any).prototype._fields ?? []; + let props: any = {}; + fields.forEach(([key, propType]) => { + props[key] = propType.empty(); + }); + return Object.assign(Object.create(this.prototype), props); + } +} + +function prop(this: any, target: any, key: string) { + const fieldType = Reflect.getMetadata('design:type', target, key); + if (!target.hasOwnProperty('_fields')) { + target._fields = []; + } + if (fieldType === undefined) { + } else if (fieldType.toFields && fieldType.fromFields) { + target._fields.push([key, fieldType]); + } else { + console.log( + `warning: property ${key} missing field element conversion methods` + ); + } +} + +function arrayProp(elementType: FlexibleProvable, length: number) { + return function (target: any, key: string) { + if (!target.hasOwnProperty('_fields')) { + target._fields = []; + } + target._fields.push([key, Provable.Array(elementType, length)]); + }; +} diff --git a/src/lib/provable-types/fields.ts b/src/lib/provable-types/fields.ts index 11dbe91061..e92cd0d51e 100644 --- a/src/lib/provable-types/fields.ts +++ b/src/lib/provable-types/fields.ts @@ -1,4 +1,4 @@ -import { ProvablePureExtended } from '../circuit-value.js'; +import { ProvablePureExtended } from './struct.js'; import { Field } from '../field.js'; export { modifiedField, fields }; diff --git a/src/lib/provable-types/merkle-list.ts b/src/lib/provable-types/merkle-list.ts index 2cbcf5b33a..83cc9b49c1 100644 --- a/src/lib/provable-types/merkle-list.ts +++ b/src/lib/provable-types/merkle-list.ts @@ -1,9 +1,10 @@ import { Bool, Field } from '../core.js'; import { Provable } from '../provable.js'; -import { Struct, Unconstrained } from '../circuit-value.js'; +import { Struct } from './struct.js'; import { assert } from '../gadgets/common.js'; -import { provableFromClass } from '../../bindings/lib/provable-snarky.js'; +import { provableFromClass } from './provable-derivers.js'; import { Poseidon, packToFields, ProvableHashable } from '../hash.js'; +import { Unconstrained } from './unconstrained.js'; export { MerkleListBase, diff --git a/src/lib/merkle-map.test.ts b/src/lib/provable-types/merkle-map.test.ts similarity index 100% rename from src/lib/merkle-map.test.ts rename to src/lib/provable-types/merkle-map.test.ts diff --git a/src/lib/merkle-map.ts b/src/lib/provable-types/merkle-map.ts similarity index 97% rename from src/lib/merkle-map.ts rename to src/lib/provable-types/merkle-map.ts index 91f8a1bdd3..4f0be85407 100644 --- a/src/lib/merkle-map.ts +++ b/src/lib/provable-types/merkle-map.ts @@ -1,8 +1,8 @@ import { arrayProp, CircuitValue } from './circuit-value.js'; -import { Field, Bool } from './core.js'; -import { Poseidon } from './hash.js'; +import { Field, Bool } from '../core.js'; +import { Poseidon } from '../hash.js'; import { MerkleTree, MerkleWitness } from './merkle-tree.js'; -import { Provable } from './provable.js'; +import { Provable } from '../provable.js'; const bits = 255; const printDebugs = false; diff --git a/src/lib/merkle-tree.test.ts b/src/lib/provable-types/merkle-tree.test.ts similarity index 100% rename from src/lib/merkle-tree.test.ts rename to src/lib/provable-types/merkle-tree.test.ts diff --git a/src/lib/merkle-tree.ts b/src/lib/provable-types/merkle-tree.ts similarity index 98% rename from src/lib/merkle-tree.ts rename to src/lib/provable-types/merkle-tree.ts index b4b24bd46a..7f1b8a7a87 100644 --- a/src/lib/merkle-tree.ts +++ b/src/lib/provable-types/merkle-tree.ts @@ -3,9 +3,9 @@ */ import { CircuitValue, arrayProp } from './circuit-value.js'; -import { Poseidon } from './hash.js'; -import { Bool, Field } from './core.js'; -import { Provable } from './provable.js'; +import { Poseidon } from '../hash.js'; +import { Bool, Field } from '../core.js'; +import { Provable } from '../provable.js'; // external API export { Witness, MerkleTree, MerkleWitness, BaseMerkleWitness }; diff --git a/src/lib/merkle-tree.unit-test.ts b/src/lib/provable-types/merkle-tree.unit-test.ts similarity index 86% rename from src/lib/merkle-tree.unit-test.ts rename to src/lib/provable-types/merkle-tree.unit-test.ts index 8258f2d629..05d6f0f028 100644 --- a/src/lib/merkle-tree.unit-test.ts +++ b/src/lib/provable-types/merkle-tree.unit-test.ts @@ -1,6 +1,6 @@ -import { Bool, Field } from './core.js'; +import { Bool, Field } from '../core.js'; import { maybeSwap, maybeSwapBad } from './merkle-tree.js'; -import { Random, test } from './testing/property.js'; +import { Random, test } from '../testing/property.js'; import { expect } from 'expect'; test(Random.bool, Random.field, Random.field, (b, x, y) => { diff --git a/src/lib/provable-types/packed.ts b/src/lib/provable-types/packed.ts index edbb3565f9..01e53e3221 100644 --- a/src/lib/provable-types/packed.ts +++ b/src/lib/provable-types/packed.ts @@ -1,9 +1,6 @@ -import { provableFromClass } from '../../bindings/lib/provable-snarky.js'; -import { - HashInput, - ProvableExtended, - Unconstrained, -} from '../circuit-value.js'; +import { provableFromClass } from './provable-derivers.js'; +import { HashInput, ProvableExtended } from './struct.js'; +import { Unconstrained } from './unconstrained.js'; import { Field } from '../field.js'; import { assert } from '../gadgets/common.js'; import { Poseidon, ProvableHashable, packToFields } from '../hash.js'; diff --git a/src/lib/provable-types/provable-derivers.ts b/src/lib/provable-types/provable-derivers.ts new file mode 100644 index 0000000000..faaea81f1c --- /dev/null +++ b/src/lib/provable-types/provable-derivers.ts @@ -0,0 +1,102 @@ +import { Provable, ProvablePure } from './provable-intf.js'; +import { Field } from '../core.js'; +import { + createDerivers, + NonMethods, + InferProvable as GenericInferProvable, + InferJson, + InferredProvable as GenericInferredProvable, + IsPure as GenericIsPure, + createHashInput, + Constructor, +} from '../../bindings/lib/provable-generic.js'; +import { Tuple } from '../util/types.js'; +import { GenericHashInput } from '../../bindings/lib/generic.js'; + +// external API +export { + ProvableExtended, + provable, + provablePure, + provableTuple, + provableFromClass, +}; + +// internal API +export { + NonMethods, + HashInput, + InferProvable, + InferJson, + InferredProvable, + IsPure, +}; + +type ProvableExtension = { + toInput: (x: T) => { fields?: Field[]; packed?: [Field, number][] }; + toJSON: (x: T) => TJson; + fromJSON: (x: TJson) => T; + empty: () => T; +}; +type ProvableExtended = Provable & + ProvableExtension; +type ProvablePureExtended = ProvablePure & + ProvableExtension; + +type InferProvable = GenericInferProvable; +type InferredProvable = GenericInferredProvable; +type IsPure = GenericIsPure; + +type HashInput = GenericHashInput; +const HashInput = createHashInput(); + +const { provable } = createDerivers(); + +function provablePure( + typeObj: A +): ProvablePureExtended, InferJson> { + return provable(typeObj, { isPure: true }) as any; +} + +function provableTuple>(types: T): InferredProvable { + return provable(types) as any; +} + +function provableFromClass>( + Class: Constructor & { check?: (x: T) => void; empty?: () => T }, + typeObj: A +): IsPure extends true + ? ProvablePureExtended> + : ProvableExtended> { + let raw = provable(typeObj); + return { + sizeInFields: raw.sizeInFields, + toFields: raw.toFields, + toAuxiliary: raw.toAuxiliary, + fromFields(fields, aux) { + return construct(Class, raw.fromFields(fields, aux)); + }, + check(value) { + if (Class.check !== undefined) { + Class.check(value); + } else { + raw.check(value); + } + }, + toInput: raw.toInput, + toJSON: raw.toJSON, + fromJSON(x) { + return construct(Class, raw.fromJSON(x)); + }, + empty() { + return Class.empty !== undefined + ? Class.empty() + : construct(Class, raw.empty()); + }, + } satisfies ProvableExtended> as any; +} + +function construct(Class: Constructor, value: Raw): T { + let instance = Object.create(Class.prototype); + return Object.assign(instance, value); +} diff --git a/src/lib/provable-types/provable-intf.ts b/src/lib/provable-types/provable-intf.ts new file mode 100644 index 0000000000..89d3f63059 --- /dev/null +++ b/src/lib/provable-types/provable-intf.ts @@ -0,0 +1,78 @@ +import type { Field } from '../field.js'; + +export { Provable, ProvablePure }; + +/** + * `Provable` is the general interface for provable types in o1js. + * + * `Provable` describes how a type `T` is made up of {@link Field} elements and "auxiliary" (non-provable) data. + * + * `Provable` is the required input type in several methods in o1js. + * One convenient way to create a `Provable` is using `Struct`. + * + * All built-in provable types in o1js ({@link Field}, {@link Bool}, etc.) are instances of `Provable` as well. + * + * Note: These methods are meant to be used by the library internally and are not directly when writing provable code. + */ +type 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[]; + + /** + * 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`. + * + * @param value - the element of type `T` to generate the auxiliary data array from, optional. + * If not provided, a default value for auxiliary data is returned. + * + * @return An array of any type describing how this `T` element is made up of "auxiliary" (non-provable) data. + */ + toAuxiliary: (value?: T) => any[]; + + /** + * A function that returns an element of type `T` from the given provable and "auxiliary" data. + * + * This function is the reverse operation of calling {@link toFields} and {@link toAuxilary} methods on an element of type `T`. + * + * @param fields - an array of {@link Field} elements describing the provable data of the new `T` element. + * @param aux - an array of any type describing the "auxiliary" data of the new `T` element, optional. + * + * @return An element of type `T` generated from the given provable and "auxiliary" data. + */ + fromFields: (fields: Field[], aux: any[]) => T; + + /** + * Return the size of the `T` type in terms of {@link Field} type, as {@link Field} is the primitive 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, instead it creates any number of assertions to prove that `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; +}; + +/** + * `ProvablePure` is a special kind of {@link Provable} interface, where the "auxiliary" (non-provable) data is empty. + * This means the type consists only of field elements, in that sense it is "pure". + * Any instance of `ProvablePure` is also an instance of `Provable` where the "auxiliary" data is empty. + * + * Examples where `ProvablePure` is required are types of on-chain state, events and actions. + */ +type ProvablePure = Omit, 'fromFields'> & { + fromFields: (fields: Field[]) => T; +}; diff --git a/src/lib/string.test.ts b/src/lib/provable-types/string.test.ts similarity index 100% rename from src/lib/string.test.ts rename to src/lib/provable-types/string.test.ts diff --git a/src/lib/string.ts b/src/lib/provable-types/string.ts similarity index 95% rename from src/lib/string.ts rename to src/lib/provable-types/string.ts index efdacfc70b..25e7c88832 100644 --- a/src/lib/string.ts +++ b/src/lib/provable-types/string.ts @@ -1,8 +1,8 @@ -import { Bool, Field } from '../lib/core.js'; -import { arrayProp, CircuitValue, prop } from './circuit-value.js'; -import { Provable } from './provable.js'; -import { Poseidon } from './hash.js'; -import { Gadgets } from './gadgets/gadgets.js'; +import { Bool, Field } from '../core.js'; +import { Provable } from '../provable.js'; +import { Poseidon } from '../hash.js'; +import { Gadgets } from '../gadgets/gadgets.js'; +import { CircuitValue, arrayProp, prop } from './circuit-value.js'; export { Character, CircuitString }; diff --git a/src/lib/circuit-value.ts b/src/lib/provable-types/struct.ts similarity index 51% rename from src/lib/circuit-value.ts rename to src/lib/provable-types/struct.ts index 3ade7bcb25..7d41286db3 100644 --- a/src/lib/circuit-value.ts +++ b/src/lib/provable-types/struct.ts @@ -1,38 +1,30 @@ -import 'reflect-metadata'; -import { ProvablePure, Snarky } from '../snarky.js'; -import { Field, Bool, Scalar, Group } from './core.js'; +import { Field, Bool, Scalar, Group } from '../core.js'; import { provable, provablePure, provableTuple, HashInput, NonMethods, -} from '../bindings/lib/provable-snarky.js'; +} from './provable-derivers.js'; import type { InferJson, InferProvable, InferredProvable, IsPure, -} from '../bindings/lib/provable-snarky.js'; -import { Provable } from './provable.js'; -import { assert } from './errors.js'; -import { inCheckedComputation } from './provable-context.js'; -import { Proof } from './proof-system.js'; +} from './provable-derivers.js'; +import { Provable } from '../provable.js'; +import { Proof } from '../proof-system.js'; +import { ProvablePure } from './provable-intf.js'; // external API export { - CircuitValue, ProvableExtended, ProvablePureExtended, - prop, - arrayProp, - matrixProp, provable, provablePure, Struct, FlexibleProvable, FlexibleProvablePure, - Unconstrained, }; // internal API @@ -41,7 +33,6 @@ export { AnyConstructor, cloneCircuitValue, circuitValueEquals, - toConstant, InferProvable, HashInput, InferJson, @@ -72,236 +63,6 @@ type FlexibleProvablePure = ProvablePure | StructPure; type Constructor = new (...args: any) => T; type AnyConstructor = Constructor; -/** - * @deprecated `CircuitValue` is deprecated in favor of {@link Struct}, which features a simpler API and better typing. - */ -abstract class CircuitValue { - constructor(...props: any[]) { - // if this is called with no arguments, do nothing, to support simple super() calls - if (props.length === 0) return; - - let fields = this.constructor.prototype._fields; - if (fields === undefined) return; - if (props.length !== fields.length) { - throw Error( - `${this.constructor.name} constructor called with ${props.length} arguments, but expected ${fields.length}` - ); - } - for (let i = 0; i < fields.length; ++i) { - let [key] = fields[i]; - (this as any)[key] = props[i]; - } - } - - static fromObject( - this: T, - value: NonMethods> - ): InstanceType { - return Object.assign(Object.create(this.prototype), value); - } - - static sizeInFields(): number { - const fields: [string, any][] = (this as any).prototype._fields; - return fields.reduce((acc, [_, typ]) => acc + typ.sizeInFields(), 0); - } - - static toFields( - this: T, - v: InstanceType - ): Field[] { - const res: Field[] = []; - const fields = this.prototype._fields; - if (fields === undefined || fields === null) { - return res; - } - for (let i = 0, n = fields.length; i < n; ++i) { - const [key, propType] = fields[i]; - const subElts: Field[] = propType.toFields((v as any)[key]); - subElts.forEach((x) => res.push(x)); - } - return res; - } - - static toAuxiliary(): [] { - return []; - } - - static toInput( - 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) { - input = HashInput.append(input, type.toInput(v[key])); - continue; - } - // as a fallback, use toFields on the type - // TODO: this is problematic -- ignores if there's a toInput on a nested type - // so, remove this? should every provable define toInput? - let xs: Field[] = type.toFields(v[key]); - input.fields!.push(...xs); - } - return input; - } - - toFields(): Field[] { - return (this.constructor as any).toFields(this); - } - - toJSON(): any { - return (this.constructor as any).toJSON(this); - } - - toConstant(): this { - return (this.constructor as any).toConstant(this); - } - - equals(x: this) { - return Provable.equal(this, x); - } - - assertEquals(x: this) { - Provable.assertEqual(this, x); - } - - isConstant() { - return this.toFields().every((x) => x.isConstant()); - } - - static fromFields( - this: T, - xs: Field[] - ): InstanceType { - const fields: [string, any][] = (this as any).prototype._fields; - if (xs.length < fields.length) { - throw Error( - `${this.name}.fromFields: Expected ${fields.length} field elements, got ${xs?.length}` - ); - } - let offset = 0; - const props: any = {}; - for (let i = 0; i < fields.length; ++i) { - const [key, propType] = fields[i]; - const propSize = propType.sizeInFields(); - const propVal = propType.fromFields( - xs.slice(offset, offset + propSize), - [] - ); - props[key] = propVal; - offset += propSize; - } - return Object.assign(Object.create(this.prototype), props); - } - - static check(this: T, v: InstanceType) { - const fields = (this as any).prototype._fields; - if (fields === undefined || fields === null) { - return; - } - for (let i = 0; i < fields.length; ++i) { - const [key, propType] = fields[i]; - const value = (v as any)[key]; - if (propType.check === undefined) - throw Error('bug: CircuitValue without .check()'); - propType.check(value); - } - } - - static toConstant( - this: T, - t: InstanceType - ): InstanceType { - const xs: Field[] = (this as any).toFields(t); - return (this as any).fromFields(xs.map((x) => x.toConstant())); - } - - static toJSON(this: T, v: InstanceType) { - const res: any = {}; - if ((this as any).prototype._fields !== undefined) { - const fields: [string, any][] = (this as any).prototype._fields; - fields.forEach(([key, propType]) => { - res[key] = propType.toJSON((v as any)[key]); - }); - } - return res; - } - - static fromJSON( - 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}.fromJSON(): invalid input ${value}`); - } - if (fields !== undefined) { - for (let i = 0; i < fields.length; ++i) { - let [key, propType] = fields[i]; - if (value[key] === undefined) { - throw Error(`${this.name}.fromJSON(): invalid input ${value}`); - } else { - props[key] = propType.fromJSON(value[key]); - } - } - } - return Object.assign(Object.create(this.prototype), props); - } - - static empty(): InstanceType { - const fields: [string, any][] = (this as any).prototype._fields ?? []; - let props: any = {}; - fields.forEach(([key, propType]) => { - props[key] = propType.empty(); - }); - return Object.assign(Object.create(this.prototype), props); - } -} - -function prop(this: any, target: any, key: string) { - const fieldType = Reflect.getMetadata('design:type', target, key); - if (!target.hasOwnProperty('_fields')) { - target._fields = []; - } - if (fieldType === undefined) { - } else if (fieldType.toFields && fieldType.fromFields) { - target._fields.push([key, fieldType]); - } else { - console.log( - `warning: property ${key} missing field element conversion methods` - ); - } -} - -function arrayProp(elementType: FlexibleProvable, length: number) { - return function (target: any, key: string) { - if (!target.hasOwnProperty('_fields')) { - target._fields = []; - } - target._fields.push([key, Provable.Array(elementType, length)]); - }; -} - -function matrixProp( - elementType: FlexibleProvable, - nRows: number, - nColumns: number -) { - return function (target: any, key: string) { - if (!target.hasOwnProperty('_fields')) { - target._fields = []; - } - target._fields.push([ - key, - Provable.Array(Provable.Array(elementType, nColumns), nRows), - ]); - }; -} - /** * `Struct` lets you declare composite types for use in o1js circuits. * @@ -496,124 +257,6 @@ function StructNoJson< return Struct(type) satisfies Provable as any; } -/** - * Container which holds an unconstrained value. This can be used to pass values - * between the out-of-circuit blocks in provable code. - * - * Invariants: - * - An `Unconstrained`'s value can only be accessed in auxiliary contexts. - * - An `Unconstrained` can be empty when compiling, but never empty when running as the prover. - * (there is no way to create an empty `Unconstrained` in the prover) - * - * @example - * ```ts - * let x = Unconstrained.from(0n); - * - * class MyContract extends SmartContract { - * `@method` myMethod(x: Unconstrained) { - * - * Provable.witness(Field, () => { - * // we can access and modify `x` here - * let newValue = x.get() + otherField.toBigInt(); - * x.set(newValue); - * - * // ... - * }); - * - * // throws an error! - * x.get(); - * } - * ``` - */ -class Unconstrained { - private option: - | { isSome: true; value: T } - | { isSome: false; value: undefined }; - - private constructor(isSome: boolean, value?: T) { - this.option = { isSome, value: value as any }; - } - - /** - * Read an unconstrained value. - * - * Note: Can only be called outside provable code. - */ - get(): T { - if (inCheckedComputation() && !Snarky.run.inProverBlock()) - throw Error(`You cannot use Unconstrained.get() in provable code. - -The only place where you can read unconstrained values is in Provable.witness() -and Provable.asProver() blocks, which execute outside the proof. -`); - assert(this.option.isSome, 'Empty `Unconstrained`'); // never triggered - return this.option.value; - } - - /** - * Modify the unconstrained value. - */ - set(value: T) { - this.option = { isSome: true, value }; - } - - /** - * Set the unconstrained value to the same as another `Unconstrained`. - */ - setTo(value: Unconstrained) { - this.option = value.option; - } - - /** - * Create an `Unconstrained` with the given `value`. - * - * Note: If `T` contains provable types, `Unconstrained.from` is an anti-pattern, - * because it stores witnesses in a space that's intended to be used outside the proof. - * Something like the following should be used instead: - * - * ```ts - * let xWrapped = Unconstrained.witness(() => Provable.toConstant(type, x)); - * ``` - */ - static from(value: T) { - return new Unconstrained(true, value); - } - - /** - * Create an `Unconstrained` from a witness computation. - */ - static witness(compute: () => T) { - return Provable.witness( - Unconstrained.provable, - () => new Unconstrained(true, compute()) - ); - } - - /** - * Update an `Unconstrained` by a witness computation. - */ - updateAsProver(compute: (value: T) => T) { - return Provable.asProver(() => { - let value = this.get(); - this.set(compute(value)); - }); - } - - static provable: Provable> & { - toInput: (x: Unconstrained) => { - fields?: Field[]; - packed?: [Field, number][]; - }; - } = { - sizeInFields: () => 0, - toFields: () => [], - toAuxiliary: (t?: any) => [t ?? new Unconstrained(false)], - fromFields: (_, [t]) => t, - check: () => {}, - toInput: () => ({}), - }; -} - let primitives = new Set([Field, Bool, Scalar, Group]); function isPrimitive(obj: any) { for (let P of primitives) { @@ -728,11 +371,3 @@ function circuitValueEquals(a: T, b: T): boolean { ([key, value]) => key in b && circuitValueEquals((b as any)[key], value) ); } - -function toConstant(type: FlexibleProvable, value: T): T; -function toConstant(type: Provable, value: T): T { - return type.fromFields( - type.toFields(value).map((x) => x.toConstant()), - type.toAuxiliary(value) - ); -} diff --git a/src/lib/circuit-value.unit-test.ts b/src/lib/provable-types/struct.unit-test.ts similarity index 90% rename from src/lib/circuit-value.unit-test.ts rename to src/lib/provable-types/struct.unit-test.ts index c5c4c7dce7..f3628b1bbb 100644 --- a/src/lib/circuit-value.unit-test.ts +++ b/src/lib/provable-types/struct.unit-test.ts @@ -1,13 +1,14 @@ -import { provable, Struct, Unconstrained } from './circuit-value.js'; -import { UInt32 } from './int.js'; -import { PrivateKey, PublicKey } from './signature.js'; +import { provable, Struct } from './struct.js'; +import { Unconstrained } from './unconstrained.js'; +import { UInt32 } from '../int.js'; +import { PrivateKey, PublicKey } from '../signature.js'; import { expect } from 'expect'; -import { method, SmartContract } from './zkapp.js'; -import { LocalBlockchain, setActiveInstance, transaction } from './mina.js'; -import { State, state } from './state.js'; -import { AccountUpdate } from './account-update.js'; -import { Provable } from './provable.js'; -import { Field } from './core.js'; +import { method, SmartContract } from '../zkapp.js'; +import { LocalBlockchain, setActiveInstance, transaction } from '../mina.js'; +import { State, state } from '../state.js'; +import { AccountUpdate } from '../account-update.js'; +import { Provable } from '../provable.js'; +import { Field } from '../core.js'; let type = provable({ nested: { a: Number, b: Boolean }, diff --git a/src/lib/provable-types/unconstrained.ts b/src/lib/provable-types/unconstrained.ts new file mode 100644 index 0000000000..70d9fd7fbb --- /dev/null +++ b/src/lib/provable-types/unconstrained.ts @@ -0,0 +1,125 @@ +import { Snarky } from '../../snarky.js'; +import { Field } from '../field.js'; +import { Provable } from '../provable.js'; +import { assert } from '../errors.js'; +import { inCheckedComputation } from '../provable-context.js'; + +export { Unconstrained }; + +/** + * Container which holds an unconstrained value. This can be used to pass values + * between the out-of-circuit blocks in provable code. + * + * Invariants: + * - An `Unconstrained`'s value can only be accessed in auxiliary contexts. + * - An `Unconstrained` can be empty when compiling, but never empty when running as the prover. + * (there is no way to create an empty `Unconstrained` in the prover) + * + * @example + * ```ts + * let x = Unconstrained.from(0n); + * + * class MyContract extends SmartContract { + * `@method` myMethod(x: Unconstrained) { + * + * Provable.witness(Field, () => { + * // we can access and modify `x` here + * let newValue = x.get() + otherField.toBigInt(); + * x.set(newValue); + * + * // ... + * }); + * + * // throws an error! + * x.get(); + * } + * ``` + */ +class Unconstrained { + private option: + | { isSome: true; value: T } + | { isSome: false; value: undefined }; + + private constructor(isSome: boolean, value?: T) { + this.option = { isSome, value: value as any }; + } + + /** + * Read an unconstrained value. + * + * Note: Can only be called outside provable code. + */ + get(): T { + if (inCheckedComputation() && !Snarky.run.inProverBlock()) + throw Error(`You cannot use Unconstrained.get() in provable code. + +The only place where you can read unconstrained values is in Provable.witness() +and Provable.asProver() blocks, which execute outside the proof. +`); + assert(this.option.isSome, 'Empty `Unconstrained`'); // never triggered + return this.option.value; + } + + /** + * Modify the unconstrained value. + */ + set(value: T) { + this.option = { isSome: true, value }; + } + + /** + * Set the unconstrained value to the same as another `Unconstrained`. + */ + setTo(value: Unconstrained) { + this.option = value.option; + } + + /** + * Create an `Unconstrained` with the given `value`. + * + * Note: If `T` contains provable types, `Unconstrained.from` is an anti-pattern, + * because it stores witnesses in a space that's intended to be used outside the proof. + * Something like the following should be used instead: + * + * ```ts + * let xWrapped = Unconstrained.witness(() => Provable.toConstant(type, x)); + * ``` + */ + static from(value: T) { + return new Unconstrained(true, value); + } + + /** + * Create an `Unconstrained` from a witness computation. + */ + static witness(compute: () => T) { + return Provable.witness( + Unconstrained.provable, + () => new Unconstrained(true, compute()) + ); + } + + /** + * Update an `Unconstrained` by a witness computation. + */ + updateAsProver(compute: (value: T) => T) { + return Provable.asProver(() => { + let value = this.get(); + this.set(compute(value)); + }); + } + + static provable: Provable> & { + toInput: (x: Unconstrained) => { + fields?: Field[]; + packed?: [Field, number][]; + }; + } = { + sizeInFields: () => 0, + toFields: () => [], + toAuxiliary: (t?: any) => [t ?? new Unconstrained(false)], + fromFields: (_, [t]) => t, + check: () => {}, + toInput: () => ({}), + }; +} diff --git a/src/lib/circuit-value.test.ts b/src/lib/provable.test.ts similarity index 99% rename from src/lib/circuit-value.test.ts rename to src/lib/provable.test.ts index 5ee8ceac2a..b19e77e670 100644 --- a/src/lib/circuit-value.test.ts +++ b/src/lib/provable.test.ts @@ -8,7 +8,7 @@ import { PublicKey, } from 'o1js'; -describe('circuit', () => { +describe('Provable', () => { it('Provable.if out of snark', () => { let x = Provable.if(Bool(false), Int64, Int64.from(-1), Int64.from(-2)); expect(x.toString()).toBe('-2'); diff --git a/src/lib/provable.ts b/src/lib/provable.ts index 57151d9483..5d83e4ec57 100644 --- a/src/lib/provable.ts +++ b/src/lib/provable.ts @@ -5,15 +5,18 @@ */ import { Bool } from './bool.js'; import { Field } from './field.js'; -import { Provable as Provable_, Snarky } from '../snarky.js'; -import type { FlexibleProvable, ProvableExtended } from './circuit-value.js'; +import type { Provable as Provable_ } from './provable-types/provable-intf.js'; +import type { + FlexibleProvable, + ProvableExtended, +} from './provable-types/struct.js'; import { Context } from './global-context.js'; import { HashInput, InferJson, InferProvable, InferredProvable, -} from '../bindings/lib/provable-snarky.js'; +} from './provable-types/provable-derivers.js'; import { inCheckedComputation, inProver, @@ -23,7 +26,7 @@ import { generateWitness, runAndCheckSync, } from './provable-context.js'; -import { existsAsync } from './provable-core/exists.js'; +import { exists, existsAsync } from './provable-core/exists.js'; // external API export { Provable }; @@ -36,11 +39,17 @@ export { getBlindingValue, }; -// TODO move type declaration here /** - * `Provable` is the general circuit type interface. It describes how a type `T` is made up of field elements and auxiliary (non-field element) data. + * `Provable` is the general interface for provable types in o1js. * - * 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`. + * `Provable` describes how a type `T` is made up of {@link Field} elements and "auxiliary" (non-provable) data. + * + * `Provable` is the required input type in several methods in o1js. + * One convenient way to create a `Provable` is using `Struct`. + * + * All built-in provable types in o1js ({@link Field}, {@link Bool}, etc.) are instances of `Provable` as well. + * + * Note: These methods are meant to be used by the library internally and are not directly when writing provable code. */ type Provable = Provable_; @@ -239,23 +248,11 @@ function witness = FlexibleProvable>( let id = snarkContext.enter({ ...ctx, inWitnessBlock: true }); try { - let [, ...fieldVars] = Snarky.run.exists(type.sizeInFields(), () => { + fields = exists(type.sizeInFields(), () => { proverValue = compute(); let fields = type.toFields(proverValue); - let fieldConstants = fields.map((x) => x.toConstant().value[1]); - - // TODO: enable this check - // currently it throws for Scalar.. which seems to be flexible about what length is returned by toFields - // if (fields.length !== type.sizeInFields()) { - // throw Error( - // `Invalid witness. Expected ${type.sizeInFields()} field elements, got ${ - // fields.length - // }.` - // ); - // } - return [0, ...fieldConstants]; + return fields.map((x) => x.toBigInt()); }); - fields = fieldVars.map((x) => new Field(x)); } finally { snarkContext.leave(id); } @@ -287,12 +284,11 @@ async function witnessAsync< // call into `existsAsync` to witness the raw field elements let id = snarkContext.enter({ ...ctx, inWitnessBlock: true }); try { - let fieldVars = await existsAsync(type.sizeInFields(), async () => { + fields = await existsAsync(type.sizeInFields(), async () => { proverValue = await compute(); let fields = type.toFields(proverValue); return fields.map((x) => x.toBigInt()); }); - fields = fieldVars.map((x) => new Field(x)); } finally { snarkContext.leave(id); } diff --git a/src/lib/provable.unit-test.ts b/src/lib/provable.unit-test.ts index 7484bc91d8..d1a6991dba 100644 --- a/src/lib/provable.unit-test.ts +++ b/src/lib/provable.unit-test.ts @@ -1,8 +1,8 @@ import { it } from 'node:test'; import { Provable } from './provable.js'; -import { exists } from './gadgets/common.js'; import { Field } from './field.js'; import { expect } from 'expect'; +import { exists } from './provable-core/exists.js'; await it('can witness large field array', async () => { let N = 100_000; diff --git a/src/lib/scalar.ts b/src/lib/scalar.ts index 055a7bbe0d..deea9a4163 100644 --- a/src/lib/scalar.ts +++ b/src/lib/scalar.ts @@ -1,6 +1,8 @@ -import { Snarky, Provable } from '../snarky.js'; -import { Scalar as Fq } from '../provable/curve-bigint.js'; -import { Field, FieldConst, FieldVar } from './field.js'; +import { Snarky } from '../snarky.js'; +import { Fq } from '../bindings/crypto/finite-field.js'; +import { Scalar as SignableFq } from '../mina-signer/src/curve-bigint.js'; +import { Field } from './field.js'; +import { FieldVar, FieldConst } from './provable-core/fieldvar.js'; import { MlArray } from './ml/base.js'; import { Bool } from './bool.js'; @@ -20,7 +22,7 @@ const ScalarConst = { }, }; -let scalarShift = Fq(1n + 2n ** 255n); +let scalarShift = Fq.mod(1n + 2n ** 255n); let oneHalf = Fq.inverse(2n)!; type ConstantScalar = Scalar & { constantValue: ScalarConst }; @@ -34,7 +36,7 @@ class Scalar { static ORDER = Fq.modulus; - private constructor(bits: MlArray, constantValue?: Fq) { + private constructor(bits: MlArray, constantValue?: bigint) { this.value = bits; constantValue ??= toConstantScalar(bits); if (constantValue !== undefined) { @@ -49,8 +51,8 @@ class Scalar { */ static from(x: Scalar | ScalarConst | bigint | number | string) { if (x instanceof Scalar) return x; - if (ScalarConst.is(x)) x = constToBigint(x); - let scalar = Fq(x); + let x_ = ScalarConst.is(x) ? constToBigint(x) : x; + let scalar = Fq.mod(BigInt(x_)); let bits = toBits(scalar); return new Scalar(bits, scalar); } @@ -321,7 +323,7 @@ class Scalar { * This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the Scalar. */ static fromJSON(x: string) { - return Scalar.from(Fq.fromJSON(x)); + return Scalar.from(SignableFq.fromJSON(x)); } } @@ -331,7 +333,7 @@ function assertConstant(x: Scalar, name: string) { return constantScalarToBigint(x, `Scalar.${name}`); } -function toConstantScalar([, ...bits]: MlArray): Fq | undefined { +function toConstantScalar([, ...bits]: MlArray): bigint | undefined { if (bits.length !== Fq.sizeInBits) throw Error( `Scalar: expected bits array of length ${Fq.sizeInBits}, got ${bits.length}` @@ -342,14 +344,14 @@ function toConstantScalar([, ...bits]: MlArray): Fq | undefined { if (!FieldVar.isConstant(bool)) return undefined; constantBits[i] = FieldConst.equal(bool[1], FieldConst[1]); } - let sShifted = Fq.fromBits(constantBits); + let sShifted = SignableFq.fromBits(constantBits); return shift(sShifted); } -function toBits(constantValue: Fq): MlArray { +function toBits(constantValue: bigint): MlArray { return [ 0, - ...Fq.toBits(unshift(constantValue)).map((b) => + ...SignableFq.toBits(unshift(constantValue)).map((b) => FieldVar.constant(BigInt(b)) ), ]; @@ -358,21 +360,21 @@ function toBits(constantValue: Fq): MlArray { /** * s -> 2s + 1 + 2^255 */ -function shift(s: Fq): Fq { +function shift(s: bigint) { return Fq.add(Fq.add(s, s), scalarShift); } /** * inverse of shift, 2s + 1 + 2^255 -> s */ -function unshift(s: Fq): Fq { +function unshift(s: bigint) { return Fq.mul(Fq.sub(s, scalarShift), oneHalf); } -function constToBigint(x: ScalarConst): Fq { +function constToBigint(x: ScalarConst) { return x[1]; } -function constFromBigint(x: Fq): ScalarConst { +function constFromBigint(x: bigint): ScalarConst { return [0, x]; } diff --git a/src/lib/signature.ts b/src/lib/signature.ts index e3b82ea413..e652ccd7bb 100644 --- a/src/lib/signature.ts +++ b/src/lib/signature.ts @@ -1,20 +1,19 @@ import { Field, Bool, Group, Scalar } from './core.js'; -import { prop, CircuitValue, AnyConstructor } from './circuit-value.js'; +import { AnyConstructor } from './provable-types/struct.js'; import { hashWithPrefix } from './hash.js'; +import { Fq } from '../bindings/crypto/finite-field.js'; import { deriveNonce, Signature as SignatureBigint, signaturePrefix, } from '../mina-signer/src/signature.js'; -import { Bool as BoolBigint } from '../provable/field-bigint.js'; import { - Scalar as ScalarBigint, PrivateKey as PrivateKeyBigint, PublicKey as PublicKeyBigint, -} from '../provable/curve-bigint.js'; -import { prefixes } from '../bindings/crypto/constants.js'; +} from '../mina-signer/src/curve-bigint.js'; import { constantScalarToBigint } from './scalar.js'; import { toConstantField } from './field.js'; +import { CircuitValue, prop } from './provable-types/circuit-value.js'; // external API export { PrivateKey, PublicKey, Signature }; @@ -81,7 +80,7 @@ class PrivateKey extends CircuitValue { * **Warning**: Private keys should be sampled from secure randomness with sufficient entropy. * Be careful that you don't use this method to create private keys that were sampled insecurely. */ - static fromBigInt(sk: PrivateKeyBigint) { + static fromBigInt(sk: bigint) { return new PrivateKey(Scalar.from(sk)); } @@ -219,7 +218,7 @@ class PublicKey extends CircuitValue { x = toConstantField(x, 'toBase58', 'pk', 'public key'); return PublicKeyBigint.toBase58({ x: x.toBigInt(), - isOdd: BoolBigint(isOdd.toBoolean()), + isOdd: isOdd.toBoolean() ? 1n : 0n, }); } @@ -333,5 +332,5 @@ function unshift(shiftedScalar: Scalar) { .mul(Scalar.fromBigInt(oneHalf)); } -let shift = ScalarBigint(1n + 2n ** 255n); -let oneHalf = ScalarBigint.inverse(2n)!; +let shift = Fq.mod(1n + 2n ** 255n); +let oneHalf = Fq.inverse(2n)!; diff --git a/src/lib/state.ts b/src/lib/state.ts index 7d265a3bf9..82dff11c25 100644 --- a/src/lib/state.ts +++ b/src/lib/state.ts @@ -1,5 +1,4 @@ -import { ProvablePure } from '../snarky.js'; -import { FlexibleProvablePure } from './circuit-value.js'; +import { FlexibleProvablePure } from './provable-types/struct.js'; import { AccountUpdate, TokenId } from './account-update.js'; import { PublicKey } from './signature.js'; import * as Mina from './mina.js'; @@ -8,6 +7,7 @@ import { SmartContract } from './zkapp.js'; import { Account } from './mina/account.js'; import { Provable } from './provable.js'; import { Field } from '../lib/core.js'; +import { ProvablePure } from './provable-types/provable-intf.js'; // external API export { State, state, declareState }; diff --git a/src/lib/testing/constraint-system.ts b/src/lib/testing/constraint-system.ts index 90d2839a4b..8f6e406a3d 100644 --- a/src/lib/testing/constraint-system.ts +++ b/src/lib/testing/constraint-system.ts @@ -6,7 +6,8 @@ */ import { Gate, GateType } from '../../snarky.js'; import { randomBytes } from '../../bindings/crypto/random.js'; -import { Field, FieldType, FieldVar } from '../field.js'; +import { Field } from '../field.js'; +import { FieldType, FieldVar } from '../provable-core/fieldvar.js'; import { Provable } from '../provable.js'; import { Tuple } from '../util/types.js'; import { Random } from './random.js'; diff --git a/src/lib/testing/equivalent.ts b/src/lib/testing/equivalent.ts index 8d1be369aa..a11db04eaf 100644 --- a/src/lib/testing/equivalent.ts +++ b/src/lib/testing/equivalent.ts @@ -6,7 +6,7 @@ import { Provable } from '../provable.js'; import { deepEqual } from 'node:assert/strict'; import { Bool, Field } from '../core.js'; import { AnyFunction, Tuple } from '../util/types.js'; -import { provable } from '../circuit-value.js'; +import { provable } from '../provable-types/struct.js'; import { assert } from '../gadgets/common.js'; export { diff --git a/src/lib/testing/random.ts b/src/lib/testing/random.ts index 20d509804d..1c3df8e506 100644 --- a/src/lib/testing/random.ts +++ b/src/lib/testing/random.ts @@ -29,13 +29,17 @@ import { PrimitiveTypeMap, primitiveTypeMap, } from '../../bindings/lib/generic.js'; -import { Scalar, PrivateKey, Group } from '../../provable/curve-bigint.js'; +import { + Scalar, + PrivateKey, + Group, +} from '../../mina-signer/src/curve-bigint.js'; import { Signature } from '../../mina-signer/src/signature.js'; import { randomBytes } from '../../bindings/crypto/random.js'; import { alphabet } from '../base58.js'; import { bytesToBigInt } from '../../bindings/crypto/bigint-helpers.js'; import { Memo } from '../../mina-signer/src/memo.js'; -import { Signable } from '../../bindings/lib/provable-bigint.js'; +import { Signable } from '../../mina-signer/src/derivers-bigint.js'; import { tokenSymbolLength } from '../../bindings/mina-transaction/derived-leaves.js'; import { stringLengthInBytes } from '../../bindings/lib/binable.js'; import { mocks } from '../../bindings/crypto/constants.js'; diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 78bb9a8a3d..eea39b9448 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -1,4 +1,5 @@ -import { Gate, Pickles, ProvablePure } from '../snarky.js'; +import 'reflect-metadata'; +import { Gate, Pickles } from '../snarky.js'; import { Field, Bool } from './core.js'; import { AccountUpdate, @@ -22,8 +23,7 @@ import { FlexibleProvablePure, InferProvable, provable, - toConstant, -} from './circuit-value.js'; +} from './provable-types/struct.js'; import { Provable, getBlindingValue, memoizationContext } from './provable.js'; import * as Encoding from '../bindings/lib/encoding.js'; import { @@ -71,6 +71,7 @@ import { import { deprecatedToken } from './mina/token/token-methods.js'; import type { TokenContract } from './mina/token/token-contract.js'; import { assertPromise } from './util/assert.js'; +import { ProvablePure } from './provable-types/provable-intf.js'; // external API export { SmartContract, method, DeployArgs, declareMethods, Account, Reducer }; @@ -406,7 +407,7 @@ function wrapMethod( returnType !== undefined, "Bug: returnType is undefined but the method result isn't." ); - result = toConstant(returnType, result); + result = Provable.toConstant(returnType, result); } // store inputs + result in callData diff --git a/src/mina-signer/mina-signer.ts b/src/mina-signer/mina-signer.ts index 7b74bbccd9..1b0da67db5 100644 --- a/src/mina-signer/mina-signer.ts +++ b/src/mina-signer/mina-signer.ts @@ -1,4 +1,4 @@ -import { PrivateKey, PublicKey } from '../provable/curve-bigint.js'; +import { PrivateKey, PublicKey } from './src/curve-bigint.js'; import * as Json from './src/types.js'; import type { SignedLegacy, Signed, NetworkId } from './src/types.js'; diff --git a/src/provable/curve-bigint.ts b/src/mina-signer/src/curve-bigint.ts similarity index 90% rename from src/provable/curve-bigint.ts rename to src/mina-signer/src/curve-bigint.ts index a6a1b151bb..c848eb468e 100644 --- a/src/provable/curve-bigint.ts +++ b/src/mina-signer/src/curve-bigint.ts @@ -1,18 +1,17 @@ -import { Fq, mod } from '../bindings/crypto/finite-field.js'; -import { GroupProjective, Pallas } from '../bindings/crypto/elliptic-curve.js'; -import { versionBytes } from '../bindings/crypto/constants.js'; +import { Fq, mod } from '../../bindings/crypto/finite-field.js'; +import { + GroupProjective, + Pallas, +} from '../../bindings/crypto/elliptic-curve.js'; +import { versionBytes } from '../../bindings/crypto/constants.js'; import { record, withCheck, withVersionNumber, -} from '../bindings/lib/binable.js'; -import { base58, withBase58 } from '../lib/base58.js'; +} from '../../bindings/lib/binable.js'; +import { base58, withBase58 } from '../../lib/base58.js'; import { Bool, checkRange, Field, pseudoClass } from './field-bigint.js'; -import { - BinableBigint, - ProvableBigint, - signable, -} from '../bindings/lib/provable-bigint.js'; +import { BinableBigint, SignableBigint, signable } from './derivers-bigint.js'; import { HashInputLegacy } from './poseidon-bigint.js'; export { Group, PublicKey, Scalar, PrivateKey, versionNumbers }; @@ -123,7 +122,7 @@ const Scalar = pseudoClass( return mod(BigInt(value), Fq.modulus); }, { - ...ProvableBigint(checkScalar), + ...SignableBigint(checkScalar), ...BinableBigint(Fq.sizeInBits, checkScalar), ...Fq, } diff --git a/src/mina-signer/src/derivers-bigint.ts b/src/mina-signer/src/derivers-bigint.ts new file mode 100644 index 0000000000..936c6f1246 --- /dev/null +++ b/src/mina-signer/src/derivers-bigint.ts @@ -0,0 +1,80 @@ +import { bigIntToBytes } from '../../bindings/crypto/bigint-helpers.js'; +import { createDerivers } from '../../bindings/lib/provable-generic.js'; +import { + GenericHashInput, + GenericProvableExtended, + GenericSignable, +} from '../../bindings/lib/generic.js'; +import { + BinableWithBits, + defineBinable, + withBits, +} from '../../bindings/lib/binable.js'; + +export { + signable, + ProvableExtended, + SignableBigint, + BinableBigint, + HashInput, + Signable, +}; + +type Field = bigint; + +let { signable } = createDerivers(); + +type Signable = GenericSignable; +type ProvableExtended = GenericProvableExtended; +type HashInput = GenericHashInput; + +function SignableBigint< + T extends bigint = bigint, + TJSON extends string = string +>(check: (x: bigint) => void): Signable { + return { + toInput(x) { + return { fields: [x], packed: [] }; + }, + toJSON(x) { + return x.toString() as TJSON; + }, + fromJSON(json) { + if (isNaN(json as any) || isNaN(parseFloat(json))) { + throw Error(`fromJSON: expected a numeric string, got "${json}"`); + } + let x = BigInt(json) as T; + check(x); + return x; + }, + empty() { + return 0n as T; + }, + }; +} + +function BinableBigint( + sizeInBits: number, + check: (x: bigint) => void +): BinableWithBits { + let sizeInBytes = Math.ceil(sizeInBits / 8); + return withBits( + defineBinable({ + toBytes(x) { + return bigIntToBytes(x, sizeInBytes); + }, + readBytes(bytes, start) { + let x = 0n; + let bitPosition = 0n; + let end = Math.min(start + sizeInBytes, bytes.length); + for (let i = start; i < end; i++) { + x += BigInt(bytes[i]) << bitPosition; + bitPosition += 8n; + } + check(x); + return [x as T, end]; + }, + }), + sizeInBits + ); +} diff --git a/src/provable/field-bigint.ts b/src/mina-signer/src/field-bigint.ts similarity index 87% rename from src/provable/field-bigint.ts rename to src/mina-signer/src/field-bigint.ts index 99fcc95674..137b370668 100644 --- a/src/provable/field-bigint.ts +++ b/src/mina-signer/src/field-bigint.ts @@ -1,12 +1,9 @@ -import { randomBytes } from '../bindings/crypto/random.js'; -import { Fp, mod } from '../bindings/crypto/finite-field.js'; -import { - BinableBigint, - HashInput, - ProvableBigint, -} from '../bindings/lib/provable-bigint.js'; +import { randomBytes } from '../../bindings/crypto/random.js'; +import { Fp, mod } from '../../bindings/crypto/finite-field.js'; +import { BinableBigint, HashInput, SignableBigint } from './derivers-bigint.js'; export { Field, Bool, UInt32, UInt64, Sign }; +export { BinableFp, SignableFp }; export { pseudoClass, sizeInBits, checkRange, checkField }; type Field = bigint; @@ -26,6 +23,9 @@ const checkField = checkRange(0n, Fp.modulus, 'Field'); const checkBool = checkAllowList(new Set([0n, 1n]), 'Bool'); const checkSign = checkAllowList(new Set([1n, minusOne]), 'Sign'); +const BinableFp = BinableBigint(Fp.sizeInBits, checkField); +const SignableFp = SignableBigint(checkField); + /** * The base field of the Pallas curve */ @@ -33,11 +33,7 @@ const Field = pseudoClass( function Field(value: bigint | number | string): Field { return mod(BigInt(value), Fp.modulus); }, - { - ...ProvableBigint(checkField), - ...BinableBigint(Fp.sizeInBits, checkField), - ...Fp, - } + { ...SignableFp, ...BinableFp, ...Fp } ); /** @@ -48,7 +44,7 @@ const Bool = pseudoClass( return BigInt(value) as Bool; }, { - ...ProvableBigint(checkBool), + ...SignableBigint(checkBool), ...BinableBigint(1, checkBool), toInput(x: Bool): HashInput { return { fields: [], packed: [[x, 1]] }; @@ -85,7 +81,7 @@ function Unsigned(bits: number) { return x; }, { - ...ProvableBigint(checkUnsigned), + ...SignableBigint(checkUnsigned), ...binable, toInput(x: bigint): HashInput { return { fields: [], packed: [[x, bits]] }; @@ -107,7 +103,7 @@ const Sign = pseudoClass( return mod(BigInt(value), Fp.modulus) as Sign; }, { - ...ProvableBigint(checkSign), + ...SignableBigint(checkSign), ...BinableBigint(1, checkSign), empty() { return 1n; diff --git a/src/mina-signer/src/memo.ts b/src/mina-signer/src/memo.ts index 04538c3965..087d5834f0 100644 --- a/src/mina-signer/src/memo.ts +++ b/src/mina-signer/src/memo.ts @@ -12,7 +12,7 @@ import { hashWithPrefix, packToFieldsLegacy, prefixes, -} from '../../provable/poseidon-bigint.js'; +} from './poseidon-bigint.js'; import { versionBytes } from '../../bindings/crypto/constants.js'; export { Memo }; diff --git a/src/mina-signer/src/nullifier.ts b/src/mina-signer/src/nullifier.ts index 07b5edf7d2..103f30fa8b 100644 --- a/src/mina-signer/src/nullifier.ts +++ b/src/mina-signer/src/nullifier.ts @@ -1,12 +1,7 @@ import { Fq } from '../../bindings/crypto/finite-field.js'; import { Poseidon } from '../../bindings/crypto/poseidon.js'; -import { - Group, - PublicKey, - Scalar, - PrivateKey, -} from '../../provable/curve-bigint.js'; -import { Field } from '../../provable/field-bigint.js'; +import { Group, PublicKey, Scalar, PrivateKey } from './curve-bigint.js'; +import { Field } from './field-bigint.js'; import { Nullifier } from './types.js'; export { createNullifier }; diff --git a/src/provable/poseidon-bigint.ts b/src/mina-signer/src/poseidon-bigint.ts similarity index 86% rename from src/provable/poseidon-bigint.ts rename to src/mina-signer/src/poseidon-bigint.ts index 906ab2a2d5..5ab0dbfa20 100644 --- a/src/provable/poseidon-bigint.ts +++ b/src/mina-signer/src/poseidon-bigint.ts @@ -1,9 +1,9 @@ import { Field, sizeInBits } from './field-bigint.js'; -import { Poseidon, PoseidonLegacy } from '../bindings/crypto/poseidon.js'; -import { prefixes } from '../bindings/crypto/constants.js'; -import { createHashInput } from '../bindings/lib/provable-generic.js'; -import { GenericHashInput } from '../bindings/lib/generic.js'; -import { createHashHelpers } from '../lib/hash-generic.js'; +import { Poseidon, PoseidonLegacy } from '../../bindings/crypto/poseidon.js'; +import { prefixes } from '../../bindings/crypto/constants.js'; +import { createHashInput } from '../../bindings/lib/provable-generic.js'; +import { GenericHashInput } from '../../bindings/lib/generic.js'; +import { createHashHelpers } from '../../lib/hash-generic.js'; export { Poseidon, diff --git a/src/mina-signer/src/random-transaction.ts b/src/mina-signer/src/random-transaction.ts index d86270790b..ac475348e5 100644 --- a/src/mina-signer/src/random-transaction.ts +++ b/src/mina-signer/src/random-transaction.ts @@ -5,7 +5,7 @@ import { PublicKey, ZkappCommand, } from '../../bindings/mina-transaction/gen/transaction-bigint.js'; -import { PrivateKey } from '../../provable/curve-bigint.js'; +import { PrivateKey } from './curve-bigint.js'; import { Signature } from './signature.js'; import { NetworkId } from './types.js'; diff --git a/src/mina-signer/src/rosetta.ts b/src/mina-signer/src/rosetta.ts index 835bec83a5..c95533c47b 100644 --- a/src/mina-signer/src/rosetta.ts +++ b/src/mina-signer/src/rosetta.ts @@ -1,6 +1,6 @@ import { Binable } from '../../bindings/lib/binable.js'; -import { PublicKey, Scalar } from '../../provable/curve-bigint.js'; -import { Field } from '../../provable/field-bigint.js'; +import { PublicKey, Scalar } from './curve-bigint.js'; +import { Field } from './field-bigint.js'; import { Memo } from './memo.js'; import { Signature } from './signature.js'; @@ -30,7 +30,9 @@ function fieldToHex( bytes[bytes.length - 1] |= Number(paddingBit) << 7; // map each byte to a 0-padded hex string of length 2 return bytes - .map((byte) => byte.toString(16).padStart(2, '0').split('').reverse().join('')) + .map((byte) => + byte.toString(16).padStart(2, '0').split('').reverse().join('') + ) .join(''); } diff --git a/src/mina-signer/src/sign-legacy.ts b/src/mina-signer/src/sign-legacy.ts index 5d93cc0a75..0deef8dd07 100644 --- a/src/mina-signer/src/sign-legacy.ts +++ b/src/mina-signer/src/sign-legacy.ts @@ -1,6 +1,6 @@ -import { UInt32, UInt64 } from '../../provable/field-bigint.js'; -import { PrivateKey, PublicKey } from '../../provable/curve-bigint.js'; -import { HashInputLegacy } from '../../provable/poseidon-bigint.js'; +import { UInt32, UInt64 } from './field-bigint.js'; +import { PrivateKey, PublicKey } from './curve-bigint.js'; +import { HashInputLegacy } from './poseidon-bigint.js'; import { Memo } from './memo.js'; import { SignatureJson, diff --git a/src/mina-signer/src/sign-legacy.unit-test.ts b/src/mina-signer/src/sign-legacy.unit-test.ts index 61a9de37d6..3654943682 100644 --- a/src/mina-signer/src/sign-legacy.unit-test.ts +++ b/src/mina-signer/src/sign-legacy.unit-test.ts @@ -16,8 +16,8 @@ import { } from './sign-legacy.js'; import { Signature, SignatureJson } from './signature.js'; import { expect } from 'expect'; -import { PublicKey, Scalar } from '../../provable/curve-bigint.js'; -import { Field } from '../../provable/field-bigint.js'; +import { PublicKey, Scalar } from './curve-bigint.js'; +import { Field } from './field-bigint.js'; import { Random, test } from '../../lib/testing/property.js'; import { RandomTransaction } from './random-transaction.js'; import { NetworkId } from './types.js'; diff --git a/src/mina-signer/src/sign-zkapp-command.ts b/src/mina-signer/src/sign-zkapp-command.ts index c70f8556ff..51b296be95 100644 --- a/src/mina-signer/src/sign-zkapp-command.ts +++ b/src/mina-signer/src/sign-zkapp-command.ts @@ -1,15 +1,11 @@ -import { Bool, Field, Sign, UInt32 } from '../../provable/field-bigint.js'; -import { PrivateKey, PublicKey } from '../../provable/curve-bigint.js'; +import { Bool, Field, Sign, UInt32 } from './field-bigint.js'; +import { PrivateKey, PublicKey } from './curve-bigint.js'; import { Json, AccountUpdate, ZkappCommand, } from '../../bindings/mina-transaction/gen/transaction-bigint.js'; -import { - hashWithPrefix, - packToFields, - prefixes, -} from '../../provable/poseidon-bigint.js'; +import { hashWithPrefix, packToFields, prefixes } from './poseidon-bigint.js'; import { Memo } from './memo.js'; import { Signature, 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 a6db9a9327..9d4750242a 100644 --- a/src/mina-signer/src/sign-zkapp-command.unit-test.ts +++ b/src/mina-signer/src/sign-zkapp-command.unit-test.ts @@ -11,7 +11,7 @@ import { AccountUpdate as AccountUpdateSnarky, ZkappCommand as ZkappCommandSnarky, } from '../../lib/account-update.js'; -import { FieldConst } from '../../lib/field.js'; +import { FieldConst } from '../../lib/provable-core/fieldvar.js'; import { packToFields as packToFieldsSnarky } from '../../lib/hash.js'; import { Network, setActiveInstance } from '../../lib/mina.js'; import { Ml, MlHashInput } from '../../lib/ml/conversion.js'; @@ -20,12 +20,8 @@ import { PublicKey as PublicKeySnarky, } from '../../lib/signature.js'; import { Random, test, withHardCoded } from '../../lib/testing/property.js'; -import { PrivateKey, PublicKey } from '../../provable/curve-bigint.js'; -import { - hashWithPrefix, - packToFields, - prefixes, -} from '../../provable/poseidon-bigint.js'; +import { PrivateKey, PublicKey } from './curve-bigint.js'; +import { hashWithPrefix, packToFields, prefixes } from './poseidon-bigint.js'; import { Pickles, Test } from '../../snarky.js'; import { Memo } from './memo.js'; import { RandomTransaction } from './random-transaction.js'; diff --git a/src/mina-signer/src/signature.ts b/src/mina-signer/src/signature.ts index a80ef3e80e..c8e42bd22d 100644 --- a/src/mina-signer/src/signature.ts +++ b/src/mina-signer/src/signature.ts @@ -1,12 +1,12 @@ import { blake2b } from 'blakejs'; -import { Field } from '../../provable/field-bigint.js'; +import { Field } from './field-bigint.js'; import { Group, Scalar, PrivateKey, versionNumbers, PublicKey, -} from '../../provable/curve-bigint.js'; +} from './curve-bigint.js'; import { HashInput, hashWithPrefix, @@ -17,7 +17,7 @@ import { packToFieldsLegacy, inputToBitsLegacy, HashLegacy, -} from '../../provable/poseidon-bigint.js'; +} from './poseidon-bigint.js'; import { bitsToBytes, bytesToBits, diff --git a/src/mina-signer/src/signature.unit-test.ts b/src/mina-signer/src/signature.unit-test.ts index ae8384cd24..786e64dfd1 100644 --- a/src/mina-signer/src/signature.unit-test.ts +++ b/src/mina-signer/src/signature.unit-test.ts @@ -7,16 +7,15 @@ import { verify, verifyFieldElement, } from './signature.js'; -import { Ledger, Test } from '../../snarky.js'; -import { Field as FieldSnarky } from '../../lib/core.js'; -import { Field } from '../../provable/field-bigint.js'; -import { PrivateKey, PublicKey } from '../../provable/curve-bigint.js'; +import { Test } from '../../snarky.js'; +import { Field } from './field-bigint.js'; +import { PrivateKey, PublicKey } from './curve-bigint.js'; import { PrivateKey as PrivateKeySnarky } from '../../lib/signature.js'; import { p } from '../../bindings/crypto/finite-field.js'; import { AccountUpdate } from '../../bindings/mina-transaction/gen/transaction-bigint.js'; -import { HashInput } from '../../bindings/lib/provable-bigint.js'; +import { HashInput } from './derivers-bigint.js'; import { Ml } from '../../lib/ml/conversion.js'; -import { FieldConst } from '../../lib/field.js'; +import { FieldConst } from '../../lib/provable-core/fieldvar.js'; import { NetworkId } from './types.js'; // check consistency with OCaml, where we expose the function to sign 1 field element with "testnet" diff --git a/src/mina-signer/src/transaction-hash.ts b/src/mina-signer/src/transaction-hash.ts index 7a8e56ef6b..495b36b7bd 100644 --- a/src/mina-signer/src/transaction-hash.ts +++ b/src/mina-signer/src/transaction-hash.ts @@ -1,4 +1,4 @@ -import { Bool, Field, UInt32, UInt64 } from '../../provable/field-bigint.js'; +import { Bool, Field, UInt32, UInt64 } from './field-bigint.js'; import { Binable, BinableString, @@ -21,7 +21,7 @@ import { delegationFromJson, paymentFromJson, } from './sign-legacy.js'; -import { PublicKey, Scalar } from '../../provable/curve-bigint.js'; +import { PublicKey, Scalar } from './curve-bigint.js'; import { Signature, SignatureJson } from './signature.js'; import { blake2b } from 'blakejs'; import { base58, withBase58 } from '../../lib/base58.js'; @@ -82,7 +82,10 @@ function userCommandToEnum({ common, body }: UserCommand): UserCommandEnum { let { tag: type, ...value } = body; switch (type) { case 'Payment': - return { common, body: { type, value: { receiver: body.receiver, amount: body.amount } } }; + return { + common, + body: { type, value: { receiver: body.receiver, amount: body.amount } }, + }; case 'StakeDelegation': let { receiver: newDelegate } = value; return { @@ -120,10 +123,9 @@ const Payment = record( }, ['receiver', 'amount'] ); -const Delegation = record( - { newDelegate: BinablePublicKey }, - ['newDelegate'] -); +const Delegation = record({ newDelegate: BinablePublicKey }, [ + 'newDelegate', +]); type DelegationEnum = { type: 'SetDelegate'; value: Delegation }; const DelegationEnum = enumWithArgument<[DelegationEnum]>([ { type: 'SetDelegate', value: Delegation }, @@ -254,7 +256,7 @@ const CommonV1 = with1( ) ) ); -type PaymentV1 = Payment & { source: PublicKey, tokenId: UInt64 }; +type PaymentV1 = Payment & { source: PublicKey; tokenId: UInt64 }; const PaymentV1 = with1( with1( record( diff --git a/src/mina-signer/src/transaction-hash.unit-test.ts b/src/mina-signer/src/transaction-hash.unit-test.ts index 955f451cd2..eec695f646 100644 --- a/src/mina-signer/src/transaction-hash.unit-test.ts +++ b/src/mina-signer/src/transaction-hash.unit-test.ts @@ -20,7 +20,7 @@ import { delegationFromJson, } from './sign-legacy.js'; import { Signature, SignatureJson } from './signature.js'; -import { PublicKey } from '../../provable/curve-bigint.js'; +import { PublicKey } from './curve-bigint.js'; import { Memo } from './memo.js'; import { expect } from 'expect'; import { versionBytes } from '../../bindings/crypto/constants.js'; @@ -180,7 +180,12 @@ function paymentToOcamlV1({ common: commonToOcamlV1(common), body: [ 'Payment', - { source_pk: common.feePayer, receiver_pk: receiver, amount, token_id: '1' }, + { + source_pk: common.feePayer, + receiver_pk: receiver, + amount, + token_id: '1', + }, ], }, signer: common.feePayer, @@ -220,7 +225,10 @@ function delegationToOcamlV1({ common: commonToOcamlV1(common), body: [ 'Stake_delegation', - ['Set_delegate', { delegator: common.feePayer, new_delegate: newDelegate }], + [ + 'Set_delegate', + { delegator: common.feePayer, new_delegate: newDelegate }, + ], ], }, signer: common.feePayer, diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 660b3543f0..27a2663e88 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -1,5 +1,10 @@ import type { Account as JsonAccount } from './bindings/mina-transaction/gen/transaction-json.js'; -import type { Field, FieldConst, FieldVar, VarFieldVar } from './lib/field.js'; +import type { Field } from './lib/field.js'; +import type { + FieldVar, + FieldConst, + VarFieldVar, +} from './lib/provable-core/fieldvar.ts'; import type { BoolVar, Bool } from './lib/bool.js'; import type { ScalarConst } from './lib/scalar.js'; import type { @@ -29,7 +34,7 @@ import type { KimchiGateType } from './lib/gates.ts'; import type { MlConstraintSystem } from './lib/provable-context.ts'; import type { FieldVector } from './bindings/crypto/bindings/vector.ts'; -export { ProvablePure, Provable, Ledger, Pickles, Gate, GateType, wasm }; +export { Ledger, Pickles, Gate, GateType, wasm }; // internal export { @@ -46,123 +51,6 @@ export { type WasmModule = typeof wasm; -/** - * `Provable` is the general circuit type interface in o1js. `Provable` interface describes how a type `T` is made up of {@link Field} elements and "auxiliary" (non-provable) data. - * - * `Provable` is the required input type in a few places in o1js. One convenient way to create a `Provable` is using `Struct`. - * - * 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 { - /** - * 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[]; - - /** - * 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`. - * - * @param value - the element of type `T` to generate the auxiliary data array from, optional. If not provided, a default value for auxiliary data is returned. - * - * @return An array of any type describing how this `T` element is made up of "auxiliary" (non-provable) data. - */ - toAuxiliary: (value?: T) => any[]; - - /** - * A function that returns an element of type `T` from the given provable and "auxiliary" data. - * - * **Important**: For any element of type `T`, this function is the reverse operation of calling {@link toFields} and {@link toAuxilary} methods on an element of type `T`. - * - * @param fields - an array of {@link Field} elements describing the provable data of the new `T` element. - * @param aux - an array of any type describing the "auxiliary" data of the new `T` element, optional. - * - * @return An element of type `T` generated from the given provable and "auxiliary" data. - */ - fromFields: (fields: Field[], aux: any[]) => 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, instead it creates any number of assertions to prove that `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; -} - -/** - * `ProvablePure` is a special kind of {@link Provable} interface, where the "auxiliary" (non-provable) data is empty. This means the type consists only of field elements, in that sense it is "pure". - * Any element on the interface `ProvablePure` is also an element of the interface `Provable` where the "auxiliary" data is empty. - * - * Examples where `ProvablePure` is required are types of on-chain state, events and actions. - * - * 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[]; - - /** - * 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. - * - * @param value - the element of type `T` to generate the auxiliary data array from, optional. If not provided, a default value for auxiliary data is returned. - * - * @return An empty array, as any element of the interface `ProvablePure` includes no "auxiliary" data by definition. - */ - toAuxiliary: (value?: T) => any[]; - - /** - * A function that returns an element of type `T` from the given provable data. - * - * **Important**: For any element of type `T`, this function is the reverse operation of calling {@link toFields} method on an element of type `T`. - * - * @param fields - an array of {@link Field} elements describing the provable data of the new `T` element. - * - * @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; declare namespace Snarky { @@ -182,20 +70,6 @@ declare const Snarky: { * APIs that have to do with running provable code */ run: { - /** - * witness `sizeInFields` field element variables - * - * Note: this is called "exists" because in a proof, you use it like this: - * > "I prove that there exists x, such that (some statement)" - */ - exists( - sizeInFields: number, - compute: () => MlArray - ): MlArray; - /** - * witness a single field element variable - */ - existsOne(compute: () => FieldConst): VarFieldVar; /** * Checks whether Snarky runs in "prover mode", that is, with witnesses */