Creating a Flutter App for a Celo-Based Decentralized Identity Verification System

Creating a Flutter App for a Celo-Based Decentralized Identity Verification System https://celo.academy/uploads/default/optimized/2X/9/9e9661c4d51319290349a98c48bff0487513588e_2_1024x576.png
none 0.0 0

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:

  1. Flutter: We’ll be using Flutter to create our mobile app. You can download and install Flutter from here.
  2. Dart: Flutter uses the Dart programming language. Make sure Dart is installed by following the instructions here.
  3. Node.js: Ensure that you have Node.js 12.0.1 or a later version installed. You can download Node.js from here.
  4. 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:

  1. Identity Struct: This is a user-defined type that encapsulates related variables. In this case, it encapsulates name, dateOfBirth, and nationality.
  2. identities Mapping: This is a key-value storage structure that maps an Ethereum address to an Identity object. It’s being used here to store user identities.
  3. setIdentity Function: This function accepts name, dateOfBirth, and nationality as parameters, creates an Identity object, and stores it in the identities mapping against the sender’s address. If the identity already exists, it will be overwritten.
  4. editIdentity Function: This function allows users to edit their existing identities. It checks if the passed parameters are not 0 (meaning the user wants to change them) before changing the values in the identities mapping.
  5. verifyIdentity Function: This function is used to verify an identity claim. The claim can be about a name, dateOfBirth, or nationality. The function retrieves the Identity of the provided address and checks whether the provided value matches the corresponding field in the Identity 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:

  1. 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.

  2. custom_text_field.dart - A customized textfield used across other screens.

  3. set_identity_screen.dart - Here we have the code to display the Set Identity Screen as shown above. It contains three text field that takes in name, Date of birth, and Country. It also contains a button that is inactive when you click on it without filling in all text fields.

  4. verify_identity_screen.dart - Here we have the code to display the Verify 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.

  5. 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

  6. 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:

  1. Web3Client: It initializes a connection to the Celo Alfajores testnet.
  2. contractAddress & contractABI: These represent the address of your deployed contract on the blockchain and the JSON ABI of the contract.
  3. Web3Helper class: This class contains methods for interacting with your smart contract.
  4. setIdentity Function: This function corresponds to the setIdentity function in your Solidity contract. It takes name, dob, and nationality as arguments, converts them into byte arrays, hashes them using keccak256, and then sends a transaction to set the identity of the sender.
  5. editIdentity Function: Similar to setIdentity, this function corresponds to the editIdentity function in your Solidity contract. It checks if the passed parameters are not null (meaning the user wants to change them) before hashing them and sending a transaction to edit the identity.
  6. verifyIdentity Function: This function corresponds to the verifyIdentity function in your Solidity contract. It hashes the value parameter and makes a call to the smart contract function, passing the hashed value along with the claim. 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.

References

  1. Flutter Documentation
  2. Celo Documentation
  3. web3dart Documentation
  4. Source Code
  5. Building Decentralized Applications on the Celo Platform using the Celo SDK and Celo Smart Contracts
7 Likes

What do you intend to use to connect the flutter app to your smart contract

5 Likes

@RichardOla
Web3dart package

5 Likes

ohh cool looking forward to it

4 Likes

I’ll really love to see how this works

5 Likes

@RichardOla are you a developer? Your profile looks incomplete tho.

2 Likes

Congratulations on your proposal being chosen as a standout this week at Celo Academy! As you prepare your tutorial, please ensure you follow our guidelines found here: Celo Sage Guidelines and Best Practices. Thank you for helping improve the experience of the developers learning at Celo Academy.

4 Likes

I’ll be reviewing this @Qtech

2 Likes

okay @4undRaiser

2 Likes

Thank you @Qtech for this tutorial. Not bad at all :+1:

4 Likes

Thanks @bobelr :smiling_face:

2 Likes

good piece! @Qtech

2 Likes

Thanks

2 Likes

Short and straight forward.

3 Likes

:+1::+1::clap::clap::clap:

5 Likes

Thanks :innocent:

5 Likes

Nice piece @Qtech

6 Likes

I tried wrapping my head around why the word “HERE” was in upper case. If there are no special reason I think they can be turned to lowercase to fixed into the paragraph

4 Likes

Great tutorial. Kudos

@Qtech Please fix the links in uppercase. They should be in lowercase.

5 Likes