Writing a Program
To initialize an OpenVM guest program project, you can use the following CLI command:
cargo openvm init
This will initialize a new Rust project, and you can start writing your program in src/main.rs
.
For a guest program example, see this Fibonacci program. More examples can be found in the benchmarks/guest directory.
Handling I/O
The program can take input from stdin, with some functions provided by openvm::io
. Make sure to import the openvm
library crate to use openvm
intrinsic functions.
openvm::io::read
takes from stdin and deserializes it into a generic type T
, so one should specify the type when calling it:
let n: u64 = read();
openvm::io::read_vec
will just read a vector and return Vec<u8>
.
openvm::io::reveal_bytes32
sets the user public values in the final proof (to be read by the smart contract).
For debugging purposes, openvm::io::print
and openvm::io::println
can be used normally, but println!
will only work if std
is enabled.
Configuring OpenVM
The set of VM extensions used by OpenVM is controlled by a configuration set in the openvm.toml
file. Projects created by
cargo openvm init
have the following minimal default configuration:
[app_vm_config.rv32i]
[app_vm_config.rv32m]
[app_vm_config.io]
To use additional VM extensions, you can specify and configure them in the openvm.toml
file. An example of an openvm.toml
with all commonly used extensions is:
[app_vm_config.rv32i]
[app_vm_config.rv32m]
[app_vm_config.io]
[app_vm_config.keccak]
[app_vm_config.sha256]
[app_vm_config.bigint]
[app_vm_config.modular]
supported_moduli = [
# bn254 (alt bn128)
"21888242871839275222246405745257275088696311157297823662689037894645226208583", # coordinate field
"21888242871839275222246405745257275088548364400416034343698204186575808495617", # scalar field
# secp256k1 (k256)
"115792089237316195423570985008687907853269984665640564039457584007908834671663", # coordinate field
"115792089237316195423570985008687907852837564279074904382605163141518161494337", # scalar field
# secp256r1 (p256)
"115792089210356248762697446949407573530086143415290314195533631308867097853951", # coordinate
"115792089210356248762697446949407573529996955224135760342422259061068512044369", # scalar
# bls12_381
"4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", # coordinate field
"52435875175126190479447740508185965837690552500527637822603658699938581184513", # scalar field
]
[app_vm_config.fp2]
supported_moduli = [
[
"Bn254Fp2",
# bn254 (alt bn128)
"21888242871839275222246405745257275088696311157297823662689037894645226208583",
],
# Bls12_381
[
"Bls12_381Fp2",
"4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787",
],
]
# bn254 (alt bn128)
[[app_vm_config.ecc.supported_curves]]
struct_name = "Bn254G1Affine"
modulus = "21888242871839275222246405745257275088696311157297823662689037894645226208583"
scalar = "21888242871839275222246405745257275088548364400416034343698204186575808495617"
a = "0"
b = "3"
# secp256k1 (k256)
[[app_vm_config.ecc.supported_curves]]
struct_name = "Secp256k1Point"
modulus = "115792089237316195423570985008687907853269984665640564039457584007908834671663"
scalar = "115792089237316195423570985008687907852837564279074904382605163141518161494337"
a = "0"
b = "7"
# secp256r1 (p256)
[[app_vm_config.ecc.supported_curves]]
struct_name = "P256Point"
modulus = "115792089210356248762697446949407573530086143415290314195533631308867097853951"
scalar = "115792089210356248762697446949407573529996955224135760342422259061068512044369"
a = "115792089210356248762697446949407573530086143415290314195533631308867097853948"
b = "41058363725152142129326129780047268409114441015993725554835256314039467401291"
# bls12_381
[[app_vm_config.ecc.supported_curves]]
struct_name = "Bls12_381G1Affine"
modulus = "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787"
scalar = "52435875175126190479447740508185965837690552500527637822603658699938581184513"
a = "0"
b = "4"
[app_vm_config.pairing]
supported_curves = ["Bn254", "Bls12_381"]
Rust std
library support
OpenVM supports standard Rust written using the std
library, with the following limitations that users should be aware of:
- Standard input (e.g., from console) is not supported. Use the
read
methods above instead. - Standard output and standard error (e.g.,
println!, eprintln!
) are supported and will both print to the host standard output. - System randomness calls are supported by default. Important: system randomness requests randomness from the host, and the provided randomness is unvalidated. Users must be aware of this and only use system randomness in settings where this meets their security requirements. In particular, system randomness should not be used for cryptographic purposes.
- Reading of environment variables will always return
None
. - Reading of
argc
andargv
will always return0
.
The above applies to the Rust std
library. Users should also be aware that when writing a standard Rust program, usage of external crates that use foreign function interfaces (FFI) may not work as expected.
To use the standard library, you must enable the "std"
feature in the openvm
crate. This is not one of the default features.
Due to the limitations described above, our general recommendation is that developers should write OpenVM library crates as Rust no_std
libraries when possible (see below).
Binary crates can generally be written using the standard library, although for more control over the expected behavior, we provide entrypoints for writing no_std
binaries.
Writing no_std
Rust
OpenVM fully supports no_std
Rust. We refer to The Embedded Rust Book for a more detailed introduction to no_std
Rust.
no_std
library crates
In a library crate, you should add the following to lib.rs
to declare your crate as no_std
:
// lib.rs
#![no_std]
If you want to feature gate the usage of the standard library, you can do so by adding a "std"
feature to your Cargo.toml
, where the feature must also enable
the "std"
feature in the openvm
crate:
[features]
std = ["openvm/std"]
To tell Rust to selectively enable the standard library, add the following to lib.rs
(in place of the header above):
// lib.rs
#![cfg_attr(not(feature = "std"), no_std)]
no_std
binary crates
In addition to declaring a binary crate no_std
, there is additional handling that must be done around the main
function.
First, add the following header to main.rs
:
// main.rs
#![no_std]
#![no_main]
This tells Rust there is no handler for the main
function. OpenVM provides a separate entrypoint for the main
function, with panic handler, via the openvm::entry!
macro.
You should write a main
function in the normal way, and add the following to main.rs
:
openvm::entry!(main);
fn main() {
// Your code here
}
To selectively enable the standard library, add the std
feature to your Cargo.toml
as above and modify your main.rs
header to:
// main.rs
#![cfg_attr(not(feature = "std"), no_main)]
#![cfg_attr(not(feature = "std"), no_std)]
You still need the openvm::entry!(main)
line.
This tells Rust to use the custom main
handler when the environment is no_std
, but to use the Rust std
library and the standard main
handler when the feature "std"
is enabled.
Using crates that depend on getrandom
OpenVM is compatible with getrandom v0.2
and v0.3
. The cargo openvm
CLI will always compile with the custom getrandom
backend.
By default the openvm
crate has a default feature "getrandom-unsupported"
which exports a __getrandom_v03_custom
function that always returns Err(Error::UNSUPPORTED)
. This is enabled by default to allow compilation of guest programs that pull in dependencies which require getrandom
but where the executed code does not actually use getrandom
functions.
To override the default behavior and provide a custom implementation, turn off the "getrandom-unsupported"
feature in the openvm
crate and supply your own __getrandom_v03_custom
function as specified in the getrandom docs. Similar customization options are available for getrandom
v0.2
.
Read-only reflection
OpenVM partially supports reflective programming by allowing read-only access to the program code itself during runtime execution. Program code that is modified during runtime will not be executed.
More specifically, data and executable code from the RISC-V ELF are loaded into the initial memory image at the start of runtime execution, and this memory may be freely accessed during execution. However, execution will always run with respect to the initial executable code from the ELF, and all runtime modifications will be ignored.