Creating a Decentralized management identity on the Celo blockchain Using Solidity

Introduction

In this tutorial, you’ll learn not only what an identity management system is but also how to build one on the Celo blockchain. Celo is a blockchain platform focused on inclusive finance and user empowerment. Using Solidity, we’ll create a system where individuals control their own identity information.

By the end, you’ll have the knowledge to build a decentralized identity management system on Celo. This will enable you to develop secure identity solutions for sectors like finance, healthcare, and e-commerce. Let’s get started on empowering individuals with control over their identities in the decentralized world of Celo!

Prerequisite

For the purpose of this tutorial, you will need to have some level of knowledge of Solidity and the Celo blockchain.

Requirements

For this tutorial, you will need

  • A code editor. VScode is preferable.
  • A crypto wallet: celo-wallet-extension, Celo wallet, MetaMask
  • Fund your wallet using the Celo Faucet. To do this, copy the wallet address and visit the Celo Faucet . Paste your wallet address into the designated field and confirm.
  • Nodejs is installed on your computer. If not, download from here

Check out this video on how to install and set up the celo-wallet-extension.

Decentralized Identity Management System (DID)

A decentralized identity management system (DID) is a type of identity management system that is designed to give individuals more control over their personal data and privacy. Unlike traditional identity management systems, which are often centralized and owned by a single entity (such as a government or corporation), decentralized identity management systems are based on a distributed architecture where users can store and manage their own identity information.

Each user in a DID system has a unique identifier, or “DID,” that serves as a representation of their digital identity. Depending on the situation, individuals can choose to divulge as much or as little information about themselves using this identification, which is not connected to any one organization or government.

Blockchain technology or other decentralized data storage systems are frequently used by decentralized identity management systems to store and manage user identification data. Since the user maintains control over their own data and there is no single point of failure that may be hacked or subject to data breaches, this enables improved security and privacy.

Examples of Decentralized Identity Management Systems

  1. uPort: uPort is a decentralized identity platform built on the Ethereum blockchain. It allows users to create and manage their own self-sovereign digital identities. uPort provides a wallet for storing and managing identity credentials and enables users to control the sharing of their personal information.
  2. SelfKey: SelfKey is a blockchain-based identity management system that allows individuals to manage their digital identities and control access to their personal data. It provides a digital wallet for storing identity documents, such as passports and driver’s licenses, and enables users to securely share their information with trusted parties.
  3. Civic: Civic is a decentralized identity verification platform that uses blockchain technology to provide secure and tamper-proof identity verification. Users can store their personal information in the Civic app and selectively share it with service providers when needed, eliminating the need for multiple username and password combinations.

These are just a few examples of decentralized identity management products available in the market. Each product has its own unique features and implementation, but they all share the common goal of providing individuals with control over their digital identities and enhancing privacy and security in the digital realm.

Building a Decentralized Identity Management System

We will be creating a decentralized identity management system using solidity that allows users to create and manage their own identity by storing their name, age, and email address and then deploy the smart contract to the Celo blockchain.

Project Setup

To set up the project, open a terminal and run these commands.

mkdir decentralized-identity
cd decentralized-identity
yarn init --yes
yarn add --dev hardhat @nomiclabs/hardhat-waffle hardhat-deploy

After running the commands above, in the same directory, run the command below

npx hardhat

Select Create a Javascript Project and follow the steps. This sets up a new Hardhat project.

The Smart Contract

After setting up the hardhat project, create a new solidity file in the contracts directory and name it Identity.sol

Next, copy and paste the code below into the Identity.sol file

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract IdentityManagement {
    address public owner;
    mapping(address => bool) public admins;
    
    struct Identity {
        address identityOwner;
        string name;
        uint256 age;
        string email;
        // You can add more identity attributes as needed
    }
    
    mapping(address => Identity) public identities;
    
    event IdentityCreated(address indexed _identityOwner, string name);
    event IdentityUpdated(address indexed _identityOwner, string name);
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Only contract owner can perform this action");
        _;
    }
    
    modifier onlyAdmin() {
        require(admins[msg.sender], "Only administrators can perform this action");
        _;
    }
    
    constructor() {
        owner = msg.sender;
        admins[msg.sender] = true;
    }
    
    function addAdmin(address _admin) public onlyOwner {
        admins[_admin] = true;
    }
    
    function removeAdmin(address _admin) public onlyOwner {
        require(_admin != owner, "Contract owner cannot be removed as admin");
        admins[_admin] = false;
    }
    
    function createIdentity(string memory _name, uint256 _age, string memory _email) public {
        require(identities[msg.sender].identityOwner == address(0), "Identity already exists");
        
        Identity storage newIdentity = identities[msg.sender];
        newIdentity.identityOwner = msg.sender;
        newIdentity.name = _name;
        newIdentity.age = _age;
        newIdentity.email = _email;
        
        emit IdentityCreated(msg.sender, _name);
    }
    
    function updateIdentity(string memory _name, uint256 _age, string memory _email) public onlyAdmin {
        require(identities[msg.sender].identityOwner != address(0), "Identity does not exist");
        
        Identity storage updatedIdentity = identities[msg.sender];
        updatedIdentity.name = _name;
        updatedIdentity.age = _age;
        updatedIdentity.email = _email;
        
        emit IdentityUpdated(msg.sender, _name);
    }
    
    function getIdentity(address _owner) public view returns (string memory, uint256, string memory) {
        Identity storage identity = identities[_owner];
        return (identity.name, identity.age, identity.email);
    }
}

A brief breakdown of what the contract does:

pragma solidity ^0.8.0;

contract IdentityManagement {
    address public owner;
    mapping(address => bool) public admins;
    
    struct Identity {
        address identityOwner;
        string name;
        uint256 age;
        string email;
        // Add more identity attributes as needed
    }
    
    mapping(address => Identity) public identities;
    
    event IdentityCreated(address indexed _identityOwner, string name);
    event IdentityUpdated(address indexed _identityOwner, string name);

The contract begins with the declaration of the IdentityManagement contract and its version pragma. The owner variable is of type address and represents the contract owner. The admins mapping is used to store the addresses of administrators who have certain privileges within the contract.

The Identity struct is defined to hold the identity information, including the identityOwner address, name, age, email, and any additional attributes that may be needed.

The identities mapping is used to associate each address with its corresponding Identity struct, allowing for easy lookup and retrieval of identity information.

The IdentityCreated and IdentityUpdated events are emitted to notify listeners when an identity is created or updated, respectively.

modifier onlyOwner() {
        require(msg.sender == owner, "Only contract owner can perform this action");
        _;
    }
    
    modifier onlyAdmin() {
        require(admins[msg.sender], "Only administrators can perform this action");
        _;
    }

The onlyOwner modifier is defined to restrict certain functions to being callable only by the contract owner. It checks whether the caller of the function (msg.sender) matches the owner address defined in the contract.

The onlyAdmin modifier restricts certain functions to being callable only by administrators. It checks whether the msg.sender address is present in the admins mapping and sets it to true.

constructor() {
        owner = msg.sender;
        admins[msg.sender] = true;
    }
    
    function addAdmin(address _admin) public onlyOwner {
        admins[_admin] = true;
    }
    
    function removeAdmin(address _admin) public onlyOwner {
        require(_admin != owner, "Contract owner cannot be removed as admin");
        admins[_admin] = false;
    }

The constructor function is executed only once during contract deployment. It sets the owner address to the address that deployed the contract and adds the deployer as an admin by setting admins[msg.sender] to true.

The addAdmin function allows the contract owner to add new administrators by specifying their address as an argument. It sets admins[_admin] to true, granting them admin privileges.

The removeAdmin function allows the contract owner to remove administrators by specifying their address as an argument. It ensures that the contract owner cannot be removed as an admin and sets admins[_admin] to false, revoking their admin privileges.

function createIdentity(string memory _name, uint256 _age, string memory _email) public {
        require(identities[msg.sender].identityOwner == address(0), "Identity already exists");
        
        Identity storage newIdentity = identities[msg.sender];
        newIdentity.identityOwner = msg.sender;
        newIdentity.name = _name;
        newIdentity.age = _age;
        newIdentity.email = _email;
        
        emit IdentityCreated(msg.sender, _name);
    }

The function begins by checking if an identity already exists for the caller (msg.sender). It does this by verifying if the owner address in the identities mapping is address(0), which indicates that no identity exists.

If the identity doesn’t already exist, a new Identity struct is created and assigned to newIdentity, which is a storage reference to the corresponding identity in the identities mapping. The owner field of the new identity is set to the caller’s address (msg.sender), and the provided _name, _age, and _email values are assigned to the respective fields.

Finally, the IdentityCreated event is emitted, indicating the successful creation of the identity, with the caller’s address and the provided name as the event parameters.

function updateIdentity(string memory _name, uint256 _age, string memory _email) public onlyAdmin {
        require(identities[msg.sender].identityOwner != address(0), "Identity does not exist");
        
        Identity storage updatedIdentity = identities[msg.sender];
        updatedIdentity.name = _name;
        updatedIdentity.age = _age;
        updatedIdentity.email = _email;
        
        emit IdentityUpdated(msg.sender, _name);
    }

The updateIdentity function allows an admin to update their identity by providing the new _name, _age, and _email values as arguments. It uses the onlyAdmin modifier to ensure that only administrators can invoke this function.

Before updating the identity, the function checks if an identity exists for the caller (msg.sender) by verifying if the owner address in the identities mapping is not address(0). If no identity exists, the function reverts with an error message.

If the identity exists, the function retrieves the storage reference to the corresponding identity in the identities mapping and updates the name, age, and email fields with the provided values.

Finally, the IdentityUpdated event is emitted to indicate the successful update of the identity, with the caller’s address and the updated name as the event parameters.

function getIdentity(address _identityOwner) public view returns (string memory, uint256, string memory) {
        Identity storage identity = identities[_owner];
        return (identity.name, identity.age, identity.email);
    }
}

The getIdentity function allows anyone to retrieve the identity information associated with a specific address. It takes the _owner address as an argument and returns the name, age, and email fields of the corresponding identity.

The function retrieves the storage reference to the identity from the identities mapping using the _owner address. It then returns the requested identity attributes as a tuple.

That concludes the breakdown of the smart contract code for the identity management system.

Compiling The Smart Contract

Next, you will compile the smart contract

To compile the smart contract, run the command below in your terminal

npx hardhat compile

Once the compilation is successful, you should see this message on your terminal

compilation-success

Deploying The Smart Contract

We will deploy the smart contract to the celo test blockchain, as I already mentioned. To accomplish this, we must write a deployment script that connects to the Alfajores testnet through forno. First, we will substitute the configuration code for deployment made accessible by Celo for the content of the default hardhat.config.js file provided to us by hardhat.

To do this, replace the content of the hardhat.config.js file with the code below.

require("@nomiclabs/hardhat-waffle");
require('dotenv').config({path: '.env'});
require('hardhat-deploy');

// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more

// Prints the Celo accounts associated with the mnemonic in .env
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
  const accounts = await hre.ethers.getSigners();

  for (const account of accounts) {
    console.log(account.address);
  }
});

/**
 * @type import('hardhat/config').HardhatUserConfig
 */
module.exports = {
  defaultNetwork: "alfajores",
  networks: {
    localhost: {
        url: "http://127.0.0.1:7545"
    },
    alfajores: {
      url: "https://alfajores-forno.celo-testnet.org",
      accounts: {
        mnemonic: process.env.MNEMONIC,
        path: "m/44'/52752'/0'/0"
      },
      //chainId: 44787
    },
    celo: {
      url: "https://forno.celo.org",
      accounts: {
        mnemonic: process.env.MNEMONIC,
        path: "m/44'/52752'/0'/0"
      },
      chainId: 42220
    },     
  },
  solidity: "0.8.4",
};

In the hardhat.config.js, add the gas price and gas to the alfajores object.

 alfajores: {
   gasPrice: 200000000000,
   gas: 41000000000,
   url: "https://alfajores-forno.celo-testnet.org",
   accounts: {
     mnemonic: process.env.MNEMONIC,
     path: "m/44'/52752'/0'/0
 }

Now we would have to install dotenv package in order to import the env file and use in the config file.

Run the command below in your terminal to install the dotenv package.

npm install dotenv

Now create an .env file in your project root folder and add the following line

MNEMONIC=//Go to your celo wallet, copy and paste your 12 key phrase

Deploying To The Celo Blockchain

To deploy the smart contract, first, you will need to write a deployment script.

In the script folder, there is a deploy.js file. Replace the content of the deploy.js file with the code below.

const { ethers } = require("hardhat");

async function main() {
  const IndentityContract = await ethers.getContractFactory("IdentityManagement");

  const deployedIdentityContract = await IndentityContract.deploy();

  await deployedIdentityContract.deployed();

  console.log("Identity Management Contract Address:", deployedIdentityContract.address);
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.log(error);
    process.exit(1);
  });

Run the following command in your project root directory to deploy your smart contract to the Celo Alfajores testnet.

npx hardhat run scripts/deploy.js --network alfajores

You can copy and save the smart contract address that will be printed on your terminal when deployment is successful somewhere.

View Contract Deployment

To view your deployed smart contract, take the smart contract address you copied from the terminal and visit the celo alfajores explorer to search for your deployed contract.

Conclusion

So far, we have been able to create an identity management smart contract that allows users to create and manage their own identities by storing their name, age, and email address.

To view your deployed smart contract, copy the smart contract address from the terminal and navigate to the block explorer to search for your deployed contract.

Please note that this is a simplified example for demonstration purposes, and in a real-world scenario, you would likely have additional functionality and security measures implemented.

About The Author

I am a React frontend developer with over 3 years of experience building for the web, a web3 developer, and a technical writer. Visit my GitHub profile to see some of the projects I have worked on and currently working on and also check out my profile on LinkedIn.

References

2 Likes

Congratulations on being among the highest voted proposals for this week! I’m moving this to todo so that you can get started on this project. :mortar_board: :seedling:

3 Likes

i’ll review this @Joshy

1 Like

I’ll be reviewing your poece in 1 to 2 days

2 Likes

Am on this tutorial already @Phenzic

1 Like

Ouuu sorry I honestly missed your comment before dropping mine, but is it okay i review this piece?

2 Likes

sorry, I already started maybe you get another one

1 Like

@4undRaiser can you get the review done before 6pm?

1 Like

Sure i’ll try @Joshy but i have to take my time to make sure your tutorial is ready.

1 Like

I believe this numbering here is a typo

1 Like

Can you add a screenshot of the finished dapp at the end? just like you did in other part of the tutorial.

1 Like

There is a mistake. The content is for another tutorial I am working on. Must have mixed them up. I will transfer the content to the right tutorial and move this back to “in progress”.

1 Like

@4undRaiser I have tagged you on the correct tutorial. This is the link to the tutorial:

2 Likes

Alright

2 Likes

Hi @Joshy I’ll be reviewing your piece in 1 to 2 days

Okay… Thanks