openwallet-foundation / credo-ts

Typescript framework for building decentralized identity and verifiable credential solutions
https://credo.js.org
Apache License 2.0
251 stars 188 forks source link

Unable to share specific credential in a multi credential definition proof challenge. #1790

Open trta2352 opened 4 months ago

trta2352 commented 4 months ago

Wallet has 4 credentials. 2 times name_credential (same credential definition, different data) and 2 times email_credential(same credential definition, but different data). User then receives a proof challenge asking for both types of credentials. To fulfil the proof challenge user has to share 1 name_credential and 1 email_credential. But sine they have 2 of each type they have to choose which ones to share.

Using the following code I accept the proof challenge and I provide it with the filtered proof format containing the credentials I want to share.

   const acceptedRequest = await agent.proofs.acceptRequest({
            proofRecordId: proofRecord.id,
            proofFormats: credentialToShare,
          });
{
    "proofFormats":{
        "indy":{
            "attributes":{
                "f85c5f22-f03c-42cc-b597-1c8b1f292358":[
                    {
                        "credentialId":"51ce00f7-8351-47da-aa1c-2780f1d92bfb",
                        "revealed":true,
                        "credentialInfo":{
                            "attributes":{
                                "email":"miha.novak@gmail.com"
                            },
                            "credentialDefinitionId":"2jYo5xjBZe682KxZpxUyqY:3:CL:84:email_credential",
                            "credentialId":"51ce00f7-8351-47da-aa1c-2780f1d92bfb",
                            "schemaId":"2jYo5xjBZe682KxZpxUyqY:2:test_email:1.0",
                            "credentialRevocationId":"34",
                            "revocationRegistryId":"2jYo5xjBZe682KxZpxUyqY:4:2jYo5xjBZe682KxZpxUyqY:3:CL:84:email_credential:CL_ACCUM:2a79447c-def7-4f85-8f87-9507ae4b053f",
                            "methodName":"indy"
                        }
                    }
                ],
                "eed6a94d-e0d9-41a7-8390-34e1e55873d9":[
                    {
                        "credentialId":"fc89abd0-d4b6-4449-b436-950aad03558b",
                        "revealed":true,
                        "credentialInfo":{
                            "attributes":{
                                "name":"Miha Novak"
                            },
                            "credentialDefinitionId":"2jYo5xjBZe682KxZpxUyqY:3:CL:83:name_credential",
                            "credentialId":"fc89abd0-d4b6-4449-b436-950aad03558b",
                            "schemaId":"2jYo5xjBZe682KxZpxUyqY:2:test_name:1.0",
                            "credentialRevocationId":"45",
                            "revocationRegistryId":"2jYo5xjBZe682KxZpxUyqY:4:2jYo5xjBZe682KxZpxUyqY:3:CL:83:name_credential:CL_ACCUM:dfc725b9-b9ca-49cf-8c54-20c833c0667c",
                            "methodName":"indy"
                        }
                    }
                ]
            },
            "predicates":{

            }
        }
    }
}

Agent in this case completely disregards what I added in the proofFormats parameter and defaults to the credentials it deems that fulfil the proof challenge.

Using this code:

  const _credentialsForRequest = await agent.proofs.getCredentialsForRequest({
        proofRecordId: proofExchangeRecord[0].id,
      });

I get this:

{
    "proofFormats":{
        "indy":{
            "attributes":{
                "966cdcce-c812-4e3d-97d9-1a4be00acdd0":[
                    {
                        "credentialId":"186946ff-823f-4c05-8362-6f3ff0fdcd43",
                        "revealed":true,
                        "credentialInfo":{
                            "attributes":{
                                "email":"mail2@gmail.com"
                            },
                            "credentialDefinitionId":"2jYo5xjBZe682KxZpxUyqY:3:CL:84:email_credential",
                            "credentialId":"186946ff-823f-4c05-8362-6f3ff0fdcd43",
                            "schemaId":"2jYo5xjBZe682KxZpxUyqY:2:test_email:1.0",
                            "credentialRevocationId":"33",
                            "revocationRegistryId":"2jYo5xjBZe682KxZpxUyqY:4:2jYo5xjBZe682KxZpxUyqY:3:CL:84:email_credential:CL_ACCUM:2a79447c-def7-4f85-8f87-9507ae4b053f",
                            "methodName":"indy"
                        }
                    },
                    {
                        "credentialId":"51ce00f7-8351-47da-aa1c-2780f1d92bfb",
                        "revealed":true,
                        "credentialInfo":{
                            "attributes":{
                                "email":"miha.novak@gmail.com"
                            },
                            "credentialDefinitionId":"2jYo5xjBZe682KxZpxUyqY:3:CL:84:email_credential",
                            "credentialId":"51ce00f7-8351-47da-aa1c-2780f1d92bfb",
                            "schemaId":"2jYo5xjBZe682KxZpxUyqY:2:test_email:1.0",
                            "credentialRevocationId":"34",
                            "revocationRegistryId":"2jYo5xjBZe682KxZpxUyqY:4:2jYo5xjBZe682KxZpxUyqY:3:CL:84:email_credential:CL_ACCUM:2a79447c-def7-4f85-8f87-9507ae4b053f",
                            "methodName":"indy"
                        }
                    }
                ],
                "dba5129c-f1f5-4014-9b10-1072d2ff95a8":[
                    {
                        "credentialId":"41a30307-7aef-4b6e-83ca-0e847a5da442",
                        "revealed":true,
                        "credentialInfo":{
                            "attributes":{
                                "name":"Name 2"
                            },
                            "credentialDefinitionId":"2jYo5xjBZe682KxZpxUyqY:3:CL:83:name_credential",
                            "credentialId":"41a30307-7aef-4b6e-83ca-0e847a5da442",
                            "schemaId":"2jYo5xjBZe682KxZpxUyqY:2:test_name:1.0",
                            "credentialRevocationId":"44",
                            "revocationRegistryId":"2jYo5xjBZe682KxZpxUyqY:4:2jYo5xjBZe682KxZpxUyqY:3:CL:83:name_credential:CL_ACCUM:dfc725b9-b9ca-49cf-8c54-20c833c0667c",
                            "methodName":"indy"
                        }
                    },
                    {
                        "credentialId":"fc89abd0-d4b6-4449-b436-950aad03558b",
                        "revealed":true,
                        "credentialInfo":{
                            "attributes":{
                                "name":"Miha Novak"
                            },
                            "credentialDefinitionId":"2jYo5xjBZe682KxZpxUyqY:3:CL:83:name_credential",
                            "credentialId":"fc89abd0-d4b6-4449-b436-950aad03558b",
                            "schemaId":"2jYo5xjBZe682KxZpxUyqY:2:test_name:1.0",
                            "credentialRevocationId":"45",
                            "revocationRegistryId":"2jYo5xjBZe682KxZpxUyqY:4:2jYo5xjBZe682KxZpxUyqY:3:CL:83:name_credential:CL_ACCUM:dfc725b9-b9ca-49cf-8c54-20c833c0667c",
                            "methodName":"indy"
                        }
                    }
                ]
            },
            "predicates":{

            }
        }
    }
}

So even though Aries knows that multiple credentials fulfil the proof challenge it automatically reverts to the first credential that was added per credential definition. In this case for name_credential thats "Name 2" and for email_credential is "mail2@gmail.com".

TimoGlastra commented 4 months ago

Hey @trta2352, thanks for raising this issue, which version of Credo are you using?

TimoGlastra commented 4 months ago

Also -- which version of the proof protocol are you using?

trta2352 commented 4 months ago

This are all related packages:

   "@aries-framework/anoncreds": "0.4.2",
    "@aries-framework/anoncreds-rs": "0.4.2",
    "@aries-framework/askar": "0.4.2",
    "@aries-framework/core": "0.4.2",
    "@aries-framework/indy-sdk": "^0.4.2",
    "@aries-framework/indy-vdr": "0.4.2",
    "@aries-framework/react-hooks": "0.5.0",
    "@aries-framework/react-native": "0.4.2",
    "@hyperledger/anoncreds-react-native": "0.1.0",
    "@hyperledger/aries-askar-react-native": "0.1.0",
    "@hyperledger/indy-vdr-react-native": "0.1.0",

And we are using v1 version of the proof protocol.

Just for reference here is also our agent setup:

 const config: InitConfig = {
        label: 'redacted-wallet-label',
        logger: ariesAnalytics,
        autoUpdateStorageOnStartup: true,
        walletConfig: {
          id: walletId,
          key: code,
        },
      };

      // ID manager uses V1 protocol which is only compatible with LegacyIndyCredentialFormatService
      const legacyIndyCredentialFormatService = new LegacyIndyCredentialFormatService();
      const legacyIndyProofFormatService = new LegacyIndyProofFormatService();

      const agent = new Agent({
        config,
        dependencies: agentDependencies,
        modules: {
          // Askar handles the wallet functionalities
          askar: new AskarModule({
            ariesAskar,
          }),
          connections: new ConnectionsModule({ autoAcceptConnections: true }),
          credentials: new CredentialsModule({
            autoAcceptCredentials: AutoAcceptCredential.ContentApproved,
            credentialProtocols: [
              new V1CredentialProtocol({
                indyCredentialFormat: legacyIndyCredentialFormatService,
              }),
              new V2CredentialProtocol({
                credentialFormats: [
                  legacyIndyCredentialFormatService,
                  new AnonCredsCredentialFormatService(),
                ],
              }),
            ],
          }),
          mediationRecipient: new MediationRecipientModule({
            mediatorInvitationUrl: MEDIATOR_CONNECTIONS_INVITE,
            mediatorPickupStrategy: MediatorPickupStrategy.Implicit,
            mediatorPollingInterval: appConfig.debugApp ? 10000 : 5000,
          }),
          // Proof management
          proofs: new ProofsModule({
            autoAcceptProofs: AutoAcceptProof.Always,
            proofProtocols: [
              new V1ProofProtocol({
                indyProofFormat: legacyIndyProofFormatService,
              }),
              // V2 protocol is setup, but it is not being used.
              new V2ProofProtocol({
                proofFormats: [legacyIndyProofFormatService, new AnonCredsProofFormatService()],
              }),
            ],
          }),
          anoncredsRs: new AnonCredsRsModule({
            anoncreds,
          }),
          anoncreds: new AnonCredsModule({
            registries: [new IndyVdrAnonCredsRegistry(), new IndySdkAnonCredsRegistry()],
          }),
          // indyVdr handles the ledger functionalities.
          indyVdr: new IndyVdrModule({
            indyVdr,
            networks: [
              {
                isProduction: appConfig.testEnv,
                indyNamespace: 'did:sov',
                genesisTransactions: genesis,
                connectOnStartup: true,
              },
            ],
          }),
        },
      });

      agent.registerOutboundTransport(new HttpOutboundTransport());
      agent.registerOutboundTransport(new WsOutboundTransport());

      await agent.initialize();
trta2352 commented 4 months ago

@TimoGlastra Any suggestions or is upgrading to V2 protocol the only option?

trta2352 commented 4 months ago

@TimoGlastra Issue resolved. If you provide a custom proof format then selectedCredentials.selfAttestedAttributes is not present.

diff --git a/node_modules/@aries-framework/anoncreds-rs/build/services/AnonCredsRsHolderService.js b/node_modules/@aries-framework/anoncreds-rs/build/services/AnonCredsRsHolderService.js
index 0d2263e..707afcb 100644
--- a/node_modules/@aries-framework/anoncreds-rs/build/services/AnonCredsRsHolderService.js
+++ b/node_modules/@aries-framework/anoncreds-rs/build/services/AnonCredsRsHolderService.js
@@ -107,6 +107,11 @@ let AnonCredsRsHolderService = class AnonCredsRsHolderService {
             if (!linkSecretRecord.value) {
                 throw new AnonCredsRsError_1.AnonCredsRsError('Link Secret value not stored');
             }
+           
+            if (!selectedCredentials.selfAttestedAttributes ) {
+                selectedCredentials.selfAttestedAttributes = {}
+            }
             presentation = anoncreds_shared_1.Presentation.create({
                 credentialDefinitions: rsCredentialDefinitions,
                 schemas: rsSchemas,
TimoGlastra commented 4 months ago

Ah okay, we should fix this, so reopening this issue. Thanks for figuring this out!