v3.3.0
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 lastInternally, 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 mainnetOutput 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 committeeendpoint - Validates program names in
leo newagainst reserved SnarkVM keywords - Enforces test isolation for native tests
- Speeds up
leo testby running native tests in parallel - Supports
CheatCode::set_signer("APrivateKey1...")for test signer switching