diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index 8868ed35ae..63ae8c3830 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -39,7 +39,6 @@ export class Agent { protected logger: Logger protected container: DependencyContainer protected eventEmitter: EventEmitter - protected wallet: Wallet protected messageReceiver: MessageReceiver protected transportService: TransportService protected messageSender: MessageSender @@ -54,6 +53,7 @@ export class Agent { public readonly mediationRecipient!: RecipientModule public readonly mediator!: MediatorModule public readonly discovery!: DiscoverFeaturesModule + public readonly wallet: Wallet public constructor(initialConfig: InitConfig, dependencies: AgentDependencies) { // Create child container so we don't interfere with anything outside of this agent @@ -181,7 +181,7 @@ export class Agent { this._isInitialized = true } - public async shutdown({ deleteWallet = false }: { deleteWallet?: boolean } = {}) { + public async shutdown() { // All observables use takeUntil with the stop$ observable // this means all observables will stop running if a value is emitted on this observable this.agentConfig.stop$.next(true) @@ -194,13 +194,9 @@ export class Agent { transport.stop() } - // close/delete wallet if still initialized + // close wallet if still initialized if (this.wallet.isInitialized) { - if (deleteWallet) { - await this.wallet.delete() - } else { - await this.wallet.close() - } + await this.wallet.close() } } diff --git a/packages/core/src/modules/credentials/__tests__/CredentialService.test.ts b/packages/core/src/modules/credentials/__tests__/CredentialService.test.ts index 1feb97de82..f0ca2f12fb 100644 --- a/packages/core/src/modules/credentials/__tests__/CredentialService.test.ts +++ b/packages/core/src/modules/credentials/__tests__/CredentialService.test.ts @@ -563,7 +563,7 @@ describe('CredentialService', () => { '~please_ack': expect.any(Object), }) - // We're using instance of `StubWallet`. Value of `cred` should be as same as in the credential response message. + // Value of `cred` should be as same as in the credential response message. const [cred] = await indyIssuerService.createCredential({ credentialOffer: credOffer, credentialRequest: credReq, diff --git a/packages/core/src/modules/credentials/__tests__/StubWallet.ts b/packages/core/src/modules/credentials/__tests__/StubWallet.ts deleted file mode 100644 index 02ed5860c3..0000000000 --- a/packages/core/src/modules/credentials/__tests__/StubWallet.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ - -import type { WireMessage, UnpackedMessageContext, WalletConfig } from '../../../types' -import type { Buffer } from '../../../utils/buffer' -import type { DidConfig, DidInfo, Wallet } from '../../../wallet/Wallet' - -export class StubWallet implements Wallet { - public get isInitialized() { - return true - } - - public get walletHandle() { - return 0 - } - public get publicDid() { - return undefined - } - public initialize(walletConfig: WalletConfig): Promise { - return Promise.resolve() - } - public close(): Promise { - throw new Error('Method not implemented.') - } - - public delete(): Promise { - throw new Error('Method not implemented.') - } - public initPublicDid(didConfig: DidConfig): Promise { - throw new Error('Method not implemented.') - } - public createDid(didConfig?: DidConfig | undefined): Promise { - throw new Error('Method not implemented.') - } - - public pack(payload: Record, recipientKeys: string[], senderVerkey?: string): Promise { - throw new Error('Method not implemented.') - } - public unpack(messagePackage: WireMessage): Promise { - throw new Error('Method not implemented.') - } - public sign(data: Buffer, verkey: string): Promise { - throw new Error('Method not implemented.') - } - public verify(signerVerkey: string, data: Buffer, signature: Buffer): Promise { - throw new Error('Method not implemented.') - } - - public async generateNonce(): Promise { - throw new Error('Method not implemented') - } -} diff --git a/packages/core/src/modules/routing/__tests__/mediation.test.ts b/packages/core/src/modules/routing/__tests__/mediation.test.ts index e3b4352671..f8cf4c4160 100644 --- a/packages/core/src/modules/routing/__tests__/mediation.test.ts +++ b/packages/core/src/modules/routing/__tests__/mediation.test.ts @@ -25,15 +25,12 @@ describe('mediator establishment', () => { let senderAgent: Agent afterEach(async () => { - await recipientAgent.shutdown({ - deleteWallet: true, - }) - await mediatorAgent.shutdown({ - deleteWallet: true, - }) - await senderAgent.shutdown({ - deleteWallet: true, - }) + await recipientAgent.shutdown() + await recipientAgent.wallet.delete() + await mediatorAgent.shutdown() + await mediatorAgent.wallet.delete() + await senderAgent.shutdown() + await senderAgent.wallet.delete() }) test(`Mediation end-to-end flow diff --git a/packages/core/src/wallet/IndyWallet.ts b/packages/core/src/wallet/IndyWallet.ts index 97cd71b30b..560d3904a3 100644 --- a/packages/core/src/wallet/IndyWallet.ts +++ b/packages/core/src/wallet/IndyWallet.ts @@ -14,16 +14,10 @@ import { isIndyError } from '../utils/indyError' import { WalletDuplicateError, WalletNotFoundError, WalletError } from './error' import { WalletInvalidKeyError } from './error/WalletInvalidKeyError' -export interface IndyOpenWallet { - walletHandle: number - masterSecretId: string - walletConfig: Indy.WalletConfig - walletCredentials: Indy.WalletCredentials -} - @scoped(Lifecycle.ContainerScoped) export class IndyWallet implements Wallet { - private openWalletInfo?: IndyOpenWallet + private walletConfig?: WalletConfig + private walletHandle?: number private logger: Logger private publicDidInfo: DidInfo | undefined @@ -34,8 +28,12 @@ export class IndyWallet implements Wallet { this.indy = agentConfig.agentDependencies.indy } + public get isProvisioned() { + return this.walletConfig !== undefined + } + public get isInitialized() { - return this.openWalletInfo !== undefined + return this.walletHandle !== undefined } public get publicDid() { @@ -43,23 +41,23 @@ export class IndyWallet implements Wallet { } public get handle() { - if (!this.isInitialized || !this.openWalletInfo) { + if (!this.walletHandle) { throw new AriesFrameworkError( 'Wallet has not been initialized yet. Make sure to await agent.initialize() before using the agent.' ) } - return this.openWalletInfo.walletHandle + return this.walletHandle } public get masterSecretId() { - if (!this.isInitialized || !this.openWalletInfo) { + if (!this.isInitialized || !this.walletConfig?.id) { throw new AriesFrameworkError( 'Wallet has not been initialized yet. Make sure to await agent.initialize() before using the agent.' ) } - return this.openWalletInfo.masterSecretId + return this.walletConfig.id } public async initialize(walletConfig: WalletConfig) { @@ -96,6 +94,20 @@ export class IndyWallet implements Wallet { try { await this.indy.createWallet({ id: walletConfig.id }, { key: walletConfig.key }) + + this.walletConfig = { + id: walletConfig.id, + key: walletConfig.key, + } + + // 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, walletConfig.id) + + // We opened wallet just to create master secret, we can close it now. + await this.close() } catch (error) { if (isIndyError(error, 'WalletAlreadyExistsError')) { const errorMessage = `Wallet '${walletConfig.id}' already exists` @@ -122,21 +134,17 @@ export class IndyWallet implements Wallet { * @throws {WalletError} if another error occurs */ public async open(walletConfig: WalletConfig): Promise { - if (this.isInitialized) { + if (this.walletHandle) { throw new WalletError( - 'Wallet instance already initialized. Close the currently opened wallet before re-initializing the wallet' + 'Wallet instance already opened. Close the currently opened wallet before re-opening the wallet' ) } try { - const walletHandle = await this.indy.openWallet({ id: walletConfig.id }, { key: walletConfig.key }) - const masterSecretId = await this.createMasterSecret(walletHandle, walletConfig.id) - - this.openWalletInfo = { - walletConfig: { id: walletConfig.id }, - walletCredentials: { key: walletConfig.key }, - walletHandle, - masterSecretId, + this.walletHandle = await this.indy.openWallet({ id: walletConfig.id }, { key: walletConfig.key }) + this.walletConfig = { + id: walletConfig.id, + key: walletConfig.key, } } catch (error) { if (isIndyError(error, 'WalletNotFoundError')) { @@ -171,23 +179,23 @@ export class IndyWallet implements Wallet { * @throws {WalletError} if another error occurs */ public async delete(): Promise { - const walletInfo = this.openWalletInfo - - if (!this.isInitialized || !walletInfo) { + if (!this.walletConfig) { throw new WalletError( - 'Can not delete wallet that is not initialized. Make sure to call initialize before deleting the wallet' + 'Can not delete wallet that does not have wallet config set. Make sure to call create wallet before deleting the wallet' ) } - this.logger.info(`Deleting wallet '${walletInfo.walletConfig.id}'`) + this.logger.info(`Deleting wallet '${this.walletConfig.id}'`) - await this.close() + if (this.walletHandle) { + await this.close() + } try { - await this.indy.deleteWallet(walletInfo.walletConfig, walletInfo.walletCredentials) + await this.indy.deleteWallet({ id: this.walletConfig.id }, { key: this.walletConfig.key }) } catch (error) { if (isIndyError(error, 'WalletNotFoundError')) { - const errorMessage = `Error deleting wallet: wallet '${walletInfo.walletConfig.id}' not found` + const errorMessage = `Error deleting wallet: wallet '${this.walletConfig.id}' not found` this.logger.debug(errorMessage) throw new WalletNotFoundError(errorMessage, { @@ -195,7 +203,7 @@ export class IndyWallet implements Wallet { cause: error, }) } else { - const errorMessage = `Error deleting wallet '${walletInfo.walletConfig.id}': ${error.message}` + const errorMessage = `Error deleting wallet '${this.walletConfig.id}': ${error.message}` this.logger.error(errorMessage, { error, errorMessage: error.message, @@ -210,9 +218,13 @@ export class IndyWallet implements Wallet { * @throws {WalletError} if the wallet is already closed or another error occurs */ public async close(): Promise { + if (!this.walletHandle) { + throw new WalletError('Wallet is in inavlid state, you are trying to close wallet that has no `walletHandle`.') + } + try { - await this.indy.closeWallet(this.handle) - this.openWalletInfo = undefined + await this.indy.closeWallet(this.walletHandle) + this.walletHandle = undefined this.publicDidInfo = undefined } catch (error) { if (isIndyError(error, 'WalletInvalidHandle')) { diff --git a/packages/core/src/wallet/Wallet.ts b/packages/core/src/wallet/Wallet.ts index 1302e1a3f6..630aac1dcc 100644 --- a/packages/core/src/wallet/Wallet.ts +++ b/packages/core/src/wallet/Wallet.ts @@ -4,8 +4,11 @@ import type { Buffer } from '../utils/buffer' export interface Wallet { publicDid: DidInfo | undefined isInitialized: boolean + isProvisioned: boolean initialize(walletConfig: WalletConfig): Promise + create(walletConfig: WalletConfig): Promise + open(walletConfig: WalletConfig): Promise close(): Promise delete(): Promise diff --git a/packages/core/tests/agents.test.ts b/packages/core/tests/agents.test.ts index 5923eeab68..fe47fc7846 100644 --- a/packages/core/tests/agents.test.ts +++ b/packages/core/tests/agents.test.ts @@ -23,13 +23,10 @@ describe('agents', () => { let bobConnection: ConnectionRecord afterAll(async () => { - await bobAgent.shutdown({ - deleteWallet: true, - }) - - await aliceAgent.shutdown({ - deleteWallet: true, - }) + await bobAgent.shutdown() + await bobAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() }) test('make a connection between agents', async () => { diff --git a/packages/core/tests/connectionless-credentials.test.ts b/packages/core/tests/connectionless-credentials.test.ts index 73f4559cfc..df058ba572 100644 --- a/packages/core/tests/connectionless-credentials.test.ts +++ b/packages/core/tests/connectionless-credentials.test.ts @@ -70,8 +70,10 @@ describe('credentials', () => { }) afterEach(async () => { - await faberAgent.shutdown({ deleteWallet: true }) - await aliceAgent.shutdown({ deleteWallet: true }) + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() }) test('Faber starts with connection-less credential offer to Alice', async () => { diff --git a/packages/core/tests/connections.test.ts b/packages/core/tests/connections.test.ts index 842d27d497..61deb6f569 100644 --- a/packages/core/tests/connections.test.ts +++ b/packages/core/tests/connections.test.ts @@ -40,12 +40,10 @@ describe('connections', () => { }) afterAll(async () => { - await faberAgent.shutdown({ - deleteWallet: true, - }) - await aliceAgent.shutdown({ - deleteWallet: true, - }) + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() }) it('should be able to make multiple connections using a multi use invite', async () => { diff --git a/packages/core/tests/credentials-auto-accept.test.ts b/packages/core/tests/credentials-auto-accept.test.ts index 01e396d502..3ebc8a8fa4 100644 --- a/packages/core/tests/credentials-auto-accept.test.ts +++ b/packages/core/tests/credentials-auto-accept.test.ts @@ -39,12 +39,10 @@ describe('auto accept credentials', () => { }) afterAll(async () => { - await aliceAgent.shutdown({ - deleteWallet: true, - }) - await faberAgent.shutdown({ - deleteWallet: true, - }) + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() }) test('Alice starts with credential proposal to Faber, both with autoAcceptCredential on `always`', async () => { @@ -163,12 +161,10 @@ describe('auto accept credentials', () => { }) afterAll(async () => { - await aliceAgent.shutdown({ - deleteWallet: true, - }) - await faberAgent.shutdown({ - deleteWallet: true, - }) + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() }) test('Alice starts with credential proposal to Faber, both with autoAcceptCredential on `contentApproved`', async () => { diff --git a/packages/core/tests/credentials.test.ts b/packages/core/tests/credentials.test.ts index 307e7446ac..b371f86079 100644 --- a/packages/core/tests/credentials.test.ts +++ b/packages/core/tests/credentials.test.ts @@ -31,12 +31,10 @@ describe('credentials', () => { }) afterAll(async () => { - await aliceAgent.shutdown({ - deleteWallet: true, - }) - await faberAgent.shutdown({ - deleteWallet: true, - }) + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() }) test('Alice starts with credential proposal to Faber', async () => { diff --git a/packages/core/tests/ledger.test.ts b/packages/core/tests/ledger.test.ts index b819095eab..c36c44498f 100644 --- a/packages/core/tests/ledger.test.ts +++ b/packages/core/tests/ledger.test.ts @@ -21,9 +21,8 @@ describe('ledger', () => { }) afterAll(async () => { - await faberAgent.shutdown({ - deleteWallet: true, - }) + await faberAgent.shutdown() + await faberAgent.wallet.delete() }) test(`initialization of agent's public DID`, async () => { diff --git a/packages/core/tests/proofs-auto-accept.test.ts b/packages/core/tests/proofs-auto-accept.test.ts index b50c401cc4..a990c3d070 100644 --- a/packages/core/tests/proofs-auto-accept.test.ts +++ b/packages/core/tests/proofs-auto-accept.test.ts @@ -31,12 +31,10 @@ describe('Auto accept present proof', () => { }) afterAll(async () => { - await aliceAgent.shutdown({ - deleteWallet: true, - }) - await faberAgent.shutdown({ - deleteWallet: true, - }) + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() }) test('Alice starts with proof proposal to Faber, both with autoAcceptProof on `always`', async () => { @@ -114,12 +112,10 @@ describe('Auto accept present proof', () => { }) afterAll(async () => { - await aliceAgent.shutdown({ - deleteWallet: true, - }) - await faberAgent.shutdown({ - deleteWallet: true, - }) + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() }) test('Alice starts with proof proposal to Faber, both with autoacceptproof on `contentApproved`', async () => { diff --git a/packages/core/tests/proofs.test.ts b/packages/core/tests/proofs.test.ts index d9066b8374..3fe675ea59 100644 --- a/packages/core/tests/proofs.test.ts +++ b/packages/core/tests/proofs.test.ts @@ -33,12 +33,10 @@ describe('Present Proof', () => { afterAll(async () => { testLogger.test('Shutting down both agents') - await aliceAgent.shutdown({ - deleteWallet: true, - }) - await faberAgent.shutdown({ - deleteWallet: true, - }) + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() }) test('Alice starts with proof proposal to Faber', async () => { diff --git a/packages/core/tests/wallet.test.ts b/packages/core/tests/wallet.test.ts new file mode 100644 index 0000000000..c4506410d3 --- /dev/null +++ b/packages/core/tests/wallet.test.ts @@ -0,0 +1,104 @@ +import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' + +import { Subject } from 'rxjs' + +import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { Agent } from '../src/agent/Agent' + +import { getBaseConfig } from './helpers' + +import { WalletDuplicateError, WalletInvalidKeyError, WalletNotFoundError } from '@aries-framework/core' + +const aliceConfig = getBaseConfig('wallet-tests-Alice', { + endpoints: ['rxjs:alice'], +}) + +describe('=== wallet', () => { + let aliceAgent: Agent + + beforeEach(async () => { + const aliceMessages = new Subject() + const bobMessages = new Subject() + + const subjectMap = { + 'rxjs:alice': aliceMessages, + 'rxjs:bob': bobMessages, + } + + aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(aliceMessages, subjectMap)) + return aliceAgent + }) + + afterEach(async () => { + await aliceAgent.shutdown() + if (aliceAgent.wallet.isProvisioned) { + await aliceAgent.wallet.delete() + } + }) + + test('open, create and open wallet with different wallet key that it is in agent config', async () => { + const walletConfig = { + id: 'mywallet', + key: 'mysecretwalletkey', + } + + try { + await aliceAgent.wallet.open(walletConfig) + } catch (error) { + if (error instanceof WalletNotFoundError) { + await aliceAgent.wallet.create(walletConfig) + } + } + + await aliceAgent.wallet.open(walletConfig) + await aliceAgent.initialize() + + expect(aliceAgent.isInitialized).toBe(true) + }) + + test('when creating already existing wallet throw WalletDuplicateError', async () => { + const walletConfig = { + id: 'mywallet', + key: 'mysecretwalletkey', + } + + await aliceAgent.wallet.create(walletConfig) + + await expect(aliceAgent.wallet.create(walletConfig)).rejects.toThrowError(WalletDuplicateError) + }) + + test('when opening non-existing wallet throw WalletNotFoundError', async () => { + const walletConfig = { + id: 'mywallet', + key: 'mysecretwalletkey', + } + + await expect(aliceAgent.wallet.open(walletConfig)).rejects.toThrowError(WalletNotFoundError) + }) + + test('when opening wallet with invalid key throw WalletInvalidKeyError', async () => { + const walletConfig = { + id: 'mywallet', + key: 'mysecretwalletkey', + } + + await aliceAgent.wallet.create(walletConfig) + await expect(aliceAgent.wallet.open({ ...walletConfig, key: 'abcd' })).rejects.toThrowError(WalletInvalidKeyError) + }) + + test('when create wallet and shutdown, wallet is closed', async () => { + const walletConfig = { + id: 'mywallet', + key: 'mysecretwalletkey', + } + + await aliceAgent.wallet.create(walletConfig) + + await aliceAgent.shutdown() + + await expect(aliceAgent.wallet.open(walletConfig)).resolves.toBeUndefined() + }) +}) diff --git a/tests/e2e-http.test.ts b/tests/e2e-http.test.ts index 167bf37553..ac9805df51 100644 --- a/tests/e2e-http.test.ts +++ b/tests/e2e-http.test.ts @@ -34,9 +34,12 @@ describe('E2E HTTP tests', () => { }) afterEach(async () => { - await recipientAgent.shutdown({ deleteWallet: true }) - await mediatorAgent.shutdown({ deleteWallet: true }) - await senderAgent.shutdown({ deleteWallet: true }) + await recipientAgent.shutdown() + await recipientAgent.wallet.delete() + await mediatorAgent.shutdown() + await mediatorAgent.wallet.delete() + await senderAgent.shutdown() + await senderAgent.wallet.delete() }) test('Full HTTP flow (connect, request mediation, issue, verify)', async () => { diff --git a/tests/e2e-subject.test.ts b/tests/e2e-subject.test.ts index 14cc20c0b3..63997b1bf9 100644 --- a/tests/e2e-subject.test.ts +++ b/tests/e2e-subject.test.ts @@ -35,9 +35,12 @@ describe('E2E Subject tests', () => { }) afterEach(async () => { - await recipientAgent.shutdown({ deleteWallet: true }) - await mediatorAgent.shutdown({ deleteWallet: true }) - await senderAgent.shutdown({ deleteWallet: true }) + await recipientAgent.shutdown() + await recipientAgent.wallet.delete() + await mediatorAgent.shutdown() + await mediatorAgent.wallet.delete() + await senderAgent.shutdown() + await senderAgent.wallet.delete() }) test('Full Subject flow (connect, request mediation, issue, verify)', async () => { diff --git a/tests/e2e-ws.test.ts b/tests/e2e-ws.test.ts index e28c6d94fd..b66f528103 100644 --- a/tests/e2e-ws.test.ts +++ b/tests/e2e-ws.test.ts @@ -34,9 +34,12 @@ describe('E2E WS tests', () => { }) afterEach(async () => { - await recipientAgent.shutdown({ deleteWallet: true }) - await mediatorAgent.shutdown({ deleteWallet: true }) - await senderAgent.shutdown({ deleteWallet: true }) + await recipientAgent.shutdown() + await recipientAgent.wallet.delete() + await mediatorAgent.shutdown() + await mediatorAgent.wallet.delete() + await senderAgent.shutdown() + await senderAgent.wallet.delete() }) test('Full WS flow (connect, request mediation, issue, verify)', async () => {