-
Notifications
You must be signed in to change notification settings - Fork 130
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
yunus433 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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. | ||
yunus433 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||
|
||
`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. | ||
yunus433 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
`const sum = new Field(1).add(new Field(3))` | ||
|
||
This can be simplified as: | ||
<!-- prettier-ignore --> | ||
```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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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." |
||
|
||
Note that the 3 is auto-promoted to a field type to make this cleaner. | ||
<!-- prettier-ignore --> | ||
```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. | ||
yunus433 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
<!-- 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 | ||
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 | ||
yunus433 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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. | ||
yunus433 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
<!-- prettier-ignore --> | ||
```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. | ||
|
||
<!-- prettier-ignore --> | ||
```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. | ||
|
||
<!-- prettier-ignore --> | ||
```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 | ||
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` without a conversion, you will get an unreadable output. Hopefully, o1js gives a variety of conversion methods to print `Field` elements to the screen. | ||
|
||
<!-- prettier-ignore --> | ||
```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. | ||
|
||
<!-- 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 | ||
|
||
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). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.