Skip to content

Commit

Permalink
feat: docs (#8)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
rpalakkal authored Feb 14, 2024
1 parent e3b34e0 commit 335c559
Show file tree
Hide file tree
Showing 21 changed files with 442 additions and 42 deletions.
12 changes: 12 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
116 changes: 114 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
- `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<Fr>`).

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<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:
```rust
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(),
]
}
}
```

### 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::<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:

```bash
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`.

2 changes: 1 addition & 1 deletion sdk-derive/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: Copy> = #name #ty_generics;
}
Expand Down
4 changes: 4 additions & 0 deletions sdk-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 0 additions & 1 deletion sdk/data/keccak_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"num_fixed": 1,
"num_lookup_advice_per_phase": [
5,
0,
0
],
"lookup_bits": 14,
Expand Down
3 changes: 1 addition & 2 deletions sdk/data/rlc_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
"num_fixed": 1,
"num_lookup_advice_per_phase": [
5,
1,
0
1
],
"lookup_bits": 14,
"num_rlc_columns": 1
Expand Down
14 changes: 8 additions & 6 deletions sdk/examples/account_age.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -23,10 +23,12 @@ impl AxiomComputeFn for AccountAgeInput {
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 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);
Expand Down
4 changes: 2 additions & 2 deletions sdk/examples/keccak.rs
Original file line number Diff line number Diff line change
@@ -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]
Expand Down
6 changes: 3 additions & 3 deletions sdk/examples/quickstart.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
14 changes: 10 additions & 4 deletions sdk/examples/rlc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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);
}
}

Expand Down
2 changes: 1 addition & 1 deletion sdk/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <PROVIDER_URI> <CMD>
Expand Down
43 changes: 42 additions & 1 deletion sdk/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Fr>,
/// The main chip for ZK primitives
pub range: &'a RangeChip<Fr>,
pub subquery_caller: Arc<Mutex<SubqueryCaller<Http, Fr>>>,
/// The struct that manages all subquery calls
subquery_caller: Arc<Mutex<SubqueryCaller<Http, Fr>>>,
}

impl<'a> AxiomAPI<'a> {
Expand All @@ -43,24 +47,41 @@ impl<'a> AxiomAPI<'a> {
}
}

/// Returns a thread-safe [SubqueryCaller] object.
pub fn subquery_caller(&self) -> Arc<Mutex<SubqueryCaller<Http, Fr>>> {
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<Fr> {
self.builder.base.main(0)
}

/// Returns an `AssignedValue<Fr>` from a `HiLo<AssignedValue<Fr>>`.
///
/// 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<AssignedValue<Fr>>` object to convert.
pub fn from_hi_lo(&mut self, hilo: HiLo<AssignedValue<Fr>>) -> AssignedValue<Fr> {
let ctx = self.builder.base.main(0);
from_hi_lo(ctx, self.range, hilo)
}

/// Returns a 256-bit `HiLo<AssignedValue<Fr>>` from a `AssignedValue<Fr>`.
///
/// See `check_hi_lo` for what is constrained.
///
/// * `val` - The `AssignedValue<Fr>` object to convert.
pub fn to_hi_lo(&mut self, val: AssignedValue<Fr>) -> HiLo<AssignedValue<Fr>> {
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<Fr>`.
/// * `addr` - The address as an `AssignedValue<Fr>`.
pub fn get_account(
&mut self,
block_number: AssignedValue<Fr>,
Expand All @@ -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<Fr>`.
pub fn get_header(&mut self, block_number: AssignedValue<Fr>) -> 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<Fr>`.
/// * `addr` - The address as an `AssignedValue<Fr>`.
/// * `mapping_slot` - The mapping slot as a `HiLo<AssignedValue<Fr>`.
pub fn get_mapping(
&mut self,
block_number: AssignedValue<Fr>,
Expand All @@ -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<Fr>`.
/// * `tx_idx` - The transaction index as an `AssignedValue<Fr>`.
pub fn get_receipt(
&mut self,
block_number: AssignedValue<Fr>,
Expand All @@ -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<Fr>`.
/// * `addr` - The address as an `AssignedValue<Fr>`.
pub fn get_storage(
&mut self,
block_number: AssignedValue<Fr>,
Expand All @@ -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<Fr>`.
/// * `tx_idx` - The transaction index as an `AssignedValue<Fr>`.
pub fn get_tx(&mut self, block_number: AssignedValue<Fr>, tx_idx: AssignedValue<Fr>) -> Tx {
let ctx = self.builder.base.main(0);
get_tx(ctx, self.subquery_caller.clone(), block_number, tx_idx)
Expand Down
Loading

0 comments on commit 335c559

Please sign in to comment.