diff --git a/CHANGELOG.md b/CHANGELOG.md index 57fae6892b..f94b75b336 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,24 +20,38 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Breaking changes - Rewrite of `Provable.if()` causes breaking changes to all deployed contracts https://github.com/o1-labs/snarkyjs/pull/889 +- Remove all deprecated methods and properties on `Field` https://github.com/o1-labs/snarkyjs/pull/902 +- The `Field(x)` constructor and other Field methods no longer accept a `boolean` as input. Instead, you can now pass in a `bigint` to all Field methods. https://github.com/o1-labs/snarkyjs/pull/902 + +### Added + +- Add `field.assertNotEquals()` to assert that a field element does not equal some value https://github.com/o1-labs/snarkyjs/pull/902 + - More efficient than `field.equals(x).assertFalse()` ### Changed - **Make stack traces more readable** https://github.com/o1-labs/snarkyjs/pull/890 - Stack traces thrown from SnarkyJS are cleaned up by filtering out unnecessary lines and other noisy details - Remove optional `zkappKey` argument in `smartContract.init()`, and instead assert that `provedState` is false when `init()` is called https://github.com/o1-labs/snarkyjs/pull/908 +- Improve assertion error messages on `Field` methods https://github.com/o1-labs/snarkyjs/issues/743 https://github.com/o1-labs/snarkyjs/pull/902 +- Publicly expose the internal details of the `Field` type https://github.com/o1-labs/snarkyjs/pull/902 ### Deprecated - Utility methods on `Circuit` are deprecated in favor of the same methods on `Provable` https://github.com/o1-labs/snarkyjs/pull/889 - `Circuit.if()`, `Circuit.witness()`, `Circuit.log()` and others replaced by `Provable.if()`, `Provable.witness()`, `Provable.log()` - Under the hood, some of these methods were rewritten in TypeScript +- Deprecate `field.isZero()` https://github.com/o1-labs/snarkyjs/pull/902 ### Fixed -- Fix running SnarkyJS in Node.js on Windows https://github.com/o1-labs/snarkyjs-bindings/pull/19 (@wizicer)[https://github.com/wizicer] +- Fix running SnarkyJS in Node.js on Windows https://github.com/o1-labs/snarkyjs-bindings/pull/19 [@wizicer](https://github.com/wizicer) - Fix error reporting from GraphQL requests https://github.com/o1-labs/snarkyjs/pull/919 - Resolved an `Out of Memory error` experienced on iOS devices (iPhones and iPads) during the initialization of the WASM memory https://github.com/o1-labs/snarkyjs-bindings/pull/26 +- Fix `field.greaterThan()` and other comparison methods outside provable code https://github.com/o1-labs/snarkyjs/issues/858 https://github.com/o1-labs/snarkyjs/pull/902 +- Fix `field.assertBool()` https://github.com/o1-labs/snarkyjs/issues/469 https://github.com/o1-labs/snarkyjs/pull/902 +- Fix `Field(bigint)` where `bigint` is larger than the field modulus https://github.com/o1-labs/snarkyjs/issues/432 https://github.com/o1-labs/snarkyjs/pull/902 + - The new behaviour is to use the modular residual of the input ## [0.10.1](https://github.com/o1-labs/snarkyjs/compare/bcc666f2...a632313a) diff --git a/src/bindings b/src/bindings index efebceaa35..371efb59a3 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit efebceaa35e693405be53dcc32a3aed437b7ff71 +Subproject commit 371efb59a36c97fe72955c3f9d12dbe3d02e5e22 diff --git a/src/examples/zkapps/hello_world/run.ts b/src/examples/zkapps/hello_world/run.ts index 98387d633c..9be1b6fc5d 100644 --- a/src/examples/zkapps/hello_world/run.ts +++ b/src/examples/zkapps/hello_world/run.ts @@ -93,7 +93,7 @@ try { await txn.prove(); await txn.sign([feePayer1.privateKey]).send(); } catch (err: any) { - handleError(err, 'assert_equal'); + handleError(err, 'assertEquals'); } if (!correctlyFails) { @@ -120,7 +120,7 @@ try { await txn.prove(); await txn.sign([feePayer1.privateKey]).send(); } catch (err: any) { - handleError(err, 'assert_equal'); + handleError(err, 'assertEquals'); } if (!correctlyFails) { @@ -176,7 +176,7 @@ try { await txn4.prove(); await txn4.sign([feePayer4.privateKey]).send(); } catch (err: any) { - handleError(err, 'assert_equal'); + handleError(err, 'assertEquals'); } if (!correctlyFails) { diff --git a/src/examples/zkapps/voting/test.ts b/src/examples/zkapps/voting/test.ts index 33b5f35e84..f6f9959011 100644 --- a/src/examples/zkapps/voting/test.ts +++ b/src/examples/zkapps/voting/test.ts @@ -300,10 +300,10 @@ export async function testSet( } } - if (sequenceOverflowSet.voterContract.reducer.getActions({}).length < 3) { + if (sequenceOverflowSet.voterContract.reducer.getActions().length < 3) { throw Error( `Did not emit expected actions! Only emitted ${ - sequenceOverflowSet.voterContract.reducer.getActions({}).length + sequenceOverflowSet.voterContract.reducer.getActions().length }` ); } @@ -387,7 +387,7 @@ export async function testSet( feePayer ); - if (voterContract.reducer.getActions({}).length !== 1) { + if (voterContract.reducer.getActions().length !== 1) { throw Error( 'Should have emitted 1 event after registering only one valid voter' ); @@ -468,10 +468,10 @@ export async function testSet( voting.voterRegistration(newVoter1); }, feePayer, - 'assert_equal' + 'Member already exists!' ); - if (voterContract.reducer.getActions({}).length !== 1) { + if (voterContract.reducer.getActions().length !== 1) { throw Error( 'Should have emitted 1 event after registering only one valid voter' ); @@ -540,8 +540,8 @@ export async function testSet( feePayer ); - let numberOfEvents = candidateContract.reducer.getActions({}).length; - if (candidateContract.reducer.getActions({}).length !== 2) { + let numberOfEvents = candidateContract.reducer.getActions().length; + if (candidateContract.reducer.getActions().length !== 2) { throw Error( `Should have emitted 2 event after registering 2 candidates. ${numberOfEvents} emitted` ); @@ -636,10 +636,8 @@ export async function testSet( ); Local.setGlobalSlot(params.electionPreconditions.startElection.add(1)); - let previousEventsVoter = voterContract.reducer.getActions({}).length; - let previousEventsCandidate = candidateContract.reducer.getActions( - {} - ).length; + let previousEventsVoter = voterContract.reducer.getActions().length; + let previousEventsCandidate = candidateContract.reducer.getActions().length; let lateCandidate = Member.from( PrivateKey.random().toPublicKey(), @@ -654,7 +652,7 @@ export async function testSet( voting.candidateRegistration(lateCandidate); }, feePayer, - 'assert_equal' + 'Outside of election period!' ); console.log( @@ -674,15 +672,14 @@ export async function testSet( voting.voterRegistration(lateVoter); }, feePayer, - 'assert_equal' + 'Outside of election period!' ); - if (previousEventsVoter !== voterContract.reducer.getActions({}).length) { + if (previousEventsVoter !== voterContract.reducer.getActions().length) { throw Error('events emitted but should not have been'); } if ( - previousEventsCandidate !== - candidateContract.reducer.getActions({}).length + previousEventsCandidate !== candidateContract.reducer.getActions().length ) { throw Error('events emitted but should not have been'); } @@ -784,7 +781,7 @@ export async function testSet( vote(0n, votesStore, candidatesStore); - numberOfEvents = voting.reducer.getActions({}).length; + numberOfEvents = voting.reducer.getActions().length; if (numberOfEvents !== 1) { throw Error('Should have emitted 1 event after voting for a candidate'); } @@ -825,7 +822,7 @@ export async function testSet( voting.vote(fakeCandidate, votersStore.get(0n)!); }, feePayer, - 'assert_equal' + 'Member is not a candidate!' ); console.log('unregistered voter attempting to vote'); @@ -842,7 +839,7 @@ export async function testSet( voting.vote(fakeVoter, votersStore.get(0n)!); }, feePayer, - 'assert_equal' + 'Member is not a candidate!' ); console.log('attempting to vote for voter...'); @@ -854,7 +851,7 @@ export async function testSet( voting.vote(voter, votersStore.get(0n)!); }, feePayer, - 'assert_equal' + 'Member is not a candidate!' ); /* @@ -934,7 +931,7 @@ export async function testSet( voting.voterRegistration(voter); }, feePayer, - 'assert_equal' + 'Outside of election period!' ); console.log('attempting to register candidate after election has ended'); @@ -951,7 +948,7 @@ export async function testSet( voting.candidateRegistration(candidate); }, feePayer, - 'assert_equal' + 'Outside of election period!' ); } diff --git a/src/examples/zkapps/voting/voting.ts b/src/examples/zkapps/voting/voting.ts index ccd6cc8864..2cef7197c3 100644 --- a/src/examples/zkapps/voting/voting.ts +++ b/src/examples/zkapps/voting/voting.ts @@ -154,7 +154,7 @@ export class Voting_ extends SmartContract { // the check happens here because we want to see if the other contract returns a value // if exists is true, that means the member already exists within the accumulated state // if its false, its a new entry - exists.assertEquals(false); + exists.assertFalse('Member already exists!'); } /** @@ -175,7 +175,7 @@ export class Voting_ extends SmartContract { electionPreconditions.enforce, currentSlot.lessThanOrEqual(electionPreconditions.startElection), Bool(true) - ).assertTrue(); + ).assertTrue('Outside of election period!'); // can only register candidates if their balance is gte the minimum amount required // and lte the maximum amount diff --git a/src/lib/account_update.test.ts b/src/lib/account_update.test.ts index 60eaef089a..a9aeb29fe3 100644 --- a/src/lib/account_update.test.ts +++ b/src/lib/account_update.test.ts @@ -1,7 +1,6 @@ import { isReady, Ledger, - Circuit, AccountUpdate, PrivateKey, shutdown, @@ -56,7 +55,6 @@ describe('AccountUpdate', () => { // TODO remove restriction "This function can't be run outside of a checked computation." Provable.runAndCheck(() => { let hash = accountUpdate.hash(); - expect(isField(hash)).toBeTruthy(); // if we clone the accountUpdate, hash should be the same let accountUpdate2 = AccountUpdate.clone(accountUpdate); @@ -101,9 +99,3 @@ describe('AccountUpdate', () => { expect(TokenId.fromBase58(defaultTokenId).toString()).toEqual('1'); }); }); - -// to check that we got something that looks like a Field -// note: `instanceof Field` doesn't work -function isField(x: any) { - return x?.constructor === Field(1).constructor; -} diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 5f0f325494..0d6a808b59 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -14,13 +14,7 @@ import { UInt64, UInt32, Int64, Sign } from './int.js'; import * as Mina from './mina.js'; import { SmartContract } from './zkapp.js'; import * as Precondition from './precondition.js'; -import { - dummyBase64Proof, - Empty, - inCheckedComputation, - Proof, - Prover, -} from './proof_system.js'; +import { dummyBase64Proof, Empty, Proof, Prover } from './proof_system.js'; import { Memo } from '../mina-signer/src/memo.js'; import { Events, @@ -1084,7 +1078,7 @@ class AccountUpdate implements Types.AccountUpdate { // consistency between JS & OCaml hashing on *every single account update // proof* we create. It will give us 100% confidence that the two // implementations are equivalent, and catch regressions quickly - if (inCheckedComputation()) { + if (Provable.inCheckedComputation()) { let input = Types.AccountUpdate.toInput(this); return hashWithPrefix(prefixes.body, packToFields(input)); } else { @@ -1578,7 +1572,7 @@ const CallForest = { return Provable.witness(Field, () => CallForest.hashChildrenBase(update)); } let calls = CallForest.hashChildrenBase(update); - if (callsType.type === 'Equals' && inCheckedComputation()) { + if (callsType.type === 'Equals' && Provable.inCheckedComputation()) { calls.assertEquals(callsType.value); } return calls; diff --git a/src/lib/circuit.ts b/src/lib/circuit.ts index 2d9528739c..0c9f864cfa 100644 --- a/src/lib/circuit.ts +++ b/src/lib/circuit.ts @@ -1,8 +1,8 @@ -import { snarkContext } from './proof_system.js'; import { ProvablePure, Snarky } from '../snarky.js'; import { Field } from './core.js'; import { withThreadPool } from '../bindings/js/wrapper.js'; -import { Provable, gatesFromJson } from './provable.js'; +import { Provable } from './provable.js'; +import { snarkContext, gatesFromJson } from './provable-context.js'; // external API export { public_, circuitMain, Circuit, Keypair, Proof, VerificationKey }; diff --git a/src/lib/circuit_value.unit-test.ts b/src/lib/circuit_value.unit-test.ts index 3e68f65c1d..fc3cce50b8 100644 --- a/src/lib/circuit_value.unit-test.ts +++ b/src/lib/circuit_value.unit-test.ts @@ -1,4 +1,4 @@ -import { Field, isReady, shutdown } from '../snarky.js'; +import { isReady, shutdown } from '../snarky.js'; import { provable, Struct } from './circuit_value.js'; import { UInt32 } from './int.js'; import { PrivateKey, PublicKey } from './signature.js'; @@ -8,6 +8,7 @@ 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'; await isReady; diff --git a/src/lib/core.ts b/src/lib/core.ts index d9f79e9e40..aae0a52074 100644 --- a/src/lib/core.ts +++ b/src/lib/core.ts @@ -1,44 +1,69 @@ import { bytesToBigInt } from '../bindings/crypto/bigint-helpers.js'; import { defineBinable } from '../bindings/lib/binable.js'; import { sizeInBits } from '../provable/field-bigint.js'; -import { Bool, Field, Scalar, Group } from '../snarky.js'; +import { Bool, Scalar, Group } from '../snarky.js'; +import { Field as InternalField } from './field.js'; import { Scalar as ScalarBigint } from '../provable/curve-bigint.js'; import { mod } from '../bindings/crypto/finite_field.js'; export { Field, Bool, Scalar, Group }; -type InternalConstantField = { value: [0, Uint8Array] }; +/** + * A {@link Field} is an element of a prime order [finite field](https://en.wikipedia.org/wiki/Finite_field). + * Every other provable type is built using the {@link Field} type. + * + * The field is the [pasta base field](https://electriccoin.co/blog/the-pasta-curves-for-halo-2-and-beyond/) of order 2^254 + 0x224698fc094cf91b992d30ed00000001 ({@link Field.ORDER}). + * + * You can create a new Field from everything "field-like" (`bigint`, integer `number`, decimal `string`, `Field`). + * @example + * ``` + * Field(10n); // Field contruction from a big integer + * Field(100); // Field construction from a number + * Field("1"); // Field construction from a decimal string + * ``` + * + * **Beware**: Fields _cannot_ be constructed from fractional numbers or alphanumeric strings: + * ```ts + * Field(3.141); // ERROR: Cannot convert a float to a field element + * Field("abc"); // ERROR: Invalid argument "abc" + * ``` + * + * Creating a Field from a negative number can result in unexpected behavior if you are not familiar with [modular arithmetic](https://en.wikipedia.org/wiki/Modular_arithmetic). + * @example + * ``` + * const x = Field(-1); // Valid Field construction from negative number + * const y = Field(Field.ORDER - 1n); // equivalent to `x` + * ``` + * + * **Important**: All the functions defined on a Field (arithmetic, logic, etc.) take their arguments as "field-like". A Field itself is also defined as a "field-like" element. + * + * @param value - the value to convert to a {@link Field} + * + * @return A {@link Field} with the value converted from the argument + */ +const Field = toFunctionConstructor(InternalField); +type Field = InternalField; -Field.toAuxiliary = () => []; -Bool.toAuxiliary = () => []; -Scalar.toAuxiliary = () => []; -Group.toAuxiliary = () => []; +function toFunctionConstructor any>( + Class: Class +): Class & ((...args: InferArgs) => InferReturn) { + function Constructor(...args: any) { + return new Class(...args); + } + Object.defineProperties(Constructor, Object.getOwnPropertyDescriptors(Class)); + return Constructor as any; +} -Field.toInput = function (x) { - return { fields: [x] }; -}; +type InferArgs = T extends new (...args: infer Args) => any ? Args : never; +type InferReturn = T extends new (...args: any) => infer Return + ? Return + : never; -// binable -const FieldBinable = defineBinable({ - toBytes(t: Field) { - return [...(t.toConstant() as any as InternalConstantField).value[1]]; - }, - readBytes(bytes, offset) { - let uint8array = new Uint8Array(32); - uint8array.set(bytes.slice(offset, offset + 32)); - return [ - Object.assign(Object.create(Field(1).constructor.prototype), { - value: [0, uint8array], - }) as Field, - offset + 32, - ]; - }, -}); +// patching ocaml classes -Field.toBytes = FieldBinable.toBytes; -Field.fromBytes = FieldBinable.fromBytes; -Field.readBytes = FieldBinable.readBytes; -Field.sizeInBytes = () => 32; +Bool.toAuxiliary = () => []; +Scalar.toAuxiliary = () => []; +Group.toAuxiliary = () => []; Bool.toInput = function (x) { return { packed: [[x.toField(), 1] as [Field, number]] }; diff --git a/src/lib/encryption.ts b/src/lib/encryption.ts index 837232ec63..f32798d3af 100644 --- a/src/lib/encryption.ts +++ b/src/lib/encryption.ts @@ -1,4 +1,5 @@ -import { Group, Field, Scalar } from '../snarky.js'; +import { Group, Scalar } from '../snarky.js'; +import { Field } from './core.js'; import { Poseidon } from './hash.js'; import { Provable } from './provable.js'; import { PrivateKey, PublicKey } from './signature.js'; diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 5d35a951d6..9ec5279104 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -1,5 +1,5 @@ import 'isomorphic-fetch'; -import { Field } from '../snarky.js'; +import { Field } from './core.js'; import { UInt32, UInt64 } from './int.js'; import { Actions, TokenId } from './account_update.js'; import { PublicKey } from './signature.js'; diff --git a/src/lib/field.test.ts b/src/lib/field.test.ts deleted file mode 100644 index f38b425dd7..0000000000 --- a/src/lib/field.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { shutdown, isReady, Field } from 'snarkyjs'; - -describe('Field constructor', () => { - beforeAll(() => isReady); - afterAll(() => setTimeout(shutdown, 0)); - - // Field(number), Field.fromNumber - - it('handles small numbers', () => { - expect(Field(5).toString()).toEqual('5'); - expect(Field(1313).toString()).toEqual('1313'); - }); - - it('handles large numbers 2^31 <= x < 2^53', () => { - expect(Field(2 ** 31).toString()).toEqual('2147483648'); - expect(Field(Number.MAX_SAFE_INTEGER).toString()).toEqual( - String(Number.MAX_SAFE_INTEGER) - ); - }); - - it('handles negative numbers', () => { - expect(Field(-1)).toEqual(Field(1).neg()); - expect(Field(-(2 ** 31))).toEqual(Field(2 ** 31).neg()); - }); - - it('throws on fractional numbers', () => { - expect(() => Field(0.5)).toThrow(); - expect(() => Field(-1.1)).toThrow(); - }); - - // Field(bigint), Field.fromBigInt, toBigInt - - it('handles bigints', () => { - expect(Field(-1n)).toEqual(Field(1).neg()); - expect(Field(-1n)).toEqual(Field(-1)); - expect(Field(Field.ORDER - 1n)).toEqual(Field(1).neg()); - expect(Field(1n << 64n).toString()).toEqual('18446744073709551616'); - expect(Field(1n << 64n)).toEqual(Field('18446744073709551616')); - }); - - // TODO Field(string), Field(boolean), Field(otherField) -}); - -describe('Field serialization and static props', () => { - it('toBigInt works on static props', () => { - expect(Field(1).toBigInt()).toEqual(1n); - expect(Field(0).toBigInt()).toEqual(0n); - expect(Field(-1).toBigInt()).toEqual(Field.ORDER - 1n); - expect(Field(0xff).toBigInt()).toEqual(0xffn); - }); -}); diff --git a/src/lib/field.ts b/src/lib/field.ts new file mode 100644 index 0000000000..e531df262e --- /dev/null +++ b/src/lib/field.ts @@ -0,0 +1,1238 @@ +import { Snarky, SnarkyField, Provable } from '../snarky.js'; +import { Field as Fp } from '../provable/field-bigint.js'; +import { Bool } from '../snarky.js'; +import { defineBinable } from '../bindings/lib/binable.js'; +import type { NonNegativeInteger } from '../bindings/crypto/non-negative.js'; +import { asProver } from './provable-context.js'; + +export { Field, ConstantField, FieldType, FieldVar, FieldConst, isField }; + +const SnarkyFieldConstructor = SnarkyField(1).constructor; + +type FieldConst = Uint8Array; + +function constToBigint(x: FieldConst): Fp { + return Fp.fromBytes([...x]); +} +function constFromBigint(x: Fp) { + return Uint8Array.from(Fp.toBytes(x)); +} + +const FieldConst = { + fromBigint: constFromBigint, + toBigint: constToBigint, + [0]: constFromBigint(0n), + [1]: constFromBigint(1n), + [-1]: constFromBigint(Fp(-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]; + +const FieldVar = { + constant(x: bigint | FieldConst): ConstantFieldVar { + let x0 = typeof x === 'bigint' ? FieldConst.fromBigint(x) : x; + return [FieldType.Constant, x0]; + }, + // TODO: handle (special) constants + add(x: FieldVar, y: FieldVar): FieldVar { + return [FieldType.Add, x, y]; + }, + // TODO: handle (special) constants + scale(c: FieldConst, x: FieldVar): FieldVar { + return [FieldType.Scale, c, x]; + }, + [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 }; + +/** + * A {@link Field} is an element of a prime order [finite field](https://en.wikipedia.org/wiki/Finite_field). + * Every other provable type is built using the {@link Field} type. + * + * The field is the [pasta base field](https://electriccoin.co/blog/the-pasta-curves-for-halo-2-and-beyond/) of order 2^254 + 0x224698fc094cf91b992d30ed00000001 ({@link Field.ORDER}). + * + * You can create a new Field from everything "field-like" (`bigint`, integer `number`, decimal `string`, `Field`). + * @example + * ``` + * Field(10n); // Field contruction from a big integer + * Field(100); // Field construction from a number + * Field("1"); // Field construction from a decimal string + * ``` + * + * **Beware**: Fields _cannot_ be constructed from fractional numbers or alphanumeric strings: + * ```ts + * Field(3.141); // ERROR: Cannot convert a float to a field element + * Field("abc"); // ERROR: Invalid argument "abc" + * ``` + * + * Creating a Field from a negative number can result in unexpected behavior if you are not familiar with [modular arithmetic](https://en.wikipedia.org/wiki/Modular_arithmetic). + * @example + * ``` + * const x = Field(-1); // Valid Field construction from negative number + * const y = Field(Field.ORDER - 1n); // equivalent to `x` + * ``` + * + * **Important**: All the functions defined on a Field (arithmetic, logic, etc.) take their arguments as "field-like". A Field itself is also defined as a "field-like" element. + * + * @param value - the value to convert to a {@link Field} + * + * @return A {@link Field} with the value converted from the argument + */ +class Field { + value: FieldVar; + + /** + * The order of the pasta curve that {@link Field} type build on as a `bigint`. + * Order of the {@link Field} is 28948022309329048855892746252171976963363056481941560715954676764349967630337. + */ + static ORDER = Fp.modulus; + + /** + * Coerce anything "field-like" (bigint, number, string, and {@link Field}) to a Field. + */ + constructor(x: bigint | number | string | Field | FieldVar) { + if (Field.#isField(x)) { + this.value = x.value; + return; + } + // FieldVar + if (Array.isArray(x)) { + this.value = x; + return; + } + // TODO this should handle common values efficiently by reading from a lookup table + this.value = FieldVar.constant(Fp(x)); + } + + // helpers + static #isField(x: bigint | number | string | Field | FieldVar): x is Field { + return x instanceof Field || (x as any) instanceof SnarkyFieldConstructor; + } + static #toConst(x: bigint | number | string | ConstantField): FieldConst { + if (Field.#isField(x)) return x.value[1]; + return FieldConst.fromBigint(Fp(x)); + } + static #toVar(x: bigint | number | string | Field): FieldVar { + if (Field.#isField(x)) return x.value; + return FieldVar.constant(Fp(x)); + } + static from(x: bigint | number | string | Field): Field { + if (Field.#isField(x)) return x; + return new Field(x); + } + + /** + * Check whether this {@link Field} element is a hard-coded constant in the constraint system. + * If a {@link Field} is constructed outside a zkApp method, it is a constant. + * + * @example + * ```ts + * console.log(Field(42).isConstant()); // true + * ``` + * + * @example + * ```ts + * \@method myMethod(x: Field) { + * console.log(x.isConstant()); // false + * } + * ``` + * + * @return A `boolean` showing if this {@link Field} is a constant or not. + */ + isConstant(): this is { value: ConstantFieldVar } { + return this.value[0] === FieldType.Constant; + } + + /** + * Create a {@link Field} element equivalent to this {@link Field} element's value, + * but is a constant. + * See {@link Field.isConstant} for more information about what is a constant {@link Field}. + * + * @example + * ```ts + * const someField = Field(42); + * someField.toConstant().assertEquals(someField); // Always true + * ``` + * + * @return A constant {@link Field} element equivalent to this {@link Field} element. + */ + toConstant(): ConstantField { + if (this.isConstant()) return this; + // TODO: fix OCaml error message, `Can't evaluate prover code outside an as_prover block` + let value = Snarky.field.readVar(this.value); + return new Field(FieldVar.constant(value)) as ConstantField; + } + + /** + * Serialize the {@link Field} to a bigint, e.g. for printing. Trying to print a {@link Field} without this function will directly stringify the Field object, resulting in unreadable output. + * + * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the bigint representation of the {@link Field}. Use the operation only during debugging. + * + * @example + * ```ts + * const someField = Field(42); + * console.log(someField.toBigInt()); + * ``` + * + * @return A bigint equivalent to the bigint representation of the Field. + */ + toBigInt() { + let x = this.toConstant(); + return FieldConst.toBigint(x.value[1]); + } + + /** + * Serialize the {@link Field} to a string, e.g. for printing. Trying to print a {@link Field} without this function will directly stringify the Field object, resulting in unreadable output. + * + * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the {@link Field}. Use the operation only during debugging. + * + * @example + * ```ts + * const someField = Field(42); + * console.log(someField.toString()); + * ``` + * + * @return A string equivalent to the string representation of the Field. + */ + toString() { + return this.toBigInt().toString(); + } + + /** + * Assert that this {@link Field} is equal another "field-like" value. + * Calling this function is equivalent to `Field(...).equals(...).assertEquals(Bool(true))`. + * See {@link Field.equals} for more details. + * + * **Important**: If an assertion fails, the code throws an error. + * + * @param value - the "field-like" value to compare & assert with this {@link Field}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertEquals(y: Field | bigint | number | string, message?: string) { + try { + if (this.isConstant() && isConstant(y)) { + if (this.toBigInt() !== toFp(y)) { + throw Error(`Field.assertEquals(): ${this} != ${y}`); + } + return; + } + Snarky.field.assertEqual(this.value, Field.#toVar(y)); + } catch (err) { + throw withMessage(err, message); + } + } + + /** + * Add a "field-like" value to this {@link Field} element. + * + * @example + * ```ts + * const x = Field(3); + * const sum = x.add(5); + * + * sum.assertEquals(Field(8)); + * ``` + * + * **Warning**: This is a modular addition in the pasta field. + * @example + * ```ts + * const x = Field(1); + * const sum = x.add(Field(-7)); + * + * // If you try to print sum - `console.log(sum.toBigInt())` - you will realize that it prints a very big integer because this is modular arithmetic, and 1 + (-7) circles around the field to become p - 6. + * // You can use the reverse operation of addition (substraction) to prove the sum is calculated correctly. + * + * sum.sub(x).assertEquals(Field(-7)); + * sum.sub(Field(-7)).assertEquals(x); + * ``` + * + * @param value - a "field-like" value to add to the {@link Field}. + * + * @return A {@link Field} element equivalent to the modular addition of the two value. + */ + add(y: Field | bigint | number | string): Field { + if (this.isConstant() && isConstant(y)) { + return new Field(Fp.add(this.toBigInt(), toFp(y))); + } + // return new AST node Add(x, y) + let z = Snarky.field.add(this.value, Field.#toVar(y)); + return new Field(z); + } + + /** + * Negate a {@link Field}. This is equivalent to multiplying the {@link Field} by -1. + * + * @example + * ```ts + * const negOne = Field(1).neg(); + * negOne.assertEquals(-1); + * ``` + * + * @example + * ```ts + * const someField = Field(42); + * someField.neg().assertEquals(someField.mul(Field(-1))); // This statement is always true regardless of the value of `someField` + * ``` + * + * **Warning**: This is a modular negation. For details, see the {@link sub} method. + * + * @return A {@link Field} element that is equivalent to the element multiplied by -1. + */ + neg() { + if (this.isConstant()) { + return new Field(Fp.negate(this.toBigInt())); + } + // return new AST node Scale(-1, x) + let z = Snarky.field.scale(FieldConst[-1], this.value); + return new Field(z); + } + + /** + * Substract another "field-like" value from this {@link Field} element. + * + * @example + * ```ts + * const x = Field(3); + * const difference = x.sub(5); + * + * difference.assertEquals(Field(-2)); + * ``` + * + * **Warning**: This is a modular substraction in the pasta field. + * + * @example + * ```ts + * const x = Field(1); + * const difference = x.sub(Field(2)); + * + * // If you try to print difference - `console.log(difference.toBigInt())` - you will realize that it prints a very big integer because this is modular arithmetic, and 1 - 2 circles around the field to become p - 1. + * // You can use the reverse operation of substraction (addition) to prove the difference is calculated correctly. + * difference.add(Field(2)).assertEquals(x); + * ``` + * + * @param value - a "field-like" value to substract from the {@link Field}. + * + * @return A {@link Field} element equivalent to the modular difference of the two value. + */ + sub(y: Field | bigint | number | string) { + return this.add(Field.from(y).neg()); + } + + /** + * Multiply another "field-like" value with this {@link Field} element. + * + * @example + * ```ts + * const x = Field(3); + * const product = x.mul(Field(5)); + * + * product.assertEquals(Field(15)); + * ``` + * + * @param value - a "field-like" value to multiply with the {@link Field}. + * + * @return A {@link Field} element equivalent to the modular difference of the two value. + */ + mul(y: Field | bigint | number | string): Field { + if (this.isConstant() && isConstant(y)) { + return new Field(Fp.mul(this.toBigInt(), toFp(y))); + } + // if one of the factors is constant, return Scale AST node + if (isConstant(y)) { + let z = Snarky.field.scale(Field.#toConst(y), this.value); + return new Field(z); + } + if (this.isConstant()) { + let z = Snarky.field.scale(this.value[1], y.value); + return new Field(z); + } + // create a new witness for z = x*y + let z = Snarky.existsVar(() => + FieldConst.fromBigint(Fp.mul(this.toBigInt(), toFp(y))) + ); + // add a multiplication constraint + Snarky.field.assertMul(this.value, y.value, z); + return new Field(z); + } + + /** + * [Modular inverse](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse) of this {@link Field} element. + * Equivalent to 1 divided by this {@link Field}, in the sense of modular arithmetic. + * + * Proves that this Field is non-zero, or throws a "Division by zero" error. + * + * @example + * ```ts + * const someField = Field(42); + * const inverse = someField.inv(); + * inverse.assertEquals(Field(1).div(example)); // This statement is always true regardless of the value of `someField` + * ``` + * + * **Warning**: This is a modular inverse. See {@link div} method for more details. + * + * @return A {@link Field} element that is equivalent to one divided by this element. + */ + inv() { + if (this.isConstant()) { + let z = Fp.inverse(this.toBigInt()); + if (z === undefined) throw Error('Field.inv(): Division by zero'); + return new Field(z); + } + // create a witness for z = x^(-1) + let z = Snarky.existsVar(() => { + let z = Fp.inverse(this.toBigInt()) ?? 0n; + return FieldConst.fromBigint(z); + }); + // constrain x * z === 1 + Snarky.field.assertMul(this.value, z, FieldVar[1]); + return new Field(z); + } + + /** + * Divide another "field-like" value through this {@link Field}. + * + * Proves that the denominator is non-zero, or throws a "Division by zero" error. + * + * @example + * ```ts + * const x = Field(6); + * const quotient = x.div(Field(3)); + * + * quotient.assertEquals(Field(2)); + * ``` + * + * **Warning**: This is a modular division in the pasta field. You can think this as the reverse operation of modular multiplication. + * + * @example + * ```ts + * const x = Field(2); + * const y = Field(5); + * + * const quotient = x.div(y); + * + * // If you try to print quotient - `console.log(quotient.toBigInt())` - you will realize that it prints a very big integer because this is a modular inverse. + * // You can use the reverse operation of division (multiplication) to prove the quotient is calculated correctly. + * + * quotient.mul(y).assertEquals(x); + * ``` + * + * @param value - a "field-like" value to divide with the {@link Field}. + * + * @return A {@link Field} element equivalent to the modular division of the two value. + */ + div(y: Field | bigint | number | string) { + // TODO this is the same as snarky-ml but could use 1 constraint instead of 2 + return this.mul(Field.from(y).inv()); + } + + /** + * Square this {@link Field} element. + * + * @example + * ```ts + * const someField = Field(7); + * const square = someField.square(); + * + * square.assertEquals(someField.mul(someField)); // This statement is always true regardless of the value of `someField` + * ``` + * + * ** Warning: This is a modular multiplication. See `mul()` method for more details. + * + * @return A {@link Field} element equivalent to the multiplication of the {@link Field} element with itself. + */ + square() { + if (this.isConstant()) { + return new Field(Fp.square(this.toBigInt())); + } + // create a new witness for z = x^2 + let z = Snarky.existsVar(() => + FieldConst.fromBigint(Fp.square(this.toBigInt())) + ); + // add a squaring constraint + Snarky.field.assertSquare(this.value, z); + return new Field(z); + } + + /** + * Take the square root of this {@link Field} element. + * + * Proves that the Field element has a square root in the finite field, or throws if it doesn't. + * + * @example + * ```ts + * let z = x.sqrt(); + * z.mul(z).assertEquals(x); // true for every `x` + * ``` + * + * **Warning**: This is a modular square root, which is any number z that satisfies z*z = x (mod p). + * Note that, if a square root z exists, there also exists a second one, -z (which is different if z != 0). + * Therefore, this method leaves an adversarial prover the choice between two different values to return. + * + * @return A {@link Field} element equivalent to the square root of the {@link Field} element. + */ + sqrt() { + if (this.isConstant()) { + let z = Fp.sqrt(this.toBigInt()); + if (z === undefined) + throw Error( + `Field.sqrt(): input ${this} has no square root in the field.` + ); + return new Field(z); + } + // create a witness for sqrt(x) + let z = Snarky.existsVar(() => { + let z = Fp.sqrt(this.toBigInt()) ?? 0n; + return FieldConst.fromBigint(z); + }); + // constrain z * z === x + Snarky.field.assertSquare(z, this.value); + return new Field(z); + } + + /** + * @deprecated use `x.equals(0)` which is equivalent + */ + isZero() { + if (this.isConstant()) { + return Bool(this.toBigInt() === 0n); + } + // create witnesses z = 1/x, or z=0 if x=0, + // and b = 1 - zx + let [, b, z] = Snarky.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)]; + }); + // add constraints + // b * x === 0 + Snarky.field.assertMul(b, this.value, FieldVar[0]); + // z * x === 1 - b + Snarky.field.assertMul( + z, + this.value, + Snarky.field.add(FieldVar[1], Snarky.field.scale(FieldConst[-1], 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 Bool.Unsafe.ofField(new Field(b)); + } + + /** + * Check if this {@link Field} is equal another "field-like" value. + * Returns a {@link Bool}, which is a provable type and can be used to prove the validity of this statement. + * + * @example + * ```ts + * Field(5).equals(5).assertEquals(Bool(true)); + * ``` + * + * @param value - the "field-like" value to compare with this {@link Field}. + * + * @return A {@link Bool} representing if this {@link Field} is equal another "field-like" value. + */ + equals(y: Field | bigint | number | string): Bool { + // x == y is equivalent to x - y == 0 + // TODO: this is less efficient than possible for equivalence with snarky-ml + return this.sub(y).isZero(); + // more efficient code is commented below + /* + // if one of the two is constant, we just need the two constraints in `isZero` + if (this.isConstant() || isConstant(y)) { + 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.existsVar(() => + FieldConst.fromBigint(Fp.sub(this.toBigInt(), toFp(y))) + ); + Snarky.field.assertEqual(this.sub(y).value, xMinusY); + return new Field(xMinusY).isZero(); + */ + } + + // internal base method for all comparisons + #compare(y: FieldVar) { + // TODO: support all bit lengths + let maxLength = Fp.sizeInBits - 2; + asProver(() => { + let actualLength = Math.max( + this.toBigInt().toString(2).length, + new Field(y).toBigInt().toString(2).length + ); + if (actualLength > maxLength) + throw Error( + `Provable comparison functions can only be used on Fields of size <= ${maxLength} bits, got ${actualLength} bits.` + ); + }); + let [, less, lessOrEqual] = Snarky.field.compare(maxLength, this.value, y); + return { + less: Bool.Unsafe.ofField(new Field(less)), + lessOrEqual: Bool.Unsafe.ofField(new Field(lessOrEqual)), + }; + } + + /** + * Check if this {@link Field} is less than another "field-like" value. + * Returns a {@link Bool}, which is a provable type and can be used prove to the validity of this statement. + * + * @example + * ```ts + * Field(2).lessThan(3).assertEquals(Bool(true)); + * ``` + * + * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. + * The method will throw if one of the inputs exceeds 253 bits. + * + * **Warning**: As this method compares the bigint value of a {@link Field}, it can result in unexpected behavior when used with negative inputs or modular division. + * + * @example + * ```ts + * Field(1).div(Field(3)).lessThan(Field(1).div(Field(2))).assertEquals(Bool(true)); // This code will throw an error + * ``` + * + * @param value - the "field-like" value to compare with this {@link Field}. + * + * @return A {@link Bool} representing if this {@link Field} is less than another "field-like" value. + */ + lessThan(y: Field | bigint | number | string): Bool { + if (this.isConstant() && isConstant(y)) { + return Bool(this.toBigInt() < toFp(y)); + } + return this.#compare(Field.#toVar(y)).less; + } + + /** + * Check if this {@link Field} is less than or equal to another "field-like" value. + * Returns a {@link Bool}, which is a provable type and can be used to prove the validity of this statement. + * + * @example + * ```ts + * Field(3).lessThanOrEqual(3).assertEquals(Bool(true)); + * ``` + * + * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. + * The method will throw if one of the inputs exceeds 253 bits. + * + * **Warning**: As this method compares the bigint value of a {@link Field}, it can result in unexpected behaviour when used with negative inputs or modular division. + * + * @example + * ```ts + * Field(1).div(Field(3)).lessThanOrEqual(Field(1).div(Field(2))).assertEquals(Bool(true)); // This code will throw an error + * ``` + * + * @param value - the "field-like" value to compare with this {@link Field}. + * + * @return A {@link Bool} representing if this {@link Field} is less than or equal another "field-like" value. + */ + lessThanOrEqual(y: Field | bigint | number | string): Bool { + if (this.isConstant() && isConstant(y)) { + return Bool(this.toBigInt() <= toFp(y)); + } + return this.#compare(Field.#toVar(y)).lessOrEqual; + } + + /** + * Check if this {@link Field} is greater than another "field-like" value. + * Returns a {@link Bool}, which is a provable type and can be used to prove the validity of this statement. + * + * @example + * ```ts + * Field(5).greaterThan(3).assertEquals(Bool(true)); + * ``` + * + * **Warning**: Comparison methods currently only support Field elements of size <= 253 bits in provable code. + * The method will throw if one of the inputs exceeds 253 bits. + * + * **Warning**: As this method compares the bigint value of a {@link Field}, it can result in unexpected behaviour when used with negative inputs or modular division. + * + * @example + * ```ts + * Field(1).div(Field(2)).greaterThan(Field(1).div(Field(3))).assertEquals(Bool(true)); // This code will throw an error + * ``` + * + * @param value - the "field-like" value to compare with this {@link Field}. + * + * @return A {@link Bool} representing if this {@link Field} is greater than another "field-like" value. + */ + greaterThan(y: Field | bigint | number | string) { + // TODO: this is less efficient than possible for equivalence with ml + return this.lessThanOrEqual(y).not(); + } + + /** + * Check if this {@link Field} is greater than or equal another "field-like" value. + * Returns a {@link Bool}, which is a provable type and can be used to prove the validity of this statement. + * + * @example + * ```ts + * Field(3).greaterThanOrEqual(3).assertEquals(Bool(true)); + * ``` + * + * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. + * The method will throw if one of the inputs exceeds 253 bits. + * + * **Warning**: As this method compares the bigint value of a {@link Field}, it can result in unexpected behaviour when used with negative inputs or modular division. + * + * @example + * ```ts + * Field(1).div(Field(2)).greaterThanOrEqual(Field(1).div(Field(3))).assertEquals(Bool(true)); // This code will throw an error + * ``` + * + * @param value - the "field-like" value to compare with this {@link Field}. + * + * @return A {@link Bool} representing if this {@link Field} is greater than or equal another "field-like" value. + */ + greaterThanOrEqual(y: Field | bigint | number | string) { + // TODO: this is less efficient than possible for equivalence with ml + return this.lessThan(y).not(); + } + + /** + * Assert that this {@link Field} is less than another "field-like" value. + * Calling this function is equivalent to `Field(...).lessThan(...).assertEquals(Bool(true))`. + * See {@link Field.lessThan} for more details. + * + * **Important**: If an assertion fails, the code throws an error. + * + * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. + * The method will throw if one of the inputs exceeds 253 bits. + * + * @param value - the "field-like" value to compare & assert with this {@link Field}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertLessThan(y: Field | bigint | number | string, message?: string) { + try { + if (this.isConstant() && isConstant(y)) { + if (!(this.toBigInt() < toFp(y))) { + throw Error(`Field.assertLessThan(): expected ${this} < ${y}`); + } + return; + } + let { less } = this.#compare(Field.#toVar(y)); + less.assertTrue(); + } catch (err) { + throw withMessage(err, message); + } + } + + /** + * Assert that this {@link Field} is less than or equal to another "field-like" value. + * Calling this function is equivalent to `Field(...).lessThanOrEqual(...).assertEquals(Bool(true))`. + * See {@link Field.lessThanOrEqual} for more details. + * + * **Important**: If an assertion fails, the code throws an error. + * + * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. + * The method will throw if one of the inputs exceeds 253 bits. + * + * @param value - the "field-like" value to compare & assert with this {@link Field}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertLessThanOrEqual(y: Field | bigint | number | string, message?: string) { + try { + if (this.isConstant() && isConstant(y)) { + if (!(this.toBigInt() <= toFp(y))) { + throw Error(`Field.assertLessThan(): expected ${this} <= ${y}`); + } + return; + } + let { lessOrEqual } = this.#compare(Field.#toVar(y)); + lessOrEqual.assertTrue(); + } catch (err) { + throw withMessage(err, message); + } + } + + /** + * Assert that this {@link Field} is greater than another "field-like" value. + * Calling this function is equivalent to `Field(...).greaterThan(...).assertEquals(Bool(true))`. + * See {@link Field.greaterThan} for more details. + * + * **Important**: If an assertion fails, the code throws an error. + * + * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. + * The method will throw if one of the inputs exceeds 253 bits. + * + * @param value - the "field-like" value to compare & assert with this {@link Field}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertGreaterThan(y: Field | bigint | number | string, message?: string) { + Field.from(y).assertLessThan(this, message); + } + + /** + * Assert that this {@link Field} is greater than or equal to another "field-like" value. + * Calling this function is equivalent to `Field(...).greaterThanOrEqual(...).assertEquals(Bool(true))`. + * See {@link Field.greaterThanOrEqual} for more details. + * + * **Important**: If an assertion fails, the code throws an error. + * + * **Warning**: Comparison methods only support Field elements of size <= 253 bits in provable code. + * The method will throw if one of the inputs exceeds 253 bits. + * + * @param value - the "field-like" value to compare & assert with this {@link Field}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertGreaterThanOrEqual( + y: Field | bigint | number | string, + message?: string + ) { + Field.from(y).assertLessThanOrEqual(this, message); + } + + /** + * Assert that this {@link Field} does not equal another field-like value. + * + * Note: This uses fewer constraints than `x.equals(y).assertFalse()`. + * + * @example + * ```ts + * x.assertNotEquals(0, "expect x to be non-zero"); + * ``` + */ + assertNotEquals(y: Field | bigint | number | string, message?: string) { + try { + if (this.isConstant() && isConstant(y)) { + if (this.toBigInt() === toFp(y)) { + throw Error(`Field.assertNotEquals(): ${this} = ${y}`); + } + return; + } + // inv() proves that a field element is non-zero, using 1 constraint. + // so this takes 1-2 generic gates, while x.equals(y).assertTrue() takes 3-5 + this.sub(y).inv(); + } catch (err) { + throw withMessage(err, message); + } + } + + /** + * Assert that this {@link Field} is equal to 1 or 0 as a "field-like" value. + * Calling this function is equivalent to `Bool.or(Field(...).equals(1), Field(...).equals(0)).assertEquals(Bool(true))`. + * + * **Important**: If an assertion fails, the code throws an error. + * + * @param value - the "field-like" value to compare & assert with this {@link Field}. + * @param message? - a string error message to print if the assertion fails, optional. + */ + assertBool(message?: string) { + try { + if (this.isConstant()) { + let x = this.toBigInt(); + if (x !== 0n && x !== 1n) { + throw Error(`Field.assertBool(): expected ${x} to be 0 or 1`); + } + return; + } + Snarky.field.assertBoolean(this.value); + } catch (err) { + throw withMessage(err, message); + } + } + + static #checkBitLength(name: string, length: number) { + if (length > Fp.sizeInBits) + throw Error( + `${name}: bit length must be ${Fp.sizeInBits} or less, got ${length}` + ); + if (length <= 0) + throw Error(`${name}: bit length must be positive, got ${length}`); + } + + /** + * Returns an array of {@link Bool} elements representing [little endian binary representation](https://en.wikipedia.org/wiki/Endianness) of this {@link Field} element. + * + * If you use the optional `length` argument, proves that the field element fits in `length` bits. + * The `length` has to be between 0 and 255 and the method throws if it isn't. + * + * **Warning**: The cost of this operation in a zk proof depends on the `length` you specify, + * which by default is 255 bits. Prefer to pass a smaller `length` if possible. + * + * @param length - the number of bits to fit the element. If the element does not fit in `length` bits, the functions throws an error. + * + * @return An array of {@link Bool} element representing little endian binary representation of this {@link Field}. + */ + toBits(length?: number) { + if (length !== undefined) Field.#checkBitLength('Field.toBits()', length); + if (this.isConstant()) { + let bits = Fp.toBits(this.toBigInt()); + if (length !== undefined) { + if (bits.slice(length).some((bit) => bit)) + throw Error(`Field.toBits(): ${this} does not fit in ${length} bits`); + return bits.slice(0, length).map(Bool); + } + return bits.map(Bool); + } + let [, ...bits] = Snarky.field.toBits(length ?? Fp.sizeInBits, this.value); + return bits.map((b) => Bool.Unsafe.ofField(new Field(b))); + } + + /** + * Convert a bit array into a {@link Field} element using [little endian binary representation](https://en.wikipedia.org/wiki/Endianness) + * + * The method throws if the given bits do not fit in a single Field element. A Field element can be at most 255 bits. + * + * **Important**: If the given `bytes` array is an array of `booleans` or {@link Bool} elements that all are `constant`, the resulting {@link Field} element will be a constant as well. Or else, if the given array is a mixture of constants and variables of {@link Bool} type, the resulting {@link Field} will be a variable as well. + * + * @param bytes - An array of {@link Bool} or `boolean` type. + * + * @return A {@link Field} element matching the [little endian binary representation](https://en.wikipedia.org/wiki/Endianness) of the given `bytes` array. + */ + static fromBits(bits: (Bool | boolean)[]) { + let length = bits.length; + Field.#checkBitLength('Field.fromBits()', length); + if (bits.every((b) => typeof b === 'boolean' || b.toField().isConstant())) { + let bits_ = bits + .map((b) => (typeof b === 'boolean' ? b : b.toBoolean())) + .concat(Array(Fp.sizeInBits - length).fill(false)); + return new Field(Fp.fromBits(bits_)); + } + let bitsVars = bits.map((b): FieldVar => { + if (typeof b === 'boolean') return b ? FieldVar[1] : FieldVar[0]; + return b.toField().value; + }); + let x = Snarky.field.fromBits([0, ...bitsVars]); + return new Field(x); + } + + /** + * Create a new {@link Field} element from the first `length` bits of this {@link Field} element. + * + * The `length` has to be a multiple of 16, and has to be between 0 and 255, otherwise the method throws. + * + * As {@link Field} elements are represented using [little endian binary representation](https://en.wikipedia.org/wiki/Endianness), + * the resulting {@link Field} element will equal the original one if it fits in `length` bits. + * + * @param length - The number of bits to take from this {@link Field} element. + * + * @return A {@link Field} element that is equal to the `length` of this {@link Field} element. + */ + rangeCheckHelper(length: number) { + Field.#checkBitLength('Field.rangeCheckHelper()', length); + if (length % 16 !== 0) + throw Error( + 'Field.rangeCheckHelper(): `length` has to be a multiple of 16.' + ); + let lengthDiv16 = length / 16; + if (this.isConstant()) { + let bits = Fp.toBits(this.toBigInt()) + .slice(0, length) + .concat(Array(Fp.sizeInBits - length).fill(false)); + return new Field(Fp.fromBits(bits)); + } + let x = Snarky.field.truncateToBits16(lengthDiv16, this.value); + return new Field(x); + } + + /** + * **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer. + * + * In SnarkyJS, addition and scaling (multiplication of variables by a constant) of variables is represented as an AST - [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree). For example, the expression `x.add(y).mul(2)` is represented as `Scale(2, Add(x, y))`. + * + * A new internal variable is created only when the variable is needed in a multiplicative or any higher level constraint (for example multiplication of two {@link Field} elements) to represent the operation. + * + * The `seal()` function tells SnarkyJS to stop building an AST and create a new variable right away. + * + * @return A {@link Field} element that is equal to the result of AST that was previously on this {@link Field} element. + */ + seal() { + if (this.isConstant()) return this; + let x = Snarky.field.seal(this.value); + return new Field(x); + } + + /** + * A random {@link Field} element. + * + * @example + * ```ts + * console.log(Field.random().toBigInt()); // Run this code twice! + * ``` + * + * @return A random {@link Field} element. + */ + static random() { + return new Field(Fp.random()); + } + + // internal stuff + + // Provable + + /** + * This function is the implementation of {@link Provable.toFields} for the {@link Field} type. + * + * Static function to serializes a {@link Field} into an array of {@link Field} elements. + * This will be always an array of length 1, where the first and only element equals the given parameter itself. + * + * @param value - the {@link Field} element to cast the array from. + * + * @return A {@link Field} array of length 1 created from this {@link Field}. + */ + static toFields(x: Field) { + return [x]; + } + + /** + * This function is the implementation of {@link Provable.toAuxiliary} for the {@link Field} type. + * + * As the primitive {@link Field} type has no auxiliary data associated with it, this function will always return an empty array. + * + * @param value - The {@link Field} element to get the auxiliary data of, optional. If not provided, the function returns an empty array. + */ + static toAuxiliary(): [] { + return []; + } + + /** + * This function is the implementation of {@link Provable.sizeInFields} for the {@link Field} type. + * + * Size of the {@link Field} type is 1, as it is the primitive type. + * This function returns a regular 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. + * + * @example + * ```ts + * console.log(Field.sizeInFields()); // Prints 1 + * ``` + * + * @return A number representing the size of the {@link Field} type in terms of {@link Field} type itself. + */ + static sizeInFields() { + return 1; + } + + /** + * Implementation of {@link Provable.fromFields} for the {@link Field} type. + * + * **Warning**: This function is designed for internal use. It is not intended to be used by a zkApp developer. + * + * Creates a {@link Field} from an array of Fields of length 1. + * + * @param fields - an array of length 1 serialized from {@link Field} elements. + * + * @return The first {@link Field} element of the given array. + */ + static fromFields([x]: Field[]) { + return x; + } + + /** + * This function is the implementation of {@link Provable.check} in {@link Field} type. + * + * As any field element can be a {@link Field}, this function does not create any assertions, so it does nothing. + * + * @param value - the {@link Field} element to check. + */ + static check() {} + + /** + * This function is the implementation of {@link Provable.toFields} for the {@link Field} type. + * + * The result will be always an array of length 1, where the first and only element equals the {@link Field} itself. + * + * @return A {@link Field} array of length 1 created from this {@link Field}. + */ + toFields() { + return Field.toFields(this); + } + + /** + * This function is the implementation of {@link Provable.toAuxiliary} for the {@link Field} type. + * + * As the primitive {@link Field} type has no auxiliary data associated with it, this function will always return an empty array. + */ + toAuxiliary() { + return Field.toAuxiliary(); + } + + // ProvableExtended + + /** + * Serialize the {@link Field} to a JSON string, e.g. for printing. Trying to print a {@link Field} without this function will directly stringify the Field object, resulting in unreadable output. + * + * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the JSON string representation of the {@link Field}. Use the operation only during debugging. + * + * @example + * ```ts + * const someField = Field(42); + * console.log(someField.toJSON()); + * ``` + * + * @return A string equivalent to the JSON representation of the {@link Field}. + */ + toJSON() { + return this.toString(); + } + + /** + * Serialize the given {@link Field} element to a JSON string, e.g. for printing. Trying to print a {@link Field} without this function will directly stringify the Field object, resulting in unreadable output. + * + * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the JSON string representation of the {@link Field}. Use the operation only during debugging. + * + * @example + * ```ts + * const someField = Field(42); + * console.log(Field.toJSON(someField)); + * ``` + * + * @param value - The JSON string to coerce the {@link Field} from. + * + * @return A string equivalent to the JSON representation of the given {@link Field}. + */ + static toJSON(x: Field) { + return x.toJSON(); + } + + /** + * Deserialize a JSON string containing a "field-like" value into a {@link Field} element. + * + * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the {@link Field}. + * + * @param value - the "field-like" value to coerce the {@link Field} from. + * + * @return A {@link Field} coerced from the given JSON string. + */ + static fromJSON(json: string) { + return new Field(Fp.fromJSON(json)); + } + + /** + * **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer. + * + * This function is the implementation of `ProvableExtended.toInput()` for the {@link Field} type. + * + * @param value - The {@link Field} element to get the `input` array. + * + * @return An object where the `fields` key is a {@link Field} array of length 1 created from this {@link Field}. + * + */ + static toInput(x: Field) { + return { fields: [x] }; + } + + // Binable + + /** + * Create an array of digits equal to the [little-endian](https://en.wikipedia.org/wiki/Endianness) byte order of the given {@link Field} element. + * Note that the array has always 32 elements as the {@link Field} is a `finite-field` in the order of {@link Field.ORDER}. + * + * @param value - The {@link Field} element to generate the array of bytes of. + * + * @return An array of digits equal to the [little-endian](https://en.wikipedia.org/wiki/Endianness) byte order of the given {@link Field} element. + * + */ + static toBytes(x: Field) { + return FieldBinable.toBytes(x); + } + + /** + * Part of the `Binable` interface. + * + * **Warning**: This function is for internal use. It is not intended to be used by a zkApp developer. + */ + static readBytes( + bytes: number[], + offset: NonNegativeInteger + ) { + return FieldBinable.readBytes(bytes, offset); + } + + /** + * Coerce a new {@link Field} element using the [little-endian](https://en.wikipedia.org/wiki/Endianness) representation of the given `bytes` array. + * Note that the given `bytes` array may have at most 32 elements as the {@link Field} is a `finite-field` in the order of {@link Field.ORDER}. + * + * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the byte representation of the {@link Field}. + * + * @param bytes - The bytes array to coerce the {@link Field} from. + * + * @return A new {@link Field} element created using the [little-endian](https://en.wikipedia.org/wiki/Endianness) representation of the given `bytes` array. + */ + static fromBytes(bytes: number[]) { + return FieldBinable.fromBytes(bytes); + } + + /** + * **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer. + * + * As all {@link Field} elements have 31 bits, this function returns 31. + * + * @return The size of a {@link Field} element - 31. + */ + static sizeInBytes() { + return Fp.sizeInBytes(); + } +} + +const FieldBinable = defineBinable({ + toBytes(t: Field) { + return [...t.toConstant().value[1]]; + }, + readBytes(bytes, offset) { + let uint8array = new Uint8Array(32); + uint8array.set(bytes.slice(offset, offset + 32)); + return [ + Object.assign(Object.create(new Field(1).constructor.prototype), { + value: [0, uint8array], + }) as Field, + offset + 32, + ]; + }, +}); + +function isField(x: unknown): x is Field { + return x instanceof Field || (x as any) instanceof SnarkyFieldConstructor; +} + +function isConstant( + x: bigint | number | string | Field +): x is bigint | number | string | ConstantField { + let type = typeof x; + if (type === 'bigint' || type === 'number' || type === 'string') { + return true; + } + return (x as Field).isConstant(); +} + +function toFp(x: bigint | number | string | Field): Fp { + let type = typeof x; + if (type === 'bigint' || type === 'number' || type === 'string') { + return Fp(x as bigint | number | string); + } + return (x as Field).toBigInt(); +} + +function withMessage(error: unknown, message?: string) { + if (message === undefined || !(error instanceof Error)) return error; + error.message = `${message}\n${error.message}`; + return error; +} diff --git a/src/lib/field.unit-test.ts b/src/lib/field.unit-test.ts new file mode 100644 index 0000000000..657a8c4dbd --- /dev/null +++ b/src/lib/field.unit-test.ts @@ -0,0 +1,337 @@ +import { ProvablePure } from '../snarky.js'; +import { Field } from './core.js'; +import { Field as Fp } from '../provable/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'; + +// types +Field satisfies Provable; +Field satisfies ProvablePure; +Field satisfies ProvableExtended; +Field satisfies Binable; + +// constructor +test(Random.field, Random.json.field, (x, y, assert) => { + let z = Field(x); + assert(z instanceof Field); + assert(z.toBigInt() === x); + assert(z.toString() === x.toString()); + assert(z.isConstant()); + deepEqual(z.toConstant(), z); + + assert((z = new Field(x)) instanceof Field && z.toBigInt() === x); + assert((z = Field(z)) instanceof Field && z.toBigInt() === x); + assert((z = Field(z.value)) instanceof Field && z.toBigInt() === x); + + z = Field(y); + assert(z instanceof Field); + assert(z.toString() === y); + deepEqual(Field.fromJSON(y), z); + assert(z.toJSON() === y); +}); + +// handles small numbers +test(Random.nat(1000), (n, assert) => { + assert(Field(n).toString() === String(n)); +}); +// handles large numbers 2^31 <= x < 2^53 +test(Random.int(2 ** 31, Number.MAX_SAFE_INTEGER), (n, assert) => { + assert(Field(n).toString() === String(n)); +}); +// handles negative numbers +test(Random.uint32, (n) => { + deepEqual(Field(-n), Field(n).neg()); +}); +// throws on fractional numbers +test.negative(Random.int(-10, 10), Random.fraction(1), (x, f) => { + Field(x + f); +}); +// correctly overflows the field +test(Random.field, Random.int(-5, 5), (x, k) => { + deepEqual(Field(x + BigInt(k) * Field.ORDER), Field(x)); +}); + +// special generator +let SmallField = Random.reject( + Random.field, + (x) => x.toString(2).length > Fp.sizeInBits - 2 +); + +// arithmetic, both in- and outside provable code +equivalent2((x, y) => x.add(y), Fp.add); +equivalent1((x) => x.neg(), Fp.negate); +equivalent2((x, y) => x.sub(y), Fp.sub); +equivalent2((x, y) => x.mul(y), Fp.mul); +equivalent1( + (x) => x.inv(), + (x) => Fp.inverse(x) ?? throwError('division by 0') +); +equivalent2( + (x, y) => x.div(y), + (x, y) => Fp.div(x, y) ?? throwError('division by 0') +); +equivalent1((x) => x.square(), Fp.square); +equivalent1( + (x) => x.sqrt(), + (x) => Fp.sqrt(x) ?? throwError('no sqrt') +); +equivalent2( + (x, y) => x.equals(y).toField(), + (x, y) => BigInt(x === y) +); +equivalent2( + (x, y) => x.lessThan(y).toField(), + (x, y) => BigInt(x < y), + SmallField +); +equivalent2( + (x, y) => x.lessThanOrEqual(y).toField(), + (x, y) => BigInt(x <= y), + SmallField +); +equivalentVoid2( + (x, y) => x.assertEquals(y), + (x, y) => x === y || throwError('not equal') +); +equivalentVoid2( + (x, y) => x.assertNotEquals(y), + (x, y) => x !== y || throwError('equal') +); +equivalentVoid2( + (x, y) => x.assertLessThan(y), + (x, y) => x < y || throwError('not less than'), + SmallField +); +equivalentVoid2( + (x, y) => x.assertLessThanOrEqual(y), + (x, y) => x <= y || throwError('not less than or equal'), + SmallField +); +equivalentVoid1( + (x) => x.assertBool(), + (x) => x === 0n || x === 1n || throwError('not boolean') +); + +// non-constant field vars +test(Random.field, (x0, assert) => { + Provable.runAndCheck(() => { + // Var + let x = Provable.witness(Field, () => Field(x0)); + assert(x.value[0] === FieldType.Var); + assert(typeof x.value[1] === 'number'); + throws(() => x.toConstant()); + throws(() => x.toBigInt()); + Provable.asProver(() => assert(x.toBigInt() === x0)); + + // Scale + let z = x.mul(2); + assert(z.value[0] === FieldType.Scale); + throws(() => x.toConstant()); + + // Add + let u = z.add(x); + assert(u.value[0] === FieldType.Add); + throws(() => x.toConstant()); + Provable.asProver(() => assert(u.toBigInt() === Fp.mul(x0, 3n))); + + // seal + let v = u.seal(); + assert(v.value[0] === FieldType.Var); + Provable.asProver(() => assert(v.toBigInt() === Fp.mul(x0, 3n))); + + // Provable.witness / assertEquals / assertNotEquals + let w0 = Provable.witness(Field, () => v.mul(5).add(1)); + let w1 = x.mul(15).add(1); + w0.assertEquals(w1); + throws(() => w0.assertNotEquals(w1)); + + let w2 = Provable.witness(Field, () => w0.add(1)); + w0.assertNotEquals(w2); + throws(() => w0.assertEquals(w2)); + }); +}); + +// some provable operations +test(Random.field, Random.field, (x0, y0, assert) => { + Provable.runAndCheck(() => { + // equals + let x = Provable.witness(Field, () => Field(x0)); + let y = Provable.witness(Field, () => Field(y0)); + + let b = x.equals(y); + b.assertEquals(x0 === y0); + Provable.asProver(() => assert(b.toBoolean() === (x0 === y0))); + + let c = x.equals(x0); + c.assertEquals(true); + Provable.asProver(() => assert(c.toBoolean())); + + // mul + let z = x.mul(y); + Provable.asProver(() => assert(z.toBigInt() === Fp.mul(x0, y0))); + + // toBits / fromBits + let bits = Fp.toBits(x0); + let x1 = Provable.witness(Field, () => Field.fromBits(bits)); + let bitsVars = x1.toBits(); + Provable.asProver(() => + assert(bitsVars.every((b, i) => b.toBoolean() === bits[i])) + ); + }); +}); + +// helpers + +function equivalent1( + op1: (x: Field) => Field, + op2: (x: bigint) => bigint, + rng: Random = Random.field +) { + test(rng, (x0, assert) => { + let x = Field(x0); + // outside provable code + handleErrors( + () => op1(x), + () => op2(x0), + (a, b) => assert(a.toBigInt() === b, 'equal results') + ); + // inside provable code + Provable.runAndCheck(() => { + x = Provable.witness(Field, () => x); + handleErrors( + () => op1(x), + () => op2(x0), + (a, b) => + Provable.asProver(() => assert(a.toBigInt() === b, 'equal results')) + ); + }); + }); +} +function equivalent2( + op1: (x: Field, y: Field | bigint) => Field, + op2: (x: bigint, y: bigint) => bigint, + rng: Random = Random.field +) { + test(rng, rng, (x0, y0, assert) => { + let x = Field(x0); + let y = Field(y0); + // outside provable code + handleErrors( + () => op1(x, y), + () => op2(x0, y0), + (a, b) => assert(a.toBigInt() === b, 'equal results') + ); + handleErrors( + () => op1(x, y0), + () => op2(x0, y0), + (a, b) => assert(a.toBigInt() === b, 'equal results') + ); + // inside provable code + Provable.runAndCheck(() => { + x = Provable.witness(Field, () => x); + y = Provable.witness(Field, () => y); + handleErrors( + () => op1(x, y), + () => op2(x0, y0), + (a, b) => + Provable.asProver(() => assert(a.toBigInt() === b, 'equal results')) + ); + handleErrors( + () => op1(x, y0), + () => op2(x0, y0), + (a, b) => + Provable.asProver(() => assert(a.toBigInt() === b, 'equal results')) + ); + }); + }); +} +function equivalentVoid1( + op1: (x: Field) => void, + op2: (x: bigint) => void, + rng: Random = Random.field +) { + test(rng, (x0) => { + let x = Field(x0); + // outside provable code + handleErrors( + () => op1(x), + () => op2(x0) + ); + // inside provable code + Provable.runAndCheck(() => { + x = Provable.witness(Field, () => x); + handleErrors( + () => op1(x), + () => op2(x0) + ); + }); + }); +} +function equivalentVoid2( + op1: (x: Field, y: Field | bigint) => void, + op2: (x: bigint, y: bigint) => void, + rng: Random = Random.field +) { + test(rng, rng, (x0, y0) => { + let x = Field(x0); + let y = Field(y0); + // outside provable code + handleErrors( + () => op1(x, y), + () => op2(x0, y0) + ); + handleErrors( + () => op1(x, y0), + () => op2(x0, y0) + ); + // inside provable code + Provable.runAndCheck(() => { + x = Provable.witness(Field, () => x); + y = Provable.witness(Field, () => y); + handleErrors( + () => op1(x, y), + () => op2(x0, y0) + ); + handleErrors( + () => op1(x, y0), + () => op2(x0, y0) + ); + }); + }); +} + +function handleErrors( + op1: () => T, + op2: () => S, + useResults?: (a: T, b: S) => R +): R | undefined { + let result1: T, result2: S; + let error1: Error | undefined; + let error2: Error | undefined; + try { + result1 = op1(); + } catch (err) { + error1 = err as Error; + } + try { + result2 = op2(); + } catch (err) { + error2 = err as Error; + } + if (!!error1 !== !!error2) { + error1 && console.log(error1); + error2 && console.log(error2); + } + deepEqual(!!error1, !!error2, 'equivalent errors'); + if (!(error1 || error2) && useResults !== undefined) { + return useResults(result1!, result2!); + } +} + +function throwError(message?: string): any { + throw Error(message); +} diff --git a/src/lib/hash.ts b/src/lib/hash.ts index d2da5d1347..dac9a8b6f7 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -1,6 +1,6 @@ import { HashInput, ProvableExtended, Struct } from './circuit_value.js'; -import { Poseidon as Poseidon_, Field } from '../snarky.js'; -import { inCheckedComputation } from './proof_system.js'; +import { Poseidon as Poseidon_ } from '../snarky.js'; +import { Field } from './core.js'; import { createHashHelpers } from './hash-generic.js'; import { Provable } from './provable.js'; @@ -23,7 +23,7 @@ class Sponge { private sponge: unknown; constructor() { - let isChecked = inCheckedComputation(); + let isChecked = Provable.inCheckedComputation(); this.sponge = Poseidon_.spongeCreate(isChecked); } diff --git a/src/lib/int.ts b/src/lib/int.ts index c9aeadd9c7..d9749a8acf 100644 --- a/src/lib/int.ts +++ b/src/lib/int.ts @@ -1,4 +1,4 @@ -import { Field, Bool } from '../snarky.js'; +import { Field, Bool } from './core.js'; import { AnyConstructor, CircuitValue, prop } from './circuit_value.js'; import { Types } from '../bindings/mina-transaction/types.js'; import { HashInput } from './hash.js'; diff --git a/src/lib/precondition.ts b/src/lib/precondition.ts index 9ba502aa73..53e044f6ed 100644 --- a/src/lib/precondition.ts +++ b/src/lib/precondition.ts @@ -1,4 +1,4 @@ -import { Bool, Field } from '../snarky.js'; +import { Bool, Field } from './core.js'; import { circuitValueEquals } from './circuit_value.js'; import { Provable } from './provable.js'; import * as Mina from './mina.js'; diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index eecc4e1f77..7ad981c6d1 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -4,7 +4,8 @@ import { EmptyVoid, } from '../bindings/lib/generic.js'; import { withThreadPool } from '../bindings/js/wrapper.js'; -import { Bool, Field, ProvablePure, Pickles, Poseidon } from '../snarky.js'; +import { Bool, ProvablePure, Pickles, Poseidon } from '../snarky.js'; +import { Field } from './core.js'; import { FlexibleProvable, FlexibleProvablePure, @@ -13,8 +14,8 @@ import { provablePure, toConstant, } from './circuit_value.js'; -import { Context } from './global-context.js'; import { Provable } from './provable.js'; +import { snarkContext } from './provable-context.js'; // public API export { @@ -44,29 +45,10 @@ export { methodArgumentsToConstant, methodArgumentTypesAndValues, isAsFields, - snarkContext, Prover, - inProver, - inCompile, - inAnalyze, - inCheckedComputation, - inCompileMode, dummyBase64Proof, }; -// global circuit-related context -type SnarkContext = { - witnesses?: unknown[]; - proverData?: any; - inProver?: boolean; - inCompile?: boolean; - inCheckedComputation?: boolean; - inAnalyze?: boolean; - inRunAndCheck?: boolean; - inWitnessBlock?: boolean; -}; -let snarkContext = Context.create({ default: {} }); - type Undefined = undefined; const Undefined: ProvablePureExtended = EmptyUndefined(); @@ -802,25 +784,6 @@ function Prover() { }; } -function inProver() { - return !!snarkContext.get().inProver; -} -function inCheckedComputation() { - let ctx = snarkContext.get(); - return !!ctx.inCompile || !!ctx.inProver || !!ctx.inCheckedComputation; -} -function inCompile() { - return !!snarkContext.get().inCompile; -} -function inAnalyze() { - return !!snarkContext.get().inAnalyze; -} - -function inCompileMode() { - let ctx = snarkContext.get(); - return !!ctx.inCompile || !!ctx.inAnalyze; -} - // helper types type Infer = T extends Subclass diff --git a/src/lib/proof_system.unit-test.ts b/src/lib/proof_system.unit-test.ts index 591ca24ed5..2d4ef86fb5 100644 --- a/src/lib/proof_system.unit-test.ts +++ b/src/lib/proof_system.unit-test.ts @@ -1,4 +1,5 @@ -import { Field, isReady, shutdown } from '../snarky.js'; +import { isReady, shutdown } from '../snarky.js'; +import { Field } from './core.js'; import { Struct } from './circuit_value.js'; import { UInt64 } from './int.js'; import { ZkProgram } from './proof_system.js'; diff --git a/src/lib/provable-context.ts b/src/lib/provable-context.ts new file mode 100644 index 0000000000..214c9e5c77 --- /dev/null +++ b/src/lib/provable-context.ts @@ -0,0 +1,110 @@ +import { Context } from './global-context.js'; +import { Gate, JsonGate, Snarky } from '../snarky.js'; +import { bytesToBigInt } from '../bindings/crypto/bigint-helpers.js'; + +// internal API +export { + snarkContext, + SnarkContext, + asProver, + runAndCheck, + runUnchecked, + constraintSystem, + inProver, + inAnalyze, + inCheckedComputation, + inCompile, + inCompileMode, + gatesFromJson, +}; + +// global circuit-related context + +type SnarkContext = { + witnesses?: unknown[]; + proverData?: any; + inProver?: boolean; + inCompile?: boolean; + inCheckedComputation?: boolean; + inAnalyze?: boolean; + inRunAndCheck?: boolean; + inWitnessBlock?: boolean; +}; +let snarkContext = Context.create({ default: {} }); + +// helpers to read circuit context + +function inProver() { + return !!snarkContext.get().inProver; +} +function inCheckedComputation() { + let ctx = snarkContext.get(); + return !!ctx.inCompile || !!ctx.inProver || !!ctx.inCheckedComputation; +} +function inCompile() { + return !!snarkContext.get().inCompile; +} +function inAnalyze() { + return !!snarkContext.get().inAnalyze; +} + +function inCompileMode() { + let ctx = snarkContext.get(); + return !!ctx.inCompile || !!ctx.inAnalyze; +} + +// runners for provable code + +function asProver(f: () => void) { + if (inCheckedComputation()) { + Snarky.asProver(f); + } else { + f(); + } +} + +function runAndCheck(f: () => void) { + let id = snarkContext.enter({ inCheckedComputation: true }); + try { + Snarky.runAndCheck(f); + } finally { + snarkContext.leave(id); + } +} + +function runUnchecked(f: () => void) { + let id = snarkContext.enter({ inCheckedComputation: true }); + try { + Snarky.runUnchecked(f); + } finally { + snarkContext.leave(id); + } +} + +function constraintSystem(f: () => T) { + let id = snarkContext.enter({ inAnalyze: true, inCheckedComputation: true }); + try { + let result: T; + let { rows, digest, json } = Snarky.constraintSystem(() => { + result = f(); + }); + let { gates, publicInputSize } = gatesFromJson(json); + return { rows, digest, result: result! as T, gates, publicInputSize }; + } finally { + snarkContext.leave(id); + } +} + +// helpers + +function gatesFromJson(cs: { gates: JsonGate[]; public_input_size: number }) { + let gates: Gate[] = cs.gates.map(({ typ, wires, coeffs: byteCoeffs }) => { + let coeffs = []; + for (let coefficient of byteCoeffs) { + let arr = new Uint8Array(coefficient); + coeffs.push(bytesToBigInt(arr).toString()); + } + return { type: typ, wires, coeffs }; + }); + return { publicInputSize: cs.public_input_size, gates }; +} diff --git a/src/lib/provable.ts b/src/lib/provable.ts index 1b8e30e389..ae844b626d 100644 --- a/src/lib/provable.ts +++ b/src/lib/provable.ts @@ -3,28 +3,32 @@ * - a namespace with tools for writing provable code * - the main interface for types that can be used in provable code */ -import { bytesToBigInt } from '../bindings/crypto/bigint-helpers.js'; import { Field, Bool } from './core.js'; -import { Gate, JsonGate, Provable as Provable_, Snarky } from '../snarky.js'; +import { Provable as Provable_, Snarky } from '../snarky.js'; import type { FlexibleProvable, ProvableExtended } from './circuit_value.js'; import { Context } from './global-context.js'; -import { - inCheckedComputation, - inProver, - snarkContext, -} from './proof_system.js'; import { HashInput, InferJson, InferProvable, InferredProvable, } from '../bindings/lib/provable-snarky.js'; +import { isField } from './field.js'; +import { + inCheckedComputation, + inProver, + snarkContext, + asProver, + runAndCheck, + runUnchecked, + constraintSystem, +} from './provable-context.js'; // external API export { Provable }; // internal API -export { memoizationContext, memoizeWitness, getBlindingValue, gatesFromJson }; +export { memoizationContext, memoizeWitness, getBlindingValue }; // TODO move type declaration here /** @@ -194,12 +198,10 @@ function witness = FlexibleProvable>( let id = snarkContext.enter({ ...ctx, inWitnessBlock: true }); try { - fields = Snarky.exists(type.sizeInFields(), () => { + let [, ...fieldVars] = Snarky.exists(type.sizeInFields(), () => { proverValue = compute(); let fields = type.toFields(proverValue); - - // TODO currently not needed, because fields are converted in OCaml, but will be - // fields = fields.map((x) => x.toConstant()); + 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 @@ -210,8 +212,9 @@ function witness = FlexibleProvable>( // }.` // ); // } - return fields; + return [0, ...fieldConstants]; }); + fields = fieldVars.map(Field); } finally { snarkContext.leave(id); } @@ -323,11 +326,14 @@ function ifImplicit(condition: Bool, x: T, y: T): T { `If x, y are Structs or other custom types, you can use the following:\n` + `Provable.if(bool, MyType, x, y)` ); - if (type !== y.constructor) + // TODO remove second condition once we have consolidated field class back into one + // if (type !== y.constructor) { + if (type !== y.constructor && !(isField(x) && isField(y))) { throw Error( 'Provable.if: Mismatched argument types. Try using an explicit type argument:\n' + `Provable.if(bool, MyType, x, y)` ); + } if (!('fromFields' in type && 'toFields' in type)) { throw Error( 'Provable.if: Invalid argument type. Try using an explicit type argument:\n' + @@ -376,10 +382,10 @@ function switch_>( return (type as Provable).fromFields(fields, aux); } -// runners for provable code +// logging in provable code function log(...args: any) { - Provable.asProver(() => { + asProver(() => { let prettyArgs = []; for (let arg of args) { if (arg?.toPretty !== undefined) prettyArgs.push(arg.toPretty()); @@ -395,46 +401,6 @@ function log(...args: any) { }); } -function asProver(f: () => void) { - if (inCheckedComputation()) { - Snarky.asProver(f); - } else { - f(); - } -} - -function runAndCheck(f: () => void) { - let id = snarkContext.enter({ inCheckedComputation: true }); - try { - Snarky.runAndCheck(f); - } finally { - snarkContext.leave(id); - } -} - -function runUnchecked(f: () => void) { - let id = snarkContext.enter({ inCheckedComputation: true }); - try { - Snarky.runUnchecked(f); - } finally { - snarkContext.leave(id); - } -} - -function constraintSystem(f: () => T) { - let id = snarkContext.enter({ inAnalyze: true, inCheckedComputation: true }); - try { - let result: T; - let { rows, digest, json } = Snarky.constraintSystem(() => { - result = f(); - }); - let { gates, publicInputSize } = gatesFromJson(json); - return { rows, digest, result: result! as T, gates, publicInputSize }; - } finally { - snarkContext.leave(id); - } -} - // helpers function checkLength(name: string, xs: Field[], ys: Field[]) { @@ -448,18 +414,6 @@ function checkLength(name: string, xs: Field[], ys: Field[]) { return n; } -function gatesFromJson(cs: { gates: JsonGate[]; public_input_size: number }) { - let gates: Gate[] = cs.gates.map(({ typ, wires, coeffs: byteCoeffs }) => { - let coeffs = []; - for (let coefficient of byteCoeffs) { - let arr = new Uint8Array(coefficient); - coeffs.push(bytesToBigInt(arr).toString()); - } - return { type: typ, wires, coeffs }; - }); - return { publicInputSize: cs.public_input_size, gates }; -} - function clone>(type: S, value: T): T { let fields = type.toFields(value); let aux = type.toAuxiliary?.(value) ?? []; diff --git a/src/lib/signature.ts b/src/lib/signature.ts index b954c5084f..b5fe6e2c39 100644 --- a/src/lib/signature.ts +++ b/src/lib/signature.ts @@ -1,4 +1,5 @@ -import { Group, Field, Bool, Scalar, Ledger } from '../snarky.js'; +import { Group, Bool, Scalar, Ledger } from '../snarky.js'; +import { Field } from './core.js'; import { prop, CircuitValue, AnyConstructor } from './circuit_value.js'; import { hashWithPrefix } from './hash.js'; import { diff --git a/src/lib/state.ts b/src/lib/state.ts index edfd9077b5..1c4c464e49 100644 --- a/src/lib/state.ts +++ b/src/lib/state.ts @@ -1,13 +1,13 @@ -import { Field, ProvablePure } from '../snarky.js'; +import { ProvablePure } from '../snarky.js'; import { FlexibleProvablePure } from './circuit_value.js'; import { AccountUpdate, TokenId } from './account_update.js'; import { PublicKey } from './signature.js'; import * as Mina from './mina.js'; import { fetchAccount } from './fetch.js'; -import { inCheckedComputation, inProver } from './proof_system.js'; import { SmartContract } from './zkapp.js'; import { Account } from './mina/account.js'; import { Provable } from './provable.js'; +import { Field } from '../lib/core.js'; // external API export { State, state, declareState }; @@ -240,14 +240,14 @@ function createState(): InternalStateType { this._contract.cachedVariable !== undefined && // `inCheckedComputation() === true` here always implies being inside a wrapped smart contract method, // which will ensure that the cache is cleaned up before & after each method run. - inCheckedComputation() + Provable.inCheckedComputation() ) { this._contract.wasRead = true; return this._contract.cachedVariable; } let layout = getLayoutPosition(this._contract); let contract = this._contract; - let inProver_ = inProver(); + let inProver_ = Provable.inProver(); let stateFieldsType = Provable.Array(Field, layout.length); let stateAsFields = Provable.witness(stateFieldsType, () => { let account: Account; @@ -282,7 +282,8 @@ function createState(): InternalStateType { }); let state = this._contract.stateType.fromFields(stateAsFields); - if (inCheckedComputation()) this._contract.stateType.check?.(state); + if (Provable.inCheckedComputation()) + this._contract.stateType.check?.(state); this._contract.wasRead = true; this._contract.cachedVariable = state; return state; diff --git a/src/lib/string.ts b/src/lib/string.ts index ff3110e28c..623aa4a72e 100644 --- a/src/lib/string.ts +++ b/src/lib/string.ts @@ -1,4 +1,4 @@ -import { Bool, Field } from '../snarky.js'; +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'; diff --git a/src/lib/testing/random.ts b/src/lib/testing/random.ts index 291ace472d..f9d1919502 100644 --- a/src/lib/testing/random.ts +++ b/src/lib/testing/random.ts @@ -295,6 +295,7 @@ const Random = Object.assign(Random_, { withHardCoded, dependent, apply, + reject, dice: Object.assign(dice, { ofSize: diceOfSize() }), field, bool, diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index a9b40112a5..f80f3c4dc1 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -1,13 +1,13 @@ import { Types } from '../bindings/mina-transaction/types.js'; import { Bool, - Field, Gate, Ledger, Pickles, Poseidon as Poseidon_, ProvablePure, } from '../snarky.js'; +import { Field } from './core.js'; import { AccountUpdate, AccountUpdatesLayout, @@ -51,20 +51,22 @@ import { emptyValue, GenericArgument, getPreviousProofsForProver, - inAnalyze, - inCompile, - inProver, isAsFields, methodArgumentsToConstant, methodArgumentTypesAndValues, MethodInterface, Proof, - snarkContext, sortMethodArguments, } from './proof_system.js'; import { PrivateKey, PublicKey } from './signature.js'; import { assertStatePrecondition, cleanStatePrecondition } from './state.js'; import { CatchAndPrettifyStacktraceForAllMethods } from './errors.js'; +import { + inAnalyze, + inCompile, + inProver, + snarkContext, +} from './provable-context.js'; // external API export { @@ -169,8 +171,6 @@ function wrapMethod( } }); - // TODO: the callback case is actually more similar to the composability - // case below, should reconcile with that to get the same callData hashing let insideContract = smartContractContext.get(); if (!insideContract) { return smartContractContext.runWith( diff --git a/src/mina-signer/src/signature.unit-test.ts b/src/mina-signer/src/signature.unit-test.ts index a1ba3f526f..b9fbe74747 100644 --- a/src/mina-signer/src/signature.unit-test.ts +++ b/src/mina-signer/src/signature.unit-test.ts @@ -7,12 +7,8 @@ import { verify, verifyFieldElement, } from './signature.js'; -import { - isReady, - Ledger, - Field as FieldSnarky, - shutdown, -} from '../../snarky.js'; +import { isReady, Ledger, shutdown } 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 { PrivateKey as PrivateKeySnarky } from '../../lib/signature.js'; diff --git a/src/mina-signer/tests/verify-in-snark.unit-test.ts b/src/mina-signer/tests/verify-in-snark.unit-test.ts index fcaf12ed60..7edc6ee8fc 100644 --- a/src/mina-signer/tests/verify-in-snark.unit-test.ts +++ b/src/mina-signer/tests/verify-in-snark.unit-test.ts @@ -1,4 +1,5 @@ -import { Field, isReady, shutdown } from '../../snarky.js'; +import { isReady, shutdown } from '../../snarky.js'; +import { Field } from '../../lib/core.js'; import { ZkProgram } from '../../lib/proof_system.js'; import Client from '../MinaSigner.js'; import { PrivateKey, Signature } from '../../lib/signature.js'; diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 08d9fa185b..5b57ce985c 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -1,6 +1,8 @@ import type { Account as JsonAccount } from './bindings/mina-transaction/gen/transaction-json.js'; +import type { Field, FieldConst, FieldVar } from './lib/field.js'; +// export { Field }; +export { SnarkyField }; export { - Field, Bool, Group, Scalar, @@ -21,7 +23,7 @@ export { Snarky, Test, JsonGate }; * `Provable` is the general circuit type interface. Provable interface describes how a type `T` is made up of field elements and auxiliary (non-field element) data. * * You will find `Provable` as the required input type in a few places in SnarkyJS. One convenient way to create a `Provable` is using `Struct`. - * + * * The properties and methods on the provable type exist in all base SnarkyJS types as well (aka. {@link Field}, {@link Bool}, etc.). In most cases, a zkApp developer does not need these functions to create Dapps. */ declare interface Provable { @@ -45,11 +47,19 @@ declare interface ProvablePure extends Provable { check: (x: T) => void; } +// ocaml types +type MlTuple = [0, X, Y]; +type MlArray = [0, ...T[]]; +type MlList = [0, T, 0 | MlList]; +type MlOption = 0 | [0, T]; + declare namespace Snarky { type Keypair = unknown; type VerificationKey = unknown; type Proof = unknown; } +// same representation, but use a different name to communicate intent / constraints +type BoolVar = FieldVar; /** * Internal interface to snarky-ml @@ -59,8 +69,18 @@ declare namespace Snarky { declare const Snarky: { /** * 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: () => Field[]): Field[]; + exists( + sizeInFields: number, + compute: () => MlArray + ): MlArray; + /** + * witness a single field element variable + */ + existsVar(compute: () => FieldConst): FieldVar; /** * Runs code as a prover. */ @@ -82,6 +102,81 @@ declare const Snarky: { json: JsonConstraintSystem; }; + /** + * APIs to add constraints on field variables + */ + field: { + /** + * add x, y to get a new AST node Add(x, y); handles if x, y are constants + */ + add(x: FieldVar, y: FieldVar): FieldVar; + /** + * scale x by a constant to get a new AST node Scale(c, x); handles if x is a constant + */ + scale(c: FieldConst, x: FieldVar): FieldVar; + /** + * witnesses z = x*y and constrains it with [assert_r1cs]; handles constants + */ + mul(x: FieldVar, y: FieldVar): FieldVar; + /** + * evaluates a CVar by walking the AST and reading Vars from a list of public input + aux values + */ + readVar(x: FieldVar): FieldConst; + /** + * x === y without handling of constants + */ + assertEqual(x: FieldVar, y: FieldVar): void; + /** + * x*y === z without handling of constants + */ + assertMul(x: FieldVar, y: FieldVar, z: FieldVar): void; + /** + * x*x === y without handling of constants + */ + assertSquare(x: FieldVar, y: FieldVar): void; + /** + * x*x === x without handling of constants + */ + assertBoolean(x: FieldVar): void; + /** + * check x < y and x <= y + */ + compare( + bitLength: number, + x: FieldVar, + y: FieldVar + ): [flag: 0, less: BoolVar, lessOrEqual: BoolVar]; + /** + * + */ + toBits(length: number, x: FieldVar): MlArray; + /** + * + */ + fromBits(bits: MlArray): FieldVar; + /** + * returns x truncated to the lowest `16 * lengthDiv16` bits + * => can be used to assert that x fits in `16 * lengthDiv16` bits. + * + * more efficient than `toBits()` because it uses the EC_endoscalar gate; + * does 16 bits per row (vs 1 bits per row that you can do with generic gates). + */ + truncateToBits16(lengthDiv16: number, x: FieldVar): FieldVar; + /** + * returns a new witness from an AST + * (implemented with toConstantAndTerms) + */ + seal(x: FieldVar): FieldVar; + /** + * Unfolds AST to get `x = c + c0*Var(i0) + ... + cn*Var(in)`, + * returns `(c, [(c0, i0), ..., (cn, in)])`; + * c is optional + */ + toConstantAndTerms( + x: FieldVar + ): MlTuple, MlList>>; + }; + /** * The circuit API is a low level interface to create zero-knowledge proofs */ @@ -150,7 +245,7 @@ type JsonConstraintSystem = { gates: JsonGate[]; public_input_size: number }; * Field(3.141); // ERROR: Cannot convert a float to a field element * Field("abc"); // ERROR: Invalid argument "abc" * ``` - * + * * Creating a Field from a negative number can result in unexpected behavior if you are not familiar with [modular arithmetic](https://en.wikipedia.org/wiki/Modular_arithmetic). * @example * ``` @@ -164,15 +259,16 @@ type JsonConstraintSystem = { gates: JsonGate[]; public_input_size: number }; * * @return A {@link Field} with the value converted from the argument */ -declare function Field(value: Field | number | string | boolean | bigint): Field; - -declare class Field { +declare function SnarkyField( + x: SnarkyField | Field | number | string | boolean | bigint +): SnarkyField; +declare class SnarkyField { /** * Coerce anything "field-like" (bigint, boolean, number, string, and {@link Field}) to a {@link Field}. * A {@link Field} is an element of a prime order field. Every other provable type is build using the {@link Field} type. - * + * * The field is the [pasta base field](https://electriccoin.co/blog/the-pasta-curves-for-halo-2-and-beyond/) of order 2^254 + 0x224698fc094cf91b992d30ed00000001 ({@link Field.ORDER}). - * + * * You can create a new Field from everything "field-like" (`bigint`, integer `number`, decimal `string`, `Field`). * @example * ``` @@ -186,18 +282,18 @@ declare class Field { * new Field(3.141); // ERROR: Cannot convert a float to a field element * new Field("abc"); // ERROR: Invalid argument "abc" * ``` - * -Creating a {@link Field} from a negative number can result in unexpected behavior if you are not familiar with [modular arithmetic](https://en.wikipedia.org/wiki/Modular_arithmetic).``` + * + * Creating a {@link Field} from a negative number can result in unexpected behavior if you are not familiar with [modular arithmetic](https://en.wikipedia.org/wiki/Modular_arithmetic).``` * @example * ``` * const x = new Field(-1); // Valid Field construction from negative number * const y = new Field(Field.ORDER - 1n); // equivalent to `x` * ``` - * + * * **Important**: All the functions defined on a Field (arithmetic, logic, etc.) take their arguments as "field-like". A {@link Field} itself is also defined as a "field-like" value. - * + * * @param value - the value to coerce to a {@link Field} - * + * * @return A {@link Field} element which the value coerced from the argument in the pasta field. */ constructor(value: Field | number | string | boolean | bigint); @@ -210,21 +306,21 @@ Creating a {@link Field} from a negative number can result in unexpected behavio * const negOne = Field(1).neg(); * negOne.assertEquals(-1); * ``` - * + * * @example * ```ts * const someField = Field(42); * someField.neg().assertEquals(someField.mul(Field(-1))); // This statement is always true regardless of the value of `someField` * ``` - * + * * **Warning**: This is a modular negation. For details, see the {@link sub} method. - * + * * @return A {@link Field} element that is equivalent to the element multiplied by -1. */ neg(): Field; /** - * [Modular inverse](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse) of this {@link Field} element. Equivalent to 1 divided by this {@link Field}, in the sense of modular arithmetic. + * [Modular inverse](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse) of this {@link Field} element. Equivalent to 1 divided by this {@link Field}, in the sense of modular arithmetic. * * @example * ```ts @@ -232,7 +328,7 @@ Creating a {@link Field} from a negative number can result in unexpected behavio * const inverse = someField.inv(); * inverse.assertEquals(Field(1).div(example)); // This statement is always true regardless of the value of `someField` * ``` - * + * * **Warning**: This is a modular inverse. See {@link div} method for more details. * * @return A {@link Field} element that is equivalent to one divided by this element. @@ -246,28 +342,28 @@ Creating a {@link Field} from a negative number can result in unexpected behavio * ```ts * const x = Field(3); * const sum = x.add(5); - * + * * sum.assertEquals(Field(8)); * ``` - * + * * **Warning**: This is a modular addition in the pasta field. * @example * ```ts * const x = Field(1); * const sum = x.add(Field(-7)); - * + * * // If you try to print sum - `console.log(sum.toBigInt())` - you will realize that it prints a very big integer because this is modular arithmetic, and 1 + (-7) circles around the field to become p - 6. * // You can use the reverse operation of addition (substraction) to prove the sum is calculated correctly. - * + * * sum.sub(x).assertEquals(Field(-7)); * sum.sub(Field(-7)).assertEquals(x); * ``` - * + * * @param value - a "field-like" value to add to the {@link Field}. - * + * * @return A {@link Field} element equivalent to the modular addition of the two value. */ - add(value: Field | number | string | boolean): Field; + add(y: Field | number | string | bigint): Field; /** * Substract another "field-like" value from this {@link Field} element. @@ -276,27 +372,27 @@ Creating a {@link Field} from a negative number can result in unexpected behavio * ```ts * const x = Field(3); * const difference = x.sub(5); - * + * * difference.assertEquals(Field(-2)); * ``` - * + * * **Warning**: This is a modular substraction in the pasta field. - * + * * @example * ```ts * const x = Field(1); * const difference = x.sub(Field(2)); - * + * * // If you try to print difference - `console.log(difference.toBigInt())` - you will realize that it prints a very big integer because this is modular arithmetic, and 1 - 2 circles around the field to become p - 1. * // You can use the reverse operation of substraction (addition) to prove the difference is calculated correctly. * difference.add(Field(2)).assertEquals(x); * ``` - * + * * @param value - a "field-like" value to substract from the {@link Field}. - * + * * @return A {@link Field} element equivalent to the modular difference of the two value. */ - sub(value: Field | number | string | boolean): Field; + sub(y: Field | number | string | bigint): Field; /** * Multiply another "field-like" value with this {@link Field} element. @@ -305,15 +401,15 @@ Creating a {@link Field} from a negative number can result in unexpected behavio * ```ts * const x = Field(3); * const product = x.mul(Field(5)); - * + * * product.assertEquals(Field(15)); * ``` - * + * * @param value - a "field-like" value to multiply with the {@link Field}. - * + * * @return A {@link Field} element equivalent to the modular difference of the two value. */ - mul(value: Field | number | string | boolean): Field; + mul(y: Field | number | string | bigint): Field; /** * Divide another "field-like" value through this {@link Field}. @@ -322,30 +418,30 @@ Creating a {@link Field} from a negative number can result in unexpected behavio * ```ts * const x = Field(6); * const quotient = x.div(Field(3)); - * + * * quotient.assertEquals(Field(2)); * ``` - * + * * **Warning**: This is a modular division in the pasta field. You can think this as the reverse operation of modular multiplication. - * + * * @example * ```ts * const x = Field(2); * const y = Field(5); - * + * * const quotient = x.div(y); - * + * * // If you try to print quotient - `console.log(quotient.toBigInt())` - you will realize that it prints a very big integer because this is a modular inverse. * // You can use the reverse operation of division (multiplication) to prove the quotient is calculated correctly. - * + * * quotient.mul(y).assertEquals(x); * ``` - * + * * @param value - a "field-like" value to divide with the {@link Field}. - * + * * @return A {@link Field} element equivalent to the modular division of the two value. */ - div(value: Field | number | string | boolean): Field; + div(y: Field | number | string | bigint): Field; /** * Square this {@link Field} element. @@ -354,12 +450,12 @@ Creating a {@link Field} from a negative number can result in unexpected behavio * ```ts * const someField = Field(7); * const square = someField.square(); - * + * * square.assertEquals(someField.mul(someField)); // This statement is always true regardless of the value of `someField` * ``` - * + * * ** Warning: This is a modular multiplication. See `mul()` method for more details. - * + * * @return A {@link Field} element equivalent to the multiplication of the {@link Field} element with itself. */ square(): Field; @@ -371,57 +467,57 @@ Creating a {@link Field} from a negative number can result in unexpected behavio * ```ts * const someField = Field(42); * const squareRoot = someField.sqrt(); - * + * * squareRoot.mul(squareRoot).assertEquals(someField); // This statement is always true regardless of the value of `someField` * ``` - * + * * **Warning**: This is a modular square root. See `div()` method for more details. - * + * * @return A {@link Field} element equivalent to the square root of the {@link Field} element. */ sqrt(): Field; /** * Serialize the {@link Field} to a string, e.g. for printing. Trying to print a {@link Field} without this function will directly stringify the Field object, resulting in an unreadable output. - * + * * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the {@link Field}. Use the operation only during debugging. - * + * * @example * ```ts * const someField = Field(42); * console.log(someField.toString()); * ``` - * + * * @return A string equivalent to the string representation of the Field. */ toString(): string; /** * Serialize the {@link Field} to a bigint, e.g. for printing. Trying to print a {@link Field} without this function will directly stringify the Field object, resulting in an unreadable output. - * + * * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the bigint representation of the {@link Field}. Use the operation only during debugging. - * + * * @example * ```ts * const someField = Field(42); * console.log(someField.toBigInt()); * ``` - * + * * @return A bigint equivalent to the bigint representation of the Field. */ toBigInt(): bigint; /** * Serialize the {@link Field} to a JSON string, e.g. for printing. Trying to print a {@link Field} without this function will directly stringify the Field object, resulting in an unreadable output. - * + * * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the JSON string representation of the {@link Field}. Use the operation only during debugging. - * + * * @example * ```ts * const someField = Field(42); * console.log(someField.toJSON()); * ``` - * + * * @return A string equivalent to the JSON representation of the {@link Field}. */ toJSON(): string; @@ -430,7 +526,7 @@ Creating a {@link Field} from a negative number can result in unexpected behavio * This function is the implementation of {@link Provable.toFields} in {@link Field} type. * You can use this array to calculate the {@link Poseidon} hash of a {@link Field}. * This data structure will be always an array of length 1, where the first and only element equals the {@link Field} itself. - * + * * @return A {@link Field} array of length 1 created from this {@link Field}. */ toFields(): Field[]; @@ -443,19 +539,19 @@ Creating a {@link Field} from a negative number can result in unexpected behavio * ```ts * Field(2).lessThan(3).assertEquals(Bool(true)); * ``` - * + * * **Warning**: As this method compares the bigint value of a {@link Field}, it can result in unexpected behavior when used with negative inputs or modular division. - * + * * @example * ```ts * Field(1).div(Field(3)).lessThan(Field(1).div(Field(2))).assertEquals(Bool(true)); // This code will throw an error * ``` - * + * * @param value - the "field-like" value to compare with this {@link Field}. - * + * * @return A {@link Bool} representing if this {@link Field} is less than another "field-like" value. */ - lessThan(value: Field | number | string | boolean): Bool; + lessThan(value: Field | number | string | bigint): Bool; /** * Check if this {@link Field} is less than or equal to another "field-like" value. @@ -465,19 +561,19 @@ Creating a {@link Field} from a negative number can result in unexpected behavio * ```ts * Field(3).lessThanOrEqual(3).assertEquals(Bool(true)); * ``` - * + * * **Warning**: As this method compares the bigint value of a {@link Field}, it can result in unexpected behaviour when used with negative inputs or modular division. - * + * * @example * ```ts * Field(1).div(Field(3)).lessThanOrEqual(Field(1).div(Field(2))).assertEquals(Bool(true)); // This code will throw an error * ``` - * + * * @param value - the "field-like" value to compare with this {@link Field}. - * + * * @return A {@link Bool} representing if this {@link Field} is less than or equal another "field-like" value. */ - lessThanOrEqual(value: Field | number | string | boolean): Bool; + lessThanOrEqual(value: Field | number | string | bigint): Bool; /** * Check if this {@link Field} is greater than another "field-like" value. @@ -487,20 +583,20 @@ Creating a {@link Field} from a negative number can result in unexpected behavio * ```ts * Field(5).greaterThan(3).assertEquals(Bool(true)); * ``` - * + * * **Warning**: As this method compares the bigint value of a {@link Field}, it can result in unexpected behaviour when used with negative inputs or modular division. - * + * * @example * ```ts * Field(1).div(Field(2)).greaterThan(Field(1).div(Field(3))).assertEquals(Bool(true)); // This code will throw an error * ``` - * + * * @param value - the "field-like" value to compare with this {@link Field}. - * + * * @return A {@link Bool} representing if this {@link Field} is greater than another "field-like" value. */ - greaterThan(value: Field | number | string | boolean): Bool; - + greaterThan(value: Field | number | string | bigint): Bool; + /** * Check if this {@link Field} is greater than or equal another "field-like" value. * Returns a {@link Bool}, which is a provable type and can be used to prove the validity of this statement. @@ -509,126 +605,135 @@ Creating a {@link Field} from a negative number can result in unexpected behavio * ```ts * Field(3).greaterThanOrEqual(3).assertEquals(Bool(true)); * ``` - * + * * **Warning**: As this method compares the bigint value of a {@link Field}, it can result in unexpected behaviour when used with negative inputs or modular division. - * + * * @example * ```ts * Field(1).div(Field(2)).greaterThanOrEqual(Field(1).div(Field(3))).assertEquals(Bool(true)); // This code will throw an error * ``` - * + * * @param value - the "field-like" value to compare with this {@link Field}. - * + * * @return A {@link Bool} representing if this {@link Field} is greater than or equal another "field-like" value. */ - greaterThanOrEqual(value: Field | number | string | boolean): Bool; + greaterThanOrEqual(value: Field | number | string | bigint): Bool; /** * Assert that this {@link Field} is less than another "field-like" value. * Calling this function is equivalent to `Field(...).lessThan(...).assertEquals(Bool(true))`. * See {@link Field.lessThan} for more details. - * + * * **Important**: If an assertion fails, the code throws an error. - * + * * @param value - the "field-like" value to compare & assert with this {@link Field}. * @param message? - a string error message to print if the assertion fails, optional. */ - assertLessThan(value: Field | number | string | boolean, message?: string): void; - + assertLessThan(y: Field | number | string | bigint, message?: string): void; + /** * Assert that this {@link Field} is less than or equal to another "field-like" value. * Calling this function is equivalent to `Field(...).lessThanOrEqual(...).assertEquals(Bool(true))`. * See {@link Field.lessThanOrEqual} for more details. - * + * * **Important**: If an assertion fails, the code throws an error. - * + * * @param value - the "field-like" value to compare & assert with this {@link Field}. * @param message? - a string error message to print if the assertion fails, optional. */ - assertLessThanOrEqual(value: Field | number | string | boolean, message?: string): void; + assertLessThanOrEqual( + y: Field | number | string | bigint, + message?: string + ): void; /** * Assert that this {@link Field} is greater than another "field-like" value. * Calling this function is equivalent to `Field(...).greaterThan(...).assertEquals(Bool(true))`. * See {@link Field.greaterThan} for more details. - * + * * **Important**: If an assertion fails, the code throws an error. - * + * * @param value - the "field-like" value to compare & assert with this {@link Field}. * @param message? - a string error message to print if the assertion fails, optional. */ - assertGreaterThan(value: Field | number | string | boolean, message?: string): void; + assertGreaterThan( + value: Field | number | string | bigint, + message?: string + ): void; /** * Assert that this {@link Field} is greater than or equal to another "field-like" value. * Calling this function is equivalent to `Field(...).greaterThanOrEqual(...).assertEquals(Bool(true))`. * See {@link Field.greaterThanOrEqual} for more details. - * + * * **Important**: If an assertion fails, the code throws an error. - * + * * @param value - the "field-like" value to compare & assert with this {@link Field}. * @param message? - a string error message to print if the assertion fails, optional. */ - assertGreaterThanOrEqual(value: Field | number | string | boolean, message?: string): void; + assertGreaterThanOrEqual( + y: Field | number | string | bigint, + message?: string + ): void; /** * @deprecated Deprecated - use {@link lessThan} instead. */ - lt(value: Field | number | string | boolean): Bool; - + lt(value: Field | number | string | bigint): Bool; + /** * @deprecated Deprecated - use {@link lessThanOrEqual} instead. */ - lte(value: Field | number | string | boolean): Bool; - + lte(value: Field | number | string | bigint): Bool; + /** * @deprecated Deprecated - use `{@link greaterThan}` instead. */ - gt(value: Field | number | string | boolean): Bool; - + gt(value: Field | number | string | bigint): Bool; + /** * @deprecated Deprecated - use {@link greaterThanOrEqual} instead. */ - gte(value: Field | number | string | boolean): Bool; + gte(value: Field | number | string | bigint): Bool; /** * @deprecated Deprecated - use {@link assertLessThan} instead */ - assertLt(value: Field | number | string | boolean, message?: string): void; - + assertLt(value: Field | number | string | bigint, message?: string): void; + /** * @deprecated Deprecated - use {@link assertLessThanOrEqual}instead */ - assertLte(value: Field | number | string | boolean, message?: string): void; - + assertLte(value: Field | number | string | bigint, message?: string): void; + /** * @deprecated Deprecated - use {@link assertGreaterThan} instead */ - assertGt(value: Field | number | string | boolean, message?: string): void; - + assertGt(value: Field | number | string | bigint, message?: string): void; + /** * @deprecated Deprecated - use {@link assertGreaterThanOrEqual} instead */ - assertGte(value: Field | number | string | boolean, message?: string): void; + assertGte(value: Field | number | string | bigint, message?: string): void; /** * Assert that this {@link Field} is equal another "field-like" value. * Calling this function is equivalent to `Field(...).equals(...).assertEquals(Bool(true))`. * See {@link Field.equals} for more details. - * + * * **Important**: If an assertion fails, the code throws an error. - * + * * @param value - the "field-like" value to compare & assert with this {@link Field}. * @param message? - a string error message to print if the assertion fails, optional. */ - assertEquals(value: Field | number | string | boolean, message?: string): void; + assertEquals(y: Field | number | string | bigint, message?: string): void; /** * Assert that this {@link Field} is equal to 1 or 0 as a "field-like" value. * Calling this function is equivalent to `Bool.or(Field(...).equals(1), Field(...).equals(0)).assertEquals(Bool(true))`. - * + * * **Important**: If an assertion fails, the code throws an error. - * + * * @param value - the "field-like" value to compare & assert with this {@link Field}. * @param message? - a string error message to print if the assertion fails, optional. */ @@ -643,16 +748,16 @@ Creating a {@link Field} from a negative number can result in unexpected behavio * Checks if this {@link Field} is 0, * Calling this function is equivalent to `Field(...).equals(Field(0))`. * See {@link Field.equals} for more details. - * + * * @return A {@link Bool} representing if this {@link Field} equals 0. */ isZero(): Bool; /** * Returns an array of {@link Bool} elements representing [little endian binary representation](https://en.wikipedia.org/wiki/Endianness) of this {@link Field} element. - * + * * **Warning**: This is a costly operation in a zk proof, because a Field can have 255 bits, each of which has to be constrained to prove that the bits equal the original field element. To reduce the maximum amount of bits that the input can have, use this method with the optional `length` argument. - * + * * @return An array of {@link Bool} element representing little endian binary representation of this {@link Field}. */ toBits(): Bool[]; @@ -660,11 +765,11 @@ Creating a {@link Field} from a negative number can result in unexpected behavio /** * Returns an array of {@link Bool} elements representing [little endian binary representation](https://en.wikipedia.org/wiki/Endianness) of this {@link Field} element. * Throws an error if the element cannot fit in `length` bits. - * + * * **Warning**: The cost of this operation in a zk proof depends on the `length` you specify, so prefer a smaller value if possible. - * + * * @param length - the number of bits to fit the element. If the element does not fit in `length` bits, the functions throws an error. - * + * * @return An array of {@link Bool} element representing little endian binary representation of this {@link Field}. */ toBits(length: number): Bool[]; @@ -677,32 +782,32 @@ Creating a {@link Field} from a negative number can result in unexpected behavio * ```ts * Field(5).equals(5).assertEquals(Bool(true)); * ``` - * + * * @param value - the "field-like" value to compare with this {@link Field}. - * + * * @return A {@link Bool} representing if this {@link Field} is equal another "field-like" value. */ - equals(value: Field | number | string | boolean): Bool; + equals(y: Field | number | string | bigint): Bool; /** * **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer. - * + * * In SnarkyJS, addition and scaling (multiplication of variables by a constant) of variables is represented as an AST - [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree). For example, the expression `x.add(y).mul(2)` is represented as `Scale(2, Add(x, y))`. - * + * * A new internal variable is created only when the variable is needed in a multiplicative or any higher level constraint (for example multiplication of two {@link Field} elements) to represent the operation. - * + * * The `seal()` function tells SnarkyJS to stop building an AST and create a new variable right away. - * + * * @return A {@link Field} element that is equal to the result of AST that was previously on this {@link Field} element. */ seal(): Field; - + /** * Create a new {@link Field} element from the first `numBits` of this {@link Field} element. * As {@link Field} elements are represented using [little endian binary representation](https://en.wikipedia.org/wiki/Endianness), the resulting {@link Field} element will equal the original one if the variable fits in `numBits` bits. - * + * * @param numBits - The number of bits to take from this {@link Field} element. - * + * * @return A {@link Field} element that is equal to the `numBits` of this {@link Field} element. */ rangeCheckHelper(numBits: number): Field; @@ -710,19 +815,19 @@ Creating a {@link Field} from a negative number can result in unexpected behavio /** * Check whether this {@link Field} element is a hard-coded constant in the Circuit. * If a {@link Field} is constructed outside a zkApp method, it is a constant. - * + * * @example * ```ts * console.log(Field(42).isConstant()); // True * ``` - * + * * @example * ```ts * @method(x: Field) { * console.log(x.isConstant()); // False * } * ``` - * + * * @return A `boolean` showing if this {@link Field} is a constant or not. */ isConstant(): boolean; @@ -730,13 +835,13 @@ Creating a {@link Field} from a negative number can result in unexpected behavio /** * Create a {@link Field} element equivalent to this {@link Field} elements value, but it is a constant in the Circuit. * See {@link Field.isConstant} for more information about what is a constant {@link Field}. - * + * * @example * ```ts * const someField = Field(42); * someField.toConstant().assertEquals(someField); // Always true * ``` - * + * * @return A constant {@link Field} element equivalent to this {@link Field} element. */ toConstant(): Field; @@ -772,25 +877,25 @@ Creating a {@link Field} from a negative number can result in unexpected behavio /** * A random {@link Field} element. - * + * * @example * ```ts * console.log(Field.random().toBigInt()); // Run this code twice! * ``` - * + * * @return A random {@link Field} element. */ static random(): Field; /** * **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer. - * + * * Creates a {@link Field} from an array of length 1 serialized from {@link Field} elements. * Calling this function is equivalent to `fields[0]`, the first index of the {@link Field} array. * This function might seem unnecessary for Dapps since it is designed as the reverse function of {@link Field.toFields}. - * + * * @param fields - an array of length 1 serialized from {@link Field} elements. - * + * * @return The first {@link Field} element of the given array. */ static fromFields(fields: Field[]): Field; @@ -799,14 +904,14 @@ Creating a {@link Field} from a negative number can result in unexpected behavio * This function is the implementation of {@link Provable.sizeInFields} in {@link Field} type. * Size of the {@link Field} type is always 1, as it is the primitive type. * This function returns a reular 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. - * + * * This function has the same utility as the {@link Field.sizeInFields}. - * + * * @example * ```ts * console.log(Field.sizeInFields()); // Prints 1 * ``` - * + * * @return A number representing the size of the {@link Field} type in terms of {@link Field} type itself. */ static sizeInFields(): number; @@ -816,9 +921,9 @@ Creating a {@link Field} from a negative number can result in unexpected behavio * Static function to serializes a {@link Field} into an array of {@link Field} elements. * You can use this array to calculate the {@link Poseidon} hash of a {@link Field}. * This will be always an array of length 1, where the first and only element equals the given parameter itself. - * + * * @param value - the {@link Field} element to cast the array from. - * + * * @return A {@link Field} array of length 1 created from this {@link Field}. */ static toFields(value: Field): Field[]; @@ -826,7 +931,7 @@ Creating a {@link Field} from a negative number can result in unexpected behavio /** * This function is the implementation of {@link Provable.toAuxiliary} in {@link Field} type. * As the primitive {@link Field} type has no auxiliary data associated with it, this function will always return an empty array. - * + * * @param value - The {@link Field} element to get the auxiliary data of, optional. If not provided, the function returns an empty array. */ static toAuxiliary(value?: Field): []; @@ -834,91 +939,91 @@ Creating a {@link Field} from a negative number can result in unexpected behavio /** * Convert a bit array into a {@link Field} element using [little endian binary representation](https://en.wikipedia.org/wiki/Endianness) * The function fails if the element cannot fit given too many bits. Note that a {@link Field} element can be 254 bits at most. - * + * * **Important**: If the given `bytes` array is an array of `booleans` or {@link Bool} elements that all are `constant`, the resulting {@link Field} element will be a constant as well. Or else, if the given array is a mixture of constants and variables of {@link Bool} type, the resulting {@link Field} will be a variable as well. - * + * * @param bytes - An array of {@link Bool} or `boolean` type. - * + * * @return A {@link Field} element matching the [little endian binary representation](https://en.wikipedia.org/wiki/Endianness) of the given `bytes` array. */ static fromBits(bytes: (Bool | boolean)[]): Field; /** * Serialize the given {@link Field} element to a JSON string, e.g. for printing. Trying to print a {@link Field} without this function will directly stringify the Field object, resulting in an unreadable output. - * + * * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the JSON string representation of the {@link Field}. Use the operation only during debugging. - * + * * @example * ```ts * const someField = Field(42); * console.log(Field.toJSON(someField)); * ``` - * + * * @param value - The JSON string to coerce the {@link Field} from. - * + * * @return A string equivalent to the JSON representation of the given {@link Field}. */ static toJSON(value: Field): string; /** * Deserialize a JSON string containing a "field-like" value into a {@link Field} element. - * + * * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the {@link Field}. - * + * * @param value - the "field-like" value to coerce the {@link Field} from. - * + * * @return A {@link Field} coerced from the given JSON string. */ static fromJSON(value: string): Field; /** * This function is the implementation of {@link Provable.check} in {@link Field} type. - * + * * As any {@link Provable} type can be a {@link Field}, this function does not create any assertions on the chain, so it basically does nothing :) - * + * * @param value - the {@link Field} element to check. */ static check(value: Field): void; /** * **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer. - * + * * This function is the implementation of {@link Provable.toInput} in {@link Field} type. - * + * * @param value - The {@link Field} element to get the `input` array. - * + * * @return An object where the `fields` key is a {@link Field} array of length 1 created from this {@link Field}. - * + * */ static toInput(value: Field): { fields: Field[] }; /** * Create an array of digits equal to the [little-endian](https://en.wikipedia.org/wiki/Endianness) byte order of the given {@link Field} element. * Note that the array has always 32 elements as the {@link Field} is a `finite-field` in the order of {@link Field.ORDER}. - * + * * @param value - The {@link Field} element to generate the array of bytes of. - * + * * @return An array of digits equal to the [little-endian](https://en.wikipedia.org/wiki/Endianness) byte order of the given {@link Field} element. - * + * */ static toBytes(value: Field): number[]; /** * Coerce a new {@link Field} element using the [little-endian](https://en.wikipedia.org/wiki/Endianness) representation of the given `bytes` array. * Note that the given `bytes` array may have at most 32 elements as the {@link Field} is a `finite-field` in the order of {@link Field.ORDER}. - * + * * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the byte representation of the {@link Field}. - * + * * @param bytes - The bytes array to coerce the {@link Field} from. - * + * * @return A new {@link Field} element created using the [little-endian](https://en.wikipedia.org/wiki/Endianness) representation of the given `bytes` array. */ static fromBytes(bytes: number[]): Field; /** - * - * @param bytes - * @param offset + * + * @param bytes + * @param offset */ static readBytes( bytes: number[], @@ -927,9 +1032,9 @@ Creating a {@link Field} from a negative number can result in unexpected behavio /** * **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer. - * + * * As all {@link Field} elements have 31 bits, this function returns 31. - * + * * @return The size of a {@link Field} element - 31. */ static sizeInBytes(): number; diff --git a/src/snarky.js b/src/snarky.js index 607703f73d..4ed36d6720 100644 --- a/src/snarky.js +++ b/src/snarky.js @@ -3,7 +3,7 @@ import snarkySpec from './bindings/js/snarky-class-spec.js'; import { proxyClasses } from './bindings/js/proxy.js'; export { - Field, + Field as SnarkyField, Bool, Snarky, Poseidon, diff --git a/tests/pages/on-chain-state-mgmt-zkapp.ts b/tests/pages/on-chain-state-mgmt-zkapp.ts index de1f635f46..fae58b9e73 100644 --- a/tests/pages/on-chain-state-mgmt-zkapp.ts +++ b/tests/pages/on-chain-state-mgmt-zkapp.ts @@ -88,7 +88,7 @@ export class OnChainStateMgmtZkAppPage { `Updating zkApp State from ${actualValue} to ${expectedValue}` ); await expect(this.eventsContainer).toContainText( - `zkApp State Update failure: assert_equal: ${nextValue} != ${expectedValue}` + `zkApp State Update failure: Field.assertEquals(): ${nextValue} != ${expectedValue}` ); await expect(this.zkAppStateContainer).toHaveText(actualValue); }