Skip to content

v3.3.0

Choose a tag to compare

@github-actions github-actions released this 28 Oct 20:11

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