Skip to content

Shielded transfers ​

Sanect's privacy module lets you shield SNCT into a private pool, send privately within the pool, and unshield back to a public address. Your address never links to what you send -- you choose per transaction whether to transact publicly or privately.

The system uses a custom 2-in/2-out Noir circuit on Aztec's universal Plonk SRS (structured reference string). No per-circuit trusted setup ceremony is required -- the universal SRS is already public and audited.


How it works ​

Shielded transfers have three steps:

1. Shield (deposit) ​

Deposit public SNCT into the shielded pool. Your SNCT leaves your public balance and a private commitment is added to the pool's Merkle tree. Only you (with your viewing key) know the commitment belongs to you.

2. Send (private transfer) ​

Transfer SNCT within the pool from one set of commitments to another. The sender, receiver, and amount are all hidden. The zk-SNARK proof ensures the transfer is valid (no double-spending, correct balances) without revealing any details.

3. Unshield (withdraw) ​

Withdraw SNCT from the pool back to any public EVM address. The pool burns your private commitment and sends SNCT to the recipient via an internal call{value}.


Key derivation ​

Your shielded wallet keys are derived from a MetaMask signature:

  1. MetaMask prompts you to personal_sign a frozen derivation message.
  2. The signature is fed through HKDF-SHA256 (RFC 5869) with domain-separated labels to produce:
    • A viewing key -- lets you scan the chain and identify your notes.
    • A spending key -- lets you create valid proofs to spend your notes.
  3. An X25519 public key is derived from the viewing key for encrypted memo exchange.

Your keys never leave your browser. The derivation message is frozen -- the same signature always produces the same keys.


Encrypted memos ​

When you shield or send, an encrypted memo is attached to the transaction so the recipient can identify their note:

  • Encryption: X25519 ECDH + ChaCha20-Poly1305.
  • Fresh ephemeral keypair per memo -- no key reuse across transactions.
  • Memo size: 240 bytes (144 bytes ciphertext + 96 bytes reserved for future fuzzy message detection).
  • Trial decryption: Your wallet tries to decrypt every memo on chain. Most will fail (they belong to other users) -- failures are silently discarded.

Browser proving ​

Proof generation runs entirely in your browser. No server-side prover, no trusted third party.

  • Stack: @noir-lang/noir_js + UltraHonkBackend (Aztec's Barretenberg wasm build).
  • Performance: ~2--3 seconds on a modern laptop.
  • Circuit artifacts are loaded from the dApp's static assets on first use.

The "Shielded" button in the dApp is gated by browserProvingReady() -- it only appears once the wasm prover and circuit artifacts have loaded.


Mainnet contracts ​

ContractAddress
Poseidon2Bn2540xaEE4A735027b86ccbF43042f4C31d1a856943c1C
HonkVerifier0x580d7c1Ff04a5761D58a8Db08C37fb33c5e395f2
ShieldedPool v1.40x5532Dc2B743D45Fd0121E382696521ED62DD3ecf

Testnet contracts ​

ContractAddress
Poseidon2Bn2540xE4AdD1a1aA6E9df2037b4285D0E8b052fdB6bB04
HonkVerifier0x8fDd518356e570D72f426Bc4d1485d46c60b74Cd
ShieldedPool v1.40x0571969a9C0554D908387255a30DE5c392AA4b8e

Merkle tree ​

The shielded pool uses a depth-32 incremental Poseidon-2 Merkle tree. Each leaf is a commitment to a note (containing the value, asset ID, owner, and blinding factor).

Initial root (depth-32 Poseidon-2 zeros):

0x0b59baa35b9dc267744f0ccb4e3b0255c1fc512460d91130c6bc19fb2668568d

The pool maintains a ring buffer of 32 recent roots, so a proof generated against a slightly stale root is still accepted.


Anonymity set ​

All users share a single shielded pool -- one global anonymity set. Unlike per-contract or per-user models, every shield and send contributes to the same pool, making it harder to correlate senders and receivers as adoption grows.

Current anonymity-set statistics are visible on the explorer at scan.sanect.com/shielded.


UI ​

The shielded transfer interface is available in the Staking dApp:

  1. Go to app.sanect.com.
  2. Click the Shielded button.
  3. Click Derive to generate your viewing and spending keys (MetaMask will prompt for a signature).
  4. Use the Shield, Send, or Unshield tabs.

The panel shows your shielded balance (number of unspent notes) and the pool's TVL and anonymity-set size.


What's next ​

  • Off-chain note indexer -- wallets currently trial-decrypt every memo from the pool's deploy block. An off-chain indexer will pre-filter, cutting sync time from minutes to seconds at scale.
  • Fuzzy message detection (FMD) -- Penumbra-style FMD will let wallets skip most irrelevant memos entirely, further reducing sync overhead. The 96-byte FMD reservation in the memo format is already allocated for this.
  • External audit -- the privacy module is undergoing external audit before handling significant value.
  • ERC-20 shielding -- the pool contract's storage layout already supports multi-asset via asset_id. ERC-20 shielding is additive (no redeploy needed).

Next step: Full privacy model technical details