From 47f7f0f5bd978e98959c4fc310ad106c1abae473 Mon Sep 17 00:00:00 2001 From: Arnab Ghose Date: Thu, 20 Jul 2023 10:13:07 +0530 Subject: [PATCH] feat: added support for X25519KeyVerificationKey2020 and X25519KeyAgreementKeyEIP5630 keyAgreement based keys --- tests/e2e/ssi_tests/e2e_tests.py | 82 ++++++++++++++++++++++++++- tests/e2e/ssi_tests/run.py | 1 + tests/e2e/ssi_tests/utils.py | 13 +++++ x/ssi/keeper/msg_server.go | 47 +++++++++------ x/ssi/keeper/msg_server_create_did.go | 7 +++ x/ssi/keeper/msg_server_update_did.go | 7 ++- x/ssi/types/common.go | 4 ++ x/ssi/types/diddoc_validation.go | 66 ++++++++++++++++++--- x/ssi/verification/crypto.go | 30 ++++++++++ 9 files changed, 232 insertions(+), 25 deletions(-) diff --git a/tests/e2e/ssi_tests/e2e_tests.py b/tests/e2e/ssi_tests/e2e_tests.py index 20d5ffb..a63094a 100644 --- a/tests/e2e/ssi_tests/e2e_tests.py +++ b/tests/e2e/ssi_tests/e2e_tests.py @@ -3,12 +3,92 @@ sys.path.insert(1, os.getcwd()) import time -from utils import run_blockchain_command, generate_key_pair, secp256k1_pubkey_to_address +from utils import run_blockchain_command, generate_key_pair, secp256k1_pubkey_to_address, add_keyAgreeemnt_pubKeyMultibase from generate_doc import generate_did_document, generate_schema_document, generate_cred_status_document from transactions import form_did_create_tx_multisig, form_did_update_tx_multisig, \ query_did, form_create_schema_tx, form_did_deactivate_tx_multisig, form_create_cred_status_tx from constants import DEFAULT_BLOCKCHAIN_ACCOUNT_NAME +def key_agrement_test(): + print("\n--1. FAIL: Ed25519VerificationKey2020 based Verification Method ID being added to keyAgreement attribute--\n") + + kp_alice = generate_key_pair("ed25519") + signers = [] + did_doc_string = generate_did_document(kp_alice, "ed25519") + did_doc_alice = did_doc_string["id"] + ed25519Vm = did_doc_string["verificationMethod"][0] + did_doc_string["keyAgreement"] = [ed25519Vm["id"]] + signPair_alice = { + "kp": kp_alice, + "verificationMethodId": did_doc_string["verificationMethod"][0]["id"], + "signing_algo": "ed25519" + } + 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}", True, True) + + print("\n--2. FAIL: X25519KeyAgreementKey2020 based Verification Method ID being added to authentication attribute--\n") + + kp_bob = generate_key_pair("ed25519") + signers = [] + did_doc_string = generate_did_document(kp_bob, "ed25519") + did_doc_alice = did_doc_string["id"] + x25519Vm = add_keyAgreeemnt_pubKeyMultibase(did_doc_string["verificationMethod"][0], "X25519KeyAgreementKey2020") + did_doc_string["verificationMethod"] = [x25519Vm] + did_doc_string["authentication"] = [x25519Vm["id"]] + signPair_bob = { + "kp": kp_bob, + "verificationMethodId": did_doc_string["verificationMethod"][0]["id"], + "signing_algo": "ed25519" + } + signers.append(signPair_bob) + 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}", True, True) + + print("\n--3. PASS: A DID Document is created with Ed25519VerificationKey2020 and X25519KeyAgreementKey2020 based VMs--\n") + did_doc_string = generate_did_document(kp_alice, "ed25519") + signers = [] + x25519Vm["controller"] = ed25519Vm["controller"] + did_doc_string["verificationMethod"] = [ed25519Vm, x25519Vm] + did_doc_string["authentication"] = [ed25519Vm["id"]] + did_doc_string["keyAgreement"] = [x25519Vm["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 Alice's DID with Id: {did_doc_string['id']}") + + print("\n--4. FAIL: An attempt is made to update the DID Document by passing the signature of X25519KeyVerificationKey2020 based verification method") + signers = [] + did_doc_string["context"] = ["some_context"] + did_doc_string["authentication"] = [] + signPair_x25519 = { + "kp": kp_bob, + "verificationMethodId": x25519Vm["id"], + "signing_algo": "ed25519" + } + signers.append(signPair_x25519) + update_tx_cmd = form_did_update_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME) + run_blockchain_command(update_tx_cmd, f"DID Document update using X25519KeyVerificationKey2020 based verification method", True) + + print("\n--5. PASS: An attempt is made to update the DID Document by passing the signature of Ed25519VerificationKey2020 based verification method") + signers = [] + did_doc_string["context"] = ["some_context"] + signers.append(signPair_alice) + signers.append(signPair_x25519) + update_tx_cmd = form_did_update_tx_multisig(did_doc_string, signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME) + run_blockchain_command(update_tx_cmd, f"DID Document {did_doc_string['id']} update using Ed25519VerificationKey2020 based verification method") + + print("\n--6. FAIL: An attempt is made to deactivate the DID Document by passing the signature of X25519KeyVerificationKey2020 based verification method--\n") + signers = [] + signers.append(signPair_x25519) + deactivate_tx_cmd = form_did_deactivate_tx_multisig(did_doc_string["id"], signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME) + run_blockchain_command(deactivate_tx_cmd, f"DID Document deactivate using X25519KeyVerificationKey2020 based verification method", True) + + print("\n--7. PASS: An attempt is made to deactivate the DID Document by passing the signature of Ed25519VerificationKey2020 based verification method--\n") + signers = [] + signers.append(signPair_alice) + deactivate_tx_cmd = form_did_deactivate_tx_multisig(did_doc_string["id"], signers, DEFAULT_BLOCKCHAIN_ACCOUNT_NAME) + run_blockchain_command(deactivate_tx_cmd, f"DID Document deactivate using Ed25519VerificationKey2020 based verification method") + def unique_wallet_address_test(): print("\n---1. FAIL: Alice Creates a DID Doc. Bob attempts to create a DID Document by adding one of Alice's VM.---\n") diff --git a/tests/e2e/ssi_tests/run.py b/tests/e2e/ssi_tests/run.py index 0667f32..76bd089 100644 --- a/tests/e2e/ssi_tests/run.py +++ b/tests/e2e/ssi_tests/run.py @@ -34,6 +34,7 @@ def run_all_tests(): vm_type_test() method_specific_id_test() unique_wallet_address_test() + key_agrement_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 33feb1e..ed0ec9c 100644 --- a/tests/e2e/ssi_tests/utils.py +++ b/tests/e2e/ssi_tests/utils.py @@ -59,6 +59,19 @@ def generate_key_pair(algo="ed25519"): kp = json.loads(result_str) return kp +def add_keyAgreeemnt_pubKeyMultibase(verification_method, type): + if verification_method["type"] != "Ed25519VerificationKey2020": + raise Exception("verification method " + verification_method["id"] + " must be of type Ed25519VerificationKey2020") + + if type == "X25519KeyAgreementKey2020": + verification_method["type"] = "X25519KeyAgreementKey2020" + elif type == "X25519KeyAgreementKeyEIP5630": + verification_method["type"] = "X25519KeyAgreementKeyEIP5630" + else: + raise Exception("invalid key agreement type " + type) + + return verification_method + def generate_document_id(doc_type: str, kp: dict = None, algo: str = "ed25519", is_uuid: bool =False): id = "" if not kp: diff --git a/x/ssi/keeper/msg_server.go b/x/ssi/keeper/msg_server.go index d27ca0c..6e4ea07 100644 --- a/x/ssi/keeper/msg_server.go +++ b/x/ssi/keeper/msg_server.go @@ -98,8 +98,12 @@ func (k msgServer) formMustControllerVmListMap(ctx sdk.Context, } _, presentInControllerMap := controllerMap[vmState.Controller] if presentInControllerMap { - vmExtended := types.CreateExtendedVerificationMethod(vmState, sign) - controllerMap[controller] = append(controllerMap[controller], vmExtended) + // Skip X25519KeyAgreementKey2020 or X25519KeyAgreementKey2020 because these + // are not allowed for Authentication and Assertion purposes + if (vmState.Type != types.X25519KeyAgreementKey2020) && (vmState.Type != types.X25519KeyAgreementKeyEIP5630) { + vmExtended := types.CreateExtendedVerificationMethod(vmState, sign) + controllerMap[controller] = append(controllerMap[controller], vmExtended) + } delete(inputSignMap, vmId) } } @@ -149,8 +153,12 @@ func (k msgServer) formAnyControllerVmListMap(ctx sdk.Context, } _, presentInControllerMap := controllerMap[vmState.Controller] if presentInControllerMap { - vmExtended := types.CreateExtendedVerificationMethod(vmState, sign) - controllerMap[controller] = append(controllerMap[controller], vmExtended) + // Skip X25519KeyAgreementKey2020 or X25519KeyAgreementKey2020 because these + // are not allowed for Authentication and Assertion purposes + if (vmState.Type != types.X25519KeyAgreementKey2020) && (vmState.Type != types.X25519KeyAgreementKeyEIP5630) { + vmExtended := types.CreateExtendedVerificationMethod(vmState, sign) + controllerMap[controller] = append(controllerMap[controller], vmExtended) + } } } } @@ -178,8 +186,8 @@ func (k msgServer) getControllerVmFromState(ctx sdk.Context, verificationMethodI // VerifyDocumentProof verifies the proof of a SSI Document func (k msgServer) VerifyDocumentProof(ctx sdk.Context, ssiMsg types.SsiMsg, inputDocProof types.SSIProofInterface, clientSpec *types.ClientSpec) error { // Get DID Document from State - schemaProofVmId := inputDocProof.GetVerificationMethod() - didId, _ := types.SplitDidUrl(schemaProofVmId) + docProofVmId := inputDocProof.GetVerificationMethod() + didId, _ := types.SplitDidUrl(docProofVmId) didDocumentState, err := k.GetDidDocumentState(&ctx, didId) if err != nil { return err @@ -187,24 +195,31 @@ func (k msgServer) VerifyDocumentProof(ctx sdk.Context, ssiMsg types.SsiMsg, inp didDoc := didDocumentState.DidDocument // Search for Verification Method in DID Document - var schemaVm *types.VerificationMethod = nil + var docVm *types.VerificationMethod = nil for _, vm := range didDoc.VerificationMethod { - if vm.Id == schemaProofVmId { - schemaVm = vm + if vm.Id == docProofVmId { + docVm = vm break } } - if schemaVm == nil { - return fmt.Errorf("verificationMethod %s is not present in DID document %s", schemaProofVmId, didId) + if docVm == nil { + return fmt.Errorf("verificationMethod %s is not present in DID document %s", docProofVmId, didId) + } + + // VerificationKeySignatureMap has X25519KeyAgreementKey2020 and X25519KeyAgreementKeyEIP5630 as supported Verification Type. + // However, they are not allowed to be used for Authentication or Assertion purposes. Since, their corresponding values in the map + // are empty string, the following check is in place. + if types.VerificationKeySignatureMap[docVm.Type] == "" { + return fmt.Errorf("proof type must be specified") } // Check if the Proof Type is correct - if types.VerificationKeySignatureMap[schemaVm.Type] != inputDocProof.GetType() { + if types.VerificationKeySignatureMap[docVm.Type] != inputDocProof.GetType() { return fmt.Errorf( "expected proof type to be %v as the verificationMethod type of %v is %v, recieved %v", - types.VerificationKeySignatureMap[schemaVm.Type], - schemaVm.Id, - schemaVm.Type, + types.VerificationKeySignatureMap[docVm.Type], + docVm.Id, + docVm.Type, inputDocProof.GetType(), ) } @@ -215,7 +230,7 @@ func (k msgServer) VerifyDocumentProof(ctx sdk.Context, ssiMsg types.SsiMsg, inp Signature: inputDocProof.GetProofValue(), ClientSpec: clientSpec, } - err = verification.VerifyDocumentProofSignature(ssiMsg, schemaVm, signInfo) + err = verification.VerifyDocumentProofSignature(ssiMsg, docVm, signInfo) if err != nil { return err } diff --git a/x/ssi/keeper/msg_server_create_did.go b/x/ssi/keeper/msg_server_create_did.go index 96cbc76..4aea0ce 100644 --- a/x/ssi/keeper/msg_server_create_did.go +++ b/x/ssi/keeper/msg_server_create_did.go @@ -165,6 +165,13 @@ func getVerificationMethodsForCreateDID(didDocument *types.Did) ([]*types.Verifi if vm.Controller == didDocument.Id { foundAtleastOneSubjectVM = true } + + // Skip X25519KeyAgreementKey2020 or X25519KeyAgreementKey2020 because these + // are not allowed for Authentication and Assertion purposes + if (vm.Type == types.X25519KeyAgreementKey2020) || (vm.Type == types.X25519KeyAgreementKeyEIP5630) { + continue + } + mustHaveVerificaitonMethods = append(mustHaveVerificaitonMethods, vm) } diff --git a/x/ssi/keeper/msg_server_update_did.go b/x/ssi/keeper/msg_server_update_did.go index 1a68bf7..09d8af1 100644 --- a/x/ssi/keeper/msg_server_update_did.go +++ b/x/ssi/keeper/msg_server_update_did.go @@ -270,6 +270,11 @@ func getVerificationMethodsForUpdateDID(existingVMs []*types.VerificationMethod, // Make map of existing VMs existingVmMap := map[string]*types.VerificationMethod{} for _, vm := range existingVMs { + // Skip X25519KeyAgreementKey2020 or X25519KeyAgreementKey2020 because these + // are not allowed for Authentication and Assertion purposes + if ((vm.Type == types.X25519KeyAgreementKey2020) || (vm.Type == types.X25519KeyAgreementKeyEIP5630)) { + continue + } existingVmMap[vm.Id] = vm } @@ -280,7 +285,7 @@ func getVerificationMethodsForUpdateDID(existingVMs []*types.VerificationMethod, // Check if VM is present in existing VM map. // If it's not present, the VM is being added to existing Did Document. // Add the VM to "required" group - if _, present := existingVmMap[vm.Id]; !present { + if _, present := existingVmMap[vm.Id]; !present && ((vm.Type != types.X25519KeyAgreementKey2020) && (vm.Type != types.X25519KeyAgreementKeyEIP5630)) { updatedVms = append( updatedVms, vm, diff --git a/x/ssi/types/common.go b/x/ssi/types/common.go index 440a703..b69bfb3 100644 --- a/x/ssi/types/common.go +++ b/x/ssi/types/common.go @@ -8,12 +8,16 @@ const MSINonBlockchainAccountId = "MSINonBlockchainAccountId" const Ed25519VerificationKey2020 = "Ed25519VerificationKey2020" const EcdsaSecp256k1VerificationKey2019 = "EcdsaSecp256k1VerificationKey2019" const EcdsaSecp256k1RecoveryMethod2020 = "EcdsaSecp256k1RecoveryMethod2020" +const X25519KeyAgreementKey2020 = "X25519KeyAgreementKey2020" +const X25519KeyAgreementKeyEIP5630 = "X25519KeyAgreementKeyEIP5630" // TODO: Temporary spec name for KeyAgreement type from Metamask // Mapping between Verification Key and its corresponding Signature var VerificationKeySignatureMap = map[string]string{ Ed25519VerificationKey2020: "Ed25519Signature2020", EcdsaSecp256k1VerificationKey2019: "EcdsaSecp256k1Signature2019", EcdsaSecp256k1RecoveryMethod2020: "EcdsaSecp256k1RecoverySignature2020", + X25519KeyAgreementKey2020: "", // Authentication and Assertion are not allowed + X25519KeyAgreementKeyEIP5630: "", // Authentication and Assertion are not allowed } var supportedVerificationMethodTypes []string = func() []string { diff --git a/x/ssi/types/diddoc_validation.go b/x/ssi/types/diddoc_validation.go index 039cb16..6c1ce5a 100644 --- a/x/ssi/types/diddoc_validation.go +++ b/x/ssi/types/diddoc_validation.go @@ -142,6 +142,36 @@ func verificationKeyCheck(vm *VerificationMethod) error { vm.Type, ) } + case X25519KeyAgreementKey2020: + if vm.GetPublicKeyMultibase() == "" { + return fmt.Errorf( + "publicKeyMultibase cannot be empty for verification method %s as it is of type %s", + vm.Id, + vm.Type, + ) + } + if vm.GetBlockchainAccountId() != "" { + return fmt.Errorf( + "blockchainAccountId must be empty for verification method %s as it is of type %s", + vm.Id, + vm.Type, + ) + } + case X25519KeyAgreementKeyEIP5630: + if vm.GetPublicKeyMultibase() == "" { + return fmt.Errorf( + "publicKeyMultibase cannot be empty for verification method %s as it is of type %s", + vm.Id, + vm.Type, + ) + } + if vm.GetBlockchainAccountId() != "" { + return fmt.Errorf( + "blockchainAccountId must be empty for verification method %s as it is of type %s", + vm.Id, + vm.Type, + ) + } default: return fmt.Errorf("unsupported verification method type: %v", supportedVerificationMethodTypes) } @@ -268,10 +298,10 @@ func validateVerificationMethods(vms []*VerificationMethod) error { } func validateVmRelationships(didDoc *Did) error { - // make verificationMethods map - vmMap := map[string]bool{} + // make verificationMethodType map between VM Id and VM type + vmTypeMap := map[string]string{} for _, vm := range didDoc.VerificationMethod { - vmMap[vm.Id] = true + vmTypeMap[vm.Id] = vm.Type } vmRelationshipList := map[string][]string{ @@ -284,18 +314,40 @@ func validateVmRelationships(didDoc *Did) error { for field, vmRelationship := range vmRelationshipList { // didUrl check and presence in verification methods - for _, element := range vmRelationship { - err := isDidUrl(element) + for _, vmId := range vmRelationship { + err := isDidUrl(vmId) if err != nil { return fmt.Errorf("%s: %s", field, err) } - if _, found := vmMap[element]; !found { + + if _, found := vmTypeMap[vmId]; !found { return fmt.Errorf( "%s: verification method id %s not found in verificationMethod list", field, - element, + vmId, ) } + + // keyAgreement field should harbour only those Verification Methods whose type is either X25519KeyAgreementKey2020 + // or X25519KeyAgreementKeyEIP5630 + if (vmTypeMap[vmId] == X25519KeyAgreementKey2020) || (vmTypeMap[vmId] == X25519KeyAgreementKeyEIP5630) { + if (field != "keyAgreement") { + return fmt.Errorf( + "verification method id %v is of type %v which is not allowed in '%v' attribute", + vmId, + vmTypeMap[vmId], + field, + ) + } + } else { + if (field == "keyAgreement") { + return fmt.Errorf( + "verification method id %v provided in '%v' attribute must be of type X25519KeyAgreementKey2020 or X25519KeyAgreementKeyEIP5630", + vmId, + field, + ) + } + } } } diff --git a/x/ssi/verification/crypto.go b/x/ssi/verification/crypto.go index 5fd10a6..09c72c7 100644 --- a/x/ssi/verification/crypto.go +++ b/x/ssi/verification/crypto.go @@ -53,6 +53,10 @@ func verify(extendedVm *types.ExtendedVerificationMethod, ssiMsg types.SsiMsg) e return verifyEcdsaSecp256k1VerificationKey2019Key(extendedVm, docBytes) case types.EcdsaSecp256k1RecoveryMethod2020: return verifyEcdsaSecp256k1RecoveryMethod2020Key(extendedVm, docBytes) + case types.X25519KeyAgreementKey2020: + return verifyX25519KeyAgreementKey2020Key(extendedVm) + case types.X25519KeyAgreementKeyEIP5630: + return verifyX25519KeyAgreementKeyEIP5630Key(extendedVm) default: return fmt.Errorf("unsupported verification method: %s", extendedVm.Type) } @@ -241,3 +245,29 @@ func verifyEthereumBlockchainAccountId(extendedVm *types.ExtendedVerificationMet return nil } } + +// verifyX25519KeyAgreementKey2020Key verifies the verification key for verification method type X25519KeyAgreementKey2020 +func verifyX25519KeyAgreementKey2020Key(extendedVm *types.ExtendedVerificationMethod) error { + _, _, err := multibase.Decode(extendedVm.PublicKeyMultibase) + if err != nil { + return fmt.Errorf( + "cannot decode X25519KeyAgreementKey2020 public key %s", + extendedVm.PublicKeyMultibase, + ) + } + + return nil +} + +// verifyX25519KeyAgreementKeyEIP5630Key verifies the verification key for verification method type X25519KeyAgreementKeyEIP5630 +func verifyX25519KeyAgreementKeyEIP5630Key(extendedVm *types.ExtendedVerificationMethod) error { + _, _, err := multibase.Decode(extendedVm.PublicKeyMultibase) + if err != nil { + return fmt.Errorf( + "cannot decode X25519KeyAgreementKeyEIP5630 public key %s", + extendedVm.PublicKeyMultibase, + ) + } + + return nil +}