Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 45 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 69 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This is a shielded resource machine implementation based on [Risc0-zkvm](https:/
- **`arm_circuits/`**: Demonstration circuits for arms and applications:
- **compliance**: Basic compliance checking circuit
- **trivial_logic**: Minimal logic circuit example, also used in padding resources
- **proof aggregation (batch_aggregation, sequential_aggregation)**: Circuits for single-run aggregation and IVC-based aggregation, respectively
- **logic_test**: The logic circuit contains hardcoded data to cover all instance fields and is used only in tests
- **counter**: The simple counter logic circuit
- **kudo circuits(kudo_main, simple_kudo_denomination, simple_kudo_receive)**: kudo application circuits
Expand Down Expand Up @@ -98,6 +99,10 @@ We have the following feature flags in arm lib:
| `nif` | | Enables Erlang/Elixir NIF (Native Implemented Function) bindings |
| `test_circuit` | | A simple circuit implementation for testing |
| `test` | | Includes tx and action tests; some test APIs are available outside the arm lib(Binding lib and Elixir SDK). |
| `aggregation_circuit` | | A specific feature for (pcd-based) aggregation circuits |
| `aggregation` | `aggregation_circuit`, `transaction` | Enables proof aggregation (with constant-sized proofs by default) |
|`fast_aggregation` | `aggregation` | Faster aggregation with linear-sized proofs without compression
|`groth16_aggregation` | `aggregation` | Generates groth16 aggregation proofs (requires x86_64 machines)


### Usage Examples
Expand All @@ -109,11 +114,18 @@ arm = "0.7.0"
# Blockchain deployment with Groth16 proofs
arm = { version = "0.7.0", default-features = false, features = ["groth16_prover", "transaction"] }

# Proof aggregation (a single succinct proof per transaction)
arm = { version = "0.7.0", features = ["aggregation"] }

# Blockchain deployment with a Groth16 aggregation proof
arm = { version = "0.7.0", features = ["groth16_aggregation"] }

# Logic-circuit-only usage
arm = { version = "0.7.0", default-features = false }

# Elixir Anoma SDK
arm = { version = "0.7.0", features = ["nif"] }
arm = { version = "0.7.0", features = ["nif"] }
```


Expand All @@ -140,4 +152,60 @@ arm-risc0/arm_circuits/compliance/methods/guest/target/riscv32im-risc0-zkvm-elf/
Note: The `unstable` feature of `risc0-zkvm` currently causes issues in circuits. This can be temporarily fixed by manually updating the tool. The problem will be fully resolved in the next release of RISC Zero.
```bash
cargo install --force --git https://github.com/risc0/risc0 --tag v3.0.3 -Fexperimental cargo-risczero
```
```

## Proof aggregation
If a single transaction bundles too many resources, it is possible to aggregate all compliance and logic proofs into a single aggregation proof, attesting to the validity of them all. This reduces overall verification time and transaction size.

### Before aggregation
Generate the transaction in the normal way in your workflow. But note that succinct proofs will yield faster aggregation.

**Warning:** Bonsai does not support in-circuit verification of Groth16 proofs. You would need to generate succinct compliance and logic proofs instead.


### Prove and verify aggregations
You need to enable the `aggregation` feature to be able to prove or verify aggregations.

The type of the aggregation proof is selected via a feature. It defaults to succinct stark proofs. For on-chain verification, you probably want to aggregate with the `groth16_aggregation` feature enabled. See the features table above for more information.

We currently support two different aggregation strategies. The _batch_ strategy aggregates all proofs in the transaction in a single run. It is the default aggregation.

```rust
use arm::transaction;

let mut tx = generate_test_transaction(1); // Just a dummy tx, for illustration.

// Upon succesful aggregation, compliance and resource logic proofs are erased.
assert!(tx.aggregate().is_ok());
assert!(tx.verify_aggregation().is_ok());
```

The _sequential_ strategy aggregates sequentially, in an IVC style.

```rust
use arm::aggregation::AggregationStrategy;

assert!(tx.aggregate_with_strategy(AggregationStrategy::Sequential).is_ok());
```

**Warning:** Once again, aggregation erases all the individual proofs from `tx` and replaces them with the (single) aggregation proof in a dedicated field. This is why the transaction must be `mut`. This is true independently of the strategy used.


### External verification of the aggregation proof
Use

```rust
tx.get_raw_aggregation_proof()
```
to get the RISC0 `InnerReceipt` (the actual proof). The verifier would also need to derive the aggregation instance from `tx` on its own, and wrap both in a RISC0 `Receipt`.

### Comparison

**Strategy** | **Prover cost** | **Public input size** | **Aggregation scope** | **Memory efficient**
-------------|-----------------|-----------------------|-----------------------|----------------------
**batch** | amortized among all tx proofs | linear in #{tx proofs} | fixed (single prover) | for RISC0 yes. In general, depends on the zkVM (if supports continuations)
**sequential** | linear in #{tx proofs} | constant | composable (different provers) | by design

The sequential (IVC) strategy is an example of proof-carrying data computation. PCD-based aggregation can be distributed across mutually _untrusted_ nodes, and proofs to be aggregated arbitrarily grouped and arranged in different transcripts.

**[TODO] Parallel proving at the ARM level.** It is possible with tree-like transcripts. Currently not supported, but [planned](https://github.com/anoma/arm-risc0/issues/112).
5 changes: 5 additions & 0 deletions arm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ edition = "2021"
# If you want to try (experimental) std support, add `features = [ "std" ]` to risc0-zkvm
risc0-zkvm = { version = "3.0.3", features = ["std", "unstable"], default-features = false }
serde = { version = "1.0.197", default-features = false }
serde_with = "3.14.1"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to introduce this as a dependency if it is only used once? We should minimize dependencies and be wary of supply chain attacks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compliance instances are too large to be serialized with serde. What do you suggest instead?

k256 = { version = "=0.13.3", features = ["arithmetic", "serde", "expose-field", "std", "ecdsa", "hash2curve"], default-features = false }
sha3 = { version = "0.10", optional = true }
rand = "0.8"
Expand Down Expand Up @@ -36,3 +37,7 @@ client = ["risc0-zkvm/client"]
cuda = ["risc0-zkvm/cuda"]
test_circuit = []
test = ["transaction", "test_circuit"]
aggregation = ["aggregation_circuit","transaction"]
aggregation_circuit = []
fast_aggregation = ["aggregation"]
groth16_aggregation = ["aggregation"]
Binary file added arm/elfs/batch_aggregation.bin
Binary file not shown.
Binary file added arm/elfs/sequential_aggregation.bin
Binary file not shown.
25 changes: 18 additions & 7 deletions arm/src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,8 @@ impl Action {
&self.logic_verifier_inputs
}

pub fn verify(self) -> Result<(), ArmError> {
for unit in &self.compliance_units {
unit.verify()?;
}
pub(crate) fn get_logic_verifiers(&self) -> Result<Vec<LogicVerifier>, ArmError> {
let mut logic_verifiers = Vec::new();

let compliance_intances = self
.compliance_units
Expand All @@ -61,21 +59,34 @@ impl Action {
let action_tree = MerkleTree::from(tags.clone());
let root = action_tree.root();

for input in self.logic_verifier_inputs {
for input in self.logic_verifier_inputs.iter() {
if let Some(index) = tags.iter().position(|tag| *tag == input.tag) {
if input.verifying_key != logics[index] {
// The verifying_key doesn't match the resource logic
return Err(ArmError::VerifyingKeyMismatch);
}

let is_comsumed = index % 2 == 0;
let verifier = input.to_logic_verifier(is_comsumed, root)?;
verifier.verify()?;
let verifier = input.clone().to_logic_verifier(is_comsumed, root)?;
logic_verifiers.push(verifier);
} else {
return Err(ArmError::TagNotFound);
}
}

Ok(logic_verifiers)
}

pub fn verify(self) -> Result<(), ArmError> {
for unit in &self.compliance_units {
unit.verify()?;
}

let logic_verifiers = self.get_logic_verifiers()?;
for verifier in logic_verifiers.iter() {
verifier.verify()?;
}

Ok(())
}

Expand Down
Loading