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
, andverified
.
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 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;
}
- 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"
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.
- Scaffold Approve Identity Message:
Create the message for approving Identities:
ignite scaffold message approveIdentity id:uint name birthdate nationalID
- 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.
- 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.
- 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/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
- 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: Prepare the blockchain for running.
ignite chain serve
- 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
- 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.