Anoma
EVM Protocol Adapter

End-to-end changes to Anoma's Resource Machine and EVM Protocol Adapter were the focus of this review.
report id
0677
category
Infra & Wallets
language
Solidity
final report
November 13, 2025
lines of code
2874
Content

Download audit report

1

Executive Summary

This document presents the results of the security review conducted by Nethermind Security for Anoma, including Resource Machine and EVM Protocol Adapter.

This audit focused on the end-to-end review of the above-described changes. The audit comprises 789 lines of Solidity and 2085 Rust code. The audit was performed using (a) manual analysis of the codebase, and (b) automated analysis tools.

Audit assessment
Audit TypeSecurity Review
Initial ReportNovember 10, 2025
Final ReportNovember 13, 2025
Initial Protocol Adapter Commit Hashe6cfdf8fab
Initial ARM Commit Hasha0cca9cdc8
Final Protocol Adapter Commit Hashfee4f47050
Final ARM Commit Hash087e7d05f6
Documentation AssessmentHigh
Test Suite AssessmentHigh
Total findings
8
Fixed
6
Acknowledged
2
Mitigated
0
Unresolved
0

Findings by severity

  • Critical0
  • High0
  • Medium0
  • Low3
  • Informational3
  • Best Practices2

2

Audited Files

#ContractLoCCommentsRatioBlankTotal
1ProtocolAdapter.sol26811743.66%57442
2libs/MerkleTree.sol1036866.02%32203
3state/CommitmentTree.sol883337.50%23144
4libs/RiscZeroUtils.sol785064.10%14142
5libs/proving/Logic.sol513568.63%894
6libs/proving/Delta.sol343397.06%1481
7interfaces/IProtocolAdapter.sol1643268.75%1372
8libs/TagUtils.sol301756.67%1057
9interfaces/ICommitmentTree.sol1728164.71%954
10libs/proving/Compliance.sol2326113.04%554
11state/NullifierSet.sol231252.17%944
12Types.sol221881.82%444
13libs/SHA256.sol1014140.00%327
14libs/proving/Aggregation.sol1112109.09%326
15interfaces/INullifierSet.sol613216.67%322
16interfaces/IForwarder.sol411275.00%116
17libs/Versioning.sol57140.00%214
Total78953768.06%2101536

The scope includes a function EllipticCurve.ecAdd() from the external library elliptic-curve-solidity, which was not written by the Anoma team. This function was included in the security review since the entire library was never audited.

#ContractLoCCommentsRatioBlankTotal
1resource.rs2133817.84%22273
2compliance.rs220156.82%31266
3encryption.rs1501610.67%35201
4logic_proof.rs17300.00%26199
5delta_proof.rs16442.44%26194
6transaction.rs1491510.07%15179
7aggregation/mod.rs13286.06%20160
8aggregation/batch.rs12375.69%20150
9authorization.rs11121.80%22135
10action.rs9544.21%15114
11action_tree.rs701521.43%893
12proving_system.rs7456.76%1190
13error.rs7000.00%171
14nullifier_key.rs5235.77%1166
15resource_logic.rs5159.80%864
16utils.rs4800.00%755
17logic_instance.rs4700.00%855
18merkle_path.rs4336.98%753
19compliance_unit.rs3937.69%547
20lib.rs3700.00%138
21constants.rs13430.77%320
22aggregation/constants.rs11436.36%318
Total20851517.24%3052541

4

System Overview

4.1 General

Anoma’s Resource Machine (ARM) defines what resources and transactions are, how they compose, and how validity is proven — supporting an intent-centric, privacy-preserving model where users express goals and settlement proves correctness without revealing sensitive data. The ARM specification frames intents, transactions, and proofs, and places the Resource Machine as a core protocol component for programmable, shielded state updates.

The arm-risc0 codebase is a concrete implementation of this Resource Machine that runs ARM logic inside a zkVM. It provides full transaction processing, plus circuits and helpers for resources, transactions, Merkle paths, nullifier keys, compliance checks, and batching/aggregation.

On the settlement side, the EVM Protocol Adapter is a Solidity suite that lets EVM chains verify ARM executions and update on-chain state via commitments. The adapter’s purpose is to enable ARM transaction settlement on EVM-compatible chains.

Proof verification is designed to rely on the RISC Zero Ethereum verifier contracts, so an ARM execution proven in the RISC0 zkVM can be checked on EVM.

Taking all into account, users produce intents that compile to ARM transactions; the ARM circuits compute state transitions and generate (optionally aggregated) ZK proofs; the EVM Protocol Adapter receives the transaction and verifies proofs on-chain. Fig. 2 shows the flow of execution.

Flow of execution. Presents actions performed to create and execute a valid Anoma transaction on an EVM-compatible chain
Fig. 2: Flow of execution. Presents actions performed to create and execute a valid Anoma transaction on an EVM-compatible chain.

4.2 Resources

Resources can be thought of as "units of state", they represent objects that can be created and consumed during state transitions — e.g., tokens, NFTs, rights, or any other application-specific object. A Resource carries both data and its own rules for how it may be consumed. Each Resource specifies the logic that governs its life-cycle via a reference to a resource-logic program. A Resource has the following components:

  • logic_ref — hash of the logic program that defines how this resource may be consumed or created
  • label_ref — identifies the kind/class of resource
  • quantity — amount of this resource
  • value_ref — application-specific payload
  • is_ephemeral — marks resources that do not need nullifier balancing checks
  • nonce — uniqueness salt for otherwise identical resources
  • nk_commitment — commitment derived from the nullifier key used to prevent double-consumption
  • rand_seed — randomness used for commitment and nullifier generation

Each Resource, therefore, contains both what it is (quantity) and how it behaves (logic). Through its logic_ref, the Resource enforces constraints on whether it may be consumed, and under which conditions new Resources may be created.

4.3 Compliance Unit

A compliance unit is the part of the transaction logic that proves that a consumed resource was valid and that it is correctly linked to a newly created resource. It serves as the bridge between the previous state and the new state, and it is enforced through a zero-knowledge proof.

A compliance unit contains

  • proof the ZK proof attesting correctness
  • instance the public inputs bound to that proof

The witness inputs to this ZK proof are:

  • comsumed_resource
  • merkle_path
  • ephemeral_root
  • nf_key
  • created_resource
  • rcv

The proof checks that the consumed resource was present in the commitment tree (via its Merkle path or ephemeral root), that the correct nullifier key is used to consume it, and that the newly created resource is consistent with the referenced logic program. It also calculates the “delta” curve point: consumed kind times its quantity, minus created kind times its quantity, plus a generator multiple of the random scalar. That delta captures the net change in fungible balance.

4.4 Action

The Action groups of compliance units. This allows for gathering consumed and created resources that participate in the same application, but can contain different logics. The action is represented as a Merkle Tree, where leaves are nullifiers and commitments. The action root is recreated during the execution in the Protocol Adapter to ensure the correct content of the action.

struct Action {
Logic.VerifierInput[] logicVerifierInputs;
Compliance.VerifierInput[] complianceVerifierInputs;
}

4.5 Transaction

Transaction consists of Actions, delta proof and aggregation proof (if used). The delta proof guarantees that all the resources in the transaction are balanced. The transaction is submitted to the protocol adapter, as illustrated in Fig. 3.

struct Transaction {
Action[] actions;
bytes deltaProof;
bytes aggregationProof;
}
Transaction. Transaction
Fig. 3: Transaction. Transaction's structure with example logic association.

4.6 ZK proofs

Built using the Risc-Zero zkVM, Anoma uses two different ZK proofs to prove state transitions to establish the correct consumption and creation of resources, called the compliance proofs and logic proofs. Additionally, for efficiency reasons, it also provides an in-built aggregation method to verify all the proofs in one.

Compliance proofs are a fixed proof defined by Anoma that proves that the nullifier of the resource consumed is equal to the nonce of the created resource. Additionally, it computes the delta value of the created and computed resources, following the style of a Pedersen commitment, whereby Δ = consumed kind * consumed quantity - created kind * created quantity + G * rcv where G is the generator on the curve and rcv is a random scalar generated. A resource kind is the hash to curve(logic reference||label reference) where the hash used is SHA256 and the curve is Secp256k1. The public values of the circuit are:

  • Consumed resource's nullifier.
  • Consumed resource's logic reference.
  • Consumed commitment resource's tree root (if the resource is ephemeral, this value is arbitrary).
  • Created resource's commitment.
  • Created resource's logic reference.
  • The delta as (x,y) coordinates.

These proofs convince the verifier that a user has correctly consumed and created a new resource.

Logic Proofs are user-defined proofs that outline how specific resources can or cannot be consumed or created. More specific details about writing these circuits will be discussed in Section .

The final proof circuit is the Batch Aggregation Proof, which, as the name suggests, proves that all these provided proofs verify under these provided keys. Split between compliance and logic proofs, it takes as input the journal instances and the keys/imageIDs (for compliance, it is a fixed key as the circuit does not change) to verify. Calling the in-built verify function, the proofs can be verified in sequence: first, all the compliance proofs are verified, then all the logic proofs are verified. Finally, all the instances and keys input are committed as public values.

4.7 Delta proof

The delta proof is the signature (and recovery ID) of all the deltas in a transaction. Formally, it can be expressed as Sign(H(Vec(Δ))rcv [] σ = (r,s), recID where the signature attests to the integrity and authenticity of the aggregated deltas.

To enforce the transaction being balanced, meaning the total amount created of a resource must equal the total amount consumed, the sum of all delta values must correspond to the aggregate randomness rcv. When a transaction is submitted on-chain, this condition is verified by recovering the public key from the signature rcv and comparing it to the expected public key derived from the summed delta values. Should the value not recover to the aggregate sum of the rcv's in the transaction, it means there is an imbalance.

4.8 EVM Protocol Adapter

The Protocol Adapter (PA) allows the Anoma applications to be run on existing chains. It processes the transactions created in the Anoma Resource Machine (ARM) and updates the state according to the provided resources. In this iteration of the protocol, the PA is implemented as a Solidity smart contract and can be utilised as a connector to any EVM-compatible chain. The entrypoint is the function execute() with input being the Transaction. The execute() function iterates over provided Action objects, computes the Action root from collected nullifiers and commitments (tags), and for each Compliance Unit performs the following actions:

  • Checking if the provided root corresponding to the consumed resource commitment exists in the set of historic commitment tree roots.
  • Verification of the compliance proof or collecting instance for aggregation proof.
  • Verification of the logic proof or collecting an instance for aggregation proof of both consumed and created resources.
  • Execution external call to the Forwarder smart contract with provided calldata.
  • Addition of the nullifier/commitment to the set of tags.
  • Addition of the logic reference to the set of verification keys, required for aggregation proofs.
  • Updating the accumulator with nullifier/commitment.
  • Emission of the application data.
  • Addition delta points to the delta accumulator.

Next, the delta proof is verified. The signature is utilised to recover the Keccak-256 hash of tags, which is compared against the hash of the provided public key (instance). If used, the aggregation proof is checked. If all proofs are verified correctly and the external call was executed successfully with the expected output, the new commitment root is added to the set of historic roots.

Commitment accumulator is a Merkle Tree with variable size. The leaves contain the resource commitment. The tree is expanded with empty leaves if the full capacity is reached. Roots of the tree are stored in the _roots mapping, which allows for proving commitment against historic tree state. This accumulator allows for duplicated values of leaves.

Nullifier set contains the resource nullifier. It prevents duplicates.

4.9 Fowarder

The Forwarder is a smart contract created by the application developer. It should be callable only from Anoma's protocol adapter and only with the calldata included in the resource linked to particular logic. The forwarder can contain arbitrary logic and is used to interact with an external contract.

4.10 Applications on Anoma

Users create applications on Anoma by developing logic circuits and deploying a forwarder contract on the desired blockchain. Logic consists of constraints that must be satisfied during proof creation. Logic constraints allow for defining:

  • Application logic - all the flows and requirements of the application must be included in the logic circuit. This should handle different resource types' consumption and creation or validation of calldata used to interact with the forwarder.
  • Resources behavior - the resources can be ephemeral and non-ephemeral. The ephemeral resources do not require a valid Merkle proof to be consumed, therefore, they should be handled properly. The logic of one resource may constrain another type of resource, with particular logic must be present in a particular action.
  • Ownership of the resource - the basic ownership check is enforced by the nullifier commitment, to consume a resource, the creator of a proof needs to hold the nullifier key. If the application needs delegation of proof creation, additional mechanisms, like the owner's signature, can be introduced in the logic.
  • Migration strategy - in case of pausing Anoma protocol, the applications developer can migrate to the new instance by augmenting the logic with migration operations.

4.11 Security Approach

This section outlines the approach the Nethermind Security team performed to evaluate the security of the Anoma Resource Machine protocol, outlining the properties checked.

Additionally, the assumptions about the system are provided, taking into account when assessing the protocol and the limitations of the security review.

4.11.1 Assumptions

The assumptions used for the security audit were as follows:

  • All external, out-of-scope components, including cryptographic libraries and the Risc Zero framework, were verified to function correctly.
  • The secure implementation of resource logic for all resource types (except padding) is the responsibility of the Anoma application builders.
  • Fee handling mechanisms and any associated risks are managed by the application builders.
  • The implementation of third-party solvers and executors follows secure and fair design principles.
  • Forwarders have been implemented correctly, ensuring reliable message and data transmission.
  • zkVM proofs are correctly and securely processed and return valid cryptographic proofs, maintaining integrity and confidentiality throughout the proof lifecycle.

4.11.2 Limitations

During this security review, we were unable to assess:

  • The impact of potential MEV scenarios available for solvers, transaction builders, and executors.
  • Estimate the probability of pairing the nullifier and commitments.

4.11.3 Resources

The following properties were assessed with regard to the security and validity of the resource objects:

  • The computation of commitment (Resource::commit()) and nullifier (Resource::nullifier_from_commitment()) is done in a secure way and results in unique values. The nullifier hash correctly encapsulates the resource commitment such that it does not allow two values to map to the same hash.
  • The Resource::kind(...) is correctly calculated from the resource’s logic reference and label. It is also correctly represented on the curve.
  • Neither the commitment nor the nullifier reveals any information about the contents of a resource.
  • The nullifier and commitment cannot be linked (assuming lack of access to global state).
  • A resource can only be consumed by the owner of the nullifier key to create a nullifier.
  • Evaluated rand_seed value generation using thread_rng(), which is used in both nullifier and commitment creation.

4.11.4 Compliance Unit

The following properties were assessed with regard to the security and validity of the resource objects:

  • Correctly computes consumed resource's commitment and nullifier and created resource's commitment.
  • The nullifier can only be computed if the prover holds the nullifier key.
  • The created resource and consumed resource are linked by the nullifier used as the nonce, which enforces creation of each resource by consumption of a single unique resource.
  • The delta is correctly calculated (ComplianceWitness::delta()) by correctly using the resources' kinds, quantities, and random scalar.
  • The commitment tree root is correctly constructed from the path and consumed resource's commitment (ComplianceWitness::consumed_commitment_tree_root()) and the ephemeral resource contains any root.
  • The logic references for each resource are included in the instance.
  • The instance does not reveal any additional information, e.g., information about the connection between nullifier and commitment.
  • Verification of compliance proof is performed correctly both in ComplianceUnit::verify() and ProtocolAdapter._processCompliance(). Only case in which such proof won't be verified is if the Risc0 verifying router stops supporting the version for which the proof was generated. Such a scenario is handled by Anoma.

4.11.5 Action

The properties checked for actions were:

4.11.6 Transaction

The properties checked for transactions were:

  • Verified that all transactions are balanced, ensuring proper enforcement of this constraint.
  • Confirmed that the composition of two transactions correctly aggregates their respective delta proofs.
  • Validated that the batch aggregation proof appropriately constrains and verifies the input proofs, and correctly commits to the intended public values.
  • Assessed on-chain transaction execution to ensure:
    • Correct verification of the delta proof.
    • Accurate validation of the action root against the submitted actions.
    • Proper distinction between batch and non-batch proofs for verification.
    • Prevention of replay attacks by disallowing resubmission of old transactions or nullifiers.

4.11.7 Delta Proofs

For the delta proof, the following was checked:

  • Verified that Resource::kind() (consisting of the hash of the logic reference and label) is correctly projected onto the curve.
  • Confirmed that all performed calculations are consistent with elliptic curve (EC) operations.
  • Verified that each transaction is balanced by ensuring equality between quantities of the same kind within the transaction.
  • Ensured that it is not possible to craft a special quantity value that could artificially balance a transaction.
  • Similarly confirmed that crafting a special rcv value to artificially balance a transaction is not possible.
  • Validated correct delta accumulation in Action::delta(), Transaction::delta(), and in the Protocol Adapter at vars.transactionDelta.add().
  • Reviewed signature creation in DeltaProof::prove(), verifying that the public key is computed as the sum of deltas, the secret key as the sum of rcv values, and the signed message as the hash of all nullifiers and commitments.
  • Verified signature validation in DeltaProof::verify() and in the Protocol Adapter’s Delta.verify() implementation.
  • Confirmed that the set of signed tags cannot be modified in a way that would produce a different or unauthorized set of resources.
  • The signature is created correctly and is compatible between RM and PA.

4.11.8 Protocol Adapter

The following properties were assessed in the Protocol Adapter:

  • Correct invocation of proof verification: _processCompliance() and _processLogic() call into the Risc Zero router with the exact public instance values reconstructed from calldata.
  • Correctness of delta accumulation via transactionDelta.add() and that the delta used for Delta.verify is identical to the delta enforced when updating state.
  • State-transition side effects (nullifier storage, commitment insertion) only occur after a successful verification step
  • Confirm emit logic only exposes intended payloads, respects deletion criteria, and doesn’t leak sensitive state.
  • The execution is only correct if all proofs are valid and the on-chain call is successful.

4.11.9 Attack Scenarios Considered

In addition to ensuring the above properties of each component of Anoma, the review considered the following potential attack vectors:

  • Resources
    • Can produce two different commitments or nullifiers for the same resource.
    • Two different commitments map to the same hash
    • A deterministic RNG could potentially break the security of a secret.
  • Compliance Unit
    • Taking advantage of a badly designed delta to forge quantities of resources.
    • Proving the existence of an insecure Merkle tree implementation to include fake resources.
  • Actions:
    • Providing an incorrect root in order to prove the inclusion of a non-existent leaf in the action tree.
    • Providing a resource outside of action.
  • Transactions:
    • Resubmitting a transaction to inflate the number of commitments.
    • Providing an unbalanced transaction enabling malicious growth of the resource number owned by a user.
    • Anonymity of the user submitting the transaction is revealed.
    • Resubmitting a transaction to inflate the number of commitments.
    • Providing an unbalanced transaction enabling malicious growth of the resource number owned by a user.
    • Anonymity of the user submitting the transaction.
  • Batch Transactions:
    • Submit two different proofs for the same transaction.
    • Provide mismatching proof instances & logic verifying keys.
  • Delta Proofs:
    • Could reuse the same rcv value across multiple proofs, leaking the corresponding private key material.
    • Could create a malleable signature that passes verification but represents a different message hash.
    • Could reorder actions or transactions to exploit non-commutative accumulation, producing divergent final deltas.
  • Protocol Adapter:
    • Updating state without all proofs verification.
    • Front-runs and reentrancy.
    • Finalising execution without expected external call output.
    • Updating incorrectly the commitment and nullifier accumulators.
    • Updating historic roots set with incorrect root.
    • Using an incorrect verifier.
    • Using migration to process to create incorrect state updates.
  • Privacy Properties
    • Revealing the user's identity with available data.
    • Revealing data in the resource.
    • Detecting a connection between the nullifier. and commitment to the same resource, given the public data.

5

Risk Rating Methodology

The risk rating methodology used by Nethermind Security follows the principles established by the OWASP Foundation. The severity of each finding is determined by two factors: Likelihood and Impact.

Likelihood measures how likely the finding is to be uncovered and exploited by an attacker. This factor will be one of the following values:

  1. High: the issue is trivial to exploit and has no specific conditions that need to be met;
  2. Medium: the issue is moderately complex and may have some conditions that need to be met;
  3. Low: the issue is very complex and requires very specific conditions to be met.

When defining the likelihood of a finding, other factors are also considered. These can include but are not limited to motive, opportunity, exploit accessibility, ease of discovery, and ease of exploit.

Impact is a measure of the damage that may be caused if an attacker exploits the finding. This factor will be one of the following values:

  1. High: the issue can cause significant damage, such as loss of funds or the protocol entering an unrecoverable state;
  2. Medium: the issue can cause moderate damage, such as impacts that only affect a small group of users or only a particular part of the protocol;
  3. Low: the issue can cause little to no damage, such as bugs that are easily recoverable or cause unexpected interactions that cause minor inconveniences.

When defining the impact of a finding, other factors are also considered. These can include but are not limited to data and state integrity, loss of availability, financial loss, and reputation damage. After defining the likelihood and impact of an issue, the severity is determined according to the table below.

Impact / LikelihoodLowMediumHigh
HighMediumHighCritical
MediumLowMediumHigh
LowInfo / Best PracticesLowMedium
UndeterminedUndeterminedUndeterminedUndetermined

To address issues that do not fit a High, Medium or Low severity, Nethermind Security also uses three more finding severities: Informational, Best Practices, and Undetermined.

  1. Informational findings do not pose any risk to the application, but they carry some information that the audit team intends to pass to the client formally;
  2. Best Practice findings are used when some piece of code does not conform with smart contract development best practices;
  3. Undetermined findings are used when we cannot predict the impact or likelihood of the issue.

6

Issues

6.1 Check a point is on the curve

FileFix commitTypeStatus
contracts/src/libs/proving/Delta.solPR #396LowFixed

The function ecAdd(...) uses functions jacAdd(...) and jacDouble(...) to add points on the elliptic curve, secp256k1 specifically. However, inputs to these functions are of type uint256, which do not assert the validity that these points are initially on the curve and Jacobian coordinates mapping, as well as the additional formulas that assume that the points are already on the curve.

Moreover, isOnCurve(...) function won't validate the point at infinity " (0, 0)", so this case must be handled separately.

Recommendation

Check that points are on the Curve in add(...).

Resolution

Added in protocol adapter PR #396.

Update from Nethermind Security

We note that this fix assumes the first elliptic-curve point used is already on the curve, as is also mentioned in the function's comment. Since the resulting point would—with high probability—fall outside the curve.

6.2 ECDSA signature creation and verification

The Anoma Resource Machine implements the DeltaProof::verify() function, which differs from the Delta.verify() in the Ethereum Protocol Adapter. Below we list the differences:

  • The ARM DeltaProof::verify() accepts the malleable signatures, where the s > secp256k1n ÷ 2 + 1, while the Protocol Adapter Delta.verify() uses the OpenZeppelin library that prevents malleability by ensuring the s is less than half of the curve order;
  • The ARM DeltaProof::verify() handles the cases where recovery ID is equal to 2 or 3, which represent the overflow of r. Such cases are unlikely to happen, but the on-chain ECDSA verification ecrecover() does not handle such signatures, since EVM treats them as invalid (Ethereum Yellow Paper Appendix F);

Moreover, the ARM's DeltaProof::prove() function utilises the k256 crate, which may generate a signature with the recid equal to 2 or 3. Those signatures are not supported in on-chain verification and should be handled during generation. Note, however, that there is a low probability of generating such signatures.

Recommendation

Consider checking if the generated signature is compatible with the on-chain verification algorithm. Also consider unifying the verification function in ARM and the Ethereum Protocol Adapter.

Resolution

Added in PRs arm-risc0 #162 and #161.

6.3 Update rand_seed to stronger randomness guarantees

FileFix commitTypeStatus
arm/src/resource.rsPR #159LowFixed

When a resource is created, rand::thread_rng().gen() is used as a seed in the psi() function. Although thread_rng() is initially seeded from OS entropy and is generally secure, for stronger guarantees, it’s recommended to use OsRng directly.

Recomendations: Replace rand_seed initialization using thread_rng() with OsRng().

Resolution

Added in PR arm-risc0 #159.

6.4 Privacy Leakage through imageID usage

FileFix commitTypeStatus
contracts/src/ProtocolAdapter.sol—InfoAcknowledged

The imageID of a circuit is its identifier, but also its verifying key. In Anoma, users can construct their own Risc-0 circuits, known as the logic, which defines a resource's ability to be consumed, created, and how it should deal with being ephemeral.

Though the circuit itself is not necessarily public, and is up to the app how it is communicated, when the Anoma transaction is sent on-chain to add the commitments to the tree and nullify consumed resources, the imageID is used to verify the logic zk proof for each resource. This is shown in the code below:

// snippet from execute(...) in ProtocolAdapter.sol
// @audit the logic ref is the same as the imageID
vars = _processLogic({
isConsumed: true,
// The `lookup` function reverts if the nullifier is not part of the logic verifier inputs.
input: action.logicVerifierInputs.lookup(complianceVerifierInput.instance.consumed.nullifier),
logicRefFromComplianceUnit: complianceVerifierInput.instance.consumed.logicRef,
actionTreeRoot: actionTreeRoot,
vars: vars
});

// Process the logic proof of the created resource.
vars = _processLogic({
isConsumed: false,
// The `lookup` function reverts if the commitment is not part of the logic verifier inputs.
input: action.logicVerifierInputs.lookup(complianceVerifierInput.instance.created.commitment),
logicRefFromComplianceUnit: complianceVerifierInput.instance.created.logicRef,
actionTreeRoot: actionTreeRoot,
vars: vars
});

Ultimately this calls, in the function _processLogic(...) the following:

// snippet from _processLogic(...) in ProtocolAdapter.sol
// Verify the logic proof.
//slither-disable-next-line calls-loop
_TRUSTED_RISC_ZERO_VERIFIER_ROUTER.verify({
seal: input.proof,
imageId: logicRef,
journalDigest: sha256(instance.toJournal())
});

The nullifier and its commitment should not be linkable given a large enough privacy pool, however, the usage of this imageID enables them to be linkable under certain conditions. Since it is possible for an observer to monitor the transactions on Ethereum, they can create a mapping of each imageID to its commitment/nullifier. This ultimately shrinks the privacy to pool from one with all commitments, to a subset with only the specific imageID.

This means the privacy pool can be shrunk by the other public information available and the "rules" that Anoma transactions and actions work with. More specifically:

  • The Merkle root that needed to be provided for the compliance proof reveals that some nullifier corresponds to a commitment that is in this tree, not to any later, and since the tree and the commitments/leaves are public, it is possible to read them all;
  • The commitment and nullifier of the same resource can’t be in the same transaction, so those pairs are excluded as potential pairs given a non-ephemeral resource;
  • The initial creation of resources for a particular logic does not increase the commitment privacy pool, because they are ephemeral and made for initial creation to balance the transaction;
  • Knowing the circuit behind the imageID can also reveal additional information, for example, if only ephemeral resources with zero quantity perform a certain type of on-chain call or store a particular field from application data, the observer would know that those won't have nullifiers;

The simplest example is given, an existing commitment tree with a large number of commitments and a long list of nullifiers in the nullifier accumulator. If a user creates a new resource with a unique image ID. This means that an observer watching this will see a unique image ID be submitted on-chain to verify a commitment's logic zk proof. If no other resource of the same imageID is created, then an observer will know for certain which commitment corresponds to which nullifier, as the imageID will need to be used again to nullify the resource and verify its zk proof. Thus, the resources commitment and nullifier are linked.

In summary, the usage of imageID to verify the resource logic zk proof can thus be used to link together commitments and their corresponding nullifiers.

Recommendation

Add explicit explanation for developers that, on first usage of a specific imageID, it can cause the aforementioned problem. The solution currently, without function privacy, is to, on first usage, create many of the resources to initialise the privacy pool.

Resolution

Planning on updating specification.

6.5 Differences between the specification and implementation

FileFix commitTypeStatus
*—InfoAcknowledged

The specification that describes the Anoma RM components differs from the implementation. Below we list the identified differences:

Delta Proving System:

  • Described proof aggregation is not implemented
  • The description of proving does not match the implementation, since the proving function takes a message and a witness

Commitment accumulator:

  • The described types are not implemented: Accumulator, AccumulatorWitness, CommitmentIdentifier, and AccumulatedValue
  • Adding the resource commitment to the tree does not return the path
  • The "witness" functionality, which finds the resource commitment in the tree and returns the path to it, is not implemented
  • Names of the functions differ: add() is _addCommitment(), verify() is verifyMerkleProof() and value() is latestCommitmentTreeRoot()

Nullifier set:

  • Function names are different from the description. Adding nullifier is done with _addNullifier() not insert() and checking nullifier existence is done with isNullifierContained() not contains()

Compliance Unit:

  • The definition of the ComplianceUnit in the specification contains VerifyingKey, which is not present in the implementation
  • The definition of Instance is different in the specification, since it does not include logic references
  • create() function implementation takes only the witness, while the specification states that parameters are ProvingKey, VerifyingKey, Instance, Witness
  • verify() does in specification returns a boolean, while the implemented function throws an error on incorrect proof
  • The created() and consumed() functions are not implemented
  • The delta() function does not return data of type DeltaHash

Compliance proof:

  • Instance definition in the specification is different from the implementation. There are no lists of consumed and created resources. There are also no logicVKOuter fields. Implemented ComplianceInstance contains a single nullifier and commitment, logic references, consumed resource commitment tree root, and delta
  • specification states that witnesses objects are defined with different fields depending on whether the resource is consumed or not, but in implementation, the witness structure is identical
  • Compliance constraints section describes the CMT path validity check, but the comparison between the expected root and the one generated from the path is not done in the implementation.
  • Specification describes constraints that are not implemented logicVKOuter = logicVKOuterHash(r.logicRef, ...)

Action:

  • The implemented Action structure does not contain actionTreeSize
  • The implemented LogicVerifierInputs structure contains tag, which is not present in the specification
  • The above structures are implemented with different types
  • There is no create() function that generates compliance proofs and resource logic proofs. Proofs are generated first, and an action is created with new()
  • Lack of to_instance() function which is in specification

Transaction:

  • Transaction structure does not contain delta_vk, and the types are different except for the actions field
  • The implemented function create() takes actions and delta as parameters, which is different from the description. Creation of a delta proof is not done in create() but in separate function generate_delta_proof()
  • The compose() function in the specification states that two transactions with generated delta proofs can be aggregated using their delta proofs and verifying keys. However, in the implementation, transactions are composed using delta witnesses before proof generation, using the signing key (rcv) and the set of tags
  • The implementation of the verify() does not contain checks that do not require access to global structures described in point 2 (actions partition the state change induced by the transaction). This is addressed in v0.8.2
  • Delta proof instance structure does not match implemented delta proof structures DeltaProof and DeltInstance

Transaction With Payment and TransactionFunction - it is not implemented;

Action in Protocol Adapter:

  • The specification describes that the actionTreeRoot is computed as the root of a Merkle tree of depth 4, but in the implementation, the action tree does not have a defined depth.

Recommendation

Consider unifying the specification and the implementation.

Resolution

Will update documentation.

6.6 The function verifyMerkleProof() does not distinguish between the leaves and nodes

FileFix commitTypeStatus
contracts/src/state/CommitmentTree.solPR #392InfoFixed

The function verifyMerkleProof() in CommitmentTree verifies that a Merkle path and a commitment leaf reproduce a given root. However, if this function received the node as a commitment parameter and a shorter path, the proof would still be valid. This is possible, since there is no lower limit on the path size and no distinction between generating the leaf and the node during the proof check.

Recommendation

Consider introducing the domain separation in the commitment tree to protect against providing valid proofs for the nodes.

Resolution

The verifyMerkleProof() is not used in production or in any way affects state change. However, due to the fact that this function can be misinterpreted by external parties to possess usual path-verification semantics, we have decided to delete it from production code in #392.

6.7 Should constrain logic instances' length to match the number of keys in the batch circuit

FileFix commitTypeStatus
arm_circuits/batch_aggregation/methods/guest/src/main.rsPR #160Best PracticesFixed

In the batch aggregation circuit, the inputs to the zkVM are read, but there is a lack of checking that the same number of keys and instances exist before beginning to verify the proofs.

Recommendation

It is best practice to add a constraint that logic instances length == logic keys length to ensure this is the case, and so it does not rely on the panic.

Resolution

Added in arm risc0 PR #160.

6.8 Remove debug trait for secret key

FileFix commitTypeStatus
encryption.rsPR #164Best PracticesFixed

The Debug trait is being derived for the SecretKey, which makes it possible to log and therefore retain in memory the secret key of a user, potentially leaking the value. This issue arises mainly in two scenarios:

  1. It could be accidentally printed during a panic crash log.
  2. Any future print statements could inadvertently leak this value.

Enabling logging of this value increases the risk of exposing sensitive information such as the user's secret key. As this function may be used by the users of Anoma, it is possible that they will not run in release mode when inheriting the struct and could inadvertently leak the secret key.

Recommendation

Remove the Debug trait derivation from the SecretKey implementation.

Resolution

Added in arm risc0 PR #164.

7

Documentation Evaluation

Software documentation refers to the written or visual information that describes the functionality, architecture, design, and implementation of software. It provides a comprehensive overview of the software system and helps users, developers, and stakeholders understand how the software works, how to use it, and how to maintain it. Software documentation can take different forms, such as user manuals, system manuals, technical specifications, requirements documents, design documents, and code comments. Software documentation is critical in software development, enabling effective communication between developers, testers, users, and other stakeholders. It helps to ensure that everyone involved in the development process has a shared understanding of the software system and its functionality. Moreover, software documentation can improve software maintenance by providing a clear and complete understanding of the software system, making it easier for developers to maintain, modify, and update the software over time. Smart contracts can use various types of software documentation. Some of the most common types include:

  • Technical whitepaper: A technical whitepaper is a comprehensive document describing the smart contract’s design and technical details. It includes information about the purpose of the contract, its architecture, its components, and how they interact with each other;
  • User manual: A user manual is a document that provides information about how to use the smart contract. It includes step-by-step instructions on how to perform various tasks and explains the different features and functionalities of the contract;
  • Code documentation: Code documentation is a document that provides details about the code of the smart contract. It includes information about the functions, variables, and classes used in the code, as well as explanations of how they work;
  • API documentation: API documentation is a document that provides information about the API (Application Programming Interface) of the smart contract. It includes details about the methods, parameters, and responses that can be used to interact with the contract;
  • Testing documentation: Testing documentation is a document that provides information about how the smart contract was tested. It includes details about the test cases that were used, the results of the tests, and any issues that were identified during testing;
  • Audit documentation: Audit documentation includes reports, notes, and other materials related to the security audit of the smart contract. This type of documentation is critical in ensuring that the smart contract is secure and free from vulnerabilities.

These types of documentation are essential for smart contract development and maintenance. They help ensure that the contract is properly designed, implemented, and tested, and they provide a reference for developers who need to modify or maintain the contract in the future.

8

Remarks & Test Suite

The Anoma Resource Machine protocol provided detailed specification: Resource Machine and Protocol Adapter. In addition, the team provided audit-specific documentation, detailing the core components and outlining the flow step by step. Furthermore, the Anoma team thoroughly addressed all questions and concerns raised by the Nethermind Security team, providing valuable insights and a comprehensive understanding of the project's technical aspects.

Because the tests perform local proving, the Kudo app test fails due to an OOM signal. However, the remaining tests successfully confirm the correct creation of proofs.

Test suite
156Tests passed0Failed

The complete test output is in the full report.

As auditors, we recommend that any changes or updates made to the audited codebase undergo a re-audit or security review to address potential vulnerabilities or risks introduced by the modifications. By conducting a re-audit or security review of the modified codebase, you can significantly enhance the overall security of your system and reduce the likelihood of exploitation. However, we do not possess the authority or right to impose obligations or restrictions on our clients regarding codebase updates, modifications, or subsequent audits. Accordingly, the decision to seek a re-audit or security review lies solely with you.

This report is based on the scope of materials and documentation provided by you to Nethermind in order that Nethermind could conduct the security review outlined in 1. Executive Summary and 2. Audited Files. The results set out in this report may not be complete nor inclusive of all vulnerabilities. Nethermind has provided the review and this report on an as-is, where-is, and as-available basis. You agree that your access and/or use, including but not limited to any associated services, products, protocols, platforms, content, and materials, will be at your sole risk. Blockchain technology remains under development and is subject to unknown risks and flaws. The review does not extend to the compiler layer, or any other areas beyond the programming language, or other programming aspects that could present security risks. This report does not indicate the endorsement of any particular project or team, nor guarantee its security. No third party should rely on this report in any way, including for the purpose of making any decisions to buy or sell a product, service or any other asset. To the fullest extent permitted by law, Nethermind disclaims any liability in connection with this report, its content, and any related services and products and your use thereof, including, without limitation, the implied warranties of merchantability, fitness for a particular purpose, and non-infringement. Nethermind does not warrant, endorse, guarantee, or assume responsibility for any product or service advertised or offered by a third party through the product, any open source or third-party software, code, libraries, materials, or information linked to, called by, referenced by or accessible through the report, its content, and the related services and products, any hyperlinked websites, any websites or mobile applications appearing on any advertising, and Nethermind will not be a party to or in any way be responsible for monitoring any transaction between you and any third-party providers of products or services. As with the purchase or use of a product or service through any medium or in any environment, you should use your best judgment and exercise caution where appropriate. FOR AVOIDANCE OF DOUBT, THE REPORT, ITS CONTENT, ACCESS, AND/OR USAGE THEREOF, INCLUDING ANY ASSOCIATED SERVICES OR MATERIALS, SHALL NOT BE CONSIDERED OR RELIED UPON AS ANY FORM OF FINANCIAL, INVESTMENT, TAX, LEGAL, REGULATORY, OR OTHER ADVICE.