Decentralized Identity Verification Module

Decentralized Identity Verification Module

Overview

In this tutorial, you'll learn how to create a module for decentralized identity verification. This module will enable users to register and manage their digital identities on the blockchain, ensuring security and privacy.

Prerequisites

  • Basic understanding of blockchain concepts.
  • Familiarity with Ignite CLI and Cosmos SDK.
  • Go programming language knowledge.
  • Installed Ignite CLI.

Tutorial Steps

Step 1: Setting Up the Blockchain

  • Create a New Blockchain: Start by creating a new blockchain project.
ignite scaffold chain identity && cd identity

Step 2: Define the Identity Module

  • Define Identity List: Scaffold a type to represent an identity with fields like name, birthdate, nationalID, and verified.
ignite scaffold list identity creator name birthdate nationalID --no-message

Step 3: Implementing Identity Registration

  1. Scaffold Registration Message:

Create a message for identity registration

ignite scaffold message registerIdentity name birthdate nationalID
  1. Add verified Status:

To the proto file, add verified status. This is only internal and should not be passed with message by the user.

Modify the Identity in proto/identity/identity/identity.proto

message Identity {
  uint64 id = 1;
  string creator = 2; 
  string name = 3; 
  string birthdate = 4; 
  string nationalId = 5;   
  bool verified   = 6;
}
  1. Modify Keeper Methods: In the keeper folder, implement logic in the RegisterIdentity method to store and manage identity data.

In the x/identity/keeper/msg_server_register_identity.go update the RegisterIdentity function:

package keeper

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"identity/x/identity/types"

	sdk "github.com/cosmos/cosmos-sdk/types"
)

func (k msgServer) RegisterIdentity(goCtx context.Context, msg *types.MsgRegisterIdentity) (*types.MsgRegisterIdentityResponse, error) {
	ctx := sdk.UnwrapSDKContext(goCtx)

	hash := sha256.Sum256([]byte(msg.NationalId))
	nationalIDhash := hex.EncodeToString(hash[:])

	// Create a new Identity object
	var identity = types.Identity{
		Creator:    msg.Creator,
		Name:       msg.Name,
		Birthdate:  msg.Birthdate,
		NationalId: nationalIDhash,
		Verified:   false,
	}

	// Store the identity
	k.AppendIdentity(ctx, identity)

	return &types.MsgRegisterIdentityResponse{}, nil
}

This example would have the NationalID of the user obscured by sha256 so it is not publicly available but verifyiable by an entity that has access to it.
Birthday and Names are publicly available for control of external entities.

  1. Scaffold Approve Identity Message:

Create the message for approving Identities:

ignite scaffold message approveIdentity id:uint name birthdate nationalID
  1. Implement Verification Logic: Add methods in the keeper to verify an identity based on provided credentials

In your module's keeper/msg_approve_identity.go file, enhance the verification logic

package keeper

import (
	"context"
	"crypto/sha256"
	"encoding/hex"

	"identity/x/identity/types"

	errorsmod "cosmossdk.io/errors"
	sdk "github.com/cosmos/cosmos-sdk/types"
)

func (k msgServer) ApproveIdentity(goCtx context.Context, msg *types.MsgApproveIdentity) (*types.MsgApproveIdentityResponse, error) {
	ctx := sdk.UnwrapSDKContext(goCtx)

	hash := sha256.Sum256([]byte(msg.NationalId))
	nationalIDhash := hex.EncodeToString(hash[:])

	identity, found := k.GetIdentity(ctx, msg.Id)
	if !found {
		return nil, errorsmod.Wrapf(types.ErrIdentityNotFound, "key %d doesn't exist", msg.Id)
	}
	if identity.Verified != false {
		return nil, errorsmod.Wrapf(types.ErrAlreadyVerified, "Identity not pending", msg.Id)
	}

	if identity.NationalId != nationalIDhash {
		return nil, errorsmod.Wrapf(types.WrongHash, "hash %d is wrong", msg.Id)
	}

	identity.Verified = true
	k.SetIdentity(ctx, identity)
	return &types.MsgApproveIdentityResponse{}, nil
}

This function would be the right place to check that only certain entities are allowed to verify identities. For this example we'll keep it simple and anyone can verify identities.

  1. Add Custom Errors: Add your custom errors to x/identity/types/errors.go
package types

// DONTCOVER

import (
	sdkerrors "cosmossdk.io/errors"
)

// x/identity module sentinel errors
var (
	ErrInvalidSigner    = sdkerrors.Register(ModuleName, 1100, "expected gov account as only signer for proposal message")
	ErrSample           = sdkerrors.Register(ModuleName, 1101, "sample error")
	ErrIdentityNotFound = sdkerrors.Register(ModuleName, 1102, "Identity not found")
	ErrAlreadyVerified  = sdkerrors.Register(ModuleName, 1103, "Identity already verified")
	WrongHash           = sdkerrors.Register(ModuleName, 1104, "NationalID hashes not matching, wrong NationalID")
)

Step 4: Querying Identities

In case someone has been given your NationalID, they could verify your name and age in order to grant access.

  1. Scaffold Identity Query: Create a query to fetch identity data.
ignite scaffold query searchIdentity nationalID --response Identity:Identity
  1. Add SearchByNationalID: Append the a Search function for NationalID in your x/identity/keeper/identity.go
// GetNationalID returns the identity from its NationalId
func (k Keeper) SearchByNationalID(ctx context.Context, NationalIDHash string) (val types.Identity, found bool) {
	storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx))
	store := prefix.NewStore(storeAdapter, types.KeyPrefix(types.IdentityKey))
	iterator := storetypes.KVStorePrefixIterator(store, []byte{})

	defer iterator.Close()

	for ; iterator.Valid(); iterator.Next() {
		var val types.Identity
		k.cdc.MustUnmarshal(iterator.Value(), &val)

		if val.NationalId == NationalIDHash {
			return val, true
		}
	}

	return val, false
}

In your module's keeper/query_search_identity.go, implement the query logic.

package keeper

import (
	"context"
	"crypto/sha256"
	"encoding/hex"

	"identity/x/identity/types"

	sdk "github.com/cosmos/cosmos-sdk/types"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func (k Keeper) SearchIdentity(goCtx context.Context, req *types.QuerySearchIdentityRequest) (*types.QuerySearchIdentityResponse, error) {
	if req == nil {
		return nil, status.Error(codes.InvalidArgument, "invalid request")
	}

	ctx := sdk.UnwrapSDKContext(goCtx)

	hash := sha256.Sum256([]byte(req.NationalId))
	nationalIDhash := hex.EncodeToString(hash[:])

	identity, found := k.SearchByNationalID(ctx, nationalIDhash)
	if !found {
		return nil, status.Error(codes.NotFound, "identity not found")
	}

	return &types.QuerySearchIdentityResponse{Identity: &identity}, nil
}

Step 5: Testing and Debugging

  1. Write Unit Tests: Create tests for your module's functionalities.
  2. Run Simulation tests: Use Ignlite CLI's simulation tools to test the module under various conditions.

Step 6: Running the Blockchain

  1. Initialize the Blockchain: Prepare the blockchain for running.
ignite chain serve
  1. Interact with Your Module: Use the CLI commands to register and verify identities.

Register a new Identity

identityd tx identity register-identity "Alice Wonderland" "07/04/1865" NationIDNumber123 --from alice --chain-id identity

Query an Identity

identityd query identity get-identity [identity-id]

Verify an Identity

For verifying an identity, you would typically send a transaction from a privileged account (like an admin or a validator), which has the authority to verify identities

identityd tx identity approve-identity 0 "Alice Wonderland" "07/04/1865" NationIDNumber123 --from bob --chain-id identity

Check Identity

identityd q identity show-identity 0

Search Identity By NationalID

identityd q identity search-identity NationIDNumber123

Query All Identities

identityd query identity list-identity

Step 7: Front-End Integration

  1. Create a Front-End Interface: If desired, develop a front-end application to interact with your blockchain.
  • Integrate with Ignite's Front-End Library: Utilize Ignite's generated TypeScript/Vuex/React clients for easy integration.

Conclusion

By the end of this tutorial, you will have built a functional decentralized identity verification module on a blockchain. This module not only enhances user privacy and security but also opens up new possibilities for identity management in the digital world.