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
- Scaffold Registration Message:
Create a message for identity registration
ignite scaffold message registerIdentity name birthdate nationalID
- 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;
}
- Modify Keeper Methods: In the
keeper
folder, implement logic in theRegisterIdentity
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.
- Scaffold Approve Identity Message:
Create the message for approving identities:
ignite scaffold message approveIdentity id:uint name birthdate nationalID
- 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.
- 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.
- Scaffold Identity Query: Create a query to fetch identity data.
ignite scaffold query searchIdentity nationalID --response Identity:Identity
- Add
SearchByNationalID
: Append the a Search function forNationalID
in yourx/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
- Write Unit Tests: Create tests for your module's functionalities.
- Run Simulation tests: Use Ignlite CLI's simulation tools to test the module under various conditions.
Step 6: Running the Blockchain
- Initialize the Blockchain:
ignite chain serve
- 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
- 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.