Skip to content

Commit 0a58fcf

Browse files
author
noirwhal
committed
chore(docs): cut new docs version for tag v1.0.0-beta.19
1 parent 0ece482 commit 0a58fcf

File tree

105 files changed

+14274
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

105 files changed

+14274
-0
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
title: Oracles
3+
description: This guide provides an in-depth understanding of how Oracles work in Noir programming. Learn how to use outside calculations in your programs, constrain oracles, and understand their uses and limitations.
4+
keywords:
5+
- Noir Programming
6+
- Oracles
7+
- JSON-RPC
8+
- Foreign Call Handlers
9+
- Constrained Functions
10+
- Blockchain Programming
11+
sidebar_position: 1
12+
---
13+
14+
If you've seen "The Matrix" you may recall "The Oracle" as Gloria Foster smoking cigarettes and baking cookies. While she appears to "know things", she is actually providing a calculation of a pre-determined future. Noir Oracles are similar, in a way. They don't calculate the future (yet), but they allow you to use outside calculations in your programs.
15+
16+
![matrix oracle prediction](@site/static/img/memes/matrix_oracle.jpeg)
17+
18+
A Noir program is usually self-contained. You can pass certain inputs to it, and it will generate a deterministic output for those inputs. But what if you wanted to defer some calculation to an outside process or source?
19+
20+
Oracles are functions that provide this feature.
21+
22+
## Use cases
23+
24+
An example usage for Oracles is proving something on-chain. For example, proving that the ETH-USDC quote was below a certain target at a certain block time. Or even making more complex proofs like proving the ownership of an NFT as an anonymous login method.
25+
26+
Another interesting use case is to defer expensive calculations to be made outside of the Noir program, and then constraining the result; similar to the use of [unconstrained functions](../noir/concepts//unconstrained.md).
27+
28+
In short, anything that can be constrained in a Noir program but needs to be fetched from an external source is a great candidate to be used in oracles.
29+
30+
## Constraining oracles
31+
32+
Just like in The Matrix, Oracles are powerful. But with great power, comes great responsibility. Just because you're using them in a Noir program doesn't mean they're true. Noir has no superpowers. If you want to prove that Portugal won the Euro Cup 2016, you're still relying on potentially untrusted information.
33+
34+
To give a concrete example, Alice wants to login to the [NounsDAO](https://nouns.wtf/) forum with her username "noir_nouner" by proving she owns a noun without revealing her ethereum address. Her Noir program could have an oracle call like this:
35+
36+
```rust
37+
#[oracle(getNoun)]
38+
unconstrained fn get_noun(address: Field) -> Field
39+
```
40+
41+
This oracle could naively resolve with the number of Nouns she possesses. However, it is useless as a trusted source, as the oracle could resolve to anything Alice wants. In order to make this oracle call actually useful, Alice would need to constrain the response from the oracle, by proving her address and the noun count belongs to the state tree of the contract.
42+
43+
In short, **Oracles don't prove anything. Your Noir program does.**
44+
45+
:::danger
46+
47+
If you don't constrain the return of your oracle, you could be clearly opening an attack vector on your Noir program. Make double-triple sure that the return of an oracle call is constrained!
48+
49+
:::
50+
51+
## Return Type
52+
53+
Oracles cannot return references (e.g., `&Field`, `&mut T`) because oracle functions represent external computations that execute outside the Noir program's memory space. Since oracles bridge between the Noir runtime and external environments, any values they return must be copied into the caller's context rather than referenced. Attempting to return a reference from an oracle would be meaningless, as there is no shared memory space between the oracle's execution environment and the Noir program.
54+
55+
This restriction applies to any type containing references, including:
56+
- Direct references: `&Field`, `&mut u32`
57+
- Tuples with references: `(Field, &u32)`
58+
- Structs with reference fields
59+
- Enums with reference variants
60+
61+
The compiler will produce an error if an oracle function's return type contains any references.
62+
63+
## How to use Oracles
64+
65+
On CLI, Nargo resolves oracles by making JSON RPC calls, which means it would require an RPC node to be running.
66+
67+
In JavaScript, NoirJS accepts and resolves arbitrary call handlers (that is, not limited to JSON) as long as they match the expected types the developer defines.
68+
69+
If you want to build using oracles, follow through to the [oracle guide](../how_to/how-to-oracles.md) for a simple example on how to do that.
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
---
2+
title: Thinking in Circuits
3+
description: Considerations when writing Noir programs
4+
keywords: [Noir, programming, rust]
5+
tags: [Optimization]
6+
sidebar_position: 0
7+
---
8+
9+
10+
This article intends to set you up with key concepts essential for writing more viable applications that use zero knowledge proofs, namely around efficient circuits.
11+
12+
## Context - 'Efficient' is subjective
13+
14+
When writing a web application for a performant computer with high-speed internet connection, writing efficient code sometimes is seen as an afterthought only if needed. Large multiplications running at the innermost of nested loops may not even be on a dev's radar.
15+
When writing firmware for a battery-powered microcontroller, you think of cpu cycles as rations to keep within a product's power budget.
16+
17+
> Code is written to create applications that perform specific tasks within specific constraints
18+
19+
And these constraints differ depending on where the compiled code is execute.
20+
21+
### The Ethereum Virtual Machine (EVM)
22+
23+
In scenarios where extremely low gas costs are required for an Ethereum application to be viable/competitive, Ethereum smart contract developers get into what is colloquially known as: "*gas golfing*". Finding the lowest execution cost of their compiled code (EVM bytecode) to achieve a specific task.
24+
25+
The equivalent optimization task when writing zk circuits is affectionately referred to as "*gate golfing*", finding the lowest gate representation of the compiled Noir code.
26+
27+
### Coding for circuits - a paradigm shift
28+
29+
In zero knowledge cryptography, code is compiled to "circuits" consisting of arithmetic gates, and gate count is the significant cost. Depending on the proving system this is linearly proportionate to proof size and proving time, so from a product point of view this should be kept as low as possible.
30+
31+
Whilst writing efficient code for web apps and Solidity have some differences, writing efficient circuits have a different set of considerations. It is a bit of a paradigm shift, like writing code for GPUs for the first time...
32+
33+
For example, drawing a circle at (0, 0) of radius `r`:
34+
- For a single CPU thread,
35+
```
36+
for theta in 0..2*pi {
37+
let x = r * cos(theta);
38+
let y = r * sin(theta);
39+
draw(x, y);
40+
} // note: would do 0 - pi/2 and draw +ve/-ve x and y.
41+
```
42+
43+
- For GPUs (simultaneous parallel calls with x, y across image),
44+
```
45+
if (x^2 + y^2 = r^2) {
46+
draw(x, y);
47+
}
48+
```
49+
50+
([Related](https://www.youtube.com/watch?v=-P28LKWTzrI))
51+
52+
Whilst this CPU -> GPU does not translate to circuits exactly, it is intended to exemplify the difference in intuition when coding for different machine capabilities/constraints.
53+
54+
### Context Takeaway
55+
56+
For those coming from a primarily web app background, this article will explain what you need to consider when writing circuits. Furthermore, for those experienced writing efficient machine code, prepare to shift what you think is efficient 😬
57+
58+
## Translating from Rust
59+
60+
Programs written in anything from pseudo code to C, can be translated into Noir. A Rust program written for execution can be readily ported to Noir thanks to the similarities in syntax.
61+
62+
:::note
63+
Many valuable functions and algorithms have been written in more established languages (C/C++), and converted to modern ones (like Rust).
64+
:::
65+
66+
Fortunately for Noir developers, when needing a particular function a Rust implementation can be readily compiled into Noir with some key changes. While the compiler does a decent amount of optimizations, it won't be able to change code that has been optimized for clock-cycles into code optimized for arithmetic gates.
67+
68+
A few things to do when converting Rust code to Noir:
69+
- `println!` is not a macro, use `println` function (same for `assert_eq`)
70+
- No early `return` in function. Use constrain via assertion instead
71+
- No passing by reference. Remove `&` operator to pass by value (copy)
72+
- No boolean operators (`&&`, `||`). Use bitwise operators (`&`, `|`) with boolean values
73+
- No type `usize`. Use types `u8`, `u32`, `u64`, ...
74+
- `main` return must be public, `pub`
75+
- No `const`, use `global`
76+
- Noir's LSP is your friend, so error message should be informative enough to resolve syntax issues.
77+
78+
## Writing efficient Noir for performant products
79+
80+
The following points help refine our understanding over time.
81+
82+
:::note
83+
A Noir program makes a statement that can be verified.
84+
:::
85+
86+
It compiles to a structure that represents the calculation, and can assert results within the calculation at any stage (via the `constrain` keyword).
87+
88+
A Noir program compiles to an Abstract Circuit Intermediate Representation which is:
89+
- Conceptually a tree structure
90+
- Leaves (inputs) are the `Field` type
91+
- Nodes contain arithmetic operations to combine them (gates)
92+
- The root is the final result (return value)
93+
94+
:::tip
95+
The command `nargo info` shows the programs circuit size, and is useful to compare the value of changes made.
96+
You can dig deeper and use the `--print-acir` param to take a closer look at individual ACIR opcodes, and the proving backend to see its gate count (eg for barretenberg, the `bb` binary has a `gates` option).
97+
:::
98+
99+
### Numerical types
100+
101+
As mentioned earlier Noir has many familiar integer types (eg `i8`, `u64`). Ideally after bringing a program into Noir, proving/verifying of its execution just works where needed: client/server side, on an evm, or on the Aztec network.
102+
103+
A program optimized for execution may leverage the binary representations of integers, reducing the number of clock cycles, and thus time of execution.
104+
The cryptography in a proving backend makes use of a `Field` type, and leveraging this lower level type correctly can reduce gate count, and thus proof size and proving time.
105+
106+
In some instances simply replacing the integer type with a `Field` could save on some range checks (and hence gates).
107+
Note: when casting a `Field` to an integer type, the value is converted based on the integer binary representation. Eg a Field variable with a value of 260 `as u8` becomes 4
108+
109+
### `Field`s for efficiency
110+
111+
`Field` types have their own underlying representation that is efficient for cryptography, which is different to binary representations efficient for CPUs. So, mathematically speaking, things like bitwise operations do not directly translate to fields. That said, the same outcome can be achieved if wanting to use the Field type as a number with lower overhead.
112+
113+
For instance shift (`<<`) and or (`|`) work seamlessly with integer types (bit-packing `u8`'s into a `u16`):
114+
```
115+
high as u16 << 8 | low as u16
116+
```
117+
118+
More efficiently with `Field` types, the equivalent is:
119+
```
120+
low.assert_max_bit_size::<8>(); // ensure Field values could be represented as 8 bit numbers
121+
high.assert_max_bit_size::<8>();
122+
(high * 2.pow_32(8) + low)
123+
```
124+
(Note, the power of two can instead be a constant (256) or global evaluated at compile time)
125+
126+
The first snippet is good for compatibility when using existing code, converting to the latter can help optimize frequently used functions.
127+
128+
:::tip
129+
Where possible, use the `Field` type for values. Writing code with smaller value types and bit-packing strategies will result in MORE gates
130+
:::
131+
132+
### Use Arithmetic over non-arithmetic operations
133+
134+
Since circuits are made of arithmetic gates, the cost of arithmetic operations tends to be one gate. Whereas for procedural code, they represent several clock cycles.
135+
136+
Inversely, non-arithmetic operators are achieved with multiple gates, vs 1 clock cycle for procedural code.
137+
138+
| (cost\op) | arithmetic<br>(`*`, `+`) | bit-wise ops<br>(eg `<`, `\|`, `>>`) |
139+
| - | - | - |
140+
| **cycles** | 10+ | 1 |
141+
| **gates** | 1 | 10+ |
142+
143+
Bit-wise operations (e.g. bit shifts `<<` and `>>`), albeit commonly used in general programming and especially for clock cycle optimizations, are on the contrary expensive in gates when performed within circuits.
144+
145+
Translate away from bit shifts when writing constrained functions for the best performance.
146+
147+
On the flip side, feel free to use bit shifts in unconstrained functions and tests if necessary, as they are executed outside of circuits and does not induce performance hits.
148+
149+
### Use static over dynamic values
150+
151+
Another general theme that manifests in different ways is that static reads are represented with less gates than dynamic ones.
152+
153+
Reading from read-only memory (ROM) adds less gates than random-access memory (RAM), 2 vs ~3.25 due to the additional bounds checks. Arrays of fixed length (albeit used at a lower capacity), will generate less gates than dynamic storage.
154+
155+
Related to this, if an index used to access an array is not known at compile time (ie unknown until run time), then ROM will be converted to RAM, expanding the gate count.
156+
157+
:::tip
158+
Use arrays and indices that are known at compile time where possible.
159+
Using `assert_constant(i);` before an index, `i`, is used in an array will give a compile error if `i` is NOT known at compile time.
160+
:::
161+
162+
### Reduce what is inside loops and conditional logic
163+
164+
Putting less logic inside an `if` (`else`, etc) paths, or inside a loop, translates to less gates required to represent the program. The compiler should mostly take care of this.
165+
166+
A loop duplicates the gates for each iteration of the loop, or put another way, "unrolls" the loop. Any calculations/calls that are unchanged in the loop should be calculated once before, and the result used in the loop.
167+
168+
An `if` statement is "flattened" and gates created for each path even if execution uses only one path. Furthermore, there are additional operations required for each path. Sometimes this can have a multiplying effect on the operations in the `if` and `else` etc.
169+
170+
:::tip
171+
Only have essential computation inside conditional logic and loops, and calculate anything else once (before, or after, depending).
172+
:::
173+
174+
### Leverage unconstrained execution
175+
176+
Constrained verification can leverage unconstrained execution, this is especially useful for operations that are represented by many gates.
177+
Use an [unconstrained function](../noir/concepts/unconstrained.md) to perform gate-heavy calculations, then verify and constrain the result.
178+
179+
Eg division generates more gates than multiplication, so calculating the quotient in an unconstrained function then constraining the product for the quotient and divisor (+ any remainder) equals the dividend will be more efficient.
180+
181+
Use ` if is_unconstrained() { /`, to conditionally execute code if being called in an unconstrained vs constrained way.
182+
183+
## Advanced
184+
185+
Unless you're well into the depth of gate optimization, this advanced section can be ignored.
186+
187+
### Combine arithmetic operations
188+
189+
A Noir program can be honed further by combining arithmetic operators in a way that makes the most of each constraint of the backend proving system. This is in scenarios where the backend might not be doing this perfectly.
190+
191+
Eg Barretenberg backend (current default for Noir) is a width-4 PLONKish constraint system
192+
$ w_1*w_2*q_m + w_1*q_1 + w_2*q_2 + w_3*q_3 + w_4*q_4 + q_c = 0 $
193+
194+
Here we see there is one occurrence of witness 1 and 2 ($w_1$, $w_2$) being multiplied together, with addition to witnesses 1-4 ($w_1$ .. $w_4$) multiplied by 4 corresponding circuit constants ($q_1$ .. $q_4$) (plus a final circuit constant, $q_c$).
195+
196+
Use `nargo info --print-acir`, to inspect the ACIR opcodes (and the proving system for gates), and it may present opportunities to amend the order of operations and reduce the number of constraints.
197+
198+
#### Variable as witness vs expression
199+
200+
If you've come this far and really know what you're doing at the equation level, a temporary lever (that will become unnecessary/useless over time) is: `std::as_witness`. This informs the compiler to save a variable as a witness not an expression.
201+
202+
The compiler will mostly be correct and optimal, but this may help some near term edge cases that are yet to optimize.
203+
Note: When used incorrectly it will create **less** efficient circuits (higher gate count).
204+
205+
## References
206+
- Guillaume's ["`Crypdoku`" talk](https://www.youtube.com/watch?v=MrQyzuogxgg) (Jun'23)
207+
- [Idiomatic Noir](https://www.vlayer.xyz/blog/idiomatic-noir-part-1-collections) blog post
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
---
2+
title: Standalone Noir Installation
3+
description: There are different ways to install Nargo, the one-stop shop and command-line tool for developing Noir programs. This guide explains how to specify which version to install when using noirup, and using WSL for windows.
4+
keywords: [
5+
Installation
6+
Nargo
7+
Noirup
8+
Binaries
9+
Compiling from Source
10+
WSL for Windows
11+
macOS
12+
Linux
13+
Nix
14+
Direnv
15+
Uninstalling Nargo
16+
]
17+
sidebar_position: 2
18+
---
19+
20+
Noirup is the endorsed method for installing Nargo, streamlining the process of fetching binaries or compiling from source. It supports a range of options to cater to your specific needs, from nightly builds and specific versions to compiling from various sources.
21+
22+
### Installing Noirup
23+
24+
First, ensure you have `noirup` installed:
25+
26+
```sh
27+
curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash
28+
```
29+
30+
### Fetching Binaries
31+
32+
With `noirup`, you can easily switch between different Nargo versions, including nightly builds:
33+
34+
- **Nightly Version**: Install the latest nightly build.
35+
36+
```sh
37+
noirup --version nightly
38+
```
39+
40+
- **Specific Version**: Install a specific version of Nargo.
41+
42+
```sh
43+
noirup --version <version>
44+
```
45+
46+
### Compiling from Source
47+
48+
`noirup` also enables compiling Nargo from various sources:
49+
50+
- **From a Specific Branch**: Install from the latest commit on a branch.
51+
52+
```sh
53+
noirup --branch <branch-name>
54+
```
55+
56+
- **From a Fork**: Install from the main branch of a fork.
57+
58+
```sh
59+
noirup --repo <username/repo>
60+
```
61+
62+
- **From a Specific Branch in a Fork**: Install from a specific branch in a fork.
63+
64+
```sh
65+
noirup --repo <username/repo> --branch <branch-name>
66+
```
67+
68+
- **From a Specific Pull Request**: Install from a specific PR.
69+
70+
```sh
71+
noirup --pr <pr-number>
72+
```
73+
74+
- **From a Specific Commit**: Install from a specific commit.
75+
76+
```sh
77+
noirup -C <commit-hash>
78+
```
79+
80+
- **From Local Source**: Compile and install from a local directory.
81+
82+
```sh
83+
noirup --path ./path/to/local/source
84+
```
85+
86+
## Installation on Windows
87+
88+
The default backend for Noir (Barretenberg) doesn't provide Windows binaries at this time. For that reason, Noir cannot be installed natively. However, it is available by using Windows Subsystem for Linux (WSL).
89+
90+
Step 1: Follow the instructions [here](https://learn.microsoft.com/en-us/windows/wsl/install) to install and run WSL.
91+
92+
step 2: Follow the [Noirup instructions](#installing-noirup).
93+
94+
## Setting up shell completions
95+
96+
Once `nargo` is installed, you can [set up shell completions for it](../tooling/shell_completions.md).
97+
98+
## Uninstalling Nargo
99+
100+
If you installed Nargo with `noirup`, you can uninstall Nargo by removing the files in `~/.nargo`, `~/nargo`, and `~/noir_cache`. This ensures that all installed binaries, configurations, and cache related to Nargo are fully removed from your system.
101+
102+
```bash
103+
rm -r ~/.nargo
104+
rm -r ~/nargo
105+
rm -r ~/noir_cache
106+
```

0 commit comments

Comments
 (0)