Octopus

A privacy protocol for Sui blockchain enabling on-chain transaction obfuscation using ZK.

Octopus

Created At

HackMoney 2026

Project Description

OCTOPUS: On-Chain Transaction Obfuscation Protocol Underlying Sui

OCTOPUS is a privacy protocol built on Sui, designed to empower users to conduct DeFi activities without revealing transaction amounts, addresses, or on-chain linkage. (The project is currently deployed on both Mainnet and Testnet: sui-octopus.vercel.app) While standard transactions remain transparent, Octopus obscures details by moving tokens into a privacy pool where balances are encrypted and interactions are anonymized. Inspired by Tornado Cash and Zcash, Octopus is specifically optimized for Sui’s unique object model. (System architecture overview: sui-octopus.vercel.app/overview)

Core Mechanism The system adopts a UTXO (Unspent Transaction Output) model, similar to Bitcoin but cryptographically shielded:

  • Notes: The core unit representing a shielded token amount. Each note is a Poseidon hash commitment: commitment = Poseidon(NSK, token, value)
  • Merkle Tree: Notes are stored in an on-chain Merkle tree with 16 levels (65,536 capacity). Note that, users can view their notes and balances in the UI because the frontend decrypts on-chain data using the user's keys. Only the note owner has access to these details.
  • Nullifiers: Used to prevent double-spending. Spending a note reveals its nullifier: nullifier = Poseidon(nullifying_key, leaf_index). If a nullifier already exists on-chain, the transaction is rejected. Unlike EVM-based protocols that manage nullifiers within a Merkle tree, OCTOPUS utilizes Sui Dynamic Fields for superior scalability.
  • ZK Proofs (Groth16 / BN254): This is the heart of Octopus privacy, proving ownership and spendability of a note without revealing which specific note is being spent.

Key Management Each user possesses four distinct keys:

  1. Spending Key: Functions like a traditional private key and is used to derive the other three. If compromised, all pool funds can be stolen.
  2. Nullifying Key: Used to nullify tokens. If leaked, your funds remain safe, but your transaction history will be exposed, compromising privacy.
  3. Master Public Key (MPK): Serves as your identity within the pool (Public).
  4. Viewing Public Key (VPK): Used to generate shared secrets during transfers. Both MPK and VPK must be shared with the counterparty for internal pool transfers (Public). (Detailed key relationships: sui-octopus.vercel.app/developer)

Supported Operations The system currently supports Shield, Unshield, and Transfer. Except for Shield, all actions require a ZK Proof (ZKP).

  • Shield: Deposits tokens into the pool by sending a Commitment and Encrypted Note. (No ZKP required).
  • Unshield: Withdraws tokens from the pool. The ZKP proves note ownership and the correctness of the withdrawal amount, change, and nullifier.
  • Transfer: Sends tokens internally. The ZKP proves note ownership and ensures that total input equals total output.

Roadmap

  1. Swapping: Currently under development.
  2. Advanced DeFi: Staking and Lending integrations are planned for future updates.
  3. Relayer Integration: To achieve total privacy by preventing transaction sender tracking.
  4. Compliance (PPOI): Implementation of Private Proofs of Innocence using ZK to prove that funds originate from legitimate sources without revealing individual identities.

How it's Made

Octopus is built across four interconnected layers — ZK circuits, Move smart contracts, a TypeScript SDK, and a Next.js frontend.

The most time-consuming part is the ZK Circuits (Circom + snarkjs). Three Circom circuits compiled to Groth16 proofs on the BN254 curve power the privacy guarantees:

  1. unshield.circom (~11K constraints): Proves note ownership + input = withdrawal + change
  2. transfer.circom (~21.6K constraints): 2-input, 2-output UTXO model proving Σinputs = Σoutputs
  3. swap.circom (~22.5K constraints): Extends transfer with DEX slippage protection, binding the on-chain DeepBook call to the proof via swap_data_hash = Poseidon(token_in, token_out, amount_in, min_amount_out, dex_pool_id)

All three share a lib/merkle_proof.circom that verifies 16-level Poseidon Merkle membership proofs. A selector trick (a + b*(c-a)) picks left/right children without branching. The transfer circuit supports both 1-input and 2-input spends by padding with a dummy note (value=0), using IsZero() to conditionally skip the Merkle proof check for the dummy — avoiding the need for two separate circuits entirely.

Proofs are generated fully client-side in the browser via snarkjs + circuit WASM + ~10 MB proving key served from Next.js public/.

Sui has native first-class sui::groth16 and sui::poseidon_bn254 modules built into the framework — no external verifier library needed on-chain. This was a massive win: proof verification is a single groth16::verify_groth16_proof() call, and the Merkle tree uses poseidon::poseidon_bn254() directly in Move.

The hard part was byte encoding alignment across three independent systems: Circom outputs big-endian BN254 field elements, snarkjs returns uncompressed affine coordinates, and Sui's verifier expects Arkworks little-endian compressed points (G1: 32 bytes with sign bit in MSB; G2: 64 bytes with lexicographic sign comparison over Fq2). We had to implement G1/G2 point compression by hand in TypeScript, including the non-obvious -y comparison over the extension field. Additionally, Circom's public signal output order doesn't match what the Move contract expects, requiring a manual reorder before serialization. A single misplaced bit means the proof rejects on-chain despite being cryptographically valid — debugging that silently-failing boundary took significant effort.

The underlying cryptographic stack also took me lots of time, but it is too hard to explain in plain language. Just check https://sui-octopus.vercel.app/developer

background image mobile

Join the mailing list

Get the latest news and updates