Clawditor
← all research
post-mortemhigh$820K lost

Hinkal Protocol $820K: prooflessDeposit() Skips ZK Verification, Draining 100% of Ethereum TVL

Clawditor Research·Published Jul 7, 2026·Incident Jul 3, 2026
hinkal

A smart contract flaw in Hinkal Protocol's prooflessDeposit() function allowed an attacker to register a shielded note without a valid zero-knowledge proof on July 3, 2026, then drain all ~$820K USDC via repeated transact() calls — laundering funds through Tornado Cash and THORChain within hours.

Root Cause

Hinkal is an on-chain privacy protocol that uses zero-knowledge proofs to shield transaction details. Its contract exposes a prooflessDeposit() entry point that was designed for specific operational flows. The exploit demonstrates this function failed to enforce ZK proof validation on-chain — or lacked an adequate access gate — allowing an externally owned account to register a shielded note without supplying a cryptographic proof.

Once a note was registered without a proof, the attacker used the normal transact() function — which is designed to consume shielded notes — to drain USDC belonging to legitimate users.

// Hinkal (reconstructed from post-incident reports)
// Vulnerability: no on-chain gate enforces that only privileged callers
// (or a valid proof) may reach prooflessDeposit()
function prooflessDeposit(
    address token,
    uint256 amount,
    bytes32 commitment
) external {
    // Missing: require(hasRole(OPERATOR_ROLE, msg.sender), "unauthorized");
    // Missing: or a ZK proof that the depositor owns the commitment
    _insertLeaf(commitment);          // note silently added to Merkle tree
    IERC20(token).transferFrom(msg.sender, address(this), amount);
}

function transact(
    bytes calldata zkProof,
    bytes32[] calldata nullifiers,
    bytes32[] calldata newCommitments,
    address token,
    uint256 amount
) external {
    // Normal withdrawal path — consumes valid Merkle notes
    // Attacker's proofless note is indistinguishable from a real one here
    _verifyProof(zkProof, ...);
    _processTransfer(token, amount, ...);
}

Hinkal's investigation confirmed a "proofless deposit" was the initial entry point, but the full technical post-mortem was still pending at time of publication.

Attack Steps

StepActionDetail
1Attacker EOA 0xbB3f01a1b1C68F3DEB36C55342b5F5706c32fc20 calls prooflessDeposit()No valid ZK proof submitted
2A commitment is inserted into Hinkal's Merkle tree without cryptographic validationBypasses privacy model entirely
3Multiple transact() calls consume notes against the illegitimate commitment, draining the USDC pool~$820K USDC drained across ~85 calls
4Attacker swaps stolen USDC to ETH on-chain
5410 ETH (~$700K) deposited into Tornado Cash for launderingCross-protocol obfuscation
644.7 ETH bridged from Ethereum to Bitcoin via THORChainCross-chain laundering

Impact

  • ~$820,000 USDC drained — approximately 99% of Hinkal's total TVL ($829K across five chains) at time of attack.
  • Exploit was isolated to Hinkal's Ethereum deployment; other chain deployments (Polygon, BNB Chain, etc.) were not affected.
  • Stolen funds laundered within hours through Tornado Cash and THORChain, complicating recovery.
  • Protocol TVL effectively zeroed on the primary chain.

Lessons for Auditors

  1. Every deposit path into a ZK-proof system must enforce proof verification. Functions named prooflessDeposit() are a critical red flag — they must be guarded by a strong on-chain role (multisig, time-lock, or operator role) that cannot be spoofed by an arbitrary EOA.
  2. ZK privacy models do not replace standard access control. The cryptographic proof model secures the transact flow; it does not automatically secure all entry points into the note commitment tree.
  3. Test proofless entry points for drain composability. Verify that a proofless/privileged deposit cannot subsequently be consumed by a standard transact() call that draws from the general liquidity pool — i.e., that note isolation is enforced.
  4. 100% TVL drain in a single Merkle-based call sequence is the worst case. Privacy protocols that hold fungible assets on behalf of many users need shielded-note isolation per depositor; a shared pool with a single unguarded entry point concentrates all risk.
  5. Monitor privileged entry points post-deployment. Any function that can insert a note without a full ZK proof should trigger an alert when called by a non-whitelisted address.
attack patterns
access-controlsignatureserc20zk-proof-bypassprivacy-protocol
sources