TLDR: we cannot mint an SBT in a decentralized way because we can only prove "per withdrawal nullifier", not per real address (the proof constrains the nullifier, not the withdrawal address). Instead, we can record a successful proof "per nullifier".
The desired goal of the soulbound token (SBT) in sandscreener was to serve as a certificate of innocence that is minted once and forever for a given Tornado Cash (TC) pool user and blocklist. Such a token should then be accepted by any protocol that is interested in user innocence only in regards to the specified blocklist. Let's see why the described approach is difficult to implement in a ***decentralized*** system (decentralized system here implies the ***permissionless minting*** of an innocence SBT, e.g., granted by an on-chain proof of innocence).
Firstly, let's take a look at which data in the proof of innocence is verifiable onchain:
| Data | Verifiable On-Chain | Explanation | Example Exploit | Possible solution |
| ---------------------- | ------------------- |:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| User Identity | 🚫 | The ZK proof only constrains the nullifier hash. While the connection between the withdrawal address and the nullifier hash is clearly visible off-chain by looking at withdrawals, it is impossible to verify on-chain. | Someone with a withdrawal note would be able to mint an SBT for any address they control, not just the real withdrawal address. | A trusted off-chain party can scan the withdrawal events and ensure that the SBT minting address is the withdrawal address for this nullifier hash. This breaks the trustlessness principle. |
| Inclusion in a TC pool | ✅ | The deposit commitments are stored on-chain in TC. This allows to verify that the proof was constructed for a real TC deposit tree. | - | - |
| Blocklist Validity | 🚫 | Blocklist is maintained off-chain, making it impossible to verify whether the allegedly blocklisted commitments used for the proof of exclusion indeed correspond to the blocklisted addresses. | Attacker can create a ZK-proof of exclusion for an invalid set of "blocklisted commitments". While the off-chain check is trivial, the on-chain SBT minting function cannot perform it. | It is possible to put the blocklisted commitments on-chain to make them accessible to the SBT minting function, but it would require a trusted party to write that data in the Sandscreener registry. If a malicious actor from the blocklist makes another deposit, the trusted party would have to add new commitments to the registry in a timely manner to keep the data up to date. |
A possible solution is to maintain a record of valid proofs against a nullifier hash, blocklist hash and an exclusion tree root for a given TC pool. This would allow to store the proof once (similarly to minting an SBT once) and independently verify its validity later (namely, verify the validity of the exclusion tree root for the given blocklist at the moment of proving).