Resource logics and zero-knowledge circuits for the AnomaPay ERC20 transfer resource, targeting the Anoma Resource Machine (ARM) on the RISC Zero zkVM.
This repository packages the witness types, resource-logic library, and RISC
Zero guest programs that prove the validity of token-transfer resources. It was
extracted from the AnomaPay backend into a standalone workspace so the proving
artifacts (guest ELFs, ImageIDs, and the Rust APIs around them) can be
versioned and reused independently of the backend services that consume them.
There are two generations of the resource:
- v1 — the original token-transfer resource (wrap / unwrap / transfer).
- v2 — adds migration support (migrating a v1 resource to v2) on top of the v1 capabilities.
Crates live at the repository root. The witness and library crates form one
Cargo workspace; each circuit is its own excluded workspace because RISC
Zero guests are cross-compiled to the riscv32im-risc0-zkvm-elf target and must
not share the host workspace's lockfile or profile.
.
├── transfer_witness/ # v1 witness data + resource-logic constraints
├── transfer_witness_v2/ # v2 witness data (depends on v1)
├── transfer_library/ # v1 host API: TransferLogic, guest ELF + ImageID
├── transfer_library_v2/ # v2 host API: TransferLogicV2 + migration tx builder
├── transfer_circuit/ # v1 RISC Zero guest program (excluded workspace)
└── transfer_circuit_v2/ # v2 RISC Zero guest program (excluded workspace)
transfer_witness ─────────────┬─────────────► transfer_library ───► transfer_circuit
▲ │ ▲ │
│ │ │ │ (path deps,
└── transfer_witness_v2 ──► transfer_library_v2 ──► transfer_circuit_v2
(guest embeds the library ELF)
transfer_witnessis the leaf; everything else builds on it.- The
*_librarycrates embed the prebuilt guest ELF (include_bytes!) and expose the matchingImageID, so a host can verify proofs without rebuilding the guest. - The
*_circuitcrates are the guest sources — used to (re)produce those ELFs — and depend on the witness/library crates by relative path.
Defines TokenTransferWitness: the full set of inputs needed to prove the
resource logic of a single consumed or created resource (the resource itself,
nullifier key, authorization signature, encryption info, forwarder/permit data,
label and value info). Implements the ARM LogicCircuit constraint function
that the guest executes, plus the Solidity-ABI (alloy-sol-types) encoding for
the EVM forwarder Wrap/Unwrap calldata. Authorization domain:
TokenTransferAuthorization.
TokenTransferWitnessV2 — the v2 witness, reusing the shared v1 building blocks
(EncryptionInfo, LabelInfo, ValueInfo, PermitInfo, …) and adding
ForwarderInfoV2 with MigrateInfo to support migrating a v1 resource to v2.
Authorization domain: TokenTransferAuthorizationV2.
Host-side API for v1 proving. TransferLogic wraps a witness and constructs
resource-logic proofs. Exposes:
TOKEN_TRANSFER_ELF— the embedded guest binary (elf/token-transfer-guest.bin).TOKEN_TRANSFER_ID— the guestImageID(aDigest) used for verification on- and off-chain.
v2 host-side API. TransferLogicV2 plus migrate_tx::construct_migrate_tx,
which assembles a complete ARM Transaction (compliance units, logic proofs,
delta proof) that migrates a v1 resource to v2. Exposes TOKEN_TRANSFER_V2_ELF
and TOKEN_TRANSFER_V2_ID (elf/token-transfer-guest-v2.bin).
The RISC Zero guest programs (token-transfer / token-transfer-v2). Each
methods/guest main simply reads a witness, runs witness.constrain(), and
commits the resulting LogicInstance:
let witness: TokenTransferWitness = env::read();
let instance = witness.constrain().unwrap();
env::commit(&instance);Their integration tests (src/test.rs) exercise wrap/unwrap/transfer (and, for
v2, migration) end to end. See each crate's README.md for how to reproducibly
build the ELF and ImageID with cargo risczero build.
TOKEN_TRANSFER_ID (and TOKEN_TRANSFER_V2_ID) is the ImageID of a specific
committed build of the guest ELF — not necessarily of a fresh
cargo risczero build at HEAD. The ImageID is a digest over the entire
compiled guest, so it changes whenever any transitive input changes
(transfer_library, the ARM libraries, the toolchain), even if the circuit
logic is byte-for-byte identical.
Because TOKEN_TRANSFER_ID is consumed on-chain (verifier / protocol-adapter
contracts) and off-chain, it is rotated only when the proof semantics
change — not on incidental dependency or toolchain bumps. When you do need to
rotate it, rebuild the guest, replace the committed elf/*.bin, update the
ImageID constant in the library crate, and coordinate the corresponding
contract redeployment. Full details are in
transfer_circuit/README.md.
Requires the toolchain pinned in rust-toolchain.toml
(Rust 1.95). Building the guests additionally needs the RISC Zero toolchain
(cargo risczero); see the circuit READMEs.
# Workspace crates (witness + library)
cargo build --workspace --all-targets
cargo test --workspace
# Each circuit is a separate workspace — build/test from its own directory.
# RISC0_DEV_MODE=1 skips real proving so tests run fast (dev/CI only — it does
# NOT produce verifiable proofs).
cd transfer_circuit && RISC0_DEV_MODE=1 cargo test -- --nocapture && cd ..
cd transfer_circuit_v2 && RISC0_DEV_MODE=1 cargo test -- --nocapture && cd ..The dev profile sets
opt-level = 3for the whole workspace: running an unoptimized guest is dramatically slower.
.github/workflows/ci.yml runs on pushes to main
and on pull requests:
- Format —
cargo fmt --all --checkfor the workspace and each circuit (rustfmtstyle_edition = "2024"), plustaplo fmt --checkfor allCargo.tomls. - Build & test —
cargo build --workspace --all-targets,cargo test --workspace, and a build +RISC0_DEV_MODE=1test of each circuit.
The workspace crates (transfer_witness*, transfer_library*) share the
version in [workspace.package] (currently 2.0.0). The circuit crates are
versioned independently (currently 2.0.0).
GPL-3.0.