Skip to content

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:

openvm.toml
[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:

openvm_standard.toml
[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 and argv will always return 0.

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.