mirror of
https://github.com/ProvableHQ/leo.git
synced 2024-11-27 12:17:35 +03:00
514 lines
10 KiB
Markdown
514 lines
10 KiB
Markdown
# The Leo Programming Language
|
|
* Programs should be formatted:
|
|
1. Import definitions
|
|
2. Circuit definitions
|
|
3. Function definitions
|
|
|
|
## Mutability
|
|
* All defined variables in Leo are immutable by default.
|
|
* Variables can be made mutable with the `mut` keyword.
|
|
|
|
```rust
|
|
function main() {
|
|
let a = 0u32;
|
|
//a = 1 <- Will fail
|
|
|
|
let mut b = 0u32;
|
|
b = 1; // <- Ok
|
|
}
|
|
```
|
|
|
|
## Booleans
|
|
|
|
Explicit types are optional.
|
|
```rust
|
|
function main() -> bool {
|
|
let a: bool = true || false;
|
|
let b = false && false;
|
|
let c = 1u32 == 1u32;
|
|
return a
|
|
}
|
|
```
|
|
|
|
## Numbers
|
|
* The definition of a number must include an explict type.
|
|
* After assignment, you can choose to explicitly add the type or let the compiler interpret implicitly.
|
|
* Type casting is not supported.
|
|
* Comparators are not supported.
|
|
|
|
### Integers
|
|
Supported integer types: `u8`, `u16`, `u32`, `u64`, `u128`
|
|
```rust
|
|
function main() -> u32 {
|
|
let a = 2u32; // explicit type
|
|
let a: u32 = 1 + 1; // explicit type
|
|
|
|
let b = a - 1; // implicit type
|
|
let c = b * 4;
|
|
let d = c / 2;
|
|
let e = d ** 3;
|
|
return e
|
|
}
|
|
```
|
|
|
|
### Field Elements
|
|
```rust
|
|
function main() -> field {
|
|
let a = 1000field; // explicit type
|
|
let a: field = 21888242871839275222246405745257275088548364400416034343698204186575808495617; // explicit type
|
|
let b = a + 1; // implicit type
|
|
let c = b - 1;
|
|
let d = c * 4;
|
|
let e = d / 2;
|
|
return e
|
|
}
|
|
```
|
|
|
|
### Affine Points
|
|
The set of affine points on the elliptic curve passed into the leo compiler forms a group.
|
|
Leo supports this set as a primitive data type.
|
|
|
|
```rust
|
|
function main() -> group {
|
|
let a = 1000group; // explicit type
|
|
let a = (21888242871839275222246405745257275088548364400416034343698204186575808495617, 21888242871839275222246405745257275088548364400416034343698204186575808495617)group; // explicit type
|
|
let b = a + 0; // implicit type
|
|
let c = b - 0;
|
|
return c
|
|
}
|
|
```
|
|
|
|
### Operator Assignment Statements
|
|
```rust
|
|
function main() -> u32 {
|
|
let mut a = 10;
|
|
a += 5;
|
|
a -= 10;
|
|
a *= 5;
|
|
a /= 5;
|
|
a **= 2;
|
|
|
|
return a
|
|
}
|
|
```
|
|
|
|
## Arrays
|
|
Leo supports static arrays with fixed length.
|
|
```rust
|
|
function main() -> u32[2] {
|
|
// initialize an integer array with integer values
|
|
let mut a: u32[3] = [1, 2, 3];
|
|
|
|
// set a mutable member to a value
|
|
a[2] = 4;
|
|
|
|
// initialize an array of 4 values all equal to 42
|
|
let b = [42u8; 4];
|
|
|
|
// initialize an array of 5 values copying all elements of b using a spread
|
|
let c = [1, ...b];
|
|
|
|
// initialize an array copying a slice from `c`
|
|
let d = c[1..3];
|
|
|
|
// initialize a field array
|
|
let e = [5field; 2];
|
|
|
|
// initialize a boolean array
|
|
let f = [true, false || true, true];
|
|
|
|
return d
|
|
}
|
|
```
|
|
|
|
### Multidimensional Arrays
|
|
```rust
|
|
function main() -> u32[3][2] {
|
|
let m = [[0u32, 0u32], [0u32, 0u32]];
|
|
|
|
let m: u32[3][2] = [[0; 3]; 2];
|
|
|
|
return m
|
|
}
|
|
```
|
|
|
|
## Conditionals
|
|
|
|
### If Else Ternary Expression
|
|
```rust
|
|
function main() -> u32 {
|
|
let y = if 3==3 ? 1 : 5;
|
|
return y
|
|
}
|
|
```
|
|
|
|
### If Else Conditional Statement
|
|
** **Experimental** **
|
|
The current constraint system is not optimized for statement branching. Please use the ternary expression above until this feature is stable.
|
|
```rust
|
|
function main(a: private bool, b: private bool) -> u32 {
|
|
let mut res = 0u32;
|
|
if (a) {
|
|
res = 1;
|
|
} else if (b) {
|
|
res = 2;
|
|
} else {
|
|
res = 3;
|
|
}
|
|
return res
|
|
}
|
|
```
|
|
|
|
### For loop
|
|
```rust
|
|
function main() -> fe {
|
|
let mut a = 1field;
|
|
for i in 0..4 {
|
|
a = a + 1;
|
|
}
|
|
return a
|
|
}
|
|
```
|
|
|
|
## Functions
|
|
```rust
|
|
function test1(a : u32) -> u32 {
|
|
return a + 1
|
|
}
|
|
|
|
function test2(b: fe) -> field {
|
|
return b * 2field
|
|
}
|
|
|
|
function test3(c: bool) -> bool {
|
|
return c && true
|
|
}
|
|
|
|
function main() -> u32 {
|
|
return test1(5)
|
|
}
|
|
```
|
|
|
|
|
|
### Function Scope
|
|
```rust
|
|
function foo() -> field {
|
|
// return myGlobal <- not allowed
|
|
return 42field
|
|
}
|
|
|
|
function main() -> field {
|
|
let myGlobal = 42field;
|
|
return foo()
|
|
}
|
|
```
|
|
|
|
### Multiple returns
|
|
Functions can return tuples whose types are specified in the function signature.
|
|
```rust
|
|
function test() -> (u32, u32[2]) {
|
|
return 1, [2, 3]
|
|
}
|
|
|
|
function main() -> u32[3] {
|
|
let (a, b) = test();
|
|
// (a, u32[2] b) = test() <- explicit type also works
|
|
return [a, ...b]
|
|
}
|
|
```
|
|
|
|
### Main function inputs
|
|
Main function inputs are allocated as public or private variables in the program's constaint system.
|
|
```rust
|
|
function main(a: private field) -> field {
|
|
return a
|
|
}
|
|
```
|
|
```rust
|
|
function main(a: public field) -> field {
|
|
return a
|
|
}
|
|
```
|
|
Private by default. Below `a` is implicitly private.
|
|
```rust
|
|
function main(a: field) -> field {
|
|
return a
|
|
}
|
|
```
|
|
Function inputs are passed by value.
|
|
```rust
|
|
function test(mut a: u32) {
|
|
a = 0;
|
|
}
|
|
|
|
function main() -> u32 {
|
|
let a = 1;
|
|
test(a);
|
|
|
|
return a // <- returns 1
|
|
}
|
|
```
|
|
|
|
## Circuits
|
|
Circuits in Leo are similar to classes in object oriented langauges. Circuits are defined above functions in a Leo program. Circuits can have one or more members.
|
|
|
|
|
|
Members can be defined as fields which hold primitive values
|
|
```rust
|
|
circuit Point {
|
|
x: u32
|
|
y: u32
|
|
}
|
|
function main() -> u32 {
|
|
let p = Point {x: 1, y: 0};
|
|
return p.x
|
|
}
|
|
```
|
|
|
|
Members can also be defined as functions.
|
|
```rust
|
|
circuit Circ {
|
|
function echo(x: u32) -> u32 {
|
|
return x
|
|
}
|
|
}
|
|
|
|
function main() -> u32 {
|
|
let c = Circ { };
|
|
return c.echo(1u32)
|
|
}
|
|
```
|
|
|
|
Circuit functions can be made static, enabling them to be called without instantiation.
|
|
```rust
|
|
circuit Circ {
|
|
static function echo(x: u32) -> u32 {
|
|
return x
|
|
}
|
|
}
|
|
|
|
function main() -> u32 {
|
|
return Circ::echo(1u32)
|
|
}
|
|
```
|
|
|
|
The `Self` keyword is supported in circuit functions.
|
|
```rust
|
|
circuit Circ {
|
|
b: bool
|
|
|
|
static function new() -> Self {
|
|
return Self { b: true }
|
|
}
|
|
}
|
|
|
|
function main() -> Circ {
|
|
let c = Circ::new();
|
|
return c.b
|
|
}
|
|
```
|
|
|
|
## Imports
|
|
Both struct and function imports are supported.
|
|
|
|
import all: `*`
|
|
import alias: `symbol as alias`
|
|
|
|
`src/simple_import.leo`
|
|
```rust
|
|
circuit Point {
|
|
x: u32
|
|
y: u32
|
|
}
|
|
|
|
function test() -> (u32, u32[2]) {
|
|
return 1, [2, 3]
|
|
}
|
|
```
|
|
|
|
`src/simple.leo`
|
|
```rust
|
|
from "./simple_import" import {
|
|
Point as Foo,
|
|
test
|
|
};
|
|
|
|
// from "./simple_import" import *
|
|
|
|
function main() -> (u32[3]) {
|
|
let p = Foo { x: 1, y: 2};
|
|
|
|
let (a, b) = test();
|
|
|
|
return [a, ...b]
|
|
}
|
|
```
|
|
|
|
## Constraints
|
|
|
|
### Assert Equals
|
|
This will enforce that the two values are equal in the constraint system.
|
|
|
|
```rust
|
|
function main() {
|
|
assert_eq(45, 45);
|
|
|
|
assert_eq(2fe, 2fe);
|
|
|
|
assert_eq(true, true);
|
|
}
|
|
```
|
|
|
|
## Testing
|
|
|
|
Use the `test` keyword to add tests to a leo program. Tests must have 0 function inputs and 0 function returns.
|
|
|
|
```rust
|
|
function main(a: u32) -> u32 {
|
|
return a
|
|
}
|
|
|
|
test function expect_pass() {
|
|
let a = 1u32;
|
|
|
|
let res = main(a);
|
|
|
|
assert_eq!(res, 1u32);
|
|
}
|
|
|
|
test function expect_fail() {
|
|
assert_eq!(1u8, 0u8);
|
|
}
|
|
```
|
|
|
|
|
|
# Leo CLI
|
|
|
|
## Develop
|
|
|
|
### `leo new`
|
|
|
|
To setup a new package, run:
|
|
```
|
|
leo new {$NAME}
|
|
```
|
|
This will create a new directory with a given package name. The new package will have a directory structure as follows:
|
|
```
|
|
- inputs # Your program inputs
|
|
- outputs # Your program outputs
|
|
- src
|
|
- lib.leo # Your program library
|
|
- main.leo # Your program
|
|
- tests
|
|
- test.leo # Your program tests
|
|
- Leo.toml # Your program manifest
|
|
```
|
|
|
|
### `leo init`
|
|
|
|
To initialize an existing directory, run:
|
|
```
|
|
leo init
|
|
```
|
|
This will initialize the current directory with the same package directory setup.
|
|
|
|
### `leo build`
|
|
|
|
To compile your program and verify that it builds properly, run:
|
|
```
|
|
leo build
|
|
```
|
|
|
|
### `leo test`
|
|
|
|
To execute unit tests on your program, run:
|
|
```
|
|
leo test
|
|
```
|
|
The results of test compilation and the constraint system will be printed:
|
|
```
|
|
INFO leo Running 2 tests
|
|
INFO leo test language::expect_pass compiled. Constraint system satisfied: true
|
|
ERROR leo test language::expect_fail errored: Assertion 1u8 == 0u8 failed
|
|
```
|
|
|
|
## Run
|
|
|
|
### `leo setup`
|
|
|
|
To perform the program setup, producing a proving key and verification key, run:
|
|
```
|
|
leo setup
|
|
```
|
|
Leo uses cryptographic randomness from your machine to perform the setup. The proving key and verification key are stored in the `target` directory as `.leo.pk` and `.leo.vk`:
|
|
|
|
```
|
|
{$LIBRARY}/target/{$PROGRAM}.leo.pk
|
|
{$LIBRARY}/target/{$PROGRAM}.leo.vk
|
|
```
|
|
|
|
### `leo prove`
|
|
|
|
To execute the program and produce an execution proof, run:
|
|
```
|
|
leo prove
|
|
```
|
|
Leo starts by checking the `target` directory for an existing `.leo.pk` file. If it doesn't exist, it will proceed to run `leo setup` and then continue.
|
|
|
|
Once again, Leo uses cryptographic randomness from your machine to produce the proof. The proof is stored in the `target` directory as `.leo.proof`:
|
|
|
|
```
|
|
{$LIBRARY}/target/{$PROGRAM}.leo.proof
|
|
```
|
|
|
|
### `leo verify`
|
|
|
|
To verify the program proof, run:
|
|
```
|
|
leo verify
|
|
```
|
|
Leo starts by checking the `target` directory for an existing `.leo.proof` file. If it doesn't exist, it will proceed to run `leo prove` and then continue.
|
|
|
|
After the verifier is run, Leo will output either `true` or `false` based on the verification.
|
|
|
|
## Remote
|
|
|
|
To use remote compilation features, start by authentication with:
|
|
```
|
|
leo login
|
|
```
|
|
You will proceed to authenticate using your username and password. Next, Leo will parse your `Leo.toml` file for `remote = True` to confirm whether remote compilation is enabled.
|
|
|
|
If remote compilation is enabled, Leo syncs your workspace so when you run `leo build`, `leo test`, `leo setup` and `leo prove`, your program will run the program setup and execution performantly on remote machines.
|
|
|
|
This speeds up the testing cycle and helps the developer to iterate significantly faster.
|
|
|
|
## Publish
|
|
|
|
To package your program as a gadget and publish it online, run:
|
|
```
|
|
leo publish
|
|
```
|
|
Leo will proceed to snapshot your directory and upload your directory to the circuit manager. Leo will verify that `leo build` succeeds and that `leo test` passes without error.
|
|
|
|
If your gadget name has already been taken, `leo publish` will fail.
|
|
|
|
## Deploy
|
|
|
|
To deploy your program to Aleo, run:
|
|
```
|
|
leo deploy
|
|
```
|
|
|
|
# Install
|
|
|
|
To install Leo from source, in the root directory of the repository, run:
|
|
```
|
|
cargo install --path .
|
|
```
|
|
|
|
## TODO
|
|
|
|
- Change `target` directory to some other directory to avoid collision.
|
|
- Figure out how `leo prove` should take in assignments.
|
|
- Come up with a serialization format for `.leo.pk`, `.leo.vk`, and `.leo.proof`. |