Introduction
Imagine a world where you could verify your identity online in a secure and decentralized way, without relying on centralized entities and without compromising your privacy. Sounds interesting, doesn’t it? In this tutorial, we’re going to build a Flutter app that will interact with the Celo blockchain to provide just such an identity verification system. We’ll design an intuitive user interface, write a Celo smart contract, and incorporate advanced privacy features. By the end of this journey, we’ll have a fully functional app that can onboard users and validate identities in a secure, decentralized manner.
Prerequisites
This tutorial is aimed at developers with intermediate knowledge of blockchain and mobile app development. Familiarity with the basics of smart contract development, the Flutter framework, and an understanding of the Celo blockchain would be beneficial. If you’re new to Celo, I’d recommend going through the beginner’s tutorial first. Please ensure that you have some Celo tokens as we’ll need them to interact with the Celo blockchain.
Requirements
Before we get started, you’ll need to have a few things set up:
- Flutter: We’ll be using Flutter to create our mobile app. You can download and install Flutter from here.
- Dart: Flutter uses the Dart programming language. Make sure Dart is installed by following the instructions here.
- Node.js: Ensure that you have Node.js 12.0.1 or a later version installed. You can download Node.js from here.
- web3dart: This is a Dart library that allows us to interact with the Ethereum blockchain (which is compatible with Celo). We’ll use it to connect our Flutter app to our Celo smart contract. You can learn more about web3dart here.
Creating the Smart Contract
Now, let’s write our smart contract using Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract IdentityVerification {
struct Identity {
bytes32 name;
bytes32 dateOfBirth;
bytes32 nationality;
// Include other relevant information
}
mapping(address => Identity) public identities;
function setIdentity(
bytes32 _name,
bytes32 _dob,
bytes32 _nationality
// Include other relevant information
) public {
Identity memory newIdentity = Identity({
name: _name,
dateOfBirth: _dob,
nationality: _nationality
// Include other relevant information
});
identities[msg.sender] = newIdentity;
}
function editIdentity(
bytes32 _name,
bytes32 _dob,
bytes32 _nationality
// Include other relevant information
) public {
require(identities[msg.sender].name != 0, "Identity does not exist.");
if(_name != 0){
identities[msg.sender].name = _name;
}
if(_dob != 0){
identities[msg.sender].dateOfBirth = _dob;
}
if(_nationality != 0){
identities[msg.sender].nationality = _nationality;
}
// And similarly for other relevant fields
}
function verifyIdentity(address _address, string memory claim, bytes32 value) public view returns (bool) {
Identity memory identity = identities[_address];
if (keccak256(abi.encodePacked((claim))) == keccak256(abi.encodePacked(("name")))) {
return identity.name == value;
} else if (keccak256(abi.encodePacked((claim))) == keccak256(abi.encodePacked(("dob")))) {
return identity.dateOfBirth == value;
} else if (keccak256(abi.encodePacked((claim))) == keccak256(abi.encodePacked(("nationality")))) {
return identity.nationality == value;
}
// Include other relevant checks
return false;
}
}
Here’s a breakdown of the Solidity contract IdentityVerification
:
Identity
Struct: This is a user-defined type that encapsulates related variables. In this case, it encapsulatesname
,dateOfBirth
, andnationality
.identities
Mapping: This is a key-value storage structure that maps an Ethereum address to anIdentity
object. It’s being used here to store user identities.setIdentity
Function: This function acceptsname
,dateOfBirth
, andnationality
as parameters, creates anIdentity
object, and stores it in theidentities
mapping against the sender’s address. If the identity already exists, it will be overwritten.editIdentity
Function: This function allows users to edit their existing identities. It checks if the passed parameters are not0
(meaning the user wants to change them) before changing the values in theidentities
mapping.verifyIdentity
Function: This function is used to verify an identity claim. The claim can be about aname
,dateOfBirth
, ornationality
. The function retrieves theIdentity
of the provided address and checks whether the provided value matches the corresponding field in theIdentity
struct.
The claim
parameter is a string that is used to determine the field of the Identity
struct to be compared with the value
. Because Solidity doesn’t support string comparison, we have to hash the strings and compare the hashes. abi.encodePacked
is used to convert the strings into bytes, and keccak256
is used to generate the hash of those bytes.
For example, if claim
is “name”, it will compare the name
in the Identity
struct with the provided value
to see if they match.
The function returns true
if the provided value matches the claimed field of the Identity
struct, and false
otherwise.
This contract can be extended to include more fields in the Identity
struct or to support more types of identity verification.
In summary, this smart contract allows a user to store/edit a hashed version of their identity on the blockchain and provides a function for verifying specific claims about a user’s identity. It’s a simple, yet powerful demonstration of how blockchain can be used for decentralized identity verification.
After writing and testing the smart contract, you can deploy it to the Celo network using a deployment tool like Truffle or Hardhat or you can check out this tutorial to learn how to deploy a smart contract. You will get a contract address and ABI as a result of the deployment. Keep these handy, as they’ll be necessary for interacting with the contract from the Flutter app.
Building the Flutter UI
First, we need to clone the Flutter code repo by running this command in our terminal:
git clone https://github.com/Qtech11/celo-identity-verification-app
Open the cloned project in your preferred IDE. You should have the following files in it:
In your terminal, run the command below to install all dependencies required for the project:
flutter pub get
The App consist majorly of two screens, One is the Set Identity Screen
where the user can set their identity, and the other is the Verify Identity Screen
where you can verify identity that has been set using different means.
Set Identity Screen
Verify Identity Screen
In our lib folder, we have:
-
main.dart
- Here, we have the root of the application and also a bottom navigation bar widget that allows us to navigate to these two screens. -
custom_text_field.dart
- A customized textfield used across other screens. -
set_identity_screen.dart
- Here we have the code to display theSet Identity Screen
as shown above. It contains three text field that takes inname
,Date of birth
, andCountry
. It also contains a button that is inactive when you click on it without filling in all text fields. -
verify_identity_screen.dart
- Here we have the code to display theVerify Identity Screen
as shown above. It contains three text fields and a button for each of them respectively which allows you to verify identity using three different means. -
web3_provider.dart
- serves as an intermediary between the logic and UI using the flutter riverpod package for managing the state of the app. It sends transactions to the blockchain, updates the app state, and displays a snackbar based on the response gotten -
web3_helper.dart
- contains the code to make the connection between the Flutter app and the deployed smart contract. It is well explained in the next section
Connecting Our Flutter App to the Smart Contract
Connecting the flutter app to the already deployed smart contract was done using the web3dart
library. In our web3_helper.dart
file, we should have the code below:
import 'dart:convert';
import 'dart:typed_data';
import 'package:http/http.dart';
import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart';
final client = Web3Client('https://alfajores-forno.celo-testnet.org', Client());
const abi = '<your-contract-abi>';
// Replace these with your actual contract ABI and remove the string quote
final contractAddress = EthereumAddress.fromHex(
'0xfcFD51761316c7420f4f011e6509Be883C0A1412'); // replace with your actual contract address
final contractABI = json.encode(abi);
class Web3Helper {
// Create a contract instance that we can interact with
final contract = DeployedContract(
ContractAbi.fromJson(contractABI, "IdentityVerification"),
contractAddress,
);
final credentials = EthPrivateKey.fromHex(
"8b3f2c08e01cd6c37ad3f57a90401d280930532868e3404361c7ffd471b2a7b4"); // replace with your celo wallet private key
Future setIdentity(String name, String dob, String nationality) async {
final function = contract.function('setIdentity');
final hashedName = keccak256(Uint8List.fromList(utf8.encode(name)));
final hashedDob = keccak256(Uint8List.fromList(utf8.encode(dob)));
final hashedNationality =
keccak256(Uint8List.fromList(utf8.encode(nationality)));
final response = await client.sendTransaction(
credentials,
Transaction.callContract(
contract: contract,
function: function,
parameters: [hashedName, hashedDob, hashedNationality],
),
chainId: 44787,
);
while (true) {
final receipt = await client.getTransactionReceipt(response);
if (receipt != null) {
print('Transaction successful');
print(receipt);
break;
}
// Wait for a while before polling again
await Future.delayed(const Duration(seconds: 1));
}
return response;
}
Future editIdentity({String? name, String? dob, String? nationality}) async {
final function = contract.function('editIdentity');
final hashedName = name != null
? keccak256(Uint8List.fromList(utf8.encode(name)))
: keccak256(Uint8List.fromList(utf8.encode('')));
final hashedDob = dob != null
? keccak256(Uint8List.fromList(utf8.encode(dob)))
: keccak256(Uint8List.fromList(utf8.encode('')));
final hashedNationality = nationality != null
? keccak256(Uint8List.fromList(utf8.encode(nationality)))
: keccak256(Uint8List.fromList(utf8.encode('')));
final response = await client.sendTransaction(
credentials,
Transaction.callContract(
contract: contract,
function: function,
parameters: [hashedName, hashedDob, hashedNationality],
),
chainId: 44787,
);
while (true) {
final receipt = await client.getTransactionReceipt(response);
if (receipt != null) {
print('Transaction successful');
print(receipt);
break;
}
// Wait for a while before polling again
await Future.delayed(const Duration(seconds: 1));
}
return response;
}
Future<bool> verifyIdentity(String claim, String value) async {
print('start');
print('start');
final function = contract.function('verifyIdentity');
final hashedValue = keccak256(Uint8List.fromList(utf8.encode(value)));
print('start1');
print('start1');
final response = await client.call(
sender: credentials.address,
contract: contract,
function: function,
params: [
EthereumAddress.fromHex(credentials.address.hex),
claim,
hashedValue,
],
);
return response[0] as bool;
}
}
This Dart code is using the web3dart
package to interact with your Solidity contract IdentityVerification
. It establishes a connection with the Celo Alfajores testnet and uses your contract’s ABI and addresses to create a DeployedContract
instance. Here’s an overview of what each part of the code does:
Web3Client
: It initializes a connection to the Celo Alfajores testnet.contractAddress
&contractABI
: These represent the address of your deployed contract on the blockchain and the JSON ABI of the contract.Web3Helper
class: This class contains methods for interacting with your smart contract.setIdentity
Function: This function corresponds to thesetIdentity
function in your Solidity contract. It takesname
,dob
, andnationality
as arguments, converts them into byte arrays, hashes them usingkeccak256
, and then sends a transaction to set the identity of the sender.editIdentity
Function: Similar tosetIdentity
, this function corresponds to theeditIdentity
function in your Solidity contract. It checks if the passed parameters are notnull
(meaning the user wants to change them) before hashing them and sending a transaction to edit the identity.verifyIdentity
Function: This function corresponds to theverifyIdentity
function in your Solidity contract. It hashes thevalue
parameter and makes a call to the smart contract function, passing the hashed value along with theclaim
. This function doesn’t change the state of the blockchain so it just calls the function without sending a transaction.
Note: The chainId
of 44787 corresponds to the Celo Alfajores testnet. Also, make sure to securely manage and protect your private key, and not expose it in your codebase for production applications.
Wrapping up the Smart Contract Interaction
Now that we have our Web3Helper class, we can call its methods whenever we need to interact with our Celo smart contract. We can set, edit, and verify identities securely and efficiently on the blockchain right from our Flutter app!
Please note that this tutorial provides a simplified demonstration of a complex process. In a production application, additional layers of security and validation should be considered. Also, while this contract operates on the principle of zero-knowledge proofs, it isn’t a full-fledged zero-knowledge system. Developing a full-fledged zero-knowledge system requires more advanced cryptography, such as zk-SNARKs or zk-STARKs, and the generation of valid proofs for contract submission.
Conclusion
In this tutorial, we built a decentralized identity verification system using the Celo blockchain and Flutter for the mobile front-end. We developed a secure Celo smart contract for storing, editing, and verifying user identity information, incorporating privacy-preserving features such as hashing to enhance confidentiality. We also created a class in our Flutter app to manage the interactions between our app and the smart contract on the blockchain.
Next Steps
For continued learning, you can try to extend this tutorial by adding more features, such as a user management system to handle multiple users or additional identity attributes. You can also explore more advanced zero-knowledge proof systems for further privacy preservation.
About the Author
Qudus Olanrewaju is a developer and technical writer who has a strong interest in blockchain technology and web3. He enjoys building web3 projects and leveraging his distinctive viewpoint to create engaging and insightful content. You can connect with me on Linkedin and check out my latest projects on GitHub.