Skip to content

Releases: ProvableHQ/leo

v3.4.0

02 Dec 21:30

Choose a tag to compare

Release Notes

Empty arrays and empty loop

Arrays of size 0 and loops over empty ranges are now supported in Leo. While these constructs do not produce any instructions in the compiled Aleo bytecode, they enable more generic or pattern-based programming styles in Leo, especially when writing code that abstracts over sizes, iterates conditionally, or uses compile-time parameters. For example:

inline build_default::[N: u32]() -> [u8; N] {
    let xs: [u8; N] = [0u8; N];
    // When N = 0 this loop is simply skipped
    for i:u32 in 0..N {
        xs[i] = 1u8;
    }
    return xs;
}

let xs = build_default::[0](); // yields []

Stability improvements

  • Improved identifier validation in Leo, resulting in clearer and more precise error messages, especially when Leo identifiers conflict with reserved Aleo identifiers.
  • Fixed an issue where local const values failed to compile correctly when used in loop ranges.
  • Resolved a crash in the common subexpression elimination optimization pass that occurred for certain patterns of function calls.

Breaking Changes

  • The *hash_native* core functions have been renamed to *hash_to_bits*.

v3.3.1

29 Oct 22:10

Choose a tag to compare

Patch

  • Fixes error in common subexpression elimination.

v3.3.0

28 Oct 20:11

Choose a tag to compare

Release Notes

Optional Types (T?)

Leo now supports first-class optional types using the T? syntax (e.g., u8?, Foo?, [u64?; 2]).
Optional values can be compared with none, assigned, passed into inline functions, and stored in arrays and structs.

program optionals.aleo {
    struct Point { x: u32, y: u32 }

    transition main() {
        // Optional integers
        let x: u8? = 42u8;
        let y = x.unwrap();        // Returns 42u8

        let z: u8? = none;
        let a = z.unwrap_or(99u8); // Returns 99u8

        // Array of optionals
        let arr: [u16?; 2] = [1u16, none];
        let first_val = arr[0].unwrap();         // Returns 1u16
        let second_val = arr[1].unwrap_or(0u16); // Returns 0u16

        // Optional struct
        let p: Point? = none;
        let p_val = p.unwrap_or(Point { x: 0u32, y: 0u32 }); // Returns default Point
    }
}

Type coercion from T to T? is supported in variable definitions, inline function calls, and intermediate expressions.
Explicit unwrapping is required to go from T? → T.


New Storage System

Leo now supports persistent storage variables and storage vectors using the storage keyword.
Storage variables and vectors are declared at program scope, similar to mappings.

program storage_ops.aleo {
    struct Point { x: field, y: field }

    storage counter: u32;        // singleton storage variable
    storage points: [Point];     // storage vector of `Point`s

    transition main() -> Future {
        return async {
            counter = 5u32;
            let old = counter.unwrap_or(0u32); // returns optional
            points.push(Point { x: 1field, y: 2field });
            let first = points.get(0u32).unwrap();
            points.set(0u32, Point { x: 3field, y: 4field });
            counter = none; // unset
        }
    }
}

Storage vector supported operations:

vec.push(10u32);         // Push 10u32 at the end of vector `vec`
let x = vec.pop();       // Pop and return the last element of `vec`
let y = vec.get(5);      // Get element at index 5
vec.set(3, 5u32);        // Set element at index 3 to `5u32`
let y = vec.len();       // Return the number of elements in `vec`
vec.swap_remove(3);      // Remove element at index 3 and replace with last

Internally, the compiler rewrites these high-level constructs into mappings and mapping operations.


ECDSA Signature Verification

ECDSA signature verification is now supported with 20 variants covering different hash algorithms and address formats.

// Verify with digest (pre-hashed message)
let valid: bool = ECDSA::verify_digest(sig, addr, digest);
let valid: bool = ECDSA::verify_digest_eth(sig, eth_addr, digest);

// Verify with Keccak256 hashing
let valid: bool = ECDSA::verify_keccak256(sig, addr, msg);
let valid: bool = ECDSA::verify_keccak256_raw(sig, addr, msg);
let valid: bool = ECDSA::verify_keccak256_eth(sig, eth_addr, msg);

Also available: keccak384, keccak512, sha3_256, sha3_384, sha3_512.

Parameters:

Parameter Type Description
sig [u8; 65] ECDSA signature (r, s, v)
addr [u8; 33] or [u8; 20] Public key or Ethereum-style address
digest [u8; 32] Pre-hashed message
msg Any Message to hash and verify

Raw Hash Operations

Leo now supports raw hash variants. These omit metadata and directly hash input bits, useful for interoperability with external (especially EVM) systems.
Inputs for raw variants of Keccak* and Sha3* must be byte-aligned (number of bits is a multiple of 8).

// Raw hash functions
let h: field = Keccak256::hash_to_field_raw(input);
let h: group = BHP256::hash_to_group_raw(input);
let h: address = Pedersen64::hash_to_address_raw(input);

// Native variants
let bits: [bool; 256] = Keccak256::hash_native(input);
let bits: [bool; 256] = Keccak256::hash_native_raw(input);

Available for:
BHP256, BHP512, BHP768, BHP1024, Pedersen64, Pedersen128, Poseidon2, Poseidon4, Poseidon8, Keccak256, Keccak384, Keccak512, SHA3_256, SHA3_384, SHA3_512.


Serialization / Deserialization Operations

Leo now supports serialize and deserialize operations to and from bits, with both metadata-inclusive and raw variants.
The compiler checks that bit sizes match.

// Standard serialization (includes metadata)
let bits: [bool; 58] = Serialize::to_bits(value);
let value: u32 = Deserialize::from_bits::[u32](bits);

// Raw serialization (no metadata)
let bits: [bool; 32] = Serialize::to_bits_raw(value);
let value: u32 = Deserialize::from_bits_raw::[u32](bits);

// Arrays
let bits: [bool; 128] = Serialize::to_bits_raw([1u32, 2u32, 3u32, 4u32]);
let arr: [u32; 4] = Deserialize::from_bits_raw::[[u32; 4]](bits);

Bit Lengths

Type Raw Bits Notes
u32 32
field 253
scalar 251
address 253
Non-raw + metadata overhead

leo synthesize Command

Generate proving and verifying keys for all transitions in a local or remote Leo program, along with circuit metadata:

leo synthesize credits.aleo --save keys \
  --endpoint https://api.explorer.provable.com/v1 \
  --network mainnet

Output includes:

  • Public inputs
  • Variables
  • Constraints
  • Non-zero entries in matrices
  • Circuit ID
  • Proving and verifying keys saved to disk

This enables better understanding of program size and key management.


Lossless Syntax Tree Parser

A new lossless syntax tree parser has been added.
While not user-facing yet, it lays the foundation for a future code formatter.


Common Subexpression Elimination (CSE)

New optimization pass reduces bytecode size by eliminating redundant expressions.


Enhanced Error Messages

Error messages now display multiple related source locations.
Example with duplicate struct members:

struct Person {
    name: field,
    age: u8,
    name: field,
}

Error:

Error [ETYC0372017]: the name `name` is defined multiple times in struct `Person`
    --> src/main.leo:3:9
   |
 3 |         name: field,
   |         ^^^^^^^^^^^ previous definition here
 ...
 5 |         name: field,
   |         ^^^^^^^^^^^ redefined here

Remaining Stability Improvements

  • Various fixes to the interpreter related to hashing correctness
  • Fixed broken leo query committee endpoint
  • Validates program names in leo new against reserved SnarkVM keywords
  • Enforces test isolation for native tests
  • Speeds up leo test by running native tests in parallel
  • Supports CheatCode::set_signer("APrivateKey1...") for test signer switching

v3.2.0

09 Sep 18:55

Choose a tag to compare

🦁 Leo 3.2.0 Release Notes

📦 Modules

Leo 3.2.0 introduces a module system.

Example: Defining a Module

Given a file other_module.leo containing struct, const, and inline definitions:

const X: u32 = 2u32;

struct S {
    a: field
}

inline increment(x: field) -> field {
    return 1field;
}

You may refer to contents of the module as:

  • other_module::X
  • other_module::S
  • other_module::increment

📁 Example Directory Structure

src
├── common.leo
├── main.leo
├── outer.leo
└── outer
    └── inner.leo

📦 Module Access Paths

Given the structure above, the following modules are defined:

  1. common.leo → defines module common
    Accessible from main.leo via:

    common::<item>
  2. outer.leo → defines module outer
    Accessible from main.leo via:

    outer::<item>
  3. outer/inner.leo → defines submodule outer::inner
    Accessible from:

    • main.leo:

      outer::inner::<item>
    • outer.leo:

      inner::<item>

📏 Module Layout Rules

New rules enforce consistency and prevent ambiguity:

  1. Leaf modules (modules without submodules) must be defined in a single file:

    foo.leo
    
  2. Modules with submodules must be defined by an optional top-level file and a subdirectory:

    foo.leo        # defines module `foo` (optional)
    foo/
      └── bar.leo  # defines submodule `foo::bar`
    

⚠️ Currently, only relative paths are supported.
For example, items in outer.leo cannot be accessed from items in inner.leo. This limitation will be removed once absolute paths are implemented.


🔄 Leo Update Command

The leo update command now supports selecting a specific version:

leo update --name v3.0.0

🔐 Other Updates

  • Decrypt records via CLI: You can now decrypt records directly from the command line.
  • Pass ciphertexts to leo execute: Record ciphertexts may be passed as inputs to execution.

🐞 Bug Fixes & Improvements

  • Fixed outputting code regarding parenthesizing operands of the ternary conditional.
  • Removed redundant type errors in async function calls.
  • Correctly handle shorthand struct initializers that are consts.
  • leo clean no longer errors when outputs/ or build/ directory is missing.
  • Correctly type-check return statements inside constructors.
  • Added a check for the snarkOS installation location.
  • Ensure const arguments are evaluated before monomorphization.
  • Improved error messages for unresolved generics.

v3.1.0

13 Aug 00:03

Choose a tag to compare

  • Upgradability: See the SnarkOS notes for more about how upgradability works in general, but in Leo code you will now include a constructor to be run during upgrades. The constructor may use an annotation to indicate use of a standard constructor, such as the following to forbid upgrades:
    @noupgrade
    async constructor() {}
	Or you may create a custom constructor:

    @custom
    async constructor() {
        assert_eq(true, true);
    }
  • Async blocks allow you to elide defining separate async functions, and write your on-chain code in the same location as the rest of the transition:
    async transition main(public a: u32, b: u32) -> Future {
        let f1: Future = other.aleo/main_inner(0u32, 0u32);
        let f: Future = async {
            f1.await();
        };
        return f;
    }
  • You can use a .aleo file as a local dependency (previously dependencies were programs already deployed to the network, or local Leo projects):
leo add --local this_dependency.aleo
  • leo devnet allows you to conveniently start a local devnet, optionally installing snarkos if desired:
leo devnet --install --snarkos ~/bin/snarkos --snarkos-features test_network
  • Identifiers may no longer contain double underscores, so for instance this__name is now disallowed.

  • Bug fix:: we no longer try to fetch the edition of credits.aleo.

v3.0.0

22 Jul 22:48

Choose a tag to compare

  • Array repeater syntax: [0u8; 3];
  • input files are gone;
  • numerous small fixes.

v2.7.3

15 Jul 17:28

Choose a tag to compare

  • Fix to type resolution in interpreter (allowing tests to work correctly)

v2.7.2

10 Jul 17:51

Choose a tag to compare

Fix: Any 2XX HTTP status is success.

v2.7.1

21 Jun 19:15

Choose a tag to compare

Version bump to 2.7.1

v2.7.0

17 Jun 16:53

Choose a tag to compare

  • Basic type inference;

  • Clearer error reporting for leo execute and leo deploy.