Hinkal Protocol

QuantstampFiles named in the report

Quantstamp reviewed Hinkal (Hinkal Protocol) on 29 Sep 2023: 37 findings, 0 critical; 32 marked fixed, 4 acknowledged, 1 open.

No open critical findings
Open the reportOpen Hinkal’s security record

Report hosted on certificate.quantstamp.com.

Trustblock does not review code.

Findings37

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical000
High004
Medium115
Low0323
MediumGas Griefing Attacks on the RelayersAcknowledged

Relayer fees are calculated by a percentage of the exchanged token amount without considering the actual gas usage of the transaction. When making a `transact()` call on Hinkal, users can specify hook contracts, which `Hinkal` will call specific functions on them before or after the token exchange. A user may specify their hook contracts and spend a large amount of gas during the hook function calls, theoretically up to the block gas limit. As a result, the relayer may spend a large amount of gas executing the user's transaction but does not receive enough fees in return. The support of user-specified hooks means external calls can be performed to arbitrary addresses for each transaction. This can result in unexpected behavior, like the aforementioned gas griefing. We advise the Hinkal team to limit such capabilities to reduce the attack surface of the protocol.

LowEmpty Leaf Nodes Map to Non-Zero CommitmentsAcknowledged

As the merkle tree is constructed, there are times when a node is hashed with another node representing an empty portion of the tree. `MerkleRemoveable` does this when it indexes into the `tree[]` for a given index for the first time: ``` // insert() for (uint256 i = 1; i <= twoPower; i++) { currentNodeIndex /= 2; tree[currentNodeIndex] = hash( tree[currentNodeIndex * 2], tree[currentNodeIndex * 2 + 1] ); } ``` `Merkle` does this for all left nodes: ``` // insertOne() for (uint256 i = 0; i <= twoPower; i++) { if (currentNodeIndex % 2 == 0) { tree[i] = prevHash; if (i != twoPower) prevHash = hash(prevHash, 0); } else { prevHash = hash(tree[i], prevHash); } currentNodeIndex /= 2; } ``` However, the empty nodes are incorrectly represented as `0`. Leaves should be represented to be `hash(0)`, the layer above to be `hash(hash(0), hash(0))`, and so on to conform to the standard definition of a Merkle tree. Hashing with `0` rather than the correct degree of hashed zeroes will mean that the tree contains valid preimages that do not map to any real transaction. If such preimages are found, they may be exploited.

LowPotential Malleability Attacks on Nullifier Due to `Groth16`Acknowledged

There's a known `Groth16` malleability vulnerability that can lead to generating two valid proofs for the same set of inputs for a replay attack. The Hinkal protocol keeps a mapping of nullifiers to prevent replay attacks by re-using them. However, due to Malleability, generating another valid proof for the same inputs for the commitment is possible. This allows every commitment to be used twice. Here is a comprehensive explanation of [Tornado Cash vulnerability](https://medium.com/@Beosin_com/exploring-tornado-cash-in-depth-to-reveal-malleability-attacks-in-zkp-projects-f7321922f82d).

LowUnconstrained `calldataHash` Could Lead to Attackers Re-Routing FundsAcknowledged

The `calldataHash` field is unconstrained in Circom and not used at all. However, before we call the verifier, the Hinkal checks in `HinkalHelper.sol` make sure that (a part) of the Circom data matches the provided hash. The `calldataHash` represents information that is not a part of the proof calculation. So, in essence, via the hash, the user/relayer commits to the information that is not checked by Circom, so that the proof cannot be used with other external actions and hooks. Now we have the following scenarios of what data the user can submit to the transaction: 1. **A hash that has the integrity with the data, and was used to generate the proof.** This is a legit scenario that passes both the helper check, and the proof verification. It works as expected. 2. **A hash that does not have the integrity with the data, but was used to generate the proof.** This is going to be rejected by the helper. It works as expected. 3. **A hash that does not have the integrity with the data, and was NOT used to generate the proof, though the proof is another passing proof (it was generated using a different hash).** Such a transaction would be rejected by the helper check, as expected. 4. **A hash that has the integrity with the data, and was NOT used to generate the proof, though the proof is another passing proof (it was generated using a different hash).** This check is going to pass the verifier, because the proof is valid for some other hash, and it is also going to pass the calldata check in `HinkalHelper.sol` as there is integrity with the calldata that was also spoofed and is not checked in the proof. <br> **This opens an opportunity for a malicious relayer or a front-runner to add external action calls and hooks that the user did not intend, as well as redirect funds to a different `externalAddress`. The impact of these calls will depend on the logic encoded in the respective contracts, hence we classify the severity of this vulnerability as undetermined.** During our testing, we found that unused public parameters in circom still led to unique proof generation. However, [in some compiler versions](https://github.com/iden3/circom/releases/tag/v2.0.4), unconstrained/unused public parameters are optimized out. If this is ever the case, now or in the future, the soundness of the zero-knowledge mechanism will break, and the attack vector will be reintroduced. This can lead to a high-severity impact on the protocol.

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Repository and files the report names (126)

  • SwapperM.circom
  • contracts/types/IHinkalHelper.sol
  • swapperMO2x2x1.circom
  • contracts/types/Dimensions.sol
  • contracts/types/IVerifier1x2x1.sol
  • contracts/types/IVerifier2x2x1.sol
  • swapperM1x2x1.circom
  • contracts/types/IVerifierToken1x1x1.sol
  • contracts/types/IHinkalHelper.sol
  • contracts/types/CircomData.sol
  • SwapperMO.circom
  • contracts/types/IMerkle.sol
  • contracts/verifiers/Verifier4x2x1.sol
  • contracts/HinkalBase.sol
  • swapperM4x2x1.circom
  • contracts/types/IHinkal.sol
  • contracts/types/IVerifierFacade.sol
  • contracts/types/IPoseidon2.sol
  • contracts/Transferer.sol
  • contracts/Transferer.sol
  • contracts/MerkleRemovable.sol
  • contracts/types/IVerifierO2x2x1.sol
  • contracts/types/IHinkalBase.sol
  • contracts/verifiers/Verifier1x2x1.sol
  • contracts/MerkleBase.sol
  • swapperMO5x2x1.circom
  • swapperM3x2x1.circom
  • Signature.circom
  • contracts/HinkalHelper.sol
  • contracts/verifiers/VerifierO2x2x1.sol
  • contracts/types/IRelayStore.sol
  • contracts/types/IVerifier4x2x1.sol
  • contracts/types/IVerifier3x2x1.sol
  • contracts/types/ICircomDataBuilder.sol
  • contracts/types/IPoseidon2.sol
  • contracts/types/IVerifierO2x2x1.sol
  • SwapperToken.circom
  • swapperM4x2x1.circom
  • contracts/HinkalBase.sol
  • contracts/HinkalHelper.sol
  • contracts/Merkle.sol
  • contracts/verifiers/VerifierO3x2x1.sol
  • swapperMO3x2x1.circom
  • contracts/types/IVerifierO3x2x1.sol
  • contracts/RelayStore.sol
  • contracts/Signer.sol
  • contracts/Signer.sol
  • contracts/types/IERC20TokenRegistry.sol
  • SwapperToken.circom
  • contracts/types/IVerifier5x2x1.sol
  • contracts/verifiers/Verifier5x2x1.sol
  • contracts/verifiers/Verifier4x2x1.sol
  • swapperM2x2x1.circom
  • contracts/types/IHinkal.sol
  • swapperMO3x2x1.circom
  • contracts/types/ITransactHook.sol
  • contracts/verifiers/VerifierToken1x1x1.sol
  • contracts/RelayStore.sol
  • swapperToken1x1x1.circom
  • contracts/verifiers/Verifier2x2x1.sol
  • contracts/types/IVerifierToken1x1x1.sol
  • swapperMO2x2x1.circom
  • contracts/types/IVerifierO5x2x1.sol
  • contracts/verifiers/VerifierO5x2x1.sol
  • SwapperMO.circom
  • contracts/MerkleBase.sol
  • contracts/verifiers/VerifierToken1x1x1.sol
  • contracts/types/IVerifier.sol
  • contracts/verifiers/VerifierO4x2x1.sol
  • contracts/types/IMerkle.sol
  • Signature.circom
  • contracts/CircomDataBuilder.sol
  • MerkleRootCalculator.circom
  • contracts/types/IVerifierO5x2x1.sol
  • swapperMO5x2x1.circom
  • swapperM5x2x1.circom
  • swapperMO4x2x1.circom
  • contracts/ERC20TokenRegistry.sol
  • contracts/AccessToken.sol
  • contracts/verifiers/Verifier5x2x1.sol
  • AccessTokenChecker.circom
  • contracts/verifiers/VerifierO3x2x1.sol
  • contracts/types/IVerifier3x2x1.sol
  • contracts/verifiers/VerifierO5x2x1.sol
  • contracts/ERC20TokenRegistry.sol
  • MerkleRootCalculator.circom
  • contracts/MerkleRemovable.sol
  • contracts/AccessToken.sol
  • contracts/types/IVerifierO4x2x1.sol
  • contracts/types/Dimensions.sol
  • contracts/types/IVerifier1x2x1.sol
  • SwapperM.circom
  • contracts/verifiers/Verifier3x2x1.sol
  • swapperToken1x1x1.circom
  • contracts/Hinkal.sol
  • contracts/types/CircomData.sol
  • contracts/types/IAccessToken.sol
  • contracts/VerifierFacade.sol
  • contracts/types/IVerifierFacade.sol
  • swapperMO4x2x1.circom
  • swapperM5x2x1.circom
  • contracts/Hinkal.sol
  • contracts/types/IHinkalBase.sol
  • contracts/types/IVerifier5x2x1.sol
  • contracts/verifiers/Verifier1x2x1.sol
  • contracts/verifiers/VerifierO4x2x1.sol
  • contracts/types/IERC20TokenRegistry.sol
  • contracts/types/IVerifierO4x2x1.sol
  • contracts/Merkle.sol
  • contracts/verifiers/Verifier3x2x1.sol
  • contracts/types/IVerifier.sol
  • contracts/types/IVerifier2x2x1.sol
  • contracts/types/ITransactHook.sol
  • contracts/types/IRelayStore.sol
  • contracts/verifiers/VerifierO2x2x1.sol
  • contracts/types/IAccessToken.sol
  • swapperM2x2x1.circom
  • swapperM1x2x1.circom
  • contracts/types/ICircomDataBuilder.sol
  • contracts/VerifierFacade.sol
  • swapperM3x2x1.circom
  • contracts/types/IVerifier4x2x1.sol
  • contracts/verifiers/Verifier2x2x1.sol
  • AccessTokenChecker.circom
  • contracts/types/IVerifierO3x2x1.sol
  • contracts/CircomDataBuilder.sol

Part of Hinkal’s security record

Hinkal
Audits2 audits by 2 firmsLast audit Feb 2024, by Zokyo.
Critical findingsNo open critical findingsAcross 2 reports.
Open Hinkal’s security record

Security firm

Quantstamp

236 projects reviewed · 332 reports · latest Oct 2026

See Quantstamp’s audits

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us