From 70985db5e1fc82e5c5626417f1ca2701adb1e4cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yunus=20G=C3=BCrlek?= Date: Tue, 13 Feb 2024 23:04:58 +0100 Subject: [PATCH 1/2] Add a new chapter for common types and expand the basic concepts --- docs/zkapps/o1js/basic-concepts.md | 146 ++++---- .../zkapps/o1js/common-types-and-functions.md | 354 ++++++++++++++++++ 2 files changed, 435 insertions(+), 65 deletions(-) create mode 100644 docs/zkapps/o1js/common-types-and-functions.md diff --git a/docs/zkapps/o1js/basic-concepts.md b/docs/zkapps/o1js/basic-concepts.md index cd39275a9..014027b9a 100644 --- a/docs/zkapps/o1js/basic-concepts.md +++ b/docs/zkapps/o1js/basic-concepts.md @@ -27,109 +27,125 @@ zkApp programmability is not yet available on the Mina Mainnet. You can get star # o1js Basic Concepts -o1js, fka. SnarkyJS, is a TypeScript (TS) library for writing general-purpose zk programs and writing zk smart contracts for Mina. +o1js, fka. SnarkyJS, is a TypeScript (TS) library for writing general-purpose ZK programs and writing ZK smart contracts for the Mina Blockchain. In order to create a ZKP (zero knowledge proof), you should use types and operations that can be converted into a ZKP. o1js gives you a lot of different built in types and customizable structures that you can use to create ZKPs. -## Field +## Field Type -Field elements are the basic unit of data in zero-knowledge proof programming. Each field element can store a number up to almost 256 bits in size. You can think of it as a uint256 in Solidity. +`Field` elements are the basic unit of data in ZK programming. Every other data type (either built-in or composite) in o1js is made up of `Field` elements. + +Each `Field` element can store a number up to almost 256 bits in size. You can think of it as a `uint256` in Solidity, but there are some fundamental differences explained below. :::note -For the cryptography inclined, the exact max value that a field can store is: 28,948,022,309,329,048,855,892,746,252,171,976,963,363,056,481,941,560,715,954,676,764,349,967,630,336 +For the cryptography inclined, the prime order of the o1js `Field` is: 28,948,022,309,329,048,855,892,746,252,171,976,963,363,056,481,941,560,715,954,676,764,349,967,630,336 ::: -For example, in typical programming, you might use: - -`const sum = 1 + 3`. +## Creating a Field Element -In o1js, you write this as: +You can create a `Field` either with the `new` constructor or by directly calling the `Field` as a constructor. The best practice is to use `Field` without the `new` constructor. -`const sum = new Field(1).add(new Field(3))` - -This can be simplified as: + +```ts +const x = new Field(42); +const y = Field(923); // Best practice +``` -`const sum = new Field(1).add(3)` +You can create a `Field` from anything that is "field-like": `number`, `string`, `bigint`, or another `Field`. Note that the argument you give must be an integer in the `Field` range. -Note that the 3 is auto-promoted to a field type to make this cleaner. + +```ts +const x = Field("12347"); // From string +const y = Field(172384782343434n); // From bigint +const z = Field(x); // From another Field -## Built-in data types +const k = Field("asdf"); // Throws, as this is not a number +const l = Field("234.34"); // Throws, as this is not an integer +``` -Some common data types you may use are: +Nevertheless, you can create a `Field` from a negative number. This method implicity calculates the [modular inverse](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse) of the given argument. This is an advanced level information, and you do not need it for simple zkApps. ```ts -new Bool(x); // accepts true or false -new Field(x); // accepts an integer, or a numeric string if you want to represent a number greater than JavaScript can represent but within the max value that a field can store. -new UInt64(x); // accepts a Field - useful for constraining numbers to 64 bits -new UInt32(x); // accepts a Field - useful for constraining numbers to 32 bits +const x = Field(-1); // If you try printing this to the screen, you will see a very big integer +``` -PrivateKey, PublicKey, Signature; // useful for accounts and signing -new Group(x, y); // a point on our elliptic curve, accepts two Fields/numbers/strings -Scalar; // the corresponding scalar field (different than Field) +## Methods in the Field Type -CircuitString.from('some string'); // string of max length 128 -``` +`Field` type includes a lot of built in methods. You can call all methods in the `Field` type either with a `Field` or with a "field-like" value. In the latter case, the argument is implicity converted to a `Field`. -In the case of `Field` and `Bool`, you can also call the constructor without `new`: +All common arithmetic operations exist in the `Field` type. They create and return a new `Field` element. + ```ts -let x = Field(10); -let b = Bool(true); +const x = Field(45); +const y = Field(78); + +const sum = x.add(y); +const sub = x.sub(y); +const mul = x.mul(47); // You can also use "field-like" values +const div = x.div(89); // This is a modular division +const neg = x.neg(); // This is equivalent to x.mul(-1) + +const square = x.square(); // x^2, equivalent to x.mul(x) +const sqrt = x.sqrt(); // Modular square root of x +const inv = x.inv(); // Modular inverse of x ``` -## Conditionals - -Traditional conditional statements are not supported by o1js: +You can also perform logical operations and comparisons with `Field` elements. These methods return a [Bool](./common-types-and-functions.md#bool) element, which is another common o1js built in type, equivalent to `boolean` in other languages and frameworks. + ```ts -// this will NOT work -if (foo) { - x.assertEquals(y); -} +const x = Field(45); + +x.equals(45); // True +x.greaterThan(45); // False +x.greaterThanOrEquals(45); // True +x.lessThan(45); // False +x.lessThanOrEquals(45); // True + +// It is also possible to check if a Field is even or not +x.isEven(); // False ``` -Instead, use the o1js built-in `Circuit.if()` method, which is a ternary operator: +## Assertions in o1js +If you want to reject a TX (transaction) in your zkApp smart contract, you can use assertions in o1js. If you create an assertion inside your ZKP, the proof fails if the assertion does not evaluate to `true`. All conditional methods on the `Field` type can be used as assertions. + + ```ts -const x = Circuit.if(new Bool(foo), a, b); // behaves like `foo ? a : b` +const x = Field(45); + +x.assertEquals(45); // True - passes +x.assertGreaterThan(45); // False - throws +x.assertGreaterThanOrEquals(45); // True - passes +x.assertLessThan(45); // False - throws +x.assertLessThanOrEquals(45); // True - passes ``` -## Functions +You can think assertions in o1js similar to `require()` function in Solidity. -Functions work as you would expect in TypeScript. For example: +## Printing a Field to the Screen +`Field` elements are stored as classes in o1js. This is why if you print a `Field` without a conversion, you will get an unreadable output. Hopefully, o1js gives a variety of conversion methods to print `Field` elements to the screen. + + ```ts -function addOneAndDouble(x: Field): Field { - return x.add(1).mul(2); -} -``` +const x = Field(45); -## Common methods +console.log(x.toString()); // string +console.log(x.toBigInt()); // bigint +console.log(x.toJSON()); // JSON string +``` -Some common methods you will use often are: +However, note that these methods are _not_ provable, meaning you cannot use them in your final zkApp as a part of the ZKP. Use them only to debug your code. + ```ts -let x = new Field(4); // x = 4 -x = x.add(3); // x = 7 -x = x.sub(1); // x = 6 -x = x.mul(3); // x = 18 -x = x.div(2); // x = 9 -x = x.square(); // x = 81 -x = x.sqrt(); // x = 9 - -let b = x.equals(8); // b = Bool(false) -b = x.greaterThan(8); // b = Bool(true) -b = b.not().or(b).and(b); // b = Bool(true) -b.toBoolean(); // true - -let hash = Poseidon.hash([x]); // takes array of Fields, returns Field - -let privKey = PrivateKey.random(); // create a private key -let pubKey = PublicKey.fromPrivateKey(privKey); // derive public key -let msg = [hash]; -let sig = Signature.create(privKey, msg); // sign a message -sig.verify(pubKey, msg); // Bool(true) +const x = Field(45); +const y = Field(x.toString()); // You should NOT write this ``` -For a full list, see the [o1js reference](../o1js-reference). +## Other Types and Methods + +In addition to the `Field` type, o1js gives you a variety structures to help you build a powerful zkApp. You can learn more about them in the next chapter, [Common Types and Functions](./common-types-and-functions.md). diff --git a/docs/zkapps/o1js/common-types-and-functions.md b/docs/zkapps/o1js/common-types-and-functions.md new file mode 100644 index 000000000..97c8d0327 --- /dev/null +++ b/docs/zkapps/o1js/common-types-and-functions.md @@ -0,0 +1,354 @@ +--- +title: o1js Common Types and Functions +hide_title: true +sidebar_label: Common Types and Functions +description: o1js includes a lot of built in types and functions for you to start creating your zkApp right away. +keywords: + - zero knowledge proof programming + - zk proof + - zk + - data types + - o1js + - blockchain + - mina + - typescript + - methods + - common types + - built in types + - bool + - uint32 + - uint64 + - provable if + - loops + - functions +--- + +:::info + +zkApp programmability is not yet available on the Mina Mainnet. You can get started now by deploying zkApps to the Berkeley Testnet. + +::: + +# o1js Built-in Data Types + +o1js includes built in types and methods that you can use inside your zkApp and ZKPs (zero knowledge proofs). All o1js provable types are built using the [Field](./basic-concepts.md#field) type, each with unique properties and advantages. You can find detailed class description in [o1js reference](../o1js-reference). + +## Bool + +`Bool` is the type used for `boolean` values. It is equivalent to the `boolean` type in order languages and frameworks. + + +```ts +const x = new Bool(true); +const y = new Bool(false); +``` + +With the `Bool` type, you can call the contructor without `new`, just like the `Field` type. The best practice is to use the constructor function without `new`: + + +```ts +const x = Bool(true); +const y = Bool(false); +``` + +You can also create a `Bool` from another `Bool`. + + +```ts +const x = Bool(true); +const y = Bool(x); +``` + +It is also possible to convert a `Field` into a `Bool`. However, you should make sure that the `Field` is "bool-like" (with a value either 1 or 0) before converting it. + +```ts +let x = Field(1); +x.assertBool(); // Asserts that x has a value either 1 or 0 + +let x_bool = Bool(x.value); +``` + +`Bool` type includes the following conditional methods and converters. Every method created and returns a new `Bool`. All conditional methods can be called with regular `boolean` values as well as `Bool` variables: + + +```ts +const x = Bool(true); +const y = Bool(false); +const z = y.not(); // True + +const equals = x.equals(y); // False +const and = x.and(y); // False +const or = x.or(y); // True + +const equals_from_boolean = x.equals(true); // True +const and_from_boolean = x.and(true); // True +const or_from_boolean = y.or(false); // False +``` + +You can also assert `Bool` variables. When you assert something, your zkApp method throws if the assertion fails. + + +```ts +const x = Bool(true); +const y = Bool(false); + +x.assertEquals(y); // Throws +x.assertTrue(); // Nothing happens, as assertion passes +x.assertFalse(); // Throws +``` + +You can also combine multiple logical expressions one after another. + + +```ts +const x = Bool(true); +const y = Bool(false); + +x.not().and(y).or(true); // True +``` + +**Important:** All logical expressions are executed in the method call order, as with any other regular language or framework. However, as these are logical statements inside ZKPs, all expressions are always evaluated regardless of the logical ordering. Please see conditionals chapter [below](./common-types-and-functions.md#conditionals) for more details. + +## UInt32 + +`UInt32` type is a 32 bit positive integer. You can create it using the `new` constructor or the `UInt32.from()` static method. The best practice is to use `UInt32.from()`. + +Both the constructor and `UInt32.from()` method accepts `number`, `string`, `bigint`, `Field`, and `UInt32` type. In order the conversion to be successful, the type should evaluate to "uint32-like", meaning an integer in the range [0, $2^{32}$]. + + +```ts +const x = new UInt32(45); // From number, use from() rather than the new constructor +const y = UInt32.from("788"); // From string +const z = UInt32.from(812934812n); // From bigint +const v = UInt32.from(Field(34)); // From Field +const w = UInt32.from(v); // From UInt32 + +const k = UInt32.from("asdfas"); // Throws, as this is not a number +const l = UInt32.from(9182394814234n) // Throws, as this bigint is bigger than 2^32 +``` + +You can perform arithmetic operations on `UInt32` variables using built in methods. These methods can also be called with "uint32-like" values. Each method creates and returns a new `UInt32`. + +If the result of a method is not in the range of `UIn32`, either a too big or a negative number, the code throws an error. + + +```ts +const x = UInt32.from(45); +const y = UInt32.from(94); + +const add = x.add(y); // Regular addition +const sub = x.sub(y); // If the result is out of range of the UInt32, the code throws +const mul = x.mul(47); // You can also use "uint32-like" values +const div = x.div(33); // This is an integer division, different from the Field type +const divMod = x.divMod(33); // Returns an object with the keys `quotient` and `rest`, both of the UInt32 type. +``` + +You can also perform logical operations on `UInt32` types. All logical methods return a `Bool` variable. + + +```ts +const x = UInt32.from(45); + +x.equals(45); // True +x.greaterThan(45); // False +x.greaterThanOrEquals(45); // True +x.lessThan(45); // False +x.lessThanOrEquals(45); // True +``` + +Similarly to the `Bool` type, you can assert logical statements. + + +```ts +const x = UInt32.from(45); + +x.assertEquals(45); // True - passes +x.assertGreaterThan(45); // False - throws +x.assertGreaterThanOrEquals(45); // True - passes +x.assertLessThan(45); // False - throws +x.assertLessThanOrEquals(45); // True - passes +``` + +Finally, you can convert a `UInt32` to a `Field` by calling the `.value` property. + + +```ts +const x = UInt32.from(45); +const y = x.value; // This is a Field with the value 45 +``` + +## UInt64 + +`UInt64` type is the 64 bit version of `UInt32` type. There is no difference between the two, except their range. + + +```ts +// Constructors + +const x = new UInt64(45); // From number, use from() rather than the new constructor +const y = UInt64.from("788"); // From string +const z = UInt64.from(812934812n); // From bigint +const v = UInt64.from(Field(34)); // From Field +const w = UInt64.from(v); // From UInt64 + +const k = UInt64.from("asdfas"); // Throws, as this is not a number +const l = UInt64.from(91823948182394892384923849348923849238494234n) // Throws, as this bigint is bigger than 2^64 + +// Arithmetic Operations + +const add = x.add(y); // Regular addition +const sub = x.sub(y); // If the result is out of range of the UInt64, the code throws +const mul = x.mul(47); // You can also use "uint64-like" values +const div = x.div(33); // This is an integer division, different from the Field type +const divMod = x.divMod(33); // Returns an object with the keys `quotient` and `rest`, both of the UInt64 type. + +// Logical Operators + +x.equals(45); // True +x.greaterThan(45); // False +x.greaterThanOrEquals(45); // True +x.lessThan(45); // False +x.lessThanOrEquals(45); // True + +// Assertions + +x.assertEquals(45); // True - passes +x.assertGreaterThan(45); // False - throws +x.assertGreaterThanOrEquals(45); // True - passes +x.assertLessThan(45); // False - throws +x.assertLessThanOrEquals(45); // True - passes + +// Converting to Field + +const y = x.value; // This is a Field with the value 45 +``` + +## Public and Private Keys + +For accessing Mina blockchain accounts, you can use `PublicKey` and `PrivateKey` types of o1js. You can create a key from a [base58 string](https://en.wikipedia.org/wiki/Binary-to-text_encoding). + + +```ts +const x_private = PrivateKey.fromBase58("base58_private_key"); +const y_private = PrivateKey.fromJSON("base58_private_key"); + +const x = PublicKey.fromBase58("B62qq8sm7JdsED6VuDKNWKLAi1Tvz1jrnffuud5gXMq3mgtd"); +const y = PublicKey.fromJSON("B62qq8sm7JdsED6VuDKNWKLAi1Tvz1jrnffuud5gXMq3mgtd"); +``` + +You can also create a random `PrivateKey`, an empty `PublicKey`, or convert a `PrivateKey` to a `PublicKey`. + + +```ts +const random = PrivateKey.random(); +const empty = PublicKey.empty(); + +const fromPrivKey = PublicKey.fromPrivateKey(random); // or equivalently random.toPublicKey() +``` + +# Conditionals + +Traditional conditional statements are not supported by o1js. This is due to the fact that o1js creates ZKPs, and conditional evaluation does not make sense in a ZKP. ZKPs are evaluated into mathematical statements when compiled by o1js, thus you cannot chose to evaluate a statement based on a truth value unknown at the compile time. + + +```ts +// This will NOT work +if (foo) { + x.assertEquals(y); +} +``` + +Nevertheless, you can use the o1js built-in `Provable.if()` method, which is a ternary operator: + + +```ts +const x = Provable.if( + Bool(foo), // The condition to evaluate inside if, must be of type Bool + a, // You can return any provable type from Provable.if + b // a and b must be of the same provable type +); // Behaves like `foo ? a : b` +``` + +`Provable.if()` allows you to include any logical computation you need inside your ZKP. There is nothing wrong with evaluating more complex expressions inside `Provable.if()` and returning the final result. + +However, it works differently from ternary statements in the other languages and frameworks. Both arguments of `Provable.if()` _always_ executes, regardless of the truth value of the condition. `Provable.if()` choses which one to return based on the truth value and sets the new variable. This is why you should never assign anything outside of the if scope or assert anything. + + +```ts +const x = Provable.if( + Bool(foo), + (() => { // Here, we use arrow functions to return a Field element, but you can use any function that returns a provable type + return Field(5).div(3); + })(), + (() => { + return Field(5).div(2); + })() +); // This variable is of type Field +``` + +# Loops + +Loops also work differently in o1js because of the same reason with conditionals. In a mathematical statement, you cannot chose how many times to do something based on a variable that is not known at the compile time. Similarly, o1js does not allow you to use dynamically sized loops, loops that the iteration count is not known at the compilation. + + +```ts +// This will NOT work +while (foo) { + x.add(y); +} +``` + +You can overcome this limitation by using static sized loops that iterate always the same amount of time. You can perform provable operations with these loops using the `Provable.if`. + + +```ts +function powerOfTwo(pow: Field): Field { + const MAX_ITERATION_COUNT = 64; + + pow.assertGreaterThanOrEqual(0); + pow.assertSmallerThanOrEqual(MAX_ITERATION_COUNT); // This code is to calculate up to 2^64, you can optimize this based on your needs + + let result = Field(1); // Result of the calculation + let power = Field(0); // Current power of the iteration + let loopEnded = Bool(false); // Checking if the loop has ended yet + + for (let i = 0; i <= MAX_ITERATION_COUNT; i++) { // Static sized loop + loopEnded = Provable.if( // End Condition + Field(i).greaterThanOrEqual(power), + Bool(true), + Bool(false) + ); + result = result.mul(Provable.if( // Result + loopEnded, + Field(1), + Field(2) + )); + power = power.add(Provable.if( // Power + loopEnded, + Field(0), + Field(1) + )); + }; + + return result; +}; +``` + +# Functions + +Functions work as you would expect in TS. However, your functions inside ZKPs must take provable arguments and return provable types, just like anything in o1js. For example: + + +```ts +function addOneAndDouble(x: Field): Field { + return x.add(1).mul(2); +} +``` + +**Warning:** You should _not_ use recursion with functions in o1js. If you want to recurse a ZKP, you can use [o1js recursion](./recursion.mdx). + +:::info + +Note that this is not a full list of built in types and methods that exist in o1js. For a full list, see the [o1js reference](../o1js-reference). + +::: \ No newline at end of file From b13c4c01302f7dd62dd9b422fb21567e767f95ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yunus=20G=C3=BCrlek?= Date: Tue, 20 Feb 2024 17:07:48 +0100 Subject: [PATCH 2/2] Finish the review and add some more details on the Field type --- docs/zkapps/o1js/basic-concepts.md | 160 ++++++++++++----- .../zkapps/o1js/common-types-and-functions.md | 166 +++++++++--------- 2 files changed, 204 insertions(+), 122 deletions(-) diff --git a/docs/zkapps/o1js/basic-concepts.md b/docs/zkapps/o1js/basic-concepts.md index 014027b9a..0a0f97c64 100644 --- a/docs/zkapps/o1js/basic-concepts.md +++ b/docs/zkapps/o1js/basic-concepts.md @@ -29,11 +29,11 @@ zkApp programmability is not yet available on the Mina Mainnet. You can get star o1js, fka. SnarkyJS, is a TypeScript (TS) library for writing general-purpose ZK programs and writing ZK smart contracts for the Mina Blockchain. In order to create a ZKP (zero knowledge proof), you should use types and operations that can be converted into a ZKP. o1js gives you a lot of different built in types and customizable structures that you can use to create ZKPs. -## Field Type +## Field -`Field` elements are the basic unit of data in ZK programming. Every other data type (either built-in or composite) in o1js is made up of `Field` elements. +Field elements are the basic unit of data in ZK programming. Every other data type (either built-in or composite) in o1js is made up of field elements. -Each `Field` element can store a number up to almost 256 bits in size. You can think of it as a `uint256` in Solidity, but there are some fundamental differences explained below. +Each `Field` element in o1js can store a number up to almost 256 bits in size. You can think of it as a `uint256` in Solidity, but there are some fundamental differences explained below. :::note @@ -41,62 +41,61 @@ For the cryptography inclined, the prime order of the o1js `Field` is: 28,948,02 ::: -## Creating a Field Element +## Creating a `Field` Element -You can create a `Field` either with the `new` constructor or by directly calling the `Field` as a constructor. The best practice is to use `Field` without the `new` constructor. +You can create a `Field` either with the `new` constructor or by directly calling the `Field` as a constructor. The convention is to use `Field` without the `new` constructor. ```ts -const x = new Field(42); -const y = Field(923); // Best practice +let x = Field(42); ``` You can create a `Field` from anything that is "field-like": `number`, `string`, `bigint`, or another `Field`. Note that the argument you give must be an integer in the `Field` range. ```ts -const x = Field("12347"); // From string -const y = Field(172384782343434n); // From bigint -const z = Field(x); // From another Field +let x = Field("12347"); // From string +let y = Field(172384782343434n); // From bigint +let z = Field(x); // From another Field -const k = Field("asdf"); // Throws, as this is not a number -const l = Field("234.34"); // Throws, as this is not an integer +let k = Field("asdf"); // Throws, as this is not a number +let l = Field("234.34"); // Throws, as this is not an integer ``` -Nevertheless, you can create a `Field` from a negative number. This method implicity calculates the [modular inverse](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse) of the given argument. This is an advanced level information, and you do not need it for simple zkApps. +Nevertheless, you can create a `Field` from a negative number. This method implicity calculates the additive modular inverse of the given argument. This is an advanced level information, and you do not need it for simple zkApps. ```ts -const x = Field(-1); // If you try printing this to the screen, you will see a very big integer +let x = Field(-1); // If you try printing this to the screen, you will see a very big integer ``` -## Methods in the Field Type +## Methods on the `Field` Type `Field` type includes a lot of built in methods. You can call all methods in the `Field` type either with a `Field` or with a "field-like" value. In the latter case, the argument is implicity converted to a `Field`. -All common arithmetic operations exist in the `Field` type. They create and return a new `Field` element. +All common arithmetic operations are available on the `Field` type. They create and return a new `Field` element. ```ts -const x = Field(45); -const y = Field(78); - -const sum = x.add(y); -const sub = x.sub(y); -const mul = x.mul(47); // You can also use "field-like" values -const div = x.div(89); // This is a modular division -const neg = x.neg(); // This is equivalent to x.mul(-1) - -const square = x.square(); // x^2, equivalent to x.mul(x) -const sqrt = x.sqrt(); // Modular square root of x -const inv = x.inv(); // Modular inverse of x +let x = Field(45); +let y = Field(78); + +let sum = x.add(y); +let sub = x.sub(y); +let mul = x.mul(47); // You can also use "field-like" values +let div = x.div(89); // This is a modular division +let neg = x.neg(); // This is equivalent to x.mul(-1) + +let square = x.square(); // x^2, equivalent to x.mul(x) +let sqrt = x.sqrt(); // Modular square root of x +let inv = x.inv(); // Modular inverse of x ``` You can also perform logical operations and comparisons with `Field` elements. These methods return a [Bool](./common-types-and-functions.md#bool) element, which is another common o1js built in type, equivalent to `boolean` in other languages and frameworks. ```ts -const x = Field(45); +let x = Field(45); x.equals(45); // True x.greaterThan(45); // False @@ -114,7 +113,7 @@ If you want to reject a TX (transaction) in your zkApp smart contract, you can u ```ts -const x = Field(45); +let x = Field(45); x.assertEquals(45); // True - passes x.assertGreaterThan(45); // False - throws @@ -127,25 +126,106 @@ You can think assertions in o1js similar to `require()` function in Solidity. ## Printing a Field to the Screen -`Field` elements are stored as classes in o1js. This is why if you print a `Field` without a conversion, you will get an unreadable output. Hopefully, o1js gives a variety of conversion methods to print `Field` elements to the screen. +`Field` elements are stored as classes in o1js. This is why if you print a `Field` using `console.log`, you will get an unreadable output. Hopefully, o1js provides an equivalent function to `console.log` for provable types: `Provable.log` ```ts -const x = Field(45); +let x = Field(45); +Provable.log(x); // Prints 45 +``` + +## Other Types and Methods + +In addition to the `Field` type, o1js gives you a variety structures to help you build a powerful zkApp. You can learn more about them in the next chapter, [Common Types and Functions](./common-types-and-functions.md). + +:::note + +The information given until here is more than enough for you to start creating powerful zkApps with o1js. For more advanced level content, you can read the following sections. If you are new to o1js, just continue with the next chapter, [Common Types and Functions](./common-types-and-functions.md). + +::: + +## Deeper into the Field Type + +`Field` element is a positive integer in a prime order [finite field](https://en.wikipedia.org/wiki/Finite_field), which is a set of integers in between 1 and the prime order of the field (a.k.a. an arbitrary prime number describing the field). + +The order of the o1js `Field` can be accessed as a static property of the class. -console.log(x.toString()); // string -console.log(x.toBigInt()); // bigint -console.log(x.toJSON()); // JSON string + +```ts +console.log(Field.ORDER); ``` -However, note that these methods are _not_ provable, meaning you cannot use them in your final zkApp as a part of the ZKP. Use them only to debug your code. +When you perform an operation inside a finite field, the result always stays in the range. This is achieved through [modular arithmetic](https://en.wikipedia.org/wiki/Modular_arithmetic). This behaviour may be unfamiliar to you if you have not worked with ZKPs before. Let us explain more with some examples. + +In the addition or multiplication operations where the result is bigger than the order of the finite field, you receive the modulus of the result with the prime order of the field. ```ts -const x = Field(45); -const y = Field(x.toString()); // You should NOT write this +Provable.log(Field(Field.ORDER - 1) + Field(2)); // This will print 1 ``` -## Other Types and Methods +If you try creating a `Field` from a negative number you will receive the modular additive inverse of the number. For instance `(1 + (Field.ORDER - 1)) % Field.ORDER` is 0, so the additive inverse of 1 is `Field.ORDER - 1` in this field. -In addition to the `Field` type, o1js gives you a variety structures to help you build a powerful zkApp. You can learn more about them in the next chapter, [Common Types and Functions](./common-types-and-functions.md). + +```ts +Provable.log(Field(-1)); // This will print `Field.ORDER - 1` +``` + +Similarly, if your substraction results in a negative number you will get the modular additive inverse. + +For division, if the result is not an integer, you receive the [modular multiplicative inverse](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse) of the division: If `(x * y) % p` is `z` (where `p` is the order of the finite field), then the modular multiplicative inverse of the division `z / x` equals `y`. Note that `x`, `y`, and `z` are all integers. + + +```ts +Provable.log(Field(3).div(Field(5))); // This will print a very big integer +``` + +Because of this behaviour, it is usually not very useful to use the final result of an uneven division. Instead, you can prove the result with multiplication. + + +```ts +// We want x, an argument of the smart contract, to be equal to 7 / 3 +x.mul(Field(3)).assertEquals(Field(7)); +``` + +If you want to have integer division instead, you can use `UInt32` or `UInt64` types (see [Common Types and Functions](./common-types-and-functions.md)). + +## Provability and Provable Code in o1js + +We have stated many times above that all the code you write in o1js must be provable, but what is provability? + +A provable code can be thought as a code that o1js compiler can convert into a ZKP. Just because you write something with o1js, it does not become automatically provable. You must follow all the rules above and always use provable types in your code. + +However, provable types are created from regular TS variables. + + +```ts +const x = 42; // This is TS number +let x_provable = Field(x); // This code is perfectly valid +``` + +In reality, the `x_provable` variable in the example above is a _constant_ `Field` variable. By constant, we do not mean that it is a TS constant (which you define by using `const`), but we say that the value of `x_provable` is known at the compilation time. When you receive an argument to your smart contract method, then the argument is not a constant, thus you should only use provable methods on it. + + +```ts +@method foo(x: Field) { + // Here, the value of x depends on the user input, thus type of x must be provable +} +``` + +You can learn if a variable is a constant or not in o1js by using `isConstant()` method. Note that this is a function mostly for internal use, but it might give you a good insight on how to work with types in o1js. + + +```ts +let x = Field(42); +console.log(x.isConstant()); // This will print true +``` + +Always remember that o1js is used for writing ZKPs, and what you write with o1js must have a sense in the final proof. The public or private inputs of a ZKP may change depending on the user input, but once the inputs are provided, the final ZKP must make sure they are not changed. By using provable o1js variables, you make sure that your variables are always consistent with your proof logic. + + +```ts +@method foo(x: Field, y: Field) { + x.add(y).assertEquals(5); // we know neither the value of x, nor of the y, but we are sure that (x + y) % Field.ORDER equals 5 +} +``` diff --git a/docs/zkapps/o1js/common-types-and-functions.md b/docs/zkapps/o1js/common-types-and-functions.md index 97c8d0327..e415ab814 100644 --- a/docs/zkapps/o1js/common-types-and-functions.md +++ b/docs/zkapps/o1js/common-types-and-functions.md @@ -39,31 +39,33 @@ o1js includes built in types and methods that you can use inside your zkApp and ```ts -const x = new Bool(true); -const y = new Bool(false); +let x = new Bool(true); +let y = new Bool(false); ``` -With the `Bool` type, you can call the contructor without `new`, just like the `Field` type. The best practice is to use the constructor function without `new`: +With the `Bool` type, you can call the contructor without `new`, just like the `Field` type. The convention is to use the constructor function without `new`: ```ts -const x = Bool(true); -const y = Bool(false); +let x = Bool(true); +let y = Bool(false); ``` You can also create a `Bool` from another `Bool`. ```ts -const x = Bool(true); -const y = Bool(x); +let x = Bool(true); +let y = Bool(x); ``` It is also possible to convert a `Field` into a `Bool`. However, you should make sure that the `Field` is "bool-like" (with a value either 1 or 0) before converting it. ```ts let x = Field(1); -x.assertBool(); // Asserts that x has a value either 1 or 0 + +// The line below makes sure x can be safely converted into a `Bool`. +x.equals(Field(1)).or(x.equals(Field(0))).assertTrue(); // You will learn about the methods used here below. let x_bool = Bool(x.value); ``` @@ -72,25 +74,25 @@ let x_bool = Bool(x.value); ```ts -const x = Bool(true); -const y = Bool(false); -const z = y.not(); // True +let x = Bool(true); +let y = Bool(false); +let z = y.not(); // True -const equals = x.equals(y); // False -const and = x.and(y); // False -const or = x.or(y); // True +let equals = x.equals(y); // False +let and = x.and(y); // False +let or = x.or(y); // True -const equals_from_boolean = x.equals(true); // True -const and_from_boolean = x.and(true); // True -const or_from_boolean = y.or(false); // False +let equals_from_boolean = x.equals(true); // True +let and_from_boolean = x.and(true); // True +let or_from_boolean = y.or(false); // False ``` -You can also assert `Bool` variables. When you assert something, your zkApp method throws if the assertion fails. +You can also assert `Bool` variables. When you assert something, your zkApp method throws if the assertion fails. You can read more about assertions in the [basic concepts](./basic-concepts.md#assertions-in-o1js) chapter. ```ts -const x = Bool(true); -const y = Bool(false); +let x = Bool(true); +let y = Bool(false); x.assertEquals(y); // Throws x.assertTrue(); // Nothing happens, as assertion passes @@ -101,30 +103,30 @@ You can also combine multiple logical expressions one after another. ```ts -const x = Bool(true); -const y = Bool(false); +let x = Bool(true); +let y = Bool(false); x.not().and(y).or(true); // True ``` -**Important:** All logical expressions are executed in the method call order, as with any other regular language or framework. However, as these are logical statements inside ZKPs, all expressions are always evaluated regardless of the logical ordering. Please see conditionals chapter [below](./common-types-and-functions.md#conditionals) for more details. +**Important:** All logical expressions are executed in the method call order, as with any other regular language or framework. However, as these are logical statements inside ZKPs, all expressions are always evaluated regardless of the logical ordering. See [Conditionals](./common-types-and-functions.md#conditionals) for more details. ## UInt32 -`UInt32` type is a 32 bit positive integer. You can create it using the `new` constructor or the `UInt32.from()` static method. The best practice is to use `UInt32.from()`. +`UInt32` type is a 32 bit positive integer. You can create it using the `new` constructor or the `UInt32.from()` static method. The convention is to use `UInt32.from()`. -Both the constructor and `UInt32.from()` method accepts `number`, `string`, `bigint`, `Field`, and `UInt32` type. In order the conversion to be successful, the type should evaluate to "uint32-like", meaning an integer in the range [0, $2^{32}$]. +Both the constructor and `UInt32.from()` method accepts `number`, `string`, `bigint`, `Field`, and `UInt32` type. In order the conversion to be successful, the type should evaluate to "uint32-like", meaning an integer in the range [0, $2^{32} - 1$]. ```ts -const x = new UInt32(45); // From number, use from() rather than the new constructor -const y = UInt32.from("788"); // From string -const z = UInt32.from(812934812n); // From bigint -const v = UInt32.from(Field(34)); // From Field -const w = UInt32.from(v); // From UInt32 - -const k = UInt32.from("asdfas"); // Throws, as this is not a number -const l = UInt32.from(9182394814234n) // Throws, as this bigint is bigger than 2^32 +let x = new UInt32(45); // From number, use from() rather than the new constructor +let y = UInt32.from("788"); // From string +let z = UInt32.from(812934812n); // From bigint +let v = UInt32.from(Field(34)); // From Field +let w = UInt32.from(v); // From UInt32 + +let k = UInt32.from("asdfas"); // Throws, as this is not a number +let l = UInt32.from(9182394814234n) // Throws, as this bigint is bigger than 2^32 - 1 ``` You can perform arithmetic operations on `UInt32` variables using built in methods. These methods can also be called with "uint32-like" values. Each method creates and returns a new `UInt32`. @@ -133,21 +135,21 @@ If the result of a method is not in the range of `UIn32`, either a too big or a ```ts -const x = UInt32.from(45); -const y = UInt32.from(94); - -const add = x.add(y); // Regular addition -const sub = x.sub(y); // If the result is out of range of the UInt32, the code throws -const mul = x.mul(47); // You can also use "uint32-like" values -const div = x.div(33); // This is an integer division, different from the Field type -const divMod = x.divMod(33); // Returns an object with the keys `quotient` and `rest`, both of the UInt32 type. +let x = UInt32.from(45); +let y = UInt32.from(94); + +let add = x.add(y); // Regular addition +let sub = x.sub(y); // If the result is out of range of the UInt32, the code throws +let mul = x.mul(47); // You can also use "uint32-like" values +let div = x.div(33); // This is an integer division, different from the Field type +let divMod = x.divMod(33); // Returns an object with the keys `quotient` and `rest`, both of the UInt32 type. ``` You can also perform logical operations on `UInt32` types. All logical methods return a `Bool` variable. ```ts -const x = UInt32.from(45); +let x = UInt32.from(45); x.equals(45); // True x.greaterThan(45); // False @@ -160,7 +162,7 @@ Similarly to the `Bool` type, you can assert logical statements. ```ts -const x = UInt32.from(45); +let x = UInt32.from(45); x.assertEquals(45); // True - passes x.assertGreaterThan(45); // False - throws @@ -173,8 +175,8 @@ Finally, you can convert a `UInt32` to a `Field` by calling the `.value` propert ```ts -const x = UInt32.from(45); -const y = x.value; // This is a Field with the value 45 +let x = UInt32.from(45); +let y = x.value; // This is a Field with the value 45 ``` ## UInt64 @@ -185,22 +187,22 @@ const y = x.value; // This is a Field with the value 45 ```ts // Constructors -const x = new UInt64(45); // From number, use from() rather than the new constructor -const y = UInt64.from("788"); // From string -const z = UInt64.from(812934812n); // From bigint -const v = UInt64.from(Field(34)); // From Field -const w = UInt64.from(v); // From UInt64 +let x = new UInt64(45); // From number, use from() rather than the new constructor +let y = UInt64.from("788"); // From string +let z = UInt64.from(812934812n); // From bigint +let v = UInt64.from(Field(34)); // From Field +let w = UInt64.from(v); // From UInt64 -const k = UInt64.from("asdfas"); // Throws, as this is not a number -const l = UInt64.from(91823948182394892384923849348923849238494234n) // Throws, as this bigint is bigger than 2^64 +let k = UInt64.from("asdfas"); // Throws, as this is not a number +let l = UInt64.from(91823948182394892384923849348923849238494234n) // Throws, as this bigint is bigger than 2^64 // Arithmetic Operations -const add = x.add(y); // Regular addition -const sub = x.sub(y); // If the result is out of range of the UInt64, the code throws -const mul = x.mul(47); // You can also use "uint64-like" values -const div = x.div(33); // This is an integer division, different from the Field type -const divMod = x.divMod(33); // Returns an object with the keys `quotient` and `rest`, both of the UInt64 type. +let add = x.add(y); // Regular addition +let sub = x.sub(y); // If the result is out of range of the UInt64, the code throws +let mul = x.mul(47); // You can also use "uint64-like" values +let div = x.div(33); // This is an integer division, different from the Field type +let divMod = x.divMod(33); // Returns an object with the keys `quotient` and `rest`, both of the UInt64 type. // Logical Operators @@ -220,7 +222,7 @@ x.assertLessThanOrEquals(45); // True - passes // Converting to Field -const y = x.value; // This is a Field with the value 45 +let y = x.value; // This is a Field with the value 45 ``` ## Public and Private Keys @@ -229,20 +231,18 @@ For accessing Mina blockchain accounts, you can use `PublicKey` and `PrivateKey` ```ts -const x_private = PrivateKey.fromBase58("base58_private_key"); -const y_private = PrivateKey.fromJSON("base58_private_key"); +const x_private = PrivateKey.fromBase58("EKEAzkozjN3TAQHLzBKD7RoscLojWeFMKLY2qgKEV8qqpxJYwpKb"); +const y_private = PrivateKey.fromJSON("EKEAzkozjN3TAQHLzBKD7RoscLojWeFMKLY2qgKEV8qqpxJYwpKb"); -const x = PublicKey.fromBase58("B62qq8sm7JdsED6VuDKNWKLAi1Tvz1jrnffuud5gXMq3mgtd"); -const y = PublicKey.fromJSON("B62qq8sm7JdsED6VuDKNWKLAi1Tvz1jrnffuud5gXMq3mgtd"); +const x = PublicKey.fromBase58("B62qp8KyqqSyKs8eQqtmzoRjuEG3Th9bD4ABuvMEGBCnkMKh3pWvGaQ"); +const y = PublicKey.fromJSON("B62qp8KyqqSyKs8eQqtmzoRjuEG3Th9bD4ABuvMEGBCnkMKh3pWvGaQ"); ``` -You can also create a random `PrivateKey`, an empty `PublicKey`, or convert a `PrivateKey` to a `PublicKey`. +You can also create a random `PrivateKey` or convert a `PrivateKey` to a `PublicKey`. ```ts const random = PrivateKey.random(); -const empty = PublicKey.empty(); - const fromPrivKey = PublicKey.fromPrivateKey(random); // or equivalently random.toPublicKey() ``` @@ -262,7 +262,7 @@ Nevertheless, you can use the o1js built-in `Provable.if()` method, which is a t ```ts -const x = Provable.if( +let x = Provable.if( Bool(foo), // The condition to evaluate inside if, must be of type Bool a, // You can return any provable type from Provable.if b // a and b must be of the same provable type @@ -271,17 +271,18 @@ const x = Provable.if( `Provable.if()` allows you to include any logical computation you need inside your ZKP. There is nothing wrong with evaluating more complex expressions inside `Provable.if()` and returning the final result. -However, it works differently from ternary statements in the other languages and frameworks. Both arguments of `Provable.if()` _always_ executes, regardless of the truth value of the condition. `Provable.if()` choses which one to return based on the truth value and sets the new variable. This is why you should never assign anything outside of the if scope or assert anything. +However, it works differently from ternary statements in the other languages and frameworks. Both arguments of `Provable.if()` _always_ execute, regardless of the truth value of the condition. `Provable.if()` chooses which one to return based on the truth value and sets the new variable. This is why you should never assign anything outside of the if scope or assert anything. ```ts -const x = Provable.if( +let x = Provable.if( Bool(foo), + // Note both of the functions below will be executed regardless of the value of `Bool(foo)` (() => { // Here, we use arrow functions to return a Field element, but you can use any function that returns a provable type return Field(5).div(3); })(), (() => { - return Field(5).div(2); + return Field(5).div(2); })() ); // This variable is of type Field ``` @@ -298,35 +299,36 @@ while (foo) { } ``` -You can overcome this limitation by using static sized loops that iterate always the same amount of time. You can perform provable operations with these loops using the `Provable.if`. +You can overcome this limitation by using static sized loops that iterate always the same amount of time. You can perform provable operations with these loops using `Provable.if()`. + +Note that the example below is very inefficient due to the expensive `Field` comparison operations. This example is just to show how static sized loops may be used with `Provable.if()`. A much more efficient way of achieving the same would be using o1js [recursion](./recursion.mdx). ```ts -function powerOfTwo(pow: Field): Field { - const MAX_ITERATION_COUNT = 64; - - pow.assertGreaterThanOrEqual(0); - pow.assertSmallerThanOrEqual(MAX_ITERATION_COUNT); // This code is to calculate up to 2^64, you can optimize this based on your needs +function powerOfTwo(pow: UInt64): Field { + const MAX_ITERATION_COUNT = 64; // pow is 64 bits let result = Field(1); // Result of the calculation - let power = Field(0); // Current power of the iteration + let power = UInt64.from(0); // Current power of the iteration let loopEnded = Bool(false); // Checking if the loop has ended yet for (let i = 0; i <= MAX_ITERATION_COUNT; i++) { // Static sized loop loopEnded = Provable.if( // End Condition - Field(i).greaterThanOrEqual(power), + UInt64.from(i).equals(power), // In o1js, equality checks are much more efficient than comparisons Bool(true), - Bool(false) + loopEnded ); + result = result.mul(Provable.if( // Result loopEnded, Field(1), Field(2) )); + power = power.add(Provable.if( // Power loopEnded, - Field(0), - Field(1) + UInt64.from(0), + UInt64.from(1) )); }; @@ -336,7 +338,7 @@ function powerOfTwo(pow: Field): Field { # Functions -Functions work as you would expect in TS. However, your functions inside ZKPs must take provable arguments and return provable types, just like anything in o1js. For example: +Functions work as you would expect in TS. However, your functions inside ZKPs must return provable types, just like anything in o1js. For example: ```ts @@ -345,7 +347,7 @@ function addOneAndDouble(x: Field): Field { } ``` -**Warning:** You should _not_ use recursion with functions in o1js. If you want to recurse a ZKP, you can use [o1js recursion](./recursion.mdx). +**Warning:** You should _not_ use dynamically sized recursion with functions in o1js, just like dynamically sized loops. Static sized recursion is safe to use as the end condition is known at the compilation time. If you want to dynamically recurse a ZKP, you can use [o1js recursion](./recursion.mdx). :::info