From 335c55902604c5799b9880f983a93640b7371a1e Mon Sep 17 00:00:00 2001 From: Roshan <19766713+rpalakkal@users.noreply.github.com> Date: Wed, 14 Feb 2024 18:07:07 -0500 Subject: [PATCH] feat: docs (#8) * feat: initial docs (readme + /sdk) * chore: re-export ethers * chore: update examples to use re-export * chore: add examples to ci * fix: create data dir for ci tests * fix: docs + re-exports * fix: doc tests --- .github/workflows/test.yml | 12 ++++ readme.md | 116 +++++++++++++++++++++++++++++- sdk-derive/src/input.rs | 2 +- sdk-derive/src/lib.rs | 4 ++ sdk/data/keccak_config.json | 1 - sdk/data/rlc_config.json | 3 +- sdk/examples/account_age.rs | 14 ++-- sdk/examples/keccak.rs | 4 +- sdk/examples/quickstart.rs | 6 +- sdk/examples/rlc.rs | 14 ++-- sdk/readme.md | 2 +- sdk/src/api.rs | 43 ++++++++++- sdk/src/cmd.rs | 31 ++++++-- sdk/src/compute.rs | 29 +++++++- sdk/src/lib.rs | 139 ++++++++++++++++++++++++++++++++++-- sdk/src/subquery/account.rs | 6 +- sdk/src/subquery/header.rs | 9 ++- sdk/src/subquery/mapping.rs | 9 ++- sdk/src/subquery/receipt.rs | 22 +++++- sdk/src/subquery/storage.rs | 6 +- sdk/src/subquery/tx.rs | 12 +++- 21 files changed, 442 insertions(+), 42 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 635bba5..c692f3a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,3 +26,15 @@ jobs: run: | export PROVIDER_URI=${{ secrets.PROVIDER_URI_SEPOLIA }} cargo test -- --test-threads=1 + - name: Test examples + run: | + export PROVIDER_URI=${{ secrets.PROVIDER_URI_SEPOLIA }} + mkdir data + cargo run --example keccak -- --input sdk/data/keccak_input.json -k 15 -c sdk/data/keccak_config.json keygen + cargo run --example keccak -- --input sdk/data/keccak_input.json -k 15 -c sdk/data/keccak_config.json run + cargo run --example rlc -- --input sdk/data/rlc_input.json -k 15 -c sdk/data/rlc_config.json keygen + cargo run --example rlc -- --input sdk/data/rlc_input.json -k 15 -c sdk/data/rlc_config.json run + cargo run --example account_age -- --input sdk/data/account_age_input.json -k 15 keygen + cargo run --example account_age -- --input sdk/data/account_age_input.json -k 15 run + cargo run --example quickstart -- --input sdk/data/quickstart_input.json -k 15 keygen + cargo run --example quickstart -- --input sdk/data/quickstart_input.json -k 15 run diff --git a/readme.md b/readme.md index bd49f6b..9a30b5e 100644 --- a/readme.md +++ b/readme.md @@ -2,6 +2,118 @@ This repository is split into 3 components: -- `circuit`: Lower-level API for writing Axiom compute circuits - `sdk`: User-friendly API for writing Axiom compute circuits -- `sdk-derive`: Procedural macros to for writing circuits with axiom-sdk \ No newline at end of file +- `circuit`: Lower-level API for writing Axiom compute circuits +- `sdk-derive`: Procedural macros to for writing circuits with axiom-sdk + +## axiom-sdk + +### Installation + +To install our Rust circuit SDK into a Cargo project, run: +```bash +cargo add axiom-sdk --git https://github.com/axiom-crypto/axiom-sdk-rs +``` + +### Overview + +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 with `Input` (ie. `MyCircuitInput`). +- Implement the `AxiomComputeFn` trait on your input struct + +### Input Specification + +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](./circuit/src/input/raw_input.rs)). 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`). + +Here is an example: + +```rust +#[AxiomComputeInput] +pub struct AccountAgeInput { + pub addr: Address, + pub claimed_block_number: u64, +} +``` + +### Compute Function Specification + +You must implement the `AxiomComputeFn` on your input struct. There is only one trait function that you must implement: +```rust +fn compute( + api: &mut AxiomAPI, + assigned_inputs: AccountAgeCircuitInput>, +) -> Vec +``` +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` -- an `AxiomResult` is either an enum of either `HiLo>` or `AssignedValue` (in which case it is converted to hi-lo for you). + +Here is an example: +```rust +impl AxiomComputeFn for AccountAgeInput { + fn compute( + api: &mut AxiomAPI, + assigned_inputs: AccountAgeCircuitInput>, + ) -> Vec { + 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(), + ] + } +} +``` + +### Running The Circuit + +To run your circuit, create a `main` function call the `run_cli` function with your input struct as the generic parameter: +```rust +fn main() { + env_logger::init(); + run_cli::(); +} +``` +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 To determine the size of your circuit (12..25) + -p, --provider JSON RPC provider URI + -i, --input JSON inputs to feed into your circuit + -d, --data-path For saving build artifacts (optional) + -c, --config For custom advanced usage only (optional) + -h, --help Print help + -V, --version Print version +``` + +For example: + +```bash +cargo run --example account_age -- --input data/account_age_input.json -k 12 -p +``` + +where `PROVIDER_URI` is a JSON-RPC URI, and `CMD` is `mock`, `prove`, `keygen`, or `run`. + diff --git a/sdk-derive/src/input.rs b/sdk-derive/src/input.rs index 2d4f000..df637b9 100644 --- a/sdk-derive/src/input.rs +++ b/sdk-derive/src/input.rs @@ -218,7 +218,7 @@ pub fn impl_flatten_and_raw_input(ast: &DeriveInput) -> TokenStream { } } - impl #old_impl_generics axiom_sdk::compute::AxiomComputeInput for #raw_circuit_name_ident #old_ty_generics { + impl #old_impl_generics axiom_sdk::axiom::AxiomComputeInput for #raw_circuit_name_ident #old_ty_generics { type LogicInput = #raw_circuit_name_ident #old_ty_generics; type Input = #name #ty_generics; } diff --git a/sdk-derive/src/lib.rs b/sdk-derive/src/lib.rs index 3fd6610..1eb47a0 100644 --- a/sdk-derive/src/lib.rs +++ b/sdk-derive/src/lib.rs @@ -11,6 +11,10 @@ mod input; #[proc_macro_attribute] #[allow(non_snake_case)] +/// Derive the `AxiomComputeInput` trait for a struct. +/// The struct must be named `_Input`. Ex: `ExampleInput`. +/// All the fields of the struct must implement `RawInput` from `axiom_circuit::input::raw_input`, +/// which has already been implemented for most primitive Rust types and Ethers types. pub fn AxiomComputeInput(_args: TokenStream, input: TokenStream) -> TokenStream { let input_clone = input.clone(); let ast = parse_macro_input!(input_clone as ItemStruct); diff --git a/sdk/data/keccak_config.json b/sdk/data/keccak_config.json index 6e306d0..fb8b95f 100644 --- a/sdk/data/keccak_config.json +++ b/sdk/data/keccak_config.json @@ -7,7 +7,6 @@ "num_fixed": 1, "num_lookup_advice_per_phase": [ 5, - 0, 0 ], "lookup_bits": 14, diff --git a/sdk/data/rlc_config.json b/sdk/data/rlc_config.json index 47fe37f..96b0624 100644 --- a/sdk/data/rlc_config.json +++ b/sdk/data/rlc_config.json @@ -7,8 +7,7 @@ "num_fixed": 1, "num_lookup_advice_per_phase": [ 5, - 1, - 0 + 1 ], "lookup_bits": 14, "num_rlc_columns": 1 diff --git a/sdk/examples/account_age.rs b/sdk/examples/account_age.rs index 01ffdfb..451b2a6 100644 --- a/sdk/examples/account_age.rs +++ b/sdk/examples/account_age.rs @@ -1,16 +1,16 @@ use std::fmt::Debug; use axiom_sdk::{ - axiom::{AxiomAPI, AxiomComputeFn, AxiomResult}, + axiom::{AxiomAPI, AxiomComputeFn, AxiomComputeInput, AxiomResult}, cmd::run_cli, + ethers::types::Address, halo2_base::{ - gates::{GateChip, GateInstructions, RangeInstructions}, + gates::{GateInstructions, RangeInstructions}, AssignedValue, }, subquery::AccountField, - AxiomComputeInput, Fr, + Fr, }; -use ethers::types::Address; #[AxiomComputeInput] pub struct AccountAgeInput { @@ -23,10 +23,12 @@ impl AxiomComputeFn for AccountAgeInput { api: &mut AxiomAPI, assigned_inputs: AccountAgeCircuitInput>, ) -> Vec { - 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 prev_block = api + .range + .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); diff --git a/sdk/examples/keccak.rs b/sdk/examples/keccak.rs index ecd2111..746c8c8 100644 --- a/sdk/examples/keccak.rs +++ b/sdk/examples/keccak.rs @@ -1,10 +1,10 @@ use std::fmt::Debug; use axiom_sdk::{ - axiom::{AxiomAPI, AxiomComputeFn, AxiomResult}, + axiom::{AxiomAPI, AxiomComputeFn, AxiomComputeInput, AxiomResult}, cmd::run_cli, halo2_base::AssignedValue, - AxiomComputeInput, Fr, + Fr, }; #[AxiomComputeInput] diff --git a/sdk/examples/quickstart.rs b/sdk/examples/quickstart.rs index ef440ea..12dbfca 100644 --- a/sdk/examples/quickstart.rs +++ b/sdk/examples/quickstart.rs @@ -1,13 +1,13 @@ use std::{fmt::Debug, str::FromStr}; use axiom_sdk::{ - axiom::{AxiomAPI, AxiomComputeFn, AxiomResult}, + axiom::{AxiomAPI, AxiomComputeFn, AxiomComputeInput, AxiomResult}, cmd::run_cli, + ethers::types::{Address, H256}, halo2_base::AssignedValue, subquery::{AccountField, HeaderField, TxField}, - AxiomComputeInput, Fr, HiLo, + Fr, HiLo, }; -use ethers::types::{Address, H256}; #[AxiomComputeInput] pub struct QuickstartInput { diff --git a/sdk/examples/rlc.rs b/sdk/examples/rlc.rs index 46579fb..4534953 100644 --- a/sdk/examples/rlc.rs +++ b/sdk/examples/rlc.rs @@ -2,13 +2,13 @@ use std::fmt::Debug; use axiom_circuit::axiom_eth::rlc::circuit::builder::RlcCircuitBuilder; use axiom_sdk::{ - axiom::{AxiomAPI, AxiomComputeFn, AxiomResult}, + axiom::{AxiomAPI, AxiomComputeFn, AxiomComputeInput, AxiomResult}, cmd::run_cli, halo2_base::{ - gates::{RangeChip, RangeInstructions}, + gates::{GateInstructions, RangeChip, RangeInstructions}, AssignedValue, }, - AxiomComputeInput, Fr, + Fr, }; #[AxiomComputeInput] @@ -42,7 +42,13 @@ impl AxiomComputeFn for RlcInput { ) { let gate = range.gate(); let rlc_chip = builder.rlc_chip(gate); - rlc_chip.compute_rlc_fixed_len(builder.base.main(1), payload); + let (ctx, rlc_ctx) = builder.rlc_ctx_pair(); + gate.add(ctx, payload[0], payload[1]); + let x = vec![ + ctx.load_constant(Fr::from(1)), + ctx.load_constant(Fr::from(2)), + ]; + rlc_chip.compute_rlc_fixed_len(rlc_ctx, x); } } diff --git a/sdk/readme.md b/sdk/readme.md index a934fd5..97c9d12 100644 --- a/sdk/readme.md +++ b/sdk/readme.md @@ -2,7 +2,7 @@ ## Usage -See [./examples/account_age.rs] for an example Axiom compute circuit. To run the `account_age` circuit: +See [./examples/account_age.rs](./examples/account_age.rs) for an example Axiom compute circuit. To run the `account_age` circuit: ``` cargo run --example account_age -- --input data/account_age_input.json -k 12 -p diff --git a/sdk/src/api.rs b/sdk/src/api.rs index b56a57a..d4736b2 100644 --- a/sdk/src/api.rs +++ b/sdk/src/api.rs @@ -24,10 +24,14 @@ use crate::{ Fr, }; +/// Axiom Circuit API for making both subquery calls (e.g. `get_account`, `get_header`, etc.) and for more general ZK primitives (e.g. `add`, `mul`, etc.). pub struct AxiomAPI<'a> { + /// The `halo2-lib` struct used to construct the circuit pub builder: &'a mut RlcCircuitBuilder, + /// The main chip for ZK primitives pub range: &'a RangeChip, - pub subquery_caller: Arc>>, + /// The struct that manages all subquery calls + subquery_caller: Arc>>, } impl<'a> AxiomAPI<'a> { @@ -43,24 +47,41 @@ impl<'a> AxiomAPI<'a> { } } + /// Returns a thread-safe [SubqueryCaller] object. pub fn subquery_caller(&self) -> Arc>> { self.subquery_caller.clone() } + /// Returns a mutable reference to the [Context] of a gate thread. Spawns a new thread for the given phase, if none exists. + /// * `phase`: The challenge phase (as an index) of the gate thread. pub fn ctx(&mut self) -> &mut Context { self.builder.base.main(0) } + /// Returns an `AssignedValue` from a `HiLo>`. + /// + /// NOTE: this can fail if the hi-lo pair is greater than the `Fr` modulus. See `check_hi_lo` for what is constrained. + /// + /// * `hilo` - The `HiLo>` object to convert. pub fn from_hi_lo(&mut self, hilo: HiLo>) -> AssignedValue { let ctx = self.builder.base.main(0); from_hi_lo(ctx, self.range, hilo) } + /// Returns a 256-bit `HiLo>` from a `AssignedValue`. + /// + /// See `check_hi_lo` for what is constrained. + /// + /// * `val` - The `AssignedValue` object to convert. pub fn to_hi_lo(&mut self, val: AssignedValue) -> HiLo> { let ctx = self.builder.base.main(0); to_hi_lo(ctx, self.range, val) } + /// Returns an [Account] builder given block number and address. + /// + /// * `block_number` - The block number as an `AssignedValue`. + /// * `addr` - The address as an `AssignedValue`. pub fn get_account( &mut self, block_number: AssignedValue, @@ -70,11 +91,19 @@ impl<'a> AxiomAPI<'a> { get_account(ctx, self.subquery_caller.clone(), block_number, addr) } + /// Returns a [Header] builder given block number. + /// + /// * `block_number` - The block number as an `AssignedValue`. pub fn get_header(&mut self, block_number: AssignedValue) -> Header { let ctx = self.builder.base.main(0); get_header(ctx, self.subquery_caller.clone(), block_number) } + /// Returns a [SolidityMapping] builder given block number, address, and mapping slot. + /// + /// * `block_number` - The block number as an `AssignedValue`. + /// * `addr` - The address as an `AssignedValue`. + /// * `mapping_slot` - The mapping slot as a `HiLo`. pub fn get_mapping( &mut self, block_number: AssignedValue, @@ -91,6 +120,10 @@ impl<'a> AxiomAPI<'a> { ) } + /// Returns a [Receipt] builder given block number and transaction index. + /// + /// * `block_number` - The block number as an `AssignedValue`. + /// * `tx_idx` - The transaction index as an `AssignedValue`. pub fn get_receipt( &mut self, block_number: AssignedValue, @@ -100,6 +133,10 @@ impl<'a> AxiomAPI<'a> { get_receipt(ctx, self.subquery_caller.clone(), block_number, tx_idx) } + /// Returns a [Storage] builder given block number and address. + /// + /// * `block_number` - The block number as an `AssignedValue`. + /// * `addr` - The address as an `AssignedValue`. pub fn get_storage( &mut self, block_number: AssignedValue, @@ -109,6 +146,10 @@ impl<'a> AxiomAPI<'a> { get_storage(ctx, self.subquery_caller.clone(), block_number, addr) } + /// Returns a [Tx] builder given block number and transaction index. + /// + /// * `block_number` - The block number as an `AssignedValue`. + /// * `tx_idx` - The transaction index as an `AssignedValue`. pub fn get_tx(&mut self, block_number: AssignedValue, tx_idx: AssignedValue) -> Tx { let ctx = self.builder.base.main(0); get_tx(ctx, self.subquery_caller.clone(), block_number, tx_idx) diff --git a/sdk/src/cmd.rs b/sdk/src/cmd.rs index c32f360..1e63ee1 100644 --- a/sdk/src/cmd.rs +++ b/sdk/src/cmd.rs @@ -29,6 +29,7 @@ use crate::{ }; #[derive(Clone, Copy, Debug, Subcommand)] +/// Circuit CLI commands pub enum SnarkCmd { /// Run the mock prover Mock, @@ -41,6 +42,7 @@ pub enum SnarkCmd { } #[derive(Serialize, Deserialize, Clone, Debug)] +/// Struct for specifying custom circuit parameters via JSON pub struct RawCircuitParams { pub k: usize, pub num_advice_per_phase: Vec, @@ -67,20 +69,39 @@ impl std::fmt::Display for SnarkCmd { /// Command-line helper for building Axiom compute circuits pub struct Cli { #[command(subcommand)] + /// The command to run pub command: SnarkCmd, - #[arg(short = 'k', long = "degree")] + #[arg( + short = 'k', + long = "degree", + help = "To determine the size of your circuit (12..25)" + )] + /// The degree of the circuit pub degree: Option, - #[arg(short = 'p', long = "provider")] + #[arg(short = 'p', long = "provider", help = "JSON RPC provider URI")] + /// The JSON RPC provider URI pub provider: Option, - #[arg(short, long = "input")] + #[arg(short, long = "input", help = "JSON inputs to feed into your circuit")] + /// The JSON inputs to feed into your circuit pub input_path: Option, - #[arg(short, long = "data-path")] + #[arg( + short, + long = "data-path", + help = "For saving build artifacts (optional)" + )] + /// The path to save build artifacts pub data_path: Option, //Advanced options - #[arg(short = 'c', long = "config")] + #[arg( + short = 'c', + long = "config", + help = "For specifying custom circuit parameters (optional)" + )] + /// The path to a custom circuit configuration pub config: Option, } +/// Runs the CLI given on any struct that implements the `AxiomComputeFn` trait pub fn run_cli() where A::Input: Default + Debug, diff --git a/sdk/src/compute.rs b/sdk/src/compute.rs index 0ac995a..56ec8d2 100644 --- a/sdk/src/compute.rs +++ b/sdk/src/compute.rs @@ -24,20 +24,26 @@ use serde::{de::DeserializeOwned, Serialize}; use crate::{api::AxiomAPI, Fr}; +/// A trait for specifying the input to an Axiom Compute function pub trait AxiomComputeInput: Clone + Default + Debug { + /// The type of the native input (ie. Rust types) to the compute function type LogicInput: Clone + Debug + Serialize + DeserializeOwned + Into>; + /// The type of the circuit input to the compute function type Input: Clone + InputFlatten; - // type ProviderType: JsonRpcClient + Clone = Http; } +/// A trait for specifying an Axiom Compute function pub trait AxiomComputeFn: AxiomComputeInput { + /// An optional type for the first phase payload -- only needed if you are using `compute_phase1` type FirstPhasePayload: Clone + Default = (); + /// Axiom Compute function fn compute( api: &mut AxiomAPI, assigned_inputs: Self::Input>, ) -> Vec; + /// An optional function that overrides `compute` to specify phase0 circuit logic for circuits that require a challenge fn compute_phase0( api: &mut AxiomAPI, assigned_inputs: Self::Input>, @@ -46,6 +52,9 @@ pub trait AxiomComputeFn: AxiomComputeInput { } #[allow(unused_variables)] + /// An optional function to specify phase1 circuit logic for circuits that require a challenge + /// + /// This function is called after the phase0 circuit logic has been executed fn compute_phase1( builder: &mut RlcCircuitBuilder, range: &RangeChip, @@ -55,6 +64,7 @@ pub trait AxiomComputeFn: AxiomComputeInput { } #[derive(Debug, Clone)] +/// Helper struct that contains all the necessary metadata and inputs to run an Axiom Compute function pub struct AxiomCompute { provider: Option>, params: Option, @@ -116,57 +126,69 @@ where A::Input: Default + Debug, A::Input>: Debug, { + /// Create a new AxiomCompute instance pub fn new() -> Self { Self::default() } + /// Set the provider for the AxiomCompute instance pub fn set_provider(&mut self, provider: Provider) { self.provider = Some(provider); } + /// Set the params for the AxiomCompute instance pub fn set_params(&mut self, params: AxiomCircuitParams) { self.params = Some(params); } + /// Set the inputs for the AxiomCompute instance pub fn set_inputs(&mut self, input: A::LogicInput) { self.input = Some(input); } + /// Set the pinning for the AxiomCompute instance pub fn set_pinning(&mut self, pinning: AxiomCircuitPinning) { self.pinning = Some(pinning); } + /// Use the given provider for the AxiomCompute instance pub fn use_provider(mut self, provider: Provider) -> Self { self.set_provider(provider); self } + /// Use the given params for the AxiomCompute instance pub fn use_params(mut self, params: AxiomCircuitParams) -> Self { self.set_params(params); self } + /// Use the given inputs for the AxiomCompute instance pub fn use_inputs(mut self, input: A::LogicInput) -> Self { self.set_inputs(input); self } + /// Use the given pinning for the AxiomCompute instance pub fn use_pinning(mut self, pinning: AxiomCircuitPinning) -> Self { self.set_pinning(pinning); self } + /// Check that all the necessary configurations are set fn check_all_set(&self) { assert!(self.provider.is_some()); assert!(self.pinning.is_some()); assert!(self.input.is_some()); } + /// Check that the provider and params are set fn check_provider_and_params_set(&self) { assert!(self.provider.is_some()); assert!(self.params.is_some()); } + /// Run the mock prover pub fn mock(&self) { self.check_provider_and_params_set(); let provider = self.provider.clone().unwrap(); @@ -175,6 +197,7 @@ where mock::(provider, params, converted_input); } + /// Run key generation and return the proving and verifying keys, and the circuit pinning pub fn keygen( &self, ) -> ( @@ -188,6 +211,7 @@ where keygen::(provider, params, None) } + /// Run the prover and return the resulting snark pub fn prove(&self, pk: ProvingKey) -> Snark { self.check_all_set(); let provider = self.provider.clone().unwrap(); @@ -195,6 +219,7 @@ where prove::(provider, self.pinning.clone().unwrap(), converted_input, pk) } + /// Run the prover and return the outputs needed to make an on-chain compute query pub fn run(&self, pk: ProvingKey) -> AxiomV2CircuitOutput { self.check_all_set(); let provider = self.provider.clone().unwrap(); @@ -202,6 +227,7 @@ where run::(provider, self.pinning.clone().unwrap(), converted_input, pk) } + /// Returns an [AxiomCircuit] instance, for functions that expect the halo2 circuit trait pub fn circuit(&self) -> AxiomCircuit { self.check_provider_and_params_set(); let provider = self.provider.clone().unwrap(); @@ -210,6 +236,7 @@ where } } +/// A `bytes32` value that your callback contract receives upon query fulfillment pub enum AxiomResult { HiLo(HiLo>), AssignedValue(AssignedValue), diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index ed077e1..0da7dcf 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -1,19 +1,144 @@ +//! ## axiom-sdk +//! +//! ### Installation +//! +//! To install our Rust circuit SDK into a Cargo project, run: +//! ```bash +//! cargo add axiom-sdk --git https://github.com/axiom-crypto/axiom-sdk-rs +//! ``` +//! +//! ### Overview +//! +//! 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 with `Input` (ie. `MyCircuitInput`). +//! - Implement the `AxiomComputeFn` trait on your input struct +//! +//! ### Input Specification +//! +//! 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](./circuit/src/input/raw_input.rs)). +//! 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`). +//! +//! Here is an example: +//! +//! ```ignore +//! #[AxiomComputeInput] +//! pub struct AccountAgeInput { +//! pub addr: Address, +//! pub claimed_block_number: u64, +//! } +//! ``` +//! +//! ### Compute Function Specification +//! +//! You must implement the `AxiomComputeFn` on your input struct. There is only one trait function that you must implement: +//! ```ignore +//! fn compute( +//! api: &mut AxiomAPI, +//! assigned_inputs: AccountAgeCircuitInput>, +//! ) -> Vec +//! ``` +//! 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` -- an `AxiomResult` is either an enum of either `HiLo>` or `AssignedValue` (in which case it is converted to hi-lo for you). +//! +//! Here is an example: +//! ```ignore +//! impl AxiomComputeFn for AccountAgeInput { +//! fn compute( +//! api: &mut AxiomAPI, +//! assigned_inputs: AccountAgeCircuitInput>, +//! ) -> Vec { +//! 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(), +//! ] +//! } +//! } +//! ``` +//! +//! ### Running The Circuit +//! +//! To run your circuit, create a `main` function call the `run_cli` function with your input struct as the generic parameter: +//! ```ignore +//! fn main() { +//! env_logger::init(); +//! run_cli::(); +//! } +//! ``` +//! 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: +//! +//! ```ignore +//! 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 To determine the size of your circuit (12..25) +//! -p, --provider JSON RPC provider URI +//! -i, --input JSON inputs to feed into your circuit +//! -d, --data-path For saving build artifacts (optional) +//! -c, --config For custom advanced usage only (optional) +//! -h, --help Print help +//! -V, --version Print version +//! ``` +//! +//! For example: +//! +//! ```bash +//! cargo run --example account_age -- --input data/account_age_input.json -k 12 -p +//! ``` +//! +//! where `PROVIDER_URI` is a JSON-RPC URI, and `CMD` is `mock`, `prove`, `keygen`, or `run`. + #![allow(incomplete_features)] #![feature(associated_type_defaults)] mod api; -pub use axiom_circuit::{axiom_codec::HiLo, axiom_eth::halo2curves::bn256::Fr}; - -pub(crate) mod utils; -pub use axiom_circuit::{self, axiom_eth::halo2_base}; -pub use axiom_sdk_derive::AxiomComputeInput; +// pub(crate) mod utils; +pub use axiom_circuit::{ + self, + axiom_codec::HiLo, + axiom_eth::{halo2_base, halo2curves::bn256::Fr}, +}; +/// The types and traits required to implement an Axiom Compute function pub mod axiom { + pub use axiom_sdk_derive::AxiomComputeInput; + pub use crate::{ api::AxiomAPI, - compute::{AxiomCompute, AxiomComputeFn, AxiomResult}, + compute::{AxiomCompute, AxiomComputeFn, AxiomComputeInput, AxiomResult}, }; } +/// Contains a CLI for running any Axiom Compute function (any struct that implements the `AxiomComputeFn` trait) pub mod cmd; -pub mod compute; +/// Contains the traits and types required to implement an Axiom Compute function (re-exported from the `axiom` module) +pub(crate) mod compute; +/// Module with all subquery types and builders pub mod subquery; +/// Re-export ethers-rs +pub use ethers; diff --git a/sdk/src/subquery/account.rs b/sdk/src/subquery/account.rs index ff6d85c..e565c61 100644 --- a/sdk/src/subquery/account.rs +++ b/sdk/src/subquery/account.rs @@ -9,6 +9,7 @@ use ethers::providers::Http; use crate::Fr; +/// Account subquery builder pub struct Account<'a> { pub block_number: AssignedValue, pub addr: AssignedValue, @@ -16,7 +17,7 @@ pub struct Account<'a> { caller: Arc>>, } -pub fn get_account( +pub(crate) fn get_account( ctx: &mut Context, caller: Arc>>, block_number: AssignedValue, @@ -31,6 +32,9 @@ pub fn get_account( } impl<'a> Account<'a> { + /// Fetches the account subquery and returns the HiLo> result + /// + /// * `field` - The account field to fetch pub fn call(self, field: AccountField) -> HiLo> { let field_constant = self.ctx.load_constant(Fr::from(field)); let mut subquery_caller = self.caller.lock().unwrap(); diff --git a/sdk/src/subquery/header.rs b/sdk/src/subquery/header.rs index 02405f3..9c46b40 100644 --- a/sdk/src/subquery/header.rs +++ b/sdk/src/subquery/header.rs @@ -9,13 +9,14 @@ use ethers::providers::Http; use crate::Fr; +/// Header subquery builder pub struct Header<'a> { pub block_number: AssignedValue, ctx: &'a mut Context, caller: Arc>>, } -pub fn get_header( +pub(crate) fn get_header( ctx: &mut Context, caller: Arc>>, block_number: AssignedValue, @@ -28,6 +29,9 @@ pub fn get_header( } impl<'a> Header<'a> { + /// Fetches the header subquery and returns the HiLo> result + /// + /// * `field` - The header field to fetch pub fn call(self, field: HeaderField) -> HiLo> { let field_constant = self.ctx.load_constant(Fr::from(field)); let mut subquery_caller = self.caller.lock().unwrap(); @@ -38,6 +42,9 @@ impl<'a> Header<'a> { subquery_caller.call(self.ctx, subquery) } + /// Fetches the header logs bloom subquery and returns the HiLo> result + /// + /// * `logs_bloom_idx` - The logs bloom field index to fetch pub fn logs_bloom(self, logs_bloom_idx: usize) -> HiLo> { let mut subquery_caller = self.caller.lock().unwrap(); if logs_bloom_idx >= 8 { diff --git a/sdk/src/subquery/mapping.rs b/sdk/src/subquery/mapping.rs index c006518..f50961e 100644 --- a/sdk/src/subquery/mapping.rs +++ b/sdk/src/subquery/mapping.rs @@ -9,6 +9,7 @@ use ethers::providers::Http; use crate::Fr; +/// Solidity nested mapping subquery builder pub struct SolidityMapping<'a> { pub block_number: AssignedValue, pub addr: AssignedValue, @@ -17,7 +18,7 @@ pub struct SolidityMapping<'a> { caller: Arc>>, } -pub fn get_mapping( +pub(crate) fn get_mapping( ctx: &mut Context, caller: Arc>>, block_number: AssignedValue, @@ -34,6 +35,9 @@ pub fn get_mapping( } impl<'a> SolidityMapping<'a> { + /// Fetches the Solidity nested mapping subquery and returns the HiLo> result + /// + /// * `keys` - A vector of nested keys into the specified mapping pub fn nested(self, keys: Vec>>) -> HiLo> { if keys.is_empty() || keys.len() > MAX_SOLIDITY_MAPPING_KEYS { panic!( @@ -60,6 +64,9 @@ impl<'a> SolidityMapping<'a> { subquery_caller.call(self.ctx, subquery) } + /// Fetches the Solidity mapping subquery and returns the HiLo> result + /// + /// * `key` - The key into the specified mapping pub fn key(self, key: HiLo>) -> HiLo> { self.nested(vec![key]) } diff --git a/sdk/src/subquery/receipt.rs b/sdk/src/subquery/receipt.rs index de7f902..6f9c3c7 100644 --- a/sdk/src/subquery/receipt.rs +++ b/sdk/src/subquery/receipt.rs @@ -21,6 +21,7 @@ use ethers::{providers::Http, types::H256}; use crate::Fr; +/// Receipt subquery builder pub struct Receipt<'a> { pub block_number: AssignedValue, pub tx_idx: AssignedValue, @@ -28,6 +29,7 @@ pub struct Receipt<'a> { caller: Arc>>, } +/// Log subquery builder pub struct Log<'a> { pub block_number: AssignedValue, pub tx_idx: AssignedValue, @@ -36,7 +38,7 @@ pub struct Log<'a> { caller: Arc>>, } -pub fn get_receipt( +pub(crate) fn get_receipt( ctx: &mut Context, caller: Arc>>, block_number: AssignedValue, @@ -51,6 +53,9 @@ pub fn get_receipt( } impl<'a> Receipt<'a> { + /// Fetches the receipt subquery and returns the HiLo> result + /// + /// * `field` - The receipt field to fetch pub fn call(self, field: ReceiptField) -> HiLo> { let field_constant = self.ctx.load_constant(Fr::from(field)); let mut subquery_caller = self.caller.lock().unwrap(); @@ -67,6 +72,9 @@ impl<'a> Receipt<'a> { subquery_caller.call(self.ctx, subquery) } + /// Returns a receipt [Log] subquery builder + /// + /// * `log_idx` - The log index in the block pub fn log(self, log_idx: AssignedValue) -> Log<'a> { let log_offset = self .ctx @@ -82,6 +90,9 @@ impl<'a> Receipt<'a> { } } + /// Fetches the receipt logs bloom subquery and returns the HiLo> result + /// + /// * `logs_bloom_idx` - the index of a 32 byte chunk of the logsBloom field pub fn logs_bloom(self, logs_bloom_idx: usize) -> HiLo> { let mut subquery_caller = self.caller.lock().unwrap(); if logs_bloom_idx >= 8 { @@ -104,6 +115,10 @@ impl<'a> Receipt<'a> { } impl<'a> Log<'a> { + /// Fetches the receipt log subquery and returns the HiLo> result + /// + /// * `topic_idx` - the index of a topic in the log + /// * `event_schema` - The event schema of the log pub fn topic( self, topic_idx: AssignedValue, @@ -126,6 +141,10 @@ impl<'a> Log<'a> { subquery_caller.call(self.ctx, subquery) } + /// Fetches the receipt extra data subquery and returns the HiLo> result + /// + /// * `data_idx` - the index of a 32 byte chunk of the extra data field + /// * `event_schema` - The event schema of the log pub fn data( self, data_idx: AssignedValue, @@ -153,6 +172,7 @@ impl<'a> Log<'a> { subquery_caller.call(self.ctx, subquery) } + /// Fetches the address from which the log was emitted from and returns the HiLo> result pub fn address(self) -> HiLo> { let mut subquery_caller = self.caller.lock().unwrap(); let topic = self.ctx.load_constant(Fr::from(RECEIPT_ADDRESS_IDX as u64)); diff --git a/sdk/src/subquery/storage.rs b/sdk/src/subquery/storage.rs index 4d8b3cc..a015bf5 100644 --- a/sdk/src/subquery/storage.rs +++ b/sdk/src/subquery/storage.rs @@ -9,6 +9,7 @@ use ethers::providers::Http; use crate::Fr; +/// Storage subquery builder pub struct Storage<'a> { pub block_number: AssignedValue, pub addr: AssignedValue, @@ -16,7 +17,7 @@ pub struct Storage<'a> { caller: Arc>>, } -pub fn get_storage( +pub(crate) fn get_storage( ctx: &mut Context, caller: Arc>>, block_number: AssignedValue, @@ -31,6 +32,9 @@ pub fn get_storage( } impl<'a> Storage<'a> { + /// Fetches the storage subquery and returns the HiLo> result + /// + /// * `slot` - The storage slot to fetch pub fn slot(self, slot: HiLo>) -> HiLo> { let mut subquery_caller = self.caller.lock().unwrap(); let subquery = AssignedStorageSubquery { diff --git a/sdk/src/subquery/tx.rs b/sdk/src/subquery/tx.rs index 3641d46..07ed096 100644 --- a/sdk/src/subquery/tx.rs +++ b/sdk/src/subquery/tx.rs @@ -15,6 +15,7 @@ use ethers::providers::Http; use crate::Fr; +/// Tx subquery builder pub struct Tx<'a> { pub block_number: AssignedValue, pub tx_idx: AssignedValue, @@ -22,7 +23,7 @@ pub struct Tx<'a> { caller: Arc>>, } -pub fn get_tx( +pub(crate) fn get_tx( ctx: &mut Context, caller: Arc>>, block_number: AssignedValue, @@ -37,6 +38,9 @@ pub fn get_tx( } impl<'a> Tx<'a> { + /// Fetches the tx subquery and returns the HiLo> result + /// + /// * `field` - The tx field to fetch pub fn call(self, field: TxField) -> HiLo> { let field_constant = self.ctx.load_constant(Fr::from(field)); let mut subquery_caller = self.caller.lock().unwrap(); @@ -48,6 +52,9 @@ impl<'a> Tx<'a> { subquery_caller.call(self.ctx, subquery) } + /// Fetches the tx calldata subquery and returns the HiLo> result + /// + /// * `calldata_idx` - the index of a 32 byte calldata chunk pub fn calldata(self, calldata_idx: AssignedValue) -> HiLo> { let mut subquery_caller = self.caller.lock().unwrap(); let calldata_offset = self @@ -63,6 +70,9 @@ impl<'a> Tx<'a> { subquery_caller.call(self.ctx, subquery) } + /// Fetches the tx contract data subquery and returns the HiLo> result + /// + /// * `contract_data_idx` - the index of a 32 byte chunk of the transaction input data pub fn contract_data(self, contract_data_idx: AssignedValue) -> HiLo> { let mut subquery_caller = self.caller.lock().unwrap(); let contract_data_offset = self