EVM Compatibility Layer & Precompiled Contracts
Anubis's core competitive advantage lies in enabling millions of Solidity developers to develop privacy applications without needing to learn Rust, Circom, or complex circuit description languages (DSLs). This is achieved by encapsulating complex cryptographic primitives into pre-compiled Ethereum contracts.
6.1 Compatibility Scope
Anubis EVM is fully compatible with Ethereum EVM.
Fully supported:
All EVM opcodes
All precompile contracts
EIP-1559 Transaction Type
EIP-2930 Access List
Solidity 0.8.x
Vyper
mainstream development tools
Extended support:
Privacy pre-compiled contracts
Privacy transaction types
View key RPC interface
6.2 Pre-compiled contract architecture
To support PLONK proof verification and elliptic curve computation, Anubis has added the following pre-compiled contracts to the EVM. These contracts are implemented in native Go/Rust code, bypassing the EVM bytecode interpreter and leveraging the underlying hardware-accelerated instruction set to achieve extremely high performance.6
Address
Identifier
Function
Gas Estimation
Algorithm Description
0x0100
VERIFY_PROOF
Verify PLONK proof
180,000+ Dynamics
Supports multi-pairing checks on the BN254 curve.
0x0101
PEDERSEN
Pedersen commitments and hashes
5,000
Used for building and validating Merkle tree nodes, based on ECC addition.
0x0102
MIMC_HASH
MiMC hash function
3,000
ZK-friendly hash functions for in-circuit computation
0x0103
STEALTH
Stealth address calculation
8,500
Perform ECDH and public key derivation in accordance with EIP-55647
0x0104
NULLIFIER
Null value status check
10,000
Query the global Bitfield or Bloom Filter status to prevent double-spending.
0x0105
ENCRYPT
ECIES Encryption
2,500
Used for on-chain encryption of Note data, ensuring that only the recipient can decrypt it.
Table 6-1 List of Pre-compiled EVM Cores
Precompiled contract security boundary
In Anubis' architecture, precompiled contracts not only provide computational acceleration (such as VERIFY_PROOF) but also interact directly with on-chain privacy states (such as NULLIFIER_CHECK). This direct state access, if poorly designed, can become a side channel for privacy leaks. Therefore, we have defined strict security boundaries and access control policies.
1. Separation of permissions between stateless and stateful pre-compiled systems
We categorize pre-compiled contracts into two types and implement different security strategies:
Stateless pre-compilation:
Addresses involved 0x0100 (Verify Proof), 0x0101 (Pedersen), 0x0102 (Stealth Address), 0x0105 (View Key Derive)。
Security attributes Pure functions have no side effects.
Access policy:Fully open. Any contract or external account (EOA) can invoke it. This allows developers to leverage these low-cost primitives to build custom privacy applications, such as privacy voting or on-chain mixers.
Stateful pre-compilation:
Addresses involved: 0x0104 (Nullifier Check/Update).
Security attributes This involves reading and writing global state, which poses risks of privacy oracles and denial-of-service.
Access Policy Access is restricted, as described below.
2. Privacy oracle attacks and mitigations against nullifier queries
If the isSpent(bytes32 nullifier) function of contract 0x0104 is left unrestricted, it will constitute a Privacy Oracle.
Attack Vector Even if attackers cannot directly decrypt private transactions, they can still deanonymize them through a "guess-and-verify" process.
Guess An attacker suspects that a transaction was initiated by a specific user and generates a series of nullifiers that the user might have generated locally (e.g., exhaustively listing the user's nonce).
Verify The attacker deploys a malicious contract that repeatedly calls isSpent(guess_nullifier).
Related If the value returns true, the attacker has confirmed the user's spending behavior, thus compromising privacy.
Defense mechanism
Strategy A: Economic Barriers (Gas Price Firewall) We set the base gas of isSpent to 5,000 (Several times higher than the 100/2100 Gas required for standard storage read SLOAD). This makes large-scale brute-force attacks (Dictionary Attacks) economically infeasible. Verifying 1000 possibilities for a user would consume 5 million Gas, which is prohibitively expensive.
Strategy B: Rate Limiting The EVM layer implements dynamic rate limiting for calls to isSpent:
Single transaction limit A single transaction is allowed a maximum of 10 calls to isSpent.
Context awareness If called in a ZK Proof context where verification fails (i.e., an attempt is made to probe but no valid proof is provided), gas consumption will increase exponentially.
Strategy C: Zero-Knowledge Membership Requirement (ZK) This is the most stringent defense. In version V2, isSpent will no longer accept plaintext queries. The caller must submit a lightweight ZK proof that "I have the right to query this nullifier" (e.g., proving that I possess the private key that generated the nullifier). This will completely eliminate third-party probing.
3. Status write permission
The markSpent(bytes32 nullifier) function is used to prevent double-spending. If any user is allowed to call this function:
Denial-of-Service (DoS) Attack Attackers can preemptively mark a victim's nullifier as "spent," causing the victim's funds to be permanently locked.
Therefore, Anubis implemented System-Level Write Protection:
Solidity // Pseudocode: Access control logic implemented at the EVM level function markSpent(bytes32 nullifier) internal { // Check 1: The caller must be a system address. // System Address refers to the virtual privileged account that executes // Type 70-73 transaction logic. if (msg.sender!= SYSTEM_ENTRY_POINT) { revert("Access Denied: Only protocol can mark nullifiers"); } // Check 2: Must be accompanied by valid ZK proof verification if (!GlobalVerifyState.isProofVerified()) { revert("Invariant Violation: Proof not verified"); } _nullifierSet[nullifier] = true; }
User-deployed smart contracts are strictly prohibited from calling markSpent. The state change of the nullifier can only be used as a side effect of the execution of Type 101/102/103 transactions, and should be atomically written by the underlying protocol after verifying the successful ZK proof.
6.3 Developer SDK and Toolchain
To shield the underlying complexities, Anubis provides anubis.js and hardhat-anubis-plugin. These tools allow developers to build privacy applications without understanding elliptic curve mathematics.
Code example: Validating private deposits in Solidity
Solidity
// Introduce Anubis pre-compiled interface import "@anubis/contracts/Precompiles.sol"; contract PrivacyVault { // Deposit function: Verify ZK proofs and record commitments function deposit(bytes calldata proof, bytes32 commitment) public payable { // Construct common input: bytes32 memory publicInputs = new bytes32(3); publicInputs = AnubisState.currentRoot(); publicInputs = AnubisState.calculateNullifier(msg.sender); publicInputs[2] = bytes32(msg.value); // Call the pre-compiled contract 0x0100 to verify the proof bool isValid = AnubisPrecompiles.verifyProof(proof, publicInputs); require(isValid, "Invalid ZK Proof: Deposit verification failed"); // If the validation passes, the logic continues... emit DepositEvent(commitment); } }
This code demonstrates Anubis' design philosophy: Privacy as a Service. Developers can simply call the privacy features like regular library functions; the underlying circuit generation, proof verification, and cryptographic operations are handled automatically by the system.

Last updated
