Zinc Smart Contracts Architecture
Over the past month, we have been busy designing and implementing trustless smart contracts written in Solidity. This post will walk through the architectural decisions made and lessons learnt along the way.
Smart contracts play an important role in applications by giving the guarantee that everyone executes the same code all over the world, However; they must be designed with the right set of goals in mind. The goal for us was to have trustless smart contracts where there is no Owner in the contract and Zinc only ever has the rights explicitly granted by the user. In general, most dApp’s smart contracts are Pausable in order to avoid situations like the DAO hack. It introduces trust within the company that they will not pause the contract without a critical event and the definition of an event being critical is a topic for another day.
Smart contracts can’t be upgraded once they are deployed, there are however ways around it which makes it easy to update the logic of the contract without having to use a different address. A commonly used pattern is Proxy Upgrade Pattern which uses a proxy contract to delegate the call to another contract which can be changed as per upgrades. Once again, this adds the trust factor that the company will not upgrade the contract to an evil version without your consent and steal your money.
With that being said, lets dive into Zinc’s contracts:
Zinc contracts are inspired by the ENS architecture where the upgradability is left for the user to decide and users may be perfectly happy with the current logic and are free not to upgrade and still use the system. The philosophy of the contract is very simple: every user will have an Identity Contract which only they control and can revoke permissions of other apps as they wish. The idea is that dApp contracts can ask the user for permission to interact with their identity contract and the user can allow for certain permissions. The dApp contract’s address gets added to the users Identity Contract. One important thing to note here is that interaction among contracts is encouraged in this framework as we can have well-defined behaviours within the contracts which can be audited by the community.
The idea behind the Zinc contract was: How can we make a users life easier and incentivise them to try our platform? This is why we started developing our own version of Transaction Relay which will facilitate the user to use our platform without using gas/ether. Zinc will relay the transactions in a trustless fashion. This meant that we needed to provide strong guarantees that there isn't anything we can do with their identity (such as stealing their money).
ECDSA signatures to the rescue! Just like many other dApps, we decided to use signatures within the smart contract to verify user’s consent for interacting with their Identity or performing any function on their behalf. The transaction relay works by asking the user to sign a message through MetaMask, the signed message then gets relayed to the ZincAccessor contract which verifies the contents of the message through the Encoder contract and verifies the signature by using SignatureValidator, once these checks are performed, the action is performed with the Identity Contract.
To facilitate creation of user Identity Contracts, a function “constructUserIdentity” has been implemented which creates an Identity Contract for a given user following the same signature verification as described above. The Identity Contract created from this function adds the ZincAccessor contract with key management privileges and the user with ALL purpose privileges. This means that the user can revoke ZincAccessor permissions at any time by sending a transaction to their Identity Contract.
The Identity Contract permissions scheme is inspired by Linux Permissions where the specific bit is checked. For example, we have the following permissions:
0 : None
1 : Read_Only
2 : Write_Only
8: Funds Management
The permissions are checked in bits so 1 in binary is 0001, 2 in binary in 0010, 4 is 0100 and 8 is 1000. Now, as you can see, only the necessary bits are on per permission. Users can have multiple permissions and that is as simple as just adding the permissions together. For instance, if someone wanted to have Read_Only and Write_Only permissions, their permissions would be 1+2 = 3 and for someone who needs all permissions, they have 1+2+4+8 = 15. The user always has all permissions within our contracts and ZincAccessor will only ever have Key_Management permissions (4).
Can Zinc abuse Key_Management permissions to steal users money? Not quite. Zinc cannot ever call any function which requires Funds_Management privileges. How about Zinc removing users keys from their Identity Contract just because it can? Well, technically it’s possible but the user will have to sign a message saying they approve this action and ZincAccessor contract will verify this before performing any actions.
ZincAccessor contract is upgradable in the sense that we can deploy a new version of ZincAccessor but the existing users will need to sign a message to add it to their Identity Contract, the Identity Contract is deliberately kept simple so it doesn't have to be upgraded and will be audited by several people before it goes live.
The contracts are currently deployed on Ropsten and a bug bounty program will follow shortly after.
If you need any more information or have any questions, comment below or ask me on twitter @aliazam2251.