On-Chain KYC Verification for Crowdsales

Know Your Customer (KYC) is a process to verify the identity of investors. To comply with regulations, KYC is a necessity for the ICO owner, so that they can prove that the money they’ve collected is legitimate and that none of the funds are related in any way to money laundering or any other shady activity.

The way KYC works is that customers are required to provide certain information regarding their identity and citizenship and based on that, it is decided whether or not they are eligible to participate. That eligibility is in the form of a white list of addresses that are allowed to buy tokens. The usual KYC mechanisms fall under the following categories:

1. KYC before ICO: KYC is performed before the ICO and all the whitelisted addresses are added into the crowdsale contract.
 
2. KYC during ICO: The ICO owner keeps updating the whitelist, as per request, within the duration of the ICO.
 
3. KYC using ECRecover: This is the most cost-effective way to whitelist an investor. The ICO owner signs the investor address after the KYC verification and gives this signed data for the investor to include in the transaction.

The ECRecover Mechanism

Digital signatures are a staple of Public Key Infrastructure that allows anyone to validate the integrity and the source of the signed data. Data is signed using the private key and the public key is required to validate the signature.

The ECRecover function in Solidity takes in the raw data that was signed and the corresponding signature to return the public address of the signer, hence verifying the origin of the signature.

In case of KYC, an owner verifies the identity of a potential contributor either themselves or using a third-party service. Once verified, the owner signs the investor’s address and sends the signature to them. The investor will use this signature when making a contribution to the ICO contract, where this signature will be verified – both where it came from (the owner) and to whom it was addressed (the investor) – before allowing the purchase of tokens.

We’ll use ethereum-utils.js library’s ecsign function to sign the data,

web3.eth.sign

that signs data by appending some required text before it, such that the final message data becomes:

"\x19Ethereum Signed Message:\n" + message.length + message 

The following code snippet shows how to sign a public address using ethereum-utils.js:

var checksumAddress = web3.utils.toChecksumAddress(publicAddress);

var payload = web3.utils.sha3(checksumAddress);

var {v,r,s} = EthJS.Util.ecsign(EthJS.Util.toBuffer(payload),
EthJS.Util.toBuffer(privateKey));

var rHex = EthJS.Util.bufferToHex(r);

var sHex = EthJS.Util.bufferToHex(s);

var v = Number(v);

The signature generated using this function does not recover the actual signed address; we can only get the signer of the data. To be able to verify the signature, we also need the address which we’ve signed to make sure the intended participant is using the signed data. In this context, the msg.sender address of the transaction will be used as the message.

Implementation in Solidity

Add this modifier in your contract code.

modifier onlyWhitelisted(bytes dataframe, uint8 v, bytes32 r, bytes32 s) {
require(isKYCApproved(dataframe,v,r,s));
_;
}
function isKYCApproved(bytes _dataFrame, uint8 _v, bytes32 _r, bytes32 _s) internal view returns (bool){
address whitelistedAddress = sliceAddress(_dataFrame,0);
bytes32 hash = keccak256(_dataFrame);
address recoverdSigner = ecrecover(hash,_v,_r,_s);
require(whitelistedAddress == msg.sender);
require(recoverdSigner == owner);
return true;
}

This function is responsible for the verification of input data, the sliceAddress function will extract the actual data that is signed (in our case, it’s the address of the whitelisted investor).

Hash of dataFrame is calculated in this function as we want to verify that the msg.sender is the actual investor that we have approved. If we don’t verify msg.sender and signed address, this will allow other investors to use the same data to make contributions, hence rendering the whitelisting exercise useless.

The next two require statements verify that the incoming transaction is from a valid whitelisted address that the owner has signed.

function buyTokens(bytes dataframe, uint8 v, bytes32 r, bytes32 s) onlyWhitelisted(dataframe,v,r,s) public view returns (bool) {
// your code here
return true;
}

If the onlyWhitelisted modifier is satisfied, the transaction will be processed; otherwise, it will be reverted.

Demonstration

First, we sign an address with the owner’s private key and get the signature.

Next, we try to contribute using the buyToken function of the contract.

ContractInstance.methods.buyTokens(checksumAddress,v,rHex,sHex).encodeABI()

We use MetaMask to send transaction to the contract including the signature and singed data in the data field.

The transaction is successfully executed.

Now using the same signed data from another account.

Conclusion

We have successfully implemented On-Chain KYC using the ECRecover mechanism, which allows investor whitelisting without having to pay considerable gas fee at either side – the investor or the owner. The code can be found here for reference.