Leo is a Rust-inspired, statically-typed language designed for zero-knowledge (ZK) smart contracts on Aleo. It combines:
Privacy by default (zk-SNARKs built-in)
High performance (off-chain execution)
Developer-friendly syntax (no need to manually write circuits)
If you're serious about ZK development, Leo is a must-learn. Here’s how to master it.
How to install Leo and standard project layout you can find in the previous article:
Now let’s go further.
This article is a guide to programming in the Leo language. For what? For creating private smart contracts using zk-SNARKs.
Here I explain the setup of the development environment, the basics of syntax (data types, structures, functions). Also I provide practical code examples, including a voting system and a balance check.
We will talk abt finding and correcting common errors, such as type inconsistencies and incorrect statements too. The article concludes with tips on code optimization and testing, as well as useful resources for further study.
Let’s go.
Leo supports:
Primitives: u8
, u32
, bool
, field
, group
Structs: For complex data
Arrays: Fixed-size collections
Example: A Private Voting System
// Define a voter
struct Voter {
address: address,
voted: bool,
weight: u32 // Voting power
}
// Store votes
mapping votes: address => u32;
// Cast a private vote
transition cast_vote(
private voter: Voter,
private candidate_id: u32
) -> bool {
// Check voter hasn't voted yet
assert(!voter.voted);
// Update vote count
votes[candidate_id] += voter.weight;
// Mark as voted
let updated_voter = Voter {
voted: true,
..voter
};
return true;
}
Key Rules:
Use private
for sensitive data.
Public functions act as interfaces.
Example: Private Balance Check
function private check_balance(
private balance: u64,
private amount: u64
) -> bool {
return balance >= amount;
}
// Public wrapper
function can_withdraw(
public user: address,
private balance: u64,
private amount: u64
) -> bool {
return check_balance(balance, amount);
}
let x: u32 = 10;
let y: field = 0x123field;
let z = x + y; // ERROR: Can't mix u32 and field
Fix: Explicitly convert types:
let z = x as field + y;
assert(input > 0); // Danger! No ZK proof if false
Fix: Handle errors gracefully:
if (input <= 0) {
return false;
}
Leo doesn’t support recursion. This won’t compile:
function factorial(n: u32) -> u32 {
if (n == 0) { return 1; }
return n * factorial(n - 1); // ERROR
}
Fix: Use iterative loops:
function factorial(n: u32) -> u32 {
let result = 1u32;
for i in 1u32..n {
result *= i;
}
return result;
}
Add test cases in inputs/main.in
:
[{
"balance": 100u64,
"amount": 50u64,
"expected": true
}]
leo run check_balance
leo execute can_withdraw \
--balance 100u64 \
--amount 50u64
Minimize private computations (expensive in ZK).
Use inline
functions for small, frequent ops.
Batch proofs where possible (e.g., process 10 votes at once).
So, Leo is the future of private smart contracts. To master it:
Code daily – Try Aleo’s challenges.
Join the community – Discord.
Audit your code ;)
Now go build something groundbreaking. 🚀
Need more? Check out:
Links:
Website ~ https://www.aleo.org/
Twitter ~ https://twitter.com/AleoHQ
Community Twitter ~ https://twitter.com/aleocommunity
GitHub ~ https://github.com/AleoHQ
Community Forum — https://community.aleo.org/
Community Calendar ~ https://www.aleo.org/community/calendar
YouTube — https://www.youtube.com/channel/UCS_HKT2heOC_q88YQLiJt0g
Developer Documentation ~ https://developer.aleo.org/
Leo Playground ~ https://play.leo-lang.org/
Aleo Block Explorer ~ https://www.aleo.network/
Community Blog ~ https://medium.com/@AleoHQ
Announcements Blog ~ https://www.aleo.org/blog