Off-Chain Whitelist with On-Chain Verification for Ethereum Smart Contracts

Philippe Castonguay
5 min readMar 7, 2018

--

Many token sales have used a whitelist process in the last year, where only approved users are able to contribute. This is almost always accomplished with an on-chain whitelist, where the owner of the token sale contract update a mapping variable (e.g. mapping (address=>bool) public whitelisted) for each user that is whitelisted. When participants try to contribute, there is a require() statement that verifies whether msg.sender (i.e. the participant trying to contribute) is whitelisted or not and throws in the latter case.

While this is a simple way of doing it, it can also be quite costly. For instance, Bluzelle paid 9.345 ETH to whitelist 7473 users for their token sale, which at the time was worth 11,949.36 USD. This might be an outlier considering the congestion when Bluzelle did their token sale, but registering 7000 users isn’t that much at all either. Indeed, token sales are only one application where a controlled access might be useful. For instance, a decentralized exchange might want to be compliant with the SEC and forces its users to go through a KYC process, like most centralized exchanges. If they have million of users, updating themselves the whitelist on-chain can lead to significant cost.

In the rest of this short blog post, I will describe an approach that does not require an on-chain whitelist registry while still restricting access to certain users only.

Off-Chain Access Message and On-Chain Access Control

The idea is relatively straightforward. The owner of smart contract X signs a message for each user they want to give access to, where the signed message contains the address of the contract X and the address of the user Z;

The resulting message basically says “I, owner of contract X, give address Z access to contract X”. It’s important to include the contract address to which you want to give the users access to in case the owner created multiple contracts with the same access control logic. Indeed, the same message could be used on various contracts with the same owner if the contract address isn’t required. To be worth anything, there needs to be a statement in the smart contract that forces users to provide their access message, such as ;

The isValidAccessMessage() will verify that the signature provided is indeed valid, where it needs to be signed by the owner of contract X and needs to include both the address of contract X and address of the user Z;

Function that verifies the signature

In both functions, the input arguments v, r and s are the three ECDSA signature parameters. These can be obtained by reformatting the byte code obtained when signing the message ;

For dapps, users will never need to know they were given with this access message, since it can all be handled in the backend without user intervention. However, in the case of a token sale where participants use various wallets such as MEW, https://mycrypto.com/, metamask or parity, the team would need to provide each user with a byte code containing the access message, as shown in the example below.

Example : Restricted Access Donation

Let’s say you are running a donation campaign for your mom so she can buy herself chocolate and you only want your brother to be able to contribute.

First you would need a smart contract such as

Donation contract that uses the ControlledAccess logic

Now, since you are the owner of the contract, you will need to sign a message containing both the contract address and your brother address. Giving you something like

0x11e64b1e177fc75b859b4e55182e09b96b4518349c8a9ab438dac568ac78e7d34a0843d860a38bfa5fecb393959b5ea8fd35803ab8935af9ec84d268e00e6b3f1b

This is the signature your brother will need to be able to donate. Your brother isn’t as tech savvy and doesn’t know how to call functions or encode the arguments, so you do it for him, giving you something like

0xde5eaa83000000000000000000000000000000000000000000000000000000000000001c872e34205860ab7264602005e13ee8757be8d1532870fe518afb407007f84a4e51b286f02236898d71e9055856f2348abdd5ff61c119ffa4b88152c92de00f9a

wherede5eaa83is the first 4 bytes of the hash of the function you are calling, 000000000000000000000000000000000000000000000000000000000000001c is the left-paddedv parameter, 872e34205860ab7264602005e13ee8757be8d1532870fe518afb407007f84a4eis the r parameter and 51b286f02236898d71e9055856f2348abdd5ff61c119ffa4b88152c92de00f9a is the s parameter.

The only thing your brother needs to do is copy the byte code representing the encoded function call and paste it in the “extra data” section when sending some ethers, which would look like ;

Calling the `donate()` function with the access message on https://mycrypto.com/

Where the To Address is the smart contract address X and the Amount to Send is the amount to donate. With this extra data provided, the transaction will effectively call the donate() function in the smart contract with the access message as arguments. Only your brother can use this signed message to be able to successfully donate. All others that try to call this function will see their transaction fail, unless you sign an access message for them as well.

Going Forward

Sometimes it’s also convenient to have the whitelist on-chain. This could be possible with the above approach where the users would effectively register themselves with the access message. When a user Z calls the onlyValidAccess function for the first time, a accessRegistry variable would be updated to map Z’s address (i.e. msg.sender) to true. For all future calls, onlyValidAccess() would pass whether user Z includes the access message or not. This type of access logic could be implemented by overloading relevant functions in the smart contracts. Slightly more ugly, but definitely doable and straightforward.

The good thing about this is that users pay the cost of registering themselves, which is negligible when done for one address only (only about 5000 gas) and that subsequent calls will be cheaper for the users, since verifying a boolean is cheaper than verifying a signature. This approach is only really worth it if users will interact many times with the smart contracts, such as with a decentralized exchange.

Resources

--

--

Philippe Castonguay

Distributed Ledger Technologies | Smart contract research and development @HorizonGames | Computational neuroscience research