At ethDenver, we were challenged to solve the issue of verifying claims about DIDs for DeSci dApps. A major obstacle for any permissionless application is overcoming exploitation by unverified accounts impersonating somebody else (e.g., a posting a journal article pretending to be a scientist or NFT pretending to be its creator).
We built Holo to provide on-chain identity services, as decentralized as possible. The most common use case of Holo is the linking of Web2 accounts like Twitter and Discord to a blockchain account. We’ve gotten a lot of questions about how the protocol verifies Web2 accounts, so we wrote this piece.
How does Holo work?
Holo uses web token forwarding for linking Web2 accounts. Essentially, it links blockchain addresses to web2 accounts. It does this by forwarding (JSON) web tokens to the blockchain.
What are JSON Web Tokens (JWTs)?
JSON Web Tokens (JWTs), also pronounced “jots,” are cryptographically signed pieces of data used for account authentication and authorization throughout the web. Ever clicked “sign-in with Google?” When you signed in, Google returned a JWT. This JWT contains info about your account in JSON format, much like this:
Figure 1. A fake simplified JWT. Note that we phased out google accounts since the time of writing as email is PII. Email verification will return to Holo with ZK proofs for anonymity.
- Who you are: [email protected]
- Who issued the JWT: accounts.google.com
- Who the JWT is intended for: someapp
It really says quite a lot more, but this is just a smaller tutorial version that only says these three items.
Why should I care that Google gave me JSON which says all these claims? Because Google gives something else along with it: a cryptographic signature which proves this came from Google. Essentially, you’ve been handed a certificate which you can show someapp that you are [email protected]. Someapp can then verify the signature to know it’s from Google.
Cryptographic signatures are not only found in JWTs. They are used all over the web. Importantly for us, they’re essential to blockchains. Signatures are how you prove that you spend a currency and not anyone else – you sign the transaction to spend your money; nobody else can sign for you nor forge your signature. With a bit of code, some blockchains can verify these signatures on JWTs. Ethereum and many other EVM-compatible blockchains can do so (as long as they support the 0x05 modular exponentiation precompile, in case you were wondering).
Security and Frontrunning
The blockchain is entirely public. JWTs are meant to be private. Remember how JWTs are signed certificates for a certain app saying that you are who you say you are? If anyone finds your JWT, they can thus impersonate you. So it’s generally considered a bad idea to give away your JWT.
Luckily, our smart contracts invalidate every JWT once they’re used the first time. And these JWTs are not valid for anything but our smart contracts. This is thanks to the “aud” audience claim which restricts them to an app named someapp. JWTs are restricted to a specific app – in our case, specific smart contract. So nobody can impersonate you and present your identity to other apps.
Remember how the blockchain is public and anybody can see your transactions before they’re added to the next block? It is a huge issue if people can see your JWT before the block is finalized. Adversarial parties can see the JWT while the block is assembled, copy it, and submit it themselves to the same block. If they add more gas (effectively a bribe), they can also have miners prioritize it over yours! Basically, they can impersonate you to the smart contract.
Luckily though, cryptography is our friend. We can actually hide a JWT by submitting a cryptographic hash of it. Only somebody who knows the same JWT can reproduce that same hash – realistically speaking, that is. Technically, it’s possible to produce the hash without knowing the JWT, but for even a supercomputer to do so, it would take more time than the earth has existed for, and not the biblical literalist age
Then, once the block where we’ve committed to this JWT by giving its hash is finalized, we can reveal the actual JWT. The smart contract can then reproduce the hash commitment since it now knows the JWT, and it can see we had committed the JWT in an earlier block. Therefore, we couldn’t have just stolen someone’s JWT from the same block. We had to have known the JWT beforehand to produce its hash.
You may have noticed a flaw in this logic – somebody can frontrun the commitment (the hashing) in addition to the reveal. I.e., they can copy the hash in the first block and then copy the reveal in the later block. So it seems as if they knew the JWT in the first place, because they knew its hashed value. To fix this, instead of just commiting hash(JWT), you can commit hash(JWT + your address). If an attacker copies this commitment, they don’t gain anything. After you or they reveal the JWT, the smart contract can see it was linked to your address, not theirs.
Figure 2. Commit/reveal to prevent frontrunning.
Tying it all together
Now, you can see an overview of the web token forwarding:
- User has a blockchain address and JWT of a web2 account
- User commits hash(JWT + blockchain address)
- After block, user reveals JWT
- Smart contract verifies that commitment of (JWT + blockchain address) exists for user’s address
- If it exists, smart contract verifies the signature on the JWT
- If it exists, smart contract reads other data from the JWT, such as which web2 account it belongs to
- Smart contract links user’s address with user’s web2 account
We encourage you to build the following with Holo:
- Web3 marketplaces with verified sellers
- NFTs with embedded identities to prevent counterfeits or link to Twitter accounts
- DAO tools that can interact with Discord accounts on-chain
- Crypto payments to a Github or Twitter handle
- Noncustodial wallet recovery using Holo + Lit protocol