Decentralized Identity Verification

Decentralized Identity Verification

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

  • Go programming language knowledge.
  • Installed Ignite CLI v29.0.0.

Tutorial Steps

Step 1: Setting Up the Blockchain

Start by creating a new blockchain project:

ignite scaffold chain identity && cd identity

Step 2: Define the Identity Module

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 should be internal only and not included in messages passed by the user.

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

message Identity {
  uint64 id = 1;
  string creator = 2; 
  string name = 3; 
  string birthdate = 4; 
  string national_id = 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"

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

func (k msgServer) RegisterIdentity(ctx context.Context, msg *types.MsgRegisterIdentity) (*types.MsgRegisterIdentityResponse, error) {
	if _, err := k.addressCodec.StringToBytes(msg.Creator); err != nil {
		return nil, errorsmod.Wrap(err, "invalid authority address")
	}

	id, err := k.IdentitySeq.Next(ctx)
	if err != nil {
		return nil, errorsmod.Wrap(err, "unable to get next id")
	}

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

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

	// Store the identity
	if err := k.Identity.Set(ctx, identity.Id, identity); err != nil {
		return nil, errorsmod.Wrap(sdkerrors.ErrLogic, err.Error())
	}

	return &types.MsgRegisterIdentityResponse{}, nil
}


In this example, the NationalID is obscured with SHA-256 to hide it from the public while still allowing authorized entities to verify it.
A user's birthday and names are public for external verification.

  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 to the keeper to verify an identity using the provided credentials by updating the verification logic in your module's keeper/msg_approve_identity.go file:

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 is the appropriate place to ensure that only certain entities are allowed to verify identities. To keep it simple in this example, anyone can verify identities.

  1. Add Custom Errors:

Next, add your custom errors to x/identity/types/errors.go:

package types

// DONTCOVER

import (
	"cosmossdk.io/errors"
)

// x/identity module sentinel errors
var (
	ErrInvalidSigner = errors.Register(ModuleName, 1100, "expected gov account as only signer for proposal message")
	ErrSample           = errors.Register(ModuleName, 1101, "sample error")
	ErrIdentityNotFound = errors.Register(ModuleName, 1102, "Identity not found")
	ErrAlreadyVerified  = errors.Register(ModuleName, 1103, "Identity already verified")
	WrongHash           = errors.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/keeper.go
// GetNationalID returns the identity from its NationalId
func (k Keeper) SearchByNationalID(ctx context.Context, nationalIDHash string) (*types.Identity, bool) {
	storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx))
	store := prefix.NewStore(storeAdapter, []byte(types.IdentityKey))
	iterator := store.Iterator(nil, nil)

	defer iterator.Close()

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

		if identity.NationalId == nationalIDHash {
			return &identity, true
		}
	}

	return nil, 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"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

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

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

	identity, found := q.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:
ignite chain serve
  1. Interact with Your Module:

Register a new Identity

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

Query an Identity

identityd query identity get-identity 0

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-identities

Step 7: Front-End Integration

  1. Create a Front-End Interface: If desired, develop a front-end application to interact with your blockchain.
  2. 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.