Introduction

On October 13th, 2025, FatSolutions engaged zkSecurity to perform a security audit of its Tongo protocol and the SHE homomorphic encryption library. The audit lasted two weeks and was conducted by two consultants.

During the engagement, the team was provided access to the codebase via two private repositories. Additionally, a private document outlining the Tongo protocol was shared.

The codebase was clean and thoroughly tested. Several observations and findings were identified and communicated to the FatSolutions team. These findings are detailed in the subsequent sections of this report.

Scope

The audit covered the following components:

  • SHE Homomorphic Encryption Library:

    • Cairo contracts for verifying proofs and performing homomorphic operations.
    • TypeScript client/provers for sigma protocols.
  • Tongo Implementation of Zether:

    • Cairo code for the Tongo protocol.
    • TypeScript components related to cryptographic operations.

Overview

SHE Overview

Starknet Homomorphic Encryption (SHE) is a low-level library that provides cryptographic primitives for ElGamal encryption and zero-knowledge proofs over the Starknet elliptic curve.

ElGamal Encryption

In Tongo, user balances are protected using additively homomorphic ElGamal encryption over the Starknet elliptic curve. A balance amount b is encrypted under a public key Y=G[x] as:

Enc[Y](b,r)=(L,R)=(G[b]+Y[r],G[r])

Where:

  • G is the Starknet curve generator
  • Y=G[x] is the public key associated with private key x
  • b is the balance amount
  • r is a random blinding factor

To decrypt the ciphertext (L,R) with a private key x:

  1. Compute G[b]=LR[x]=LG[rx]=(G[b]+Y[r])G[rx]
  2. Find b from G[b], which can be computed efficiently since b is bounded (e.g., [0,232))

Given two ciphertexts (L1,R1,L2,R2) encrypting b1 and b2, we can add these encrypted balances without decryption:

(L1,R1)+(L2,R2)=(L1+L2,R1+R2)=Enc[Y](b1+b2,r1+r2)

Similarly, subtraction yields:

(L1,R1)(L2,R2)=Enc[Y](b1b2,r1r2)

This property allows confidential balance reconciliation during transactions while preserving privacy.

Proof of Exponent (POE)

A Proof of Exponent (POE) is a fundamental sigma protocol that allows a prover to demonstrate knowledge of a discrete logarithm, specifically a secret exponent x such that Y=G[x]. This is a core building block in many cryptographic systems, including in SHE protocol.

The protocol is shown in the following interactions:

  • Prover: Chooses a random k and computes A=G[k]. Then sends A to the verifier.

  • Verifier: Receives A. Chooses a random challenge c and sends it to the prover.

  • Prover: Receives challenge c and computes s=k+cx. Then sends s to the verifier.

  • Verifier: Receives s. Accept the proof if G[s]=A+Y[c]; otherwise reject the proof.

This verification holds since:

G[s]=G[k+cx]=G[k]+G[cx]=A+G[cx]=A+Y[c]

Additionally, POE can be extended to POE2 that proves Y=G1[x1]+G2[x2] and POEN that proves Y=i=1nGi[xi]

Bit Proof

A bit proof allows a prover to demonstrate that a committed value b is either 0 or 1 without revealing it. It is achieved by using sigma OR Proof, where two subproofs are combined, one real and one simulated.

The committed value b is represented as Pedersen-style commitments of the form:

V=G[b]+H[r]

where:

  • b is a bit representing the committed value (b{0,1})
  • r is a random blinding factor
  • G and H are independent generators of the elliptic curve group

The protocol is shown in the following interactions:

  • Prover: Constructs and sends two transcripts (A0,A1) where one is a simulated proof and the other is a real proof.

    • If b=1: set A0=H[s0]V[c0] for random c0 and s0, then A1=H[k] for a random k
    • if b=0: set A1=H[s1](VG)[c1] for random c1 and s1, then A0=H[k].
  • Verifier: Receives (A0,A1). Choose a random challenge c and sends it to the verifier.

  • Prover: Constructs (c0,c1,s0,s1) and sends (c0,s0,s1) to the verifier.

    • If b=1: computes c1=c0c and s1=k+c1r.
    • If b=0: computes c0=c1c and s0=k+c0r.
  • Verifier: Receives (c0,s0,s1). Computes c1=c0c and accept the proof if the following equalities hold, otherwise rejects the proof:

H[s0]=A0+V[c0] H[s1]=A1+(VG)[c1]

Range Proof

Range proof allows prover to demonstrate that the committed value x lies within a valid range (e.g., 0x<2n), by using bit decomposition and proves that each bit is indeed a bit value with bit proof. Thus, any value x<2n can be written as:

x=i=0n1bi·2i

where each bi{0,1}.

In summary, a range proof in SHE is essentially a collection of bit proofs, each proving that one binary digit of the secret value is valid. Therefore, soundness of the range proof directly inherits from the security of the underlying bit proofs.

Same Encryption Proof

Same encryption proof shows that two ciphertexts under different public keys encrypt the same plaintext message. It is an essential building block in the SHE protocol within Tongo, ensuring that transferred balances are consistent across sender, receiver, and optional-auditor encryptions.

The statement to prove is as follows:

(L1,R1)=Enc[Y1](b,r1)(L2,R2)=Enc[Y2](b,r2)

where:

  • Y1 and Y2 are two public keys
  • b is the plaintext (e.g., transfer amount)
  • r1, r2 are the random blinding factors

The prover demonstrates knowledge of (b,r1,r2) by composing multiple POE, such that:

{L1=G[b]+Y1[r1]R1=G[r1]L2=G[b]+Y2[r2]R2=G[r2]

Additionally, there is also a variant of the protocol where the prover don’t know one of the blinding factors. The prover compensates for the unknown random by proving knowledge of the secret key x corresponding to one of the public keys. This variant is called Same Encryption Unknown Random in the implementation.

Tongo Overview

Tongo is a StarkNet-based protocol that wraps ERC20 tokens to provide enhanced privacy and security features. It leverages homomorphic encryption to hide transfer amounts and balances, ensuring confidentiality. The protocol supports operations such as funding, transferring, withdrawing tokens, and optional auditing.

Fund

The fund operation allows users to deposit ERC20 tokens into the Tongo protocol. The contract transfers tokens from the caller and adds the encrypted balance to the user’s account. The operation verifies the user’s signature to ensure authorization.

Transfer

The transfer operation enables users to send encrypted tokens to another account in Tongo. The transfer amount is hidden, and the recipient’s pending balance is updated. The operation uses two encrypted balances:

  • transferBalance: encrypts the amount for the receiver.
  • transferBalanceSelf: encrypts the amount for the sender.

The contract performs the following verifications:

  1. Ensures transferBalance and transferBalanceSelf encrypt the same value (SameEncryption proof).
  2. Verifies the amount in transferBalance is within the valid range (Range Proof).
  3. Ensures the sender’s balance remains within the valid range after the transfer (Range Proof).
  4. Deducts transferBalanceSelf from the sender’s balance and adds transferBalance to the receiver’s pending balance.

Withdraw

The withdraw operation allows users to redeem their encrypted balances back into standard ERC20 tokens. The protocol verifies that the deducted balance is within the valid range to prevent underflows (Range Proof). Additionally, the ragequit operation enables users to withdraw their entire balance in a single transaction.

Pending Balance

To prevent potential DoS issues caused by balance changes during proof generation, Tongo separates user balances into two parts: balance and pending balance. Tokens received from other users are stored in the pending balance. Users must execute a rollover operation to merge the pending balance into the main balance. Withdrawals are only allowed from the main balance, ensuring that it remains stable unless explicitly authorized by the user.

The rollover operation consolidates an account’s pending balance into its main balance, finalizing incoming transfers and making them usable.

Auditor

The auditor functionality allows an optional third party to decrypt and verify encrypted balances. If an auditor is configured, the protocol maintains an audit_balance for each account, encrypted with the auditor’s public key. The auditor can use its private key to decrypt and verify balances. Whenever an account’s balance changes, the user must update the audit_balance. The SameEncryptionUnknownRandom Sigma Proof ensures that the audit_balance corresponds to the actual balance.