Applied ZK part 3 or how we made anonymous casts on Farcaster possible
This article walks you through the main technical details of how Sealcaster works — ZK, web3, OpenGSN, everything!
So you decided to dig down the rabbit hole of how Sealcaster works under the hood. This article will walk you through the main technical details — including our zero-knowledge circuits, frontend integration, OpenGSN, and smart contracts! LFG!
But first, what's covered here?
Here's the process that users follow when using Sealcaster:
- Land on Sealcaster main page
- Type in the cast and click the "Cast" button
- See how the anonymous message gets sent to Farcaster!
That's it! Users just harnessed the power of zero-knowledge proofs and cast a message anonymously! But where is the ZK? Where is Blockchain? Who's actually posting? How come it is anonymous? So many questions!
🥾 Step-by-step 🥾
It doesn't look like much, but in 27 seconds above (a bit longer in real-time, as I have cut out the waiting times for the blockchain), a whole tiny universe of cryptography got created, big-banged, and the user has harnessed the energy of it!
The obvious first step is to type in the cast you want to post and hit the "Cast anonymously" button. Note that the cast should be less than 280 characters — I have no idea why I can't cast 280 characters, but it is what it is.
Then the 🪄 Magic 🪄 begins. We ask you to connect an Ethereum address you have previously connected to Farcaster. This address will only be used for verification and will leave no trace of being used. Everything will be done from a burner wallet that we will create next and has no connection to the original address.
See the whole process in this file. We create a burner Ethereum wallet that has never been used before and securely store its private key in the local storage of the browser. We also add an encryption layer here, but if your local storage is compromised, you have way more significant issues than someone stealing the burner wallet you only use for Sealcaster.
Note the OpenGSN integration here. We wrap the wallet in a provider that allows gasless transactions. This way, users don't have to pay for gas and transfer ETH through the mixers to hide their true identity.
Then we request an attestation from the 🌈 Attestor 🌈 that the connected Ethereum address is connected to at least one Farcaster account. Here's how the 🌈 Attestor 🌈 responds to it with the EdDSA signed attestation. Find more info on how the 🌈 Attestor 🌈 works in the documentation (see "Farcaster attestation"). We also fetch the 🌈 Attestor 🌈 public key.
Afterwards, we ask you to sign a message that can only be used at Sealcaster with the connected account. This signature is then passed to the 🌈 Attestor 🌈 which responds with the EdDSA signed attestation that you indeed own the address in question. Also, see the "Ethereum address attestation" in the documentation.
So far, we've gathered two attestations:
- An Ethereum address X is connected to a Farcaster account
- You own the Ethereum address X
However, if we openly used the attestations, it would be obvious what address created the zkNFT and, consequently, a not-so-anonymous cast. This is where ZK comes to the rescue! We can create a ZK proof that we have both attestations and they are valid!
So we do precisely this. See the "Farcaster checker" circuit in the documentation for reference. But essentially, we input the two attestations into the proof generation function and prove the following:
- It is an attestation of type "own"
- It is an attestation for "farcaster"
- This ZK proof has this nonce to be used as a nullifier (so that the same public ZK proof cannot be reused by other people)
- It is an attestation signed by the 🌈 Attestor 🌈 with this public key
- Whoever created this ZK proof knows the following things (but they are not included in the ZK proof):
- Farcaster attestation
- Address from the Farcaster attestation
- Ethereum address attestation
After we generate the ZK proof, we use it in the
mint function of the
FarcasterLedger. We safely disconnect your original wallet at this time. There is no use for it anymore. This and the rest of the calls are made from the generated burner wallet. You also have access to the burner wallet private key if you want to save it for later (e.g. to follow up in the reply threads).
💥! Now you have the burner wallet with a "FARCASTER-d" zkNFT! This zkNFT is soulbound, which means no one can steal it from you. You have frictionlessly obtained a burner wallet with the social capital that you own a Farcaster account! And this wallet has no connection whatsoever to anything on the blockchain. No one knows where it came from, but everybody knows that you're one of the Farcaster users (even though no one knows which one).
The next thing we do is post the text you filled in a while (a couple dozen seconds) ago. We post to the SCPostStorage smart contract explicitly deployed for the Farcaster. The cast can be saved right on the chain because it can be longer than 279 characters, and this is a relatively tiny size. Notice that we use OpenGSN again to pay for the gas so that users don't have to find means to fund the anonymous burner wallet.
After the post gets onto the blockchain, it can be viewed at Sealcaster instantaneously. However, it isn't posted to Farcaster yet. The frontend part of the process is done; now, it simply awaits for the cast to be posted into the shared account.
The entity responsible for casting is the 📣 Poster 📣. It holds the private key for the @sealcaster account. You can find its code here. It's hosted on our centralized server and listens to the blockchain events at the post storage smart contract. When it finds a new post on the blockchain, it adds the post to the database.
Another entity inside the 📣 Poster 📣 constantly checks if there are any unposted casts and casts them as they become available. A set of filters sends some posts for moderation to our private Discord server (e.g. posts containing links or user handles). This is done entirely to prevent spam, but if everything pans out ok, we'll just remove them. However, even if we reject any questionable content, it is forever saved on-chain and can be viewed at Sealcaster.
And this is it! We should get many things right, but the result is spectacular when we do. I'm constantly awed by the power ZK has unleashed on the world. And I haven't even covered the questions of the day where anyone can reply anonymously or the reply threads where burners can follow up unlocking, for instance, AMAs!