diff --git a/tests/e2e/ssi_tests/e2e_tests.py b/tests/e2e/ssi_tests/e2e_tests.py index ca3422a..968e87a 100644 --- a/tests/e2e/ssi_tests/e2e_tests.py +++ b/tests/e2e/ssi_tests/e2e_tests.py @@ -842,6 +842,7 @@ def caip10_cosmos_support_test(): did_doc_string_2 = generate_did_document(kp, kp_algo, "osmo") did_doc_string_2_vm = did_doc_string_2["verificationMethod"][0] did_doc_string_2_vm["id"] = did_doc_string_2_vm["id"] + "new" + did_doc_string_2_vm["controller"] = did_doc_string_1["verificationMethod"][0]["controller"] did_doc_string_1["verificationMethod"] = [ did_doc_string_1["verificationMethod"][0], @@ -1028,7 +1029,7 @@ def vm_type_test(): print("4. PASS: Registering DID Document with a verification method of type EcdsaSecp256k1VerificationKey2019. Only publicKeyMultibase is passed.") kp_algo = "secp256k1" kp = generate_key_pair(algo=kp_algo) - did_doc_string = generate_did_document(kp, kp_algo) + did_doc_string = generate_did_document(kp, kp_algo, is_uuid=True, bech32prefix="") did_doc_id = did_doc_string["id"] did_doc_string["verificationMethod"][0]["blockchainAccountId"] = "" signers = [] @@ -1122,4 +1123,131 @@ def vm_type_test(): run_blockchain_command(create_tx_cmd, f"Registering DID with Id: {did_doc_id}") print("--- Test Completed ---\n") - \ No newline at end of file + +def method_specific_id_test(): + print("\n--- Method Specific ID Tests ---\n") + + print("1. PASS: Registering a DID Document where the user provides a blockchain address in MSI that they own") + + kp_algo = "secp256k1" + kp_alice = generate_key_pair(algo=kp_algo) + signers = [] + did_doc_string = generate_did_document(kp_alice, algo=kp_algo) + did_doc_alice = did_doc_string["id"] + did_doc_alice_vm = did_doc_string["verificationMethod"] + signPair_alice = { + "kp": kp_alice, + "verificationMethodId": did_doc_string["verificationMethod"][0]["id"], + "signing_algo": kp_algo + } + signers.append(signPair_alice) + create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME) + run_blockchain_command(create_tx_cmd, f"Registering Alice's DID with Id: {did_doc_alice}") + + print("2. FAIL: Registering a DID Document where the user provides a blockchain address in MSI that they don't own") + + kp_algo = "secp256k1" + kp_bob = generate_key_pair(algo=kp_algo) + signers = [] + did_doc_string = generate_did_document(kp_bob, algo=kp_algo) + did_doc_string["controller"] = [did_doc_alice] + did_doc_string["verificationMethod"] = did_doc_alice_vm + + did_doc_bob = did_doc_string["id"] + signers.append(signPair_alice) + create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME) + run_blockchain_command(create_tx_cmd, f"Registering Bob's DID with Id: {did_doc_bob}", True) + + print("3. PASS: Registering a DID Document where the user provides a multibase encoded public key in MSI that they own") + + kp_algo = "ed25519" + kp_alice = generate_key_pair(algo=kp_algo) + signers = [] + did_doc_string = generate_did_document(kp_alice, algo=kp_algo) + did_doc_alice = did_doc_string["id"] + did_doc_alice_vm = did_doc_string["verificationMethod"] + signPair_alice = { + "kp": kp_alice, + "verificationMethodId": did_doc_string["verificationMethod"][0]["id"], + "signing_algo": kp_algo + } + signers.append(signPair_alice) + create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME) + run_blockchain_command(create_tx_cmd, f"Registering Alice's DID with Id: {did_doc_alice}") + + print("4. PASS: Registering a DID Document where the user provides a multibase encoded public key in MSI that they don't own") + + kp_algo = "ed25519" + kp_bob = generate_key_pair(algo=kp_algo) + signers = [] + did_doc_string = generate_did_document(kp_bob, algo=kp_algo) + did_doc_string["controller"] = [did_doc_alice] + did_doc_string["verificationMethod"] = did_doc_alice_vm + + did_doc_bob = did_doc_string["id"] + signers.append(signPair_alice) + create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME) + run_blockchain_command(create_tx_cmd, f"Registering Bob's DID with Id: {did_doc_bob}") + + print("5. FAIL: Attempt to Register Invalid DID Documents with invalid DID Id") + + did_id_list = [ + "did:hid:1:", + "did:hid:1", + "did:hid:devnet", + "did:hid:devnet:", + "did:hid:devnet:zHiii", + "did:hid:devnet:asa54qf", + "did:hid:devnet:asa54qf|sds", + "did:hid:devnet:-asa54-qfsds", + "did:hid:devnet:.com", + ] + + for did_id in did_id_list: + did_doc_string["id"] = did_id + create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME) + run_blockchain_command(create_tx_cmd, f"Registering Invalid DID with Id: {did_id}", True, True) + + print("6. PASS: Alice tries to update their DID Document by removing the Verification Method associated with the method specific id (CAIP-10 Blockchain Address)") + + kp_algo = "secp256k1" + kp_alice = generate_key_pair(algo=kp_algo) + signers = [] + did_doc_string = generate_did_document(kp_alice, algo=kp_algo) + did_doc_alice = did_doc_string["id"] + did_doc_alice_vm = did_doc_string["verificationMethod"] + signPair_alice = { + "kp": kp_alice, + "verificationMethodId": did_doc_string["verificationMethod"][0]["id"], + "signing_algo": kp_algo + } + signers.append(signPair_alice) + create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME) + run_blockchain_command(create_tx_cmd, f"Registering Alice's DID with Id: {did_doc_alice}") + + did_doc_string["verificationMethod"] = [] + update_tx_cmd = form_did_update_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME) + run_blockchain_command(update_tx_cmd, f"Removal of Verification Method associated with method specific id") + + print("7. PASS: Alice tries to update their DID Document by removing the Verification Method associated with the method specific id (Multibase Encoded PublicKey)") + + kp_algo = "ed25519" + kp_alice = generate_key_pair(algo=kp_algo) + signers = [] + did_doc_string = generate_did_document(kp_alice, algo=kp_algo) + did_doc_alice = did_doc_string["id"] + did_doc_alice_vm = did_doc_string["verificationMethod"] + signPair_alice = { + "kp": kp_alice, + "verificationMethodId": did_doc_string["verificationMethod"][0]["id"], + "signing_algo": kp_algo + } + signers.append(signPair_alice) + create_tx_cmd = form_did_create_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME) + run_blockchain_command(create_tx_cmd, f"Registering Alice's DID with Id: {did_doc_alice}") + + did_doc_string["verificationMethod"] = [] + update_tx_cmd = form_did_update_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME) + run_blockchain_command(update_tx_cmd, f"Removal of Verification Method associated with method specific id") + + print("--- Test Completed ---\n") \ No newline at end of file diff --git a/tests/e2e/ssi_tests/generate_doc.py b/tests/e2e/ssi_tests/generate_doc.py index ecdc3e5..f668520 100644 --- a/tests/e2e/ssi_tests/generate_doc.py +++ b/tests/e2e/ssi_tests/generate_doc.py @@ -6,7 +6,7 @@ from utils import run_command, generate_document_id, get_document_signature, \ secp256k1_pubkey_to_address -def generate_did_document(key_pair, algo="ed25519", bech32prefix="hid"): +def generate_did_document(key_pair, algo="ed25519", bech32prefix="hid", is_uuid=False): base_document = { "context" : [ "https://www.w3.org/ns/did/v1" @@ -17,7 +17,7 @@ def generate_did_document(key_pair, algo="ed25519", bech32prefix="hid"): "authentication": [], } - did_id = generate_document_id("did", key_pair, algo) + did_id = generate_document_id("did", key_pair, algo, is_uuid) # Form the DID Document vm_type = "" @@ -31,12 +31,13 @@ def generate_did_document(key_pair, algo="ed25519", bech32prefix="hid"): raise Exception("unknown signing algorithm: " + key_pair) verification_method = { - "id": did_id + "#key-1", - "type": vm_type, - "controller": did_id, + "id": "", + "type": "", + "controller": "", "blockchainAccountId": "", "publicKeyMultibase": "" } + if algo == "recover-eth": verification_method["blockchainAccountId"] = "eip155:1:" + key_pair["ethereum_address"] elif algo == "secp256k1": @@ -44,16 +45,22 @@ def generate_did_document(key_pair, algo="ed25519", bech32prefix="hid"): if bech32prefix == "hid": verification_method["blockchainAccountId"] = "cosmos:jagrat:" + \ secp256k1_pubkey_to_address(key_pair["pub_key_base_64"], bech32prefix) + did_id = "did:hid:devnet:" + verification_method["blockchainAccountId"] elif bech32prefix == "osmo": verification_method["blockchainAccountId"] = "cosmos:osmosis-1:" + \ secp256k1_pubkey_to_address(key_pair["pub_key_base_64"], bech32prefix) + did_id = "did:hid:devnet:" + verification_method["blockchainAccountId"] else: - raise Exception("unsupported bech32 prefix " + bech32prefix) + verification_method["blockchainAccountId"] = "" verification_method["publicKeyMultibase"] = key_pair["pub_key_multibase"] else: verification_method["publicKeyMultibase"] = key_pair["pub_key_multibase"] + verification_method["controller"] = did_id + verification_method["type"] = vm_type + verification_method["id"] = did_id + "#k1" + base_document["id"] = did_id base_document["controller"] = [did_id] base_document["verificationMethod"] = [verification_method] diff --git a/tests/e2e/ssi_tests/run.py b/tests/e2e/ssi_tests/run.py index 75d5607..8ebe69e 100644 --- a/tests/e2e/ssi_tests/run.py +++ b/tests/e2e/ssi_tests/run.py @@ -32,6 +32,7 @@ def run_all_tests(): caip10_ethereum_support_test() caip10_cosmos_support_test() vm_type_test() + method_specific_id_test() print("============= 😃️ All test cases completed successfully ============== \n") diff --git a/tests/e2e/ssi_tests/utils.py b/tests/e2e/ssi_tests/utils.py index 3997f97..33feb1e 100644 --- a/tests/e2e/ssi_tests/utils.py +++ b/tests/e2e/ssi_tests/utils.py @@ -1,5 +1,6 @@ import subprocess import json +import uuid def run_command(cmd_string): if type(cmd_string) != str: @@ -58,16 +59,19 @@ def generate_key_pair(algo="ed25519"): kp = json.loads(result_str) return kp -def generate_document_id(doc_type: str, kp: dict = None, algo: str = "ed25519"): +def generate_document_id(doc_type: str, kp: dict = None, algo: str = "ed25519", is_uuid: bool =False): id = "" if not kp: kp = generate_key_pair(algo) - if algo in ["recover-eth"]: - method_specific_id = kp["ethereum_address"] + if is_uuid: + method_specific_id = str(uuid.uuid4()) else: - method_specific_id = kp["pub_key_multibase"] - + if algo in ["recover-eth"]: + method_specific_id = kp["ethereum_address"] + else: + method_specific_id = kp["pub_key_multibase"] + if method_specific_id == None: raise Exception("Public key is empty") diff --git a/x/ssi/keeper/msg_server_create_did.go b/x/ssi/keeper/msg_server_create_did.go index 486f93d..93be00f 100644 --- a/x/ssi/keeper/msg_server_create_did.go +++ b/x/ssi/keeper/msg_server_create_did.go @@ -25,7 +25,13 @@ func (k msgServer) CreateDID(goCtx context.Context, msg *types.MsgCreateDID) (*t } // Validate namespace in DID Document - if err := didDocNamespaceValidation(k, ctx, msgDidDocument); err != nil { + chainNamespace := k.GetChainNamespace(&ctx) + if err := types.DidChainNamespaceValidation(msgDidDocument, chainNamespace); err != nil { + return nil, sdkerrors.Wrap(types.ErrInvalidDidDoc, err.Error()) + } + + // Validate ownership of method specific id + if err := checkMethodSpecificIdOwnership(msgDidDocument.VerificationMethod, msgDidDocument.Id); err != nil { return nil, sdkerrors.Wrap(types.ErrInvalidDidDoc, err.Error()) } @@ -76,6 +82,37 @@ func (k msgServer) CreateDID(goCtx context.Context, msg *types.MsgCreateDID) (*t return &types.MsgCreateDIDResponse{Id: id}, nil } +// checkMethodSpecificIdOwnership validates the ownership of blockchain account id passed in the method specific +// identifier of DID Document. This ensures that a DID ID (containing a blockchain address) is being created by someone +// who owns the blockchain address. +func checkMethodSpecificIdOwnership(verificationMethods []*types.VerificationMethod, didId string) error { + inputMSI, inputMSIType, err := types.GetMethodSpecificIdAndType(didId) + if err != nil { + return err + } + + if inputMSIType == types.MSIBlockchainAccountId { + foundMSIinAnyVM := false + for _, vm := range verificationMethods { + if vm.BlockchainAccountId == inputMSI { + foundMSIinAnyVM = true + break + } + } + + if !foundMSIinAnyVM { + return fmt.Errorf( + "proof of ownership for method-specific-id in %v must be provided", + didId, + ) + } else { + return nil + } + } else { + return nil + } +} + // getControllersForCreateDID returns a list of controller DIDs // from controller and verification method attributes func getControllersForCreateDID(didDocument *types.Did) []string { diff --git a/x/ssi/keeper/msg_server_update_did.go b/x/ssi/keeper/msg_server_update_did.go index db62ce5..73c185a 100644 --- a/x/ssi/keeper/msg_server_update_did.go +++ b/x/ssi/keeper/msg_server_update_did.go @@ -26,7 +26,8 @@ func (k msgServer) UpdateDID(goCtx context.Context, msg *types.MsgUpdateDID) (*t } // Validate namespace in DID Document - if err := didDocNamespaceValidation(k, ctx, msgDidDocument); err != nil { + chainNamespace := k.GetChainNamespace(&ctx) + if err := types.DidChainNamespaceValidation(msgDidDocument, chainNamespace); err != nil { return nil, sdkerrors.Wrap(types.ErrInvalidDidDoc, err.Error()) } diff --git a/x/ssi/keeper/namespace.go b/x/ssi/keeper/namespace.go deleted file mode 100644 index ad6e8e2..0000000 --- a/x/ssi/keeper/namespace.go +++ /dev/null @@ -1,79 +0,0 @@ -package keeper - -import ( - "fmt" - "strings" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/hypersign-protocol/hid-node/x/ssi/types" -) - -// didNamespaceValidation validates the namespace in DID Document Id -func didNamespaceValidation(k msgServer, ctx sdk.Context, docId string) error { - genesisNamespace := k.GetChainNamespace(&ctx) - - docIdElements := strings.Split(docId, ":") - docNamespaceIndex := 2 - - if genesisNamespace == "" { - if len(docIdElements) != 3 { - return fmt.Errorf( - "expected number of did id elements for mainnet to be 3, got %s for id: %s", - fmt.Sprint(len(docIdElements)), - docId, - ) - } - } else { - docNamespace := docIdElements[docNamespaceIndex] - if genesisNamespace != docNamespace { - return fmt.Errorf( - "expected namespace for id %s to be %s, got %s", - docId, genesisNamespace, docNamespace) - } - } - - return nil -} - -// didDocNamespaceValidation validates the namespace in Did Document -func didDocNamespaceValidation(k msgServer, ctx sdk.Context, didDoc *types.Did) error { - // Subject ID check - if err := didNamespaceValidation(k, ctx, didDoc.Id); err != nil { - return err - } - - // Controllers check - for _, controller := range didDoc.Controller { - if err := didNamespaceValidation(k, ctx, controller); err != nil { - return err - } - } - - // Verification Method ID checks - for _, vm := range didDoc.VerificationMethod { - didId, _ := types.GetElementsFromDidUrl(vm.Id) - if err := didNamespaceValidation(k, ctx, didId); err != nil { - return err - } - } - - // Verification Relationships check - vmRelationshipList := [][]string{ - didDoc.Authentication, - didDoc.AssertionMethod, - didDoc.KeyAgreement, - didDoc.CapabilityDelegation, - didDoc.CapabilityInvocation, - } - - for _, vmRelationship := range vmRelationshipList { - // didUrl check and presence in verification methods - for _, id := range vmRelationship { - if err := didNamespaceValidation(k, ctx, id); err != nil { - return err - } - } - } - - return nil -} diff --git a/x/ssi/types/chain_namespace_validation.go b/x/ssi/types/chain_namespace_validation.go new file mode 100644 index 0000000..bac87ea --- /dev/null +++ b/x/ssi/types/chain_namespace_validation.go @@ -0,0 +1,62 @@ +package types + +import ( + "fmt" +) + +func chainNamespaceValidation(docId string, validChainNamespace string) error { + documentChainNamespace := getDocumentChainNamespace(docId) + if documentChainNamespace != validChainNamespace { + return fmt.Errorf( + "expected namespace for id %v to be %v, got %v", + docId, + validChainNamespace, + documentChainNamespace, + ) + } else { + return nil + } +} + +// DidDocNamespaceValidation validates the namespace in Did Document +func DidChainNamespaceValidation(didDoc *Did, validChainNamespace string) error { + // Subject ID check + if err := chainNamespaceValidation(didDoc.Id, validChainNamespace); err != nil { + return err + } + + // Controllers check + for _, controller := range didDoc.Controller { + if err := chainNamespaceValidation(controller, validChainNamespace); err != nil { + return err + } + } + + // Verification Method ID checks + for _, vm := range didDoc.VerificationMethod { + didId, _ := GetElementsFromDidUrl(vm.Id) + if err := chainNamespaceValidation(didId, validChainNamespace); err != nil { + return err + } + } + + // Verification Relationships check + vmRelationshipList := [][]string{ + didDoc.Authentication, + didDoc.AssertionMethod, + didDoc.KeyAgreement, + didDoc.CapabilityDelegation, + didDoc.CapabilityInvocation, + } + + for _, vmRelationship := range vmRelationshipList { + // didUrl check and presence in verification methods + for _, id := range vmRelationship { + if err := chainNamespaceValidation(id, validChainNamespace); err != nil { + return err + } + } + } + + return nil +} \ No newline at end of file diff --git a/x/ssi/types/common.go b/x/ssi/types/common.go index c267377..440a703 100644 --- a/x/ssi/types/common.go +++ b/x/ssi/types/common.go @@ -1,5 +1,9 @@ package types +// Supported method-specific-id Formats +const MSIBlockchainAccountId = "MSIBlockchainAccountId" +const MSINonBlockchainAccountId = "MSINonBlockchainAccountId" + // Supported Verification Method Types const Ed25519VerificationKey2020 = "Ed25519VerificationKey2020" const EcdsaSecp256k1VerificationKey2019 = "EcdsaSecp256k1VerificationKey2019" diff --git a/x/ssi/types/diddoc_validation.go b/x/ssi/types/diddoc_validation.go index d228459..039cb16 100644 --- a/x/ssi/types/diddoc_validation.go +++ b/x/ssi/types/diddoc_validation.go @@ -3,51 +3,65 @@ package types import ( "fmt" "regexp" - "strings" ) // isValidDidDoc checks if the DID Id is valid func isValidDidDocId(id string) error { - // check the number of elements in DID Document - idElements := strings.Split(id, ":") - if !(len(idElements) == 3 || len(idElements) == 4) { - return fmt.Errorf( - "number of elements in DID Id %s should be either 3 or 4", - id, - ) + inputDocumentIdentifier, err := getDocumentIdentifier(id) + if err != nil { + return err + } + + inputDidMethod, err := getDocumentMethod(id) + if err != nil { + return err } - // check if the first element is valid document identifier - if idElements[0] != DocumentIdentifierDid { + inputMSI, inputMSIType, err := GetMethodSpecificIdAndType(id) + if err != nil { + return err + } + + // Validate Document Identifier + if inputDocumentIdentifier != DocumentIdentifierDid { return fmt.Errorf( "document identifier should be %s", DocumentIdentifierDid, ) } - // check if the second element is the correct DID method - if idElements[1] != DidMethod { + // Validate DID Method + if inputDidMethod != DidMethod { return fmt.Errorf( "DID method should be %s", DidMethod, ) } - // check proper method specific id - // TODO: need to define a specification for method-specific-id - methodSpecificId := idElements[len(idElements)-1] - isProperMethodSpecificId, err := regexp.MatchString( - "^[a-zA-Z0-9]{32,}$", - methodSpecificId, - ) - if err != nil { - return fmt.Errorf("error in parsing regular expression for method-specific-id: %s", err.Error()) - } - if !isProperMethodSpecificId { - return fmt.Errorf( - "method-specific-id should be an alphanumeric string with minimum of 32 characters, received: %s", - methodSpecificId, + // Validate Method Specific ID + switch inputMSIType { + case MSIBlockchainAccountId: + if err := validateBlockchainAccountId(inputMSI); err != nil { + return err + } + case MSINonBlockchainAccountId: + // Non Blockchain Account ID should be a string that supports alphanumeric characters, + // and dot (.) and hypen (-). The first character MUST NOT be dot (.) or hyphen (-). + isValidMSI, err := regexp.MatchString( + "^[a-zA-Z0-9][a-zA-Z0-9.-]*$", + inputMSI, ) + if err != nil { + return err + } + if !isValidMSI { + return fmt.Errorf( + "method-specific-id of non BlockchainAccountId type %v should only contain alphanumeric, dot (.) and hyphen (-)", + inputMSI, + ) + } + default: + return fmt.Errorf("invalid method specific id type: %v", inputMSIType) } return nil @@ -143,19 +157,6 @@ func verificationKeyCheck(vm *VerificationMethod) error { return nil } -// checkDuplicateItems return a duplicate Id from the list, if found -func checkDuplicateItems(list []string) string { - presentMap := map[string]bool{} - for idx := range list { - if _, present := presentMap[list[idx]]; !present { - presentMap[list[idx]] = true - } else { - return list[idx] - } - } - return "" -} - // validateServices validates the Service attribute of DID Document func validateServices(services []*Service) error { for _, service := range services { @@ -224,12 +225,12 @@ func validateVerificationMethods(vms []*VerificationMethod) error { vmIdList := []string{} publicKeyMultibaseList := []string{} blockchainAccountIdList := []string{} - + var pubKeyMultibaseBlockchainAccIdMap map[string]bool = map[string]bool{} for _, vm := range vms { vmIdList = append(vmIdList, vm.Id) - + if vm.Type == EcdsaSecp256k1VerificationKey2019 { if _, present := pubKeyMultibaseBlockchainAccIdMap[vm.PublicKeyMultibase]; present { // TODO: Following is a temporary measure, where we will be allowing duplicate publicKeyMultibase values @@ -239,7 +240,7 @@ func validateVerificationMethods(vms []*VerificationMethod) error { // generated signature is figured out. if vm.BlockchainAccountId == "" { return fmt.Errorf( - "duplicate publicKeyMultibase of type EcdsaSecp256k1VerificationKey2019 without blockchainAccountId is not allowed: %s ", + "duplicate publicKeyMultibase of type EcdsaSecp256k1VerificationKey2019 without blockchainAccountId is not allowed: %s ", vm.PublicKeyMultibase, ) } @@ -249,7 +250,7 @@ func validateVerificationMethods(vms []*VerificationMethod) error { } else { publicKeyMultibaseList = append(publicKeyMultibaseList, vm.PublicKeyMultibase) } - + blockchainAccountIdList = append(blockchainAccountIdList, vm.BlockchainAccountId) } diff --git a/x/ssi/types/utils.go b/x/ssi/types/utils.go index 4bddd18..224e3e1 100644 --- a/x/ssi/types/utils.go +++ b/x/ssi/types/utils.go @@ -1,6 +1,7 @@ package types import ( + "fmt" "strings" ) @@ -46,3 +47,84 @@ func FindInSlice(list []string, element string) bool { } return false } + +// checkDuplicateItems return a duplicate Id from the list, if found +func checkDuplicateItems(list []string) string { + presentMap := map[string]bool{} + for idx := range list { + if _, present := presentMap[list[idx]]; !present { + presentMap[list[idx]] = true + } else { + return list[idx] + } + } + return "" +} + +func getDocumentChainNamespace(docId string) string { + docIdElements := strings.Split(docId, ":") + + // Non Blockchain Account ID MSI + if len(docIdElements) == 4 || len(docIdElements) == 6 { + return docIdElements[2] + } else { + return "" + } +} + +func getDocumentIdentifier(docId string) (string, error) { + docIdElements := strings.Split(docId, ":") + + if len(docIdElements) < 1 { + return "", fmt.Errorf("invalid document Id: %v", docId) + } + + return docIdElements[0], nil +} + +func getDocumentMethod(docId string) (string, error) { + docIdElements := strings.Split(docId, ":") + + if len(docIdElements) < 2 { + return "", fmt.Errorf("invalid document Id: %v", docId) + } + + return docIdElements[1], nil +} + +// GetMethodSpecificIdAndType extracts the method-specific-id from Document Id and which of following +// types it belongs to: +// +// 1. CAIP-10 Blockchain Account ID +// +// 2. String consisting of Alphanumeric, dot (.) and hyphen (-) characters only +func GetMethodSpecificIdAndType(didId string) (string, string, error) { + docIdElements := strings.Split(didId, ":") + var methodSpecificId string + var methodSpecificIdCondition string + + if isMSIBlockchainAccountId(didId) { + methodSpecificId = strings.Join(docIdElements[(len(docIdElements)-3):], ":") + methodSpecificIdCondition = MSIBlockchainAccountId + } else if isMSINonBlockchainAccountId(didId) { + methodSpecificId = docIdElements[len(docIdElements)-1] + methodSpecificIdCondition = MSINonBlockchainAccountId + } else { + return "", "", fmt.Errorf( + "unable to retrieve method-specific-id from DID Id: %v. It should either be a CAIP-10 Blockchain Account ID or a string consisting of alphanumeric, dot(.) and hyphen(-) characters only", didId) + } + + return methodSpecificId, methodSpecificIdCondition, nil +} + +// isMSINonBlockchainAccountId asserts if the Method Specific Id is a CAIP-10 Blockchain Account Id +func isMSINonBlockchainAccountId(didId string) bool { + didIdElements := strings.Split(didId, ":") + return (len(didIdElements) == 3 || len(didIdElements) == 4) +} + +// isMSIBlockchainAccountId asserts if the Method Specific Id is a string containing alphanumeric, dot (.) and hyphen (-) characters +func isMSIBlockchainAccountId(didId string) bool { + didIdElements := strings.Split(didId, ":") + return (len(didIdElements) == 5 || len(didIdElements) == 6) +} diff --git a/x/ssi/verification/crypto.go b/x/ssi/verification/crypto.go index 265765c..5fd10a6 100644 --- a/x/ssi/verification/crypto.go +++ b/x/ssi/verification/crypto.go @@ -179,7 +179,7 @@ func verifyCosmosBlockchainAccountId(blockchainAccountId, publicKeyMultibase str if err != nil { return err } - validAddressPrefix := types.CosmosCAIP10ChainIdBech32PrefixMap[chainId] + validAddressPrefix := types.CosmosCAIP10ChainIdBech32PrefixMap[chainId] convertedAddress, err := publicKeyToCosmosBech32Address(validAddressPrefix, publicKeyBytes) if err != nil { return err