Introduction
On June 26th, 2023, zkSecurity was commissioned to perform a security audit of Silent Protocol’s Ethereum smart contracts. For the next two weeks, one consultant reviewed the solidity smart contracts in search of bugs.
A number of observations and findings have been reported to the Silent Protocol team. The findings are detailed in the latter section of this report.
Silent Protocol’s smart contracts were found to be of high quality, accompanied with thorough documentation, specifications, and tests.
Note that security audits are a valuable tool for identifying and mitigating security risks, but they are not a guarantee of perfect security. Security is a continuous process, and organizations should always be working to improve their security posture.
Scope
A consultant from zkSecurity spent two weeks auditing the Solidity smart contracts for the Silent multi-asset shielded pool (SMASP) application. The audit included all of the application’s smart contracts, including the Silent token, the main SMASP logic, the compliance logic, an implementation of the Baby Jubjub curve, and a Groth16 verifier.
More explanation on the protocol and on the organization of the smart contracts can be found later in this report.
The audit was limited to the smart contracts themselves, and did not include the circuits encoding the protocol statements, or core dependencies like snarkjs.
Methodology
zkSecurity followed a number of methodologies and heuristics to audit the smart contracts. In this section we discuss some of them.
Replay attacks. The original Zether: Towards Privacy in a Smart Contract World white paper (Zether) talks about replay attacks and introduce nonces or epochs as a way to avoid these issues. While Silent Protocol uses none of these solutions, they are not vulnerable to replay attacks due to upgrading accounts instantly (on every user transaction). As such, they are not limited to one transaction per epoch (as in the original Anonymous Zether scheme) but could run into lock contention (multiple transactions trying to update the same accounts at the same time). As the application is used through “relayers”, who are agents in charge of protecting the anonymity of the users and might know more about pending updates, the risk of lock contention could be reduced. Even then, on-chain colliding updates merely reveal that some addresses might be used by multiple people instead of one, while costing some gas to the relayers instead of the users.
From the Zether paper:
Front-running. The very first problem with the simplistic version of Zether is that the ZK-proofs are generated w.r.t. a certain state of the contract. For example, the ZK-proof in a transfer transaction needs to show that the remaining balance is positive. A user Alice generates this proof w.r.t. to her current account balance, stored in an encrypted form on the contract. However, if another user Bob transfers some ZTH to Alice, and Bob’s transaction gets processed first, then Alice’s transaction will be rejected because the proof will not be valid anymore. Note that Bob may be a totally benign user yet Alice loses the fees she paid to process her transaction. We refer to this situation as the front-running problem. Burn transactions have a similar problem too: a proof that a ciphertext encrypts a certain value becomes invalid if the ciphertext changes.
Rogue-key attacks. The Many-out-of-Many Proofs and Applications to Anonymous Zether white paper refers to a rogue-key attack allowing an attacker to deanonymize accounts by registering invalid public keys. Silent Protocol is not vulnerable to such attacks as they encode knowledge of the private key associated with the public key in the required registration proofs.
Malleability. Groth16 proofs are malleable, in the sense that one who observes a proof can trivially create a different proof for the same statement (without knowledge of the witness). This is not an issue for the Silent Protocol smart contracts as proofs (or derived identifiers) are never stored on-chain, and can never be replayed anyway. The replay is prevented due to proofs having the instant effect of mutating the state of the smart contract on verification, invalidating their own predicates.
Cofactor issues. The elliptic curve used for user accounts and compliance committee accounts is the Baby Jubjub curve, which is a twisted Edwards curve and for this reason does not have a prime order field (its order has a small cofactor of 8). This has been known to cause issues and is often referred to as “contributory behavior” (https://moderncrypto.org/mail-archive/curves/2017/000896.html, https://vnhacker.blogspot.com/2016/08/the-internet-of-broken-protocols.html, https://research.kudelskisecurity.com/2017/04/25/should-ecdh-keys-be-validated/). Attacks are usually about using a point in the wrong subgroup to force the result of a scalar multiplication to be predictable. We could find no instance where this was an issue.
Layer Zero Integration. The Silent Token is built on top of LayerZero’s Omnichain Fungible Token template, which provides an integration checklist to follow for audits. As the integration is quite straightforward nothing stood out as an issue.
Recommendations
The following recommendations are based on the findings of the audit. We recommend that the Silent Protocol team take the following steps to improve the security and robustness of the protocol:
Fix findings. A number of findings have been reported in the latter section of this report. We recommend fixing all issues deemed important by the Silent Protocol team.
Review observations and discussions section. A number of design decisions were discussed in the observations and discussions section. We recommend documenting the rationale behind important decisions like the Groth16 parameters and how they were chosen/obtained, as well as the size of the anonymity set and how the anonymity set building algorithm was chosen.
Audit protocol circuits. We highly recommend auditing the Circom circuits related to the proofs verified by the SMASP smart contracts.
Observations and Discussions
During our review, we identified a number of concerns that were not necessarily security issues. We have included this section to discuss these as observations, in place of findings in this report.
Parameters for proofs
Silent Protocol currently uses snarkjs with the Groth16 backend to create and verify proofs.
The use of Groth16 as the zero-knowledge proof system implies that a trusted setup must be conducted safely. If not done correctly, this could imply leakage of so-called “toxic waste” which would allow anyone with access to it to forge invalid proofs.
During the engagement, the consultant had access to parameters output by the phase 1 of a trusted setup uploaded on S3. The phase 2 was automated via the following line:
$ echo "randomText" | snarkjs zkey contribute ${outDir}/${circuit}_0000.zkey ${outDir}/${circuit}.zkey --name="1st Contributor Name" -v
After discussions with Silent Protocol, it seems like the project intends on performing a proper ceremony. While the details of the ceremony are not yet known, we recommend reusing (if possible) well-known parameters and well-known methods.
The paper Powers-of-Tau to the People: Decentralizing Setup Ceremonies goes through a number of possible options:
The perpetual “powers-of-tau” ceremony was first run in a continuous mode, where contributions are still being accepted, by the team of the Semaphore project, a privacy preserving technology for anonymous signaling on Ethereum. The setup uses a BN254 elliptic curve and has had 71 participants so far. Other prominent projects later used this setup to run their own ceremonies on top, including Tornado.Cash [Cas20] , Hermez network [Her20], and Loopring [Dev19].
For example, the perpetual powers of tau available at https://github.com/privacy-scaling-explorations/perpetualpowersoftau seems to be one of the most promising options for phase 1 parameters.
For phase 2 of the trusted setup, one could study and reuse tools from other projects. For example, Namada (https://namada.net/trusted-setup), Tornado Cash (https://tornado-cash.medium.com/tornado-cash-trusted-setup-ceremony-b846e1e00be1), Telepathy (https://docs.telepathy.xyz/protocol/circuits#trusted-setup-ceremony), Ethereum’s ceremony (https://ceremony.ethereum.org), Hermez (https://docs.hermez.io/Hermez_1.0/about/security/#multi-party-computation-for-the-trusted-setup), etc.
Anonymity set creation and size
The anonymity set size is a key metric for privacy protocols. The larger the anonymity set, the more difficult it is to link a transaction to a specific user.
Silent Protocol follows the approach of Zether and Monero, where users form their own anonymity set by mixing the paying and receiving accounts with unrelated others. Via the use of zero-knowledge proofs, other accounts are randomized while their balances remain untouched.
Currently, the encryption layer fixes this number at 8. That is, a transfer always modifies 8 accounts, of which 2 are the sender and receiver.
As was documented in An Empirical Analysis of Traceability in the Monero Blockchain, some client-side algorithms to form anonymity sets are trivial to deanonymize, and it can be tricky to tune them to be secure.
A good practice would be to document the rationale behind the choice of the anonymity set size and the algorithm used to form it.
For subscribed users, different functions are used (e.g. _withdrawZeroFee
). Thus, a subscribed user must be careful not to form an anonymity set with accounts that do not have a subscription (otherwise it would be trivial to guess that the only subscribed accounts are the ones actually involved in the transaction).
In addition, it is notable that relayers get more information than on-chain observers. Relayers can see requests coming from certain IPs, for example. Page 44 of the Zether paper touches on that:
The idea of paying fees in a non-native currency is called economic abstraction and has been discussed intensively [But15, Rub18]. The concept is particularly interesting with respect to Zether as it is would make Zether a) more usable and b) more private. The major obstacle to this approach is that miners would need to mine these special 0 gas price transactions and properly rake the fee pool. A similar approach that circumvents the miner adoption problem is to have special delegator nodes that issue the transactions to the network. Users would send their transactions to delegators who will forward them to the miners and will pay the Ethereum gas fees. These delegator nodes could be rewarded in Zether by adding their Zether address to the transaction. The fee amount would not go to the fee pool but to that address. A general problem for any anonymous blockchain transaction is that transactions need to be relayed to either the miners or the delegators without revealing the original sender’s identity. This problem can be alleviated through anonymous communication networks like Tor [Tor].
Thus, as a global recommendation, in addition to documenting the rationale behind the choice of the anonymity set size and the algorithm used to form it, we recommend giving good guidance to the user on how to use the protocol in a way that maximizes their privacy.
Users are prevented from self-exiting the application
While the soundness of the application (the impossibility for someone else to mess with their balance) is guaranteed by the use of zero-knowledge proofs (to the extent that the circuits are free of bugs), the liveness of the application (the ability for a user to withdraw their funds) is not.
The fact that the withdraw()
function of the SMASP.sol
smart contract is onlyApprovedCallers
means that only the relayers can call it. Furthermore, the owner of the smart contract could also remove all approved callers if they wanted to.
Thus, the liveness of the application from the user’s perspective relies on many assumptions. We recommend either removing the onlyApprovedCallers
modifier, or documenting the rationale behind it.
Sanctioned users can still withdraw funds
Two compliance-related smart contracts are in charge of hosting lists of sanctioned Ethereum addresses as well as Silent Core addresses, in order to prevent them from using Silent Core. However, the withdraw()
function (and related withdrawInSilent
and withdrawZeroFee
functions) of the SMASP.sol
smart contract are not checking if the user withdrawing is in the sanction lists.
This means that the SMASP smart contract is currently not in charge of “freezing” sanctioned addresses. It simply prevents them from interacting with the protocol, and forces them to recover their funds and exit the application. This is a design choice that perhaps is not ideal, depending on the intention of the protocol.
Background on Silent Protocol and EZEE
This section provides an overview of the Silent Protocol smart contracts and the EZEE protocol.
Overview of the protocol
Silent Protocol is an anonymous Zether-based protocol deployed on Ethereum and aiming to provide privacy to its users. Users register their own accounts as keypairs on the Baby Jubjub curve (https://eips.ethereum.org/EIPS/eip-2494).
While deposits and withdrawals do not mask one side of the exchange and the amount being transferred, all other types of transfers within the application are encrypted and reveal no more than an anonymity set (of size 8). This part is called the encryption layer of the protocol.
In addition, the protocol uses Groth16 proofs to enforce the correct mutation of accounts in the anonymity set during movements of funds. Importantly, it enforces that the sender’s account has enough balance, that the funds removed from their account are equal to the funds added to the recipient’s account, and that all other accounts’ balances are untouched and correctly randomized.
In order to increase the anonymity of the users, agents called relayers are the only ones allowed to submit transactions to the smart contract. Users thus pay relayers to submit their transactions, in exchange for obtaining anonymity on the Ethereum blockchain.
This collaboration between users and relayers does not stop at transfer of funds within the smart contract itself. Users can withdraw to special addresses and use whitelisted applications through the continued help of relayers as well.
A subscription is available to the users, in order to remove all fees encoded in the smart contract logic. Fees are payable in different cryptocurrencies, including the Silent token.
In order to remain compliant with anti-money laundering laws, the encryption layer of Silent Protocol forces users to encrypt all transactions to a compliance entity.
Later, this entity can look back at the history and arbitrary decrypt transactions. To add security to this process, the compliance entity is decentralized into a compliance committee.
The creation of the committee is done using a distributed key generation, so that at no point in time does the private key exist in one place. Committee members must then collaborate if they want to decrypt a transaction.
In addition, the Silent Protocol keeps track of sanctioned addresses (both Ethereum and Silent addresses) and prevents them from interacting with the protocol.
Smart Contracts Organization
In this section, we give a quick overview of the layout of the SMASP smart contracts.
The main smart contract is SMASP.sol
, built on top of two OpenZeppelin’s templates (Ownable2Step, ReentrancyGuard) and containing the “encryption layer” of Silent Protocol.
Within SMASP.sol
, a number of tables are used to maintain the user states:
The state is also initiated with a number of smart contract addresses that are later used to externalize different aspects of the SMASP logic:
- silentTokenAddress: used as the ERC-20 smart contract for the Silent Token. It is also integrated into the LayerZero framework in order to facilitate cross-chain exchanges.
- sVault: used to store the fees collected on users using the SMASP application.
- verifyingKeys_1 and verifyingKeys_3: used to store the verifier keys used to verify Groth16 proofs.
- compliance: used for the compliance side of the application.
- uniswapRouter: used to calculate how much fees users should pay when they decide to pay fees in Silent Token.
- sanctionsListContract: used to maintain a list of sanctioned Ethereum addresses.
- silentSanctionsList: another list of sanctioned users, containing both a list of Ethereum addresses and of SMASP accounts.
- wethAddress: used with the uniswap router to compute fees. This is useful to create a path between an arbitrary token and the Silent Token, as there’s most likely always be an exchange between ETH and approved ERC-20 tokens.
The compliance smart contract enforces the correct creation of a compliance committee, in which committee members take turns to contribute to a distributed key generation (DKG) process. This is done through the use of different tables as well:
Note that we had no access to the actual circuits during the audit. We assumed that circuits were correctly encoding the statements of the Silent Protocol white paper. Actual users would benefit from having a way to check that (open sourced) circuits correctly compile down to the verifier keys used on chain (in addition to obtaining assurance on the statements encoded in those circuits).
zkSecurity had access to the compiled version of the circuits. The verification of proofs was done by manually combining a Groth16 verifier smart contract (ZKP.sol
, taken from the Hermez project) and smart contracts containing the verifier keys (obtained from the compiled circuits). In addition, verifier.sol
exposed a usable interface for the SMASP.sol
and Compliance.sol
contracts.
A solidity implementation of the Baby Jubjub curve was also present, taken from https://github.com/yondonfu/sol-baby-jubjub/.