Overview
A chip is a static descriptor of a reusable circuit building block. It declares
the columns, gate expressions, lookup arguments, and sub-chip dependencies that the block
contributes to a circuit. The estimator aggregates these descriptors to compute proof size,
verification key size, and verifier cost — no proof generation required.
Each chip is a Rust struct that implements the
Each chip is a Rust struct that implements the
Chip trait
(defined in src/plutus_gen/stats/chips/mod.rs). All methods have
default implementations returning empty vectors, so you only need to override
the ones relevant to your chip.
Chip trait methods
| Method | Required? | Description |
|---|---|---|
| advice_columns() | optional | Witness columns. Each entry declares its rotation set (which rows are queried) and whether it participates in copy constraints. |
| fixed_columns() | optional | Shared fixed columns (MDS matrices, round constants). These are merged by position across chips: if two chips both declare a shared fixed column at index 0, only one physical column is allocated. |
| extra_columns() | optional | Exclusive fixed columns, selectors, and complex selectors that belong solely to this chip and are never shared. |
| gate_args() | optional | Gate expressions from meta.create_gate(). Each Argument is a vector of ScalarExpression values, one per expression in the gate group. |
| lookup_args() | optional | Lookup expressions from meta.lookup() or meta.lookup_any(). |
| trash_args() | optional | Trashcan expressions from Constraints::with_additive_selector() (partial-round style gates with a single additive selector). |
| chip_deps() | optional | Direct sub-chips this chip depends on. Transitive deps are resolved automatically; each chip is counted once regardless of how many composites declare it. |
| nr_pow2range() | optional | Number of pow2range lookup arguments needed by this chip. Defaults to 0. |
Step-by-Step
Follow these eight steps in order. Steps 1–4 are Rust source changes;
steps 5–6 wire the chip into the public APIs; steps 7–8 update the
generated artifacts consumed by the web interface.
1
Create the chip file
Add a new
.rs file under src/plutus_gen/stats/chips/primitives/
(or a subdirectory such as hash/ or curve/) and implement
the Chip trait. Only override the methods your chip uses; the rest default
to empty.
src/plutus_gen/stats/chips/primitives/hash/my_chip.rs
use super::super::super::{Argument, Chip, Column, RotationSet, ScalarExpression, SupportedChips};
pub(crate) struct MyChip;
impl Chip for MyChip {
fn advice_columns() -> Vec<Column> {
vec![
// Columns queried at current and next row, copy-constrained
Column::advice(RotationSet::new(false, false, true, true, false), true),
Column::advice(RotationSet::curr(), true),
]
}
fn extra_columns() -> Vec<Column> {
vec![
Column::selector(), // q_enable
]
}
fn gate_args() -> Vec<Argument> {
vec![
// q_enable * (w0_curr + w1_curr - w0_next)
vec![ScalarExpression::gate_expression(2, 1, 2, 0, 1, 0)],
]
}
fn chip_deps() -> Vec<SupportedChips> {
vec![SupportedChips::Native]
}
}
ScalarExpression::gate_expression(degree, nb_neg, nb_add, nb_sub, nb_mul, nb_from_int)
Records the algebraic cost of one polynomial expression in the constraint. These counts are used to estimate the number of field operations the verifier must perform when evaluating the circuit's constraints.
Records the algebraic cost of one polynomial expression in the constraint. These counts are used to estimate the number of field operations the verifier must perform when evaluating the circuit's constraints.
2
Export from
primitives/mod.rs
Re-export your struct so it is visible to the parent
chips module.
src/plutus_gen/stats/chips/primitives/mod.rs
pub(crate) mod hash;
pub(crate) use hash::{Poseidon, MyChip}; // add MyChip here
If your chip lives in a new subdirectory, declare the module with
pub(crate) mod my_dir; and add the corresponding
mod.rs inside it following the pattern in hash/mod.rs.
3
Add a variant to
SupportedChips
Register the new chip in the enum. The
strum attribute controls
the string identifier used in the CLI, WASM API, and chip_profiles.json.
src/plutus_gen/stats/chips/mod.rs
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, strum::EnumIter, strum::Display)]
pub enum SupportedChips {
#[strum(to_string = "native")]
Native,
#[strum(to_string = "poseidon")]
Poseidon,
// ... existing variants ...
#[strum(to_string = "my_chip")] // ← add this
MyChip,
}
4
Wire into the dispatch macro
The
impl_supported_chips! macro generates all the match arms that
delegate trait methods to the concrete struct. Add one line per chip.
src/plutus_gen/stats/chips/mod.rs
impl_supported_chips! {
Native => Native,
Poseidon => Poseidon,
HashToCurve => HashToCurve,
EdwardsJubjub => EdwardsJubjub,
WeierstrassBls12381 => WeierstrassBls12381,
WeierstrassSecp256k1 => WeierstrassSecp256k1,
MyChip => MyChip, // ← add this
}
At this point
cargo build should compile cleanly. Run it now to
catch any type errors in your trait implementation before proceeding.
5
Expose via the CLI
Add a boolean flag to
EstimateCliArguments and push the variant in
the chips() method. The flag name becomes the --my-chip
long option (Clap converts underscores to hyphens automatically).
src/plutus_gen/stats/cli.rs
// In EstimateCliArguments:
#[arg(long, help_heading = "Chips", help = "Include the MyChip chip.")]
pub my_chip: bool,
// In chips():
if self.my_chip {
chips.push(SupportedChips::MyChip);
}
6
Expose via the WASM API
The web interface calls the WASM
estimate() function with a JSON array
of chip names. Add your chip's string identifier to the chip_from_str
parser so the browser can select it.
src/wasm.rs
fn chip_from_str(s: &str) -> Option<SupportedChips> {
match s {
"Native" => Some(SupportedChips::Native),
"Poseidon" => Some(SupportedChips::Poseidon),
"HashToCurve" => Some(SupportedChips::HashToCurve),
"EdwardsJubjub" => Some(SupportedChips::EdwardsJubjub),
"WeierstrassBls12381" => Some(SupportedChips::WeierstrassBls12381),
"WeierstrassSecp256k1"=> Some(SupportedChips::WeierstrassSecp256k1),
"MyChip" => Some(SupportedChips::MyChip), // ← add this
_ => None,
}
}
The string key you use here is what the JavaScript in
cost-estimator.html
passes to estimate(). Keep it consistent with the variant name to avoid
confusion.
7
Regenerate
chip_profiles.json
The chips reference page and the web estimator both read
docs/chip_profiles.json, which is generated from the live Rust
estimator. Run the dump binary to rebuild it after any chip change.
cargo run --bin dump_profiles
This writes
docs/chip_profiles.json with an entry for every
SupportedChips variant (via strum::IntoEnumIterator),
including your new chip.
8
Rebuild the WASM module
The interactive cost estimator at
docs/cost-estimator.html runs
entirely in-browser via WebAssembly. Rebuild and copy the WASM output whenever
you change any code that affects estimation results.
# Install wasm-pack if not already present
cargo install wasm-pack
# Build the WASM target
wasm-pack build --target web --out-dir docs/wasm
Commit the regenerated
docs/wasm/ and docs/chip_profiles.json
together with your Rust changes so the web interface stays in sync with the code.
Checklist
Use this list to verify you haven't missed anything before opening a pull request.
- New
.rsfile added undersrc/plutus_gen/stats/chips/primitives/ - Struct exported from
primitives/mod.rs - Variant added to
SupportedChipsenum with a#[strum(to_string = "...")]attribute - Line added to
impl_supported_chips!macro invocation - Boolean flag added to
EstimateCliArgumentsincli.rs - Variant pushed in
EstimateCliArguments::chips() - String match arm added to
chip_from_strinwasm.rs cargo run --bin dump_profilesrun anddocs/chip_profiles.jsonupdated- WASM rebuilt and
docs/wasm/updated cargo fmtrun on all modified filescargo testpasses
Tips & Patterns
Shared vs exclusive fixed columns
Declare columns as shared (
Column::shared_fixed) when they carry
data that logically spans chips — for example MDS matrix constants that both a hash chip
and an ECC chip might reuse. The merger allocates only one physical column per shared
position. Use exclusive (Column::exclusive_fixed) or selectors for
anything that belongs solely to your chip; these are always appended and never merged.
Delegating to a generic chip
The Weierstrass curve chips delegate to a generic
EccChip<T> parameterised
by field and curve constants. If your chip is a specialisation of a general construction
(foreign-field arithmetic, range checks, etc.) consider following the same pattern:
implement a generic struct with trait parameters and have the concrete chip call into it.
See src/plutus_gen/stats/chips/primitives/curve/ecc/ for a worked example.
Verifying your cost numbers
Run the estimator against a real circuit that uses your chip to sanity-check the numbers:
# Estimate from CLI flags
cargo run -- --my-chip --pi 1
# Compare against a real proof (requires a circuit example)
cargo run --example my_example
Proof size is deterministic for a given set of chips and circuit parameters.
If the estimated size does not match the actual proof, the most common cause is
a missing or over-counted advice column, or an incorrect rotation set declaration.