Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new chapter for common types and expand the basic concepts #846

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 152 additions & 56 deletions docs/zkapps/o1js/basic-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,109 +27,205 @@ 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
o1js, fka. SnarkyJS, is a TypeScript (TS) library for writing general-purpose ZK programs and writing ZK smart contracts for the Mina Blockchain. To create a ZKP (zero knowledge proof), you use types and operations that can be converted into a ZKP. o1js provides different built-in types and customizable structures that you can use to create ZKPs.


## Field

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 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

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an improvement to before. But what would also be interesting for cryptographically inclined readers is to know what kind of field this is (Pallas base field) with a link to more details.

Also, I suggest to write the modulus in hex format, because that visually shows the twoadicity


:::

For example, in typical programming, you might use:
## Creating a `Field` Element

`const sum = 1 + 3`.
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.

In o1js, you write this as:
<!-- prettier-ignore -->
```ts
let x = Field(42);
```

`const sum = new Field(1).add(new Field(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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the argument you give must be an integer in the Field range.

Can you remove the second sentence here? It's not true, an integer outside the field range is also accepted and reduced modulo the field size (as you show below, with negative inputs)

Copy link
Contributor

@mitschabaude mitschabaude Feb 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or replace it with just "Note that the argument you give must be an integer."


This can be simplified as:
<!-- prettier-ignore -->
```ts
let x = Field("12347"); // From string
let y = Field(172384782343434n); // From bigint
let z = Field(x); // From another Field

`const sum = new Field(1).add(3)`
let k = Field("asdf"); // Throws, as this is not a number
let l = Field("234.34"); // Throws, as this is not an integer
```

Note that the 3 is auto-promoted to a field type to make this cleaner.
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.

## Built-in data types
<!-- prettier-ignore -->
```ts
let x = Field(-1); // If you try printing this to the screen, you will see a very big integer
```

## 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 are available on the `Field` type. They create and return a new `Field` element.

<!-- prettier-ignore -->
```ts
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
```

Some common data types you may use are:
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.

<!-- prettier-ignore -->
```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
let x = Field(45);

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)
x.equals(45); // True
x.greaterThan(45); // False
x.greaterThanOrEquals(45); // True
x.lessThan(45); // False
x.lessThanOrEquals(45); // True

CircuitString.from('some string'); // string of max length 128
// It is also possible to check if a Field is even or not
x.isEven(); // False
```

In the case of `Field` and `Bool`, you can also call the constructor without `new`:
## 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.

<!-- prettier-ignore -->
```ts
let x = Field(10);
let b = Bool(true);
let 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
```

## Conditionals
You can think assertions in o1js similar to `require()` function in Solidity.

Traditional conditional statements are not supported by o1js:
## Printing a Field to the Screen
yunus433 marked this conversation as resolved.
Show resolved Hide resolved

`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`

<!-- prettier-ignore -->
```ts
// this will NOT work
if (foo) {
x.assertEquals(y);
}
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).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it's the set of integers between 0 and p-1, where p is the prime order!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`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).
A `Field` represents a positive integer in a prime order [finite field](https://en.wikipedia.org/wiki/Finite_field), which is the set of integers $0,...,p-1$, where $p$ is the field order.
Concretely, `Field` refers to the [Pallas base field](TODO), which is the native circuit field of the proof system underlying o1js.

@yunus433 this is how I would describe the field


The order of the o1js `Field` can be accessed as a static property of the class.

<!-- prettier-ignore -->
```ts
console.log(Field.ORDER);
```

Instead, use the o1js built-in `Circuit.if()` method, which is a ternary operator:
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is great!


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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

- you receive the modulus of the result with the prime order of the field.
+ you receive the remainder of the result modulo the prime order of the field.


<!-- prettier-ignore -->
```ts
const x = Circuit.if(new Bool(foo), a, b); // behaves like `foo ? a : b`
Provable.log(Field(Field.ORDER - 1) + Field(2)); // This will print 1
```

## Functions
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.

<!-- prettier-ignore -->
```ts
Provable.log(Field(-1)); // This will print `Field.ORDER - 1`
```

Functions work as you would expect in TypeScript. For example:
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

- For division, if the result is not an integer, you receive the 
+ For division, you receive the 

of course, if the result is an integer, it's still the modular inverse


<!-- prettier-ignore -->
```ts
function addOneAndDouble(x: Field): Field {
return x.add(1).mul(2);
}
Provable.log(Field(3).div(Field(5))); // This will print a very big integer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should always use number inputs to methods instead of Field(number), looks cleaner:

Provable.log(Field(3).div(5)); // This will print a very big integer

this applies to the entire PR!

```

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.

<!-- prettier-ignore -->
```ts
// We want x, an argument of the smart contract, to be equal to 7 / 3
x.mul(Field(3)).assertEquals(Field(7));
```

## Common methods
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)).

Some common methods you will use often are:
## 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

o1js compiler

let's not use the term "compiler", it's a confusion already that people think we compile TS to circuits


However, provable types are created from regular TS variables.

<!-- prettier-ignore -->
```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
const x = 42; // This is TS number
let x_provable = Field(x); // This code is perfectly valid
```

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
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!


let hash = Poseidon.hash([x]); // takes array of Fields, returns Field
<!-- prettier-ignore -->
```ts
@method foo(x: Field) {
// Here, the value of x depends on the user input, thus type of x must be provable
}
```

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)
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.

<!-- prettier-ignore -->
```ts
let x = Field(42);
console.log(x.isConstant()); // This will print true
```

For a full list, see the [o1js reference](../o1js-reference).
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.

<!-- prettier-ignore -->
```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
}
```
Comment on lines +224 to +231
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all this is really good and important to add, thanks @yunus433

Loading