This repository is split into 3 components:
sdk
: User-friendly API for writing Axiom compute circuitscircuit
: Lower-level API for writing Axiom compute circuitssdk-derive
: Procedural macros for writing Axiom compute functions with axiom-sdk
To install our Rust circuit SDK into a Cargo project, run:
# install sdk-rs
cargo add axiom-sdk
# install dependencies
cargo add axiom-circuit
cargo add serde
cargo add anyhow
We also rely on a nightly build of Rust, which you can configure using:
echo "nightly-2024-01-01" >> rust-toolchain
To implement an Axiom circuit using the Rust SDK you need to:
- Specify an input struct that consists of native Rust types and
ethers-rs
types (ie.u64
,Address
,H256
, etc.). The struct name must end withInput
(ie.MyCircuitInput
). - Implement the
AxiomComputeFn
trait on your input struct
Your input struct can contain native Rust types (ie. u64
, [usize; N]
, etc.) and ethers-rs
types (ie. Address
, H256
, etc.), and its name must end with Input
(ie. MyCircuitInput
). Additional types can be used if they implement the RawInput
trait (see here). The struct must be annotated with the #[AxiomComputeInput] attribute so that it implements the sufficient circuit traits. This attribute will also generate a new struct with Input
replaced with CircuitInput
(ie. AccountAgeInput
-> AccountAgeCircuitInput
), which has all the fields of the specified struct, but with halo2-lib
types to be used inside your circuit (like AssignedValue<Fr>
).
Here is an example:
#[AxiomComputeInput]
pub struct AccountAgeInput {
pub addr: Address,
pub claimed_block_number: u64,
}
You must implement the AxiomComputeFn
on your input struct. There is only one trait function that you must implement:
fn compute(
api: &mut AxiomAPI,
assigned_inputs: AccountAgeCircuitInput<AssignedValue<Fr>>,
) -> Vec<AxiomResult>
where AccountAgeCircuitInput
should be replaced with your derived circuit input struct.
The AxiomAPI
struct gives you access to subquery calling functions in addition to a RlcCircuitBuilder
to specify your circuit. Your compute function should then return any values that you wish to pass on-chain in the Vec<AxiomResult>
-- an AxiomResult
is either an enum of either HiLo<AssignedValue<Fr>>
or AssignedValue<Fr>
(in which case it is converted to hi-lo for you).
Here is an example:
impl AxiomComputeFn for AccountAgeInput {
fn compute(
api: &mut AxiomAPI,
assigned_inputs: AccountAgeCircuitInput<AssignedValue<Fr>>,
) -> Vec<AxiomResult> {
let gate = GateChip::new();
let zero = api.ctx().load_zero();
let one = api.ctx().load_constant(Fr::one());
let prev_block = gate.sub(api.ctx(), assigned_inputs.claimed_block_number, one);
let account_prev_block = api.get_account(prev_block, assigned_inputs.addr);
let prev_nonce = account_prev_block.call(AccountField::Nonce);
let prev_nonce = api.from_hi_lo(prev_nonce);
api.ctx().constrain_equal(&prev_nonce, &zero);
let account = api.get_account(assigned_inputs.claimed_block_number, assigned_inputs.addr);
let curr_nonce = account.call(AccountField::Nonce);
let curr_nonce = api.from_hi_lo(curr_nonce);
api.range.check_less_than(api.ctx(), zero, curr_nonce, 40);
vec![
assigned_inputs.addr.into(),
assigned_inputs.claimed_block_number.into(),
]
}
}
To run your circuit, create a main
function call the run_cli
function with your input struct as the generic parameter:
fn main() {
env_logger::init();
run_cli::<AccountAgeInput>();
}
The main
function will run a CLI that allows you to run mock proving, key generation, and proving of your circuit. The CLI has the following commands:
Commands:
mock Run the mock prover
keygen Generate new proving & verifying keys
prove Generate a new proof
run Generate an Axiom compute query
help Print this message or the help of the given subcommand(s)
Options:
-k, --degree <DEGREE> To determine the size of your circuit (12..25)
-p, --provider <PROVIDER> JSON RPC provider URI
-i, --input <INPUT_PATH> JSON inputs to feed into your circuit
-d, --data-path <DATA_PATH> For saving build artifacts (optional)
-c, --config <CONFIG> For custom advanced usage only (optional)
-h, --help Print help
-V, --version Print version
For example:
cargo run --example account_age -- --input data/account_age_input.json -k 12 -p <PROVIDER_URI> <CMD>
where PROVIDER_URI
is a JSON-RPC URI, and CMD
is mock
, prove
, keygen
, or run
.
Note that for the above example to work with the provided data/account_age_input.json
, the PROVIDER_URI
needs to be a JSON-RPC URI for Sepolia Testnet.