diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts index 4a34024851..b4c1e02f53 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts @@ -199,7 +199,9 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { if (!linkSecretRecord) { // No default link secret - throw new AnonCredsRsError('No default link secret has been found') + throw new AnonCredsRsError( + 'No link secret provided to createCredentialRequest and no default link secret has been found' + ) } const { credentialRequest, credentialRequestMetadata } = CredentialRequest.create({ diff --git a/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts b/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts index 33a306617a..850e9c9a0f 100644 --- a/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts +++ b/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts @@ -6,13 +6,16 @@ import { CredentialPreviewAttribute, ProofExchangeRecord, ProofState, + EventEmitter, } from '@aries-framework/core' import * as indySdk from 'indy-sdk' +import { Subject } from 'rxjs' -import { getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' +import { agentDependencies, getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' import { IndySdkHolderService, IndySdkIssuerService, + IndySdkStorageService, IndySdkVerifierService, IndySdkWallet, } from '../../../../indy-sdk/src' @@ -20,6 +23,7 @@ import { IndySdkRevocationService } from '../../../../indy-sdk/src/anoncreds/ser import { indyDidFromPublicKeyBase58 } from '../../../../indy-sdk/src/utils/did' import { InMemoryAnonCredsRegistry } from '../../../tests/InMemoryAnonCredsRegistry' import { AnonCredsModuleConfig } from '../../AnonCredsModuleConfig' +import { AnonCredsLinkSecretRecord, AnonCredsLinkSecretRepository } from '../../repository' import { AnonCredsHolderServiceSymbol, AnonCredsIssuerServiceSymbol, @@ -40,6 +44,9 @@ const anonCredsVerifierService = new IndySdkVerifierService(indySdk) const anonCredsHolderService = new IndySdkHolderService(anonCredsRevocationService, indySdk) const anonCredsIssuerService = new IndySdkIssuerService(indySdk) const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new SigningProviderRegistry([])) +const storageService = new IndySdkStorageService(indySdk) +const eventEmitter = new EventEmitter(agentDependencies, new Subject()) +const anonCredsLinkSecretRepository = new AnonCredsLinkSecretRepository(storageService, eventEmitter) const agentContext = getAgentContext({ registerInstances: [ [AnonCredsIssuerServiceSymbol, anonCredsIssuerService], @@ -47,6 +54,7 @@ const agentContext = getAgentContext({ [AnonCredsVerifierServiceSymbol, anonCredsVerifierService], [AnonCredsRegistryService, new AnonCredsRegistryService()], [AnonCredsModuleConfig, anonCredsModuleConfig], + [AnonCredsLinkSecretRepository, anonCredsLinkSecretRepository], ], agentConfig, wallet, @@ -71,6 +79,16 @@ describe('Legacy indy format services', () => { const key = await wallet.createKey({ keyType: KeyType.Ed25519 }) const indyDid = indyDidFromPublicKeyBase58(key.publicKeyBase58) + // Create link secret + await anonCredsHolderService.createLinkSecret(agentContext, { + linkSecretId: 'link-secret-id', + }) + const anonCredsLinkSecret = new AnonCredsLinkSecretRecord({ + linkSecretId: 'link-secret-id', + }) + anonCredsLinkSecret.setTag('isDefault', true) + await anonCredsLinkSecretRepository.save(agentContext, anonCredsLinkSecret) + const schema = await anonCredsIssuerService.createSchema(agentContext, { attrNames: ['name', 'age'], issuerId: indyDid, diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts index df66a6217d..be1b7c1deb 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts @@ -332,6 +332,11 @@ describe('V1 Proofs - Connectionless - Indy', () => { expect(faberConnection.isReady).toBe(true) expect(aliceConnection.isReady).toBe(true) + await aliceAgent.modules.anoncreds.createLinkSecret({ + linkSecretId: 'default', + setAsDefault: true, + }) + await issueLegacyAnonCredsCredential({ issuerAgent: faberAgent, issuerReplay: faberReplay, diff --git a/packages/anoncreds/tests/legacyAnonCredsSetup.ts b/packages/anoncreds/tests/legacyAnonCredsSetup.ts index e5a1082b64..9b6da7fd32 100644 --- a/packages/anoncreds/tests/legacyAnonCredsSetup.ts +++ b/packages/anoncreds/tests/legacyAnonCredsSetup.ts @@ -329,6 +329,12 @@ export async function setupAnonCredsTests< await holderAgent.initialize() if (verifierAgent) await verifierAgent.initialize() + // Create default link secret for holder + await holderAgent.modules.anoncreds.createLinkSecret({ + linkSecretId: 'default', + setAsDefault: true, + }) + const { credentialDefinition, schema } = await prepareForAnonCredsIssuance(issuerAgent, { attributeNames, // TODO: replace with more dynamic / generic value We should create a did using the dids module diff --git a/packages/askar/src/wallet/AskarWallet.ts b/packages/askar/src/wallet/AskarWallet.ts index 02564c4d0a..0fc11a3237 100644 --- a/packages/askar/src/wallet/AskarWallet.ts +++ b/packages/askar/src/wallet/AskarWallet.ts @@ -110,16 +110,6 @@ export class AskarWallet implements Wallet { return this._session } - public get masterSecretId() { - if (!this.isInitialized || !(this.walletConfig?.id || this.walletConfig?.masterSecretId)) { - throw new AriesFrameworkError( - 'Wallet has not been initialized yet. Make sure to await agent.initialize() before using the agent.' - ) - } - - return this.walletConfig?.masterSecretId ?? this.walletConfig.id - } - /** * Dispose method is called when an agent context is disposed. */ @@ -156,8 +146,6 @@ export class AskarWallet implements Wallet { }) this.walletConfig = walletConfig this._session = await this._store.openSession() - - // TODO: Master Secret creation (now part of IndyCredx/AnonCreds) } catch (error) { // FIXME: Askar should throw a Duplicate error code, but is currently returning Encryption // And if we provide the very same wallet key, it will open it without any error diff --git a/packages/askar/src/wallet/__tests__/AskarWallet.test.ts b/packages/askar/src/wallet/__tests__/AskarWallet.test.ts index 40dea0cbc8..e5ac122786 100644 --- a/packages/askar/src/wallet/__tests__/AskarWallet.test.ts +++ b/packages/askar/src/wallet/__tests__/AskarWallet.test.ts @@ -49,10 +49,6 @@ describe('AskarWallet basic operations', () => { await askarWallet.delete() }) - test('Get the Master Secret', () => { - expect(askarWallet.masterSecretId).toEqual('Wallet: AskarWalletTest') - }) - test('Get the wallet store', () => { expect(askarWallet.store).toEqual(expect.any(Store)) }) @@ -124,10 +120,6 @@ describe('AskarWallet basic operations', () => { }) await expect(askarWallet.verify({ key: ed25519Key, data: message, signature })).resolves.toStrictEqual(true) }) - - test('masterSecretId is equal to wallet ID by default', async () => { - expect(askarWallet.masterSecretId).toEqual(walletConfig.id) - }) }) describe.skip('Currently, all KeyTypes are supported by Askar natively', () => { diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts index 95c48a3ee9..cb2fd2b0c1 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts @@ -66,6 +66,12 @@ describe('V2 Connectionless Credentials', () => { aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() + // Create link secret for alice + await aliceAgent.modules.anoncreds.createLinkSecret({ + linkSecretId: 'default', + setAsDefault: true, + }) + const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { issuerId: faberAgent.publicDid?.did as string, attributeNames: ['name', 'age'], diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts index 0c7bd46567..114cf1cf2d 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts @@ -168,6 +168,12 @@ describe('V2 Credentials - JSON-LD - Ed25519', () => { await aliceAgent.initialize() ;[, { id: aliceConnectionId }] = await makeConnection(faberAgent, aliceAgent) + // Create link secret for alice + await aliceAgent.modules.anoncreds.createLinkSecret({ + linkSecretId: 'default', + setAsDefault: true, + }) + const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { attributeNames: ['name', 'age', 'profile_picture', 'x-ray'], issuerId: faberAgent.publicDid?.did as string, diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts index 0482ed4a86..5b343f3471 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts @@ -322,6 +322,11 @@ describe('V2 Connectionless Proofs - Indy', () => { ) agents = [aliceAgent, faberAgent, mediatorAgent] + await aliceAgent.modules.anoncreds.createLinkSecret({ + linkSecretId: 'default', + setAsDefault: true, + }) + const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { attributeNames: ['name', 'age', 'image_0', 'image_1'], issuerId: faberAgent.publicDid?.did as string, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index f621d4393e..4cd29a4ba2 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -22,7 +22,6 @@ export interface WalletConfig { key: string keyDerivationMethod?: KeyDerivationMethod storage?: WalletStorageConfig - masterSecretId?: string } export interface WalletConfigRekey { diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts index d5e82deea7..394ed39198 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts @@ -25,7 +25,8 @@ import type { IndyProofRequest, } from 'indy-sdk' -import { injectable, inject, utils } from '@aries-framework/core' +import { AnonCredsLinkSecretRepository } from '@aries-framework/anoncreds' +import { AriesFrameworkError, injectable, inject, utils } from '@aries-framework/core' import { IndySdkError, isIndyError } from '../../error' import { IndySdk, IndySdkSymbol } from '../../types' @@ -80,6 +81,8 @@ export class IndySdkHolderService implements AnonCredsHolderService { assertIndySdkWallet(agentContext.wallet) + const linkSecretRepository = agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository) + try { agentContext.config.logger.debug('Creating Indy Proof') const indyRevocationStates: RevStates = await this.indyRevocationService.createRevocationState( @@ -114,11 +117,19 @@ export class IndySdkHolderService implements AnonCredsHolderService { indySchemas[schemaId] = indySdkSchemaFromAnonCreds(schemaId, schema, seqNoMap[schemaId]) } + const linkSecretRecord = await linkSecretRepository.findDefault(agentContext) + if (!linkSecretRecord) { + // No default link secret + throw new AriesFrameworkError( + 'No default link secret found. Indy SDK requires a default link secret to be created before creating a proof.' + ) + } + const indyProof = await this.indySdk.proverCreateProof( agentContext.wallet.handle, proofRequest as IndyProofRequest, this.parseSelectedCredentials(selectedCredentials), - agentContext.wallet.masterSecretId, + linkSecretRecord.linkSecretId, indySchemas, indyCredentialDefinitions, indyRevocationStates @@ -201,10 +212,24 @@ export class IndySdkHolderService implements AnonCredsHolderService { ): Promise { assertIndySdkWallet(agentContext.wallet) + const linkSecretRepository = agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository) + // We just generate a prover did like string, as it's not used for anything and we don't need // to prove ownership of the did. It's deprecated in AnonCreds v1, but kept for backwards compatibility const proverDid = generateLegacyProverDidLikeString() + // If a link secret is specified, use it. Otherwise, attempt to use default link secret + const linkSecretRecord = options.linkSecretId + ? await linkSecretRepository.getByLinkSecretId(agentContext, options.linkSecretId) + : await linkSecretRepository.findDefault(agentContext) + + if (!linkSecretRecord) { + // No default link secret + throw new AriesFrameworkError( + 'No link secret provided to createCredentialRequest and no default link secret has been found' + ) + } + try { const result = await this.indySdk.proverCreateCredentialReq( agentContext.wallet.handle, @@ -213,9 +238,7 @@ export class IndySdkHolderService implements AnonCredsHolderService { // NOTE: Is it safe to use the cred_def_id from the offer? I think so. You can't create a request // for a cred def that is not in the offer indySdkCredentialDefinitionFromAnonCreds(options.credentialOffer.cred_def_id, options.credentialDefinition), - // FIXME: we need to remove the masterSecret from the wallet, as it is AnonCreds specific - // Issue: https://github.com/hyperledger/aries-framework-javascript/issues/1198 - agentContext.wallet.masterSecretId + linkSecretRecord.linkSecretId ) return { diff --git a/packages/indy-sdk/src/wallet/IndySdkWallet.ts b/packages/indy-sdk/src/wallet/IndySdkWallet.ts index 043e079c05..16561bbcf1 100644 --- a/packages/indy-sdk/src/wallet/IndySdkWallet.ts +++ b/packages/indy-sdk/src/wallet/IndySdkWallet.ts @@ -81,16 +81,6 @@ export class IndySdkWallet implements Wallet { return this.walletHandle } - public get masterSecretId() { - if (!this.isInitialized || !(this.walletConfig?.id || this.walletConfig?.masterSecretId)) { - throw new AriesFrameworkError( - 'Wallet has not been initialized yet. Make sure to await agent.initialize() before using the agent.' - ) - } - - return this.walletConfig?.masterSecretId ?? this.walletConfig.id - } - /** * Dispose method is called when an agent context is disposed. */ @@ -155,15 +145,8 @@ export class IndySdkWallet implements Wallet { await this.indySdk.createWallet(this.walletStorageConfig(walletConfig), this.walletCredentials(walletConfig)) this.walletConfig = walletConfig - // We usually want to create master secret only once, therefore, we can to do so when creating a wallet. await this.open(walletConfig) - - // We need to open wallet before creating master secret because we need wallet handle here. - await this.createMasterSecret(this.handle, this.masterSecretId) } catch (error) { - // If an error ocurred while creating the master secret, we should close the wallet - if (this.isInitialized) await this.close() - if (isIndyError(error, 'WalletAlreadyExistsError')) { const errorMessage = `Wallet '${walletConfig.id}' already exists` this.logger.debug(errorMessage) @@ -394,51 +377,6 @@ export class IndySdkWallet implements Wallet { } } - /** - * Create master secret with specified id in currently opened wallet. - * - * If a master secret by this id already exists in the current wallet, the method - * will return without doing anything. - * - * @throws {WalletError} if an error occurs - */ - private async createMasterSecret(walletHandle: number, masterSecretId: string): Promise { - this.logger.debug(`Creating master secret with id '${masterSecretId}' in wallet with handle '${walletHandle}'`) - - try { - await this.indySdk.proverCreateMasterSecret(walletHandle, masterSecretId) - - return masterSecretId - } catch (error) { - if (isIndyError(error, 'AnoncredsMasterSecretDuplicateNameError')) { - // master secret id is the same as the master secret id passed in the create function - // so if it already exists we can just assign it. - this.logger.debug( - `Master secret with id '${masterSecretId}' already exists in wallet with handle '${walletHandle}'`, - { - indyError: 'AnoncredsMasterSecretDuplicateNameError', - } - ) - - return masterSecretId - } else { - if (!isIndyError(error)) { - throw new AriesFrameworkError('Attempted to throw Indy error, but it was not an Indy error') - } - - this.logger.error(`Error creating master secret with id ${masterSecretId}`, { - indyError: error.indyName, - error, - }) - - throw new WalletError( - `Error creating master secret with id ${masterSecretId} in wallet with handle '${walletHandle}'`, - { cause: error } - ) - } - } - } - public async initPublicDid(didConfig: DidConfig) { const { did, verkey } = await this.createDid(didConfig) this.publicDidInfo = { diff --git a/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts b/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts index c80ea47b4a..dff7241b85 100644 --- a/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts +++ b/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts @@ -20,14 +20,6 @@ const walletConfig: WalletConfig = { keyDerivationMethod: KeyDerivationMethod.Raw, } -const walletConfigWithMasterSecretId: WalletConfig = { - id: 'Wallet: WalletTestWithMasterSecretId', - // generated using indy.generateWalletKey - key: 'CwNJroKHTSSj3XvE7ZAnuKiTn2C4QkFvxEqfm5rzhNrb', - keyDerivationMethod: KeyDerivationMethod.Raw, - masterSecretId: 'customMasterSecretId', -} - describe('IndySdkWallet', () => { let indySdkWallet: IndySdkWallet @@ -51,10 +43,6 @@ describe('IndySdkWallet', () => { }) }) - test('Get the Master Secret', () => { - expect(indySdkWallet.masterSecretId).toEqual('Wallet: IndySdkWalletTest') - }) - test('Get the wallet handle', () => { expect(indySdkWallet.handle).toEqual(expect.any(Number)) }) @@ -108,25 +96,4 @@ describe('IndySdkWallet', () => { }) await expect(indySdkWallet.verify({ key: ed25519Key, data: message, signature })).resolves.toStrictEqual(true) }) - - test('masterSecretId is equal to wallet ID by default', async () => { - expect(indySdkWallet.masterSecretId).toEqual(walletConfig.id) - }) -}) - -describe('IndySdkWallet with custom Master Secret Id', () => { - let indySdkWallet: IndySdkWallet - - beforeEach(async () => { - indySdkWallet = new IndySdkWallet(indySdk, testLogger, new SigningProviderRegistry([])) - await indySdkWallet.createAndOpen(walletConfigWithMasterSecretId) - }) - - afterEach(async () => { - await indySdkWallet.delete() - }) - - test('masterSecretId is set by config', async () => { - expect(indySdkWallet.masterSecretId).toEqual(walletConfigWithMasterSecretId.masterSecretId) - }) }) diff --git a/tests/e2e-test.ts b/tests/e2e-test.ts index b1bee59014..34960d8d8f 100644 --- a/tests/e2e-test.ts +++ b/tests/e2e-test.ts @@ -50,6 +50,9 @@ export async function e2eTest({ const [recipientSenderConnection, senderRecipientConnection] = await makeConnection(recipientAgent, senderAgent) expect(recipientSenderConnection).toBeConnectedWith(senderRecipientConnection) + // Create link secret with default options. This should create a default link secret. + await recipientAgent.modules.anoncreds.createLinkSecret() + // Issue credential from sender to recipient const { credentialDefinition } = await prepareForAnonCredsIssuance(senderAgent, { attributeNames: ['name', 'age', 'dateOfBirth'],