ponyjackal
Posted on October 30, 2022
It's true that hybrid approach in the design of web3 applications can not be ignored.
While on chain smart contracts are great to verify things as a single source of truth, it is not so great for data manipulation.
That's where hybrid comes into play.
We normally make some calculations off chain on top of synced database with on chain storages, and then reflect the result onto smart contracts as soon as we make sure that it's valid.
And yet, this validation is not that easy and this is the most vulnerable point in whole application, cause malicious actors can always try to pretend to be a valid user.
Of course there is a number of approaches for this, as you know, the most well-knowns are Merkle proof and signature verification.
Today, I am going to deep dive into signature verification in hybrid applications.
I will more focus on the overall architecture rather that how we use signature verification.
If you are not familiar with signature and EIP712, you'd better first go through it,
https://nftschool.dev/tutorial/lazy-minting/
Here is the basic diagram below about the flow.
As you can see there are 3 steps to go in terms of frontend application, I think it will make you more sense to overview this in frontend's perspective since it is actually the entry point for the users to the application.
Step1, once user triggers actions on the frontend, it will require to sign a message.
And then it will make an API request to backend in order to get signature singed by private wallet.
Example requests body would be like this
{
...
userAddress: string,
userSignature: string,
}
When it comes to request body, it will vary depends on the actions user made and the application you are building.
For instance, it would include collection address, token id, price in case user is trying to list his NFT on the marketplace.
Step 2, Once frontend gets signature from the backend side, now it's time to make a transaction, here just for your note, user is actually making a transaction through UI.
The very basic voucher struct would be something like this
{
user: address;
...
nonce: uint256;
expiry: uint256
signature: bytes;
}
In smart contract, there must be some validations about signature, like if the signature is signed by the private wallet, user is matched to transaction sender, nonce is correct, and finally voucher is not expired by checking expiry.
You are free to add more validations for your specific needs.
FYI, it's obvious to make an update on signatures mapping and nonce once signature is valid and you are about to proceed your update on states.
mapping(address => uint256) public nonces;
mapping(bytes => bool) public signatures;
nonces[_voucher.user]++;
signatures[_voucher.signature] = true;
Step3. There must be a result from the transaction, right? whether it's succeed or reverted.
Only if transaction is succeed and state variables are updated on chain, we are now ready to update a database in order to sync with on chain.
You will call the API for this with request body includes
{
...
userSignature: string, // signature by user
txSignature: string, // signature by private wallet
}
For this, you have to validate if transaction is succeed and states are updated, it might be a good way to check signature mapping if txSingature is valid or not.
and you might want to do some updates on your DB and then return the result to the user.
Finally the user will get the result of his action on UI.
Thanks for your attention, I tried to make this as short as possible but at the same time, I tried to give you more sense.))
I am going to walk through tableland implementation for dynamic metadata which is the must-solve thing in P2E games.
https://docs.tableland.xyz/
So stay tuned.
Posted on October 30, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.