@docknetwork/wallet-sdk-core 1.5.0 → 1.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,91 +1,173 @@
1
1
  import {WalletDocument} from '@docknetwork/wallet-sdk-wasm/src/types';
2
2
  import {IWallet} from './types';
3
- import {createCredentialProvider, CredentialStatus} from './credential-provider';
3
+ import {
4
+ createCredentialProvider,
5
+ Credential,
6
+ CredentialStatus,
7
+ } from './credential-provider';
4
8
  import assert from 'assert';
9
+ import {EventEmitter} from 'events';
10
+ import {createDIDProvider} from './did-provider';
5
11
 
6
- export type BiometricsPluginIssuerConfig = {
7
- networkId: string;
8
- did: string;
9
- apiKey: string;
10
- apiUrl: string;
11
- }
12
-
13
- export type BiometricsPluginConfigs = {
12
+ export type BiometricsProviderConfigs<E> = {
13
+ // Generic configs used by the biometric provider
14
14
  enrollmentCredentialType: string;
15
15
  biometricMatchCredentialType: string;
16
- biometricMatchExpirationMinutes: number;
17
- issuerConfigs:BiometricsPluginIssuerConfig[];
16
+ // IDV specific configs, it depends on the IDV provider and its implementation
17
+ idvConfigs: E;
18
+ };
19
+
20
+ export interface IDVProcessOptions {
21
+ onDeepLink?: () => void;
22
+ onMessage?: () => void;
23
+ onError?: (error: Error) => void;
24
+ onCancel?: () => void;
25
+ onComplete?: (credential: any) => void;
18
26
  }
19
27
 
20
- let configs: BiometricsPluginConfigs = null;
28
+ export interface BiometricPlugin {
29
+ onEnroll(walletDID: string): Promise<WalletDocument>;
30
+ onMatch(
31
+ walletDID: string,
32
+ enrollmentCredential: Credential,
33
+ ): Promise<WalletDocument>;
34
+ }
21
35
 
22
- export function setBiometricConfigs(_configs: BiometricsPluginConfigs) {
23
- configs = _configs;
24
- };
36
+ let currentConfigs: BiometricsProviderConfigs<unknown> = null;
37
+
38
+ export function setConfigs(configs: BiometricsProviderConfigs<unknown>) {
39
+ currentConfigs = configs;
40
+ }
41
+
42
+ export function isBiometricPluginEnabled() {
43
+ return !!currentConfigs?.biometricMatchCredentialType;
44
+ }
25
45
 
26
46
  export function assertConfigs() {
27
- assert(!!configs, 'Biometrics plugin not configured');
47
+ assert(!!currentConfigs, 'Missing biometric provider configs');
28
48
  }
29
49
 
30
50
  export function getBiometricConfigs() {
31
51
  assertConfigs();
32
- return configs;
52
+ return currentConfigs;
53
+ }
54
+
55
+ export function hasProofOfBiometrics(proofRequest) {
56
+ const fields = proofRequest.input_descriptors
57
+ ?.map(input => input.constraints?.fields)
58
+ .flat();
59
+ const paths = fields.map(field => field.path).flat();
60
+ return (
61
+ paths?.includes('$.credentialSubject.biometric.id') &&
62
+ paths?.includes('$.credentialSubject.biometric.created')
63
+ );
64
+ }
65
+
66
+ // map for events
67
+ export const IDV_EVENTS = {
68
+ onDeepLink: 'onDeepLink',
69
+ onMessage: 'onMessage',
70
+ onError: 'onError',
71
+ onCancel: 'onCancel',
72
+ onComplete: 'onComplete',
73
+ };
74
+
75
+ export interface IDVProvider {
76
+ enroll(
77
+ walletDID: string,
78
+ proofRequest: any,
79
+ ): Promise<{enrollmentCredential: Credential; matchCredential: Credential}>;
80
+ match(
81
+ walletDID: string,
82
+ enrollmentCredential: Credential,
83
+ proofRequest: any,
84
+ ): Promise<{
85
+ matchCredential: Credential;
86
+ }>;
33
87
  }
34
88
 
35
- export function getIssuerConfigsForNetwork(networkId): BiometricsPluginIssuerConfig {
36
- return getBiometricConfigs()?.issuerConfigs.find(config => config.networkId === networkId);
89
+ export interface IDVProviderFactory {
90
+ create(eventEmitter: EventEmitter, wallet: IWallet): IDVProvider;
37
91
  }
38
92
 
39
- export function createBiometricBindingProvider({
93
+ export function createBiometricProvider({
40
94
  wallet,
41
- onEnroll,
42
- onMatch,
43
- onCheckBiometryRequired,
95
+ idvProviderFactory,
44
96
  }: {
45
97
  wallet: IWallet;
46
- onEnroll: () => Promise<WalletDocument>;
47
- onMatch: (biometricTemplate: WalletDocument) => Promise<WalletDocument>;
48
- onCheckBiometryRequired: (request) => boolean;
98
+ idvProviderFactory: IDVProviderFactory;
49
99
  }) {
50
100
  const credentialProvider = createCredentialProvider({wallet});
51
- return {
52
- enrollBiometry: async () => {
53
- const enrollmentCredential = await onEnroll();
54
- return await credentialProvider.addCredential(enrollmentCredential);
55
- },
56
- matchBiometry: async () => {
57
- const CREDENTIAL_TYPE = configs.enrollmentCredentialType;
58
- const enrollmentCredentials = await wallet.getDocumentsByType(
59
- CREDENTIAL_TYPE,
101
+ const didProvider = createDIDProvider({wallet});
102
+ const eventEmitter = new EventEmitter();
103
+ const idvProvider = idvProviderFactory.create(eventEmitter, wallet);
104
+
105
+
106
+ async function startIDV(proofRequest: any): Promise<{
107
+ enrollmentCredential: Credential;
108
+ matchCredential: Credential;
109
+ }> {
110
+ const walletDID = await didProvider.getDefaultDID();
111
+ let [enrollmentCredential] = await credentialProvider.getCredentials(
112
+ currentConfigs.enrollmentCredentialType,
113
+ );
114
+
115
+ // Remove any existing match credentials
116
+ const existingMatchCredentials = await credentialProvider.getCredentials(
117
+ currentConfigs.biometricMatchCredentialType,
118
+ );
119
+ for (const credential of existingMatchCredentials) {
120
+ await credentialProvider.removeCredential(credential.id);
121
+ }
122
+
123
+ let matchCredential: Credential;
124
+
125
+ if (!enrollmentCredential) {
126
+ // call IDV to start enrollment process and issue the enrollment credential + match credential
127
+ const credentials = await idvProvider.enroll(walletDID, proofRequest);
128
+
129
+ // check if credential is already in the credential store
130
+ const receivedViaDistribution = await credentialProvider.getById(
131
+ credentials.matchCredential.id,
60
132
  );
61
133
 
62
- if (!enrollmentCredentials.length) {
63
- throw new Error('Enrollment credential not found');
134
+ if (!receivedViaDistribution) {
135
+ await credentialProvider.addCredential(
136
+ credentials.enrollmentCredential,
137
+ );
138
+ await credentialProvider.addCredential(credentials.matchCredential);
64
139
  }
65
140
 
66
- const matchConfirmationCredential = await onMatch(
67
- enrollmentCredentials[0],
141
+ matchCredential = credentials.matchCredential;
142
+ enrollmentCredential = credentials.enrollmentCredential;
143
+ } else {
144
+ // call IDV to match the enrollment credential and issue the match credential
145
+ const credentials = await idvProvider.match(
146
+ walletDID,
147
+ enrollmentCredential,
148
+ proofRequest,
149
+ );
150
+
151
+ // check if credential is already in the credential store
152
+ const receivedViaDistribution = await credentialProvider.getById(
153
+ credentials.matchCredential.id,
68
154
  );
69
155
 
70
- if (matchConfirmationCredential) {
71
- const biometricMatchCredentials = await wallet.getDocumentsByType(configs.biometricMatchCredentialType);
72
- for (let i = 0; i < biometricMatchCredentials.length; i++) {
73
- await wallet.removeDocument(biometricMatchCredentials[0].id);
74
- }
75
-
76
- await wallet.addDocument(matchConfirmationCredential);
77
- // make the biometric credential valid by default
78
- await wallet.addDocument({
79
- id: `${matchConfirmationCredential.id}#status`,
80
- status: CredentialStatus.Verified,
81
- type: 'CredentialStatus',
82
- createdAt: new Date().toISOString(),
83
- updatedAt: new Date().toISOString(),
84
- })
156
+ if (!receivedViaDistribution) {
157
+ await credentialProvider.addCredential(credentials.matchCredential);
85
158
  }
86
159
 
87
- return matchConfirmationCredential;
88
- },
89
- checkIsBiometryRequired: onCheckBiometryRequired,
160
+ matchCredential = credentials.matchCredential;
161
+ }
162
+
163
+ return {
164
+ enrollmentCredential,
165
+ matchCredential,
166
+ };
167
+ }
168
+
169
+ return {
170
+ startIDV,
171
+ eventEmitter,
90
172
  };
91
173
  }
@@ -2,13 +2,311 @@ import {
2
2
  DataStore,
3
3
  DataStoreEvents,
4
4
  } from '@docknetwork/wallet-sdk-data-store/src/types';
5
- import {logger} from '@docknetwork/wallet-sdk-data-store/src/logger';
6
- import {edvService} from '@docknetwork/wallet-sdk-wasm/src/services/edv';
7
- import base64url from 'base64url-universal';
8
-
9
- import {utilCryptoService} from '@docknetwork/wallet-sdk-wasm/src/services/util-crypto';
5
+ import { logger } from '@docknetwork/wallet-sdk-data-store/src/logger';
6
+ import { edvService, EDVService } from '@docknetwork/wallet-sdk-wasm/src/services/edv/service';
7
+ import hkdf from 'futoin-hkdf';
8
+ import crypto from '@docknetwork/universal-wallet/crypto';
9
+ import { utilCryptoService } from '@docknetwork/wallet-sdk-wasm/src/services/util-crypto';
10
10
 
11
11
  export const SYNC_MARKER_TYPE = 'SyncMarkerDocument';
12
+ export const MNEMONIC_WORD_COUNT = 12;
13
+ export const KEY_MAPPING_TYPE = 'KeyMappingDocument';
14
+ export const HKDF_LENGTH = 32;
15
+ export const HKDF_HASH = 'SHA-256';
16
+ const MASTER_KEY_SUFFIX = 'master-key';
17
+
18
+ /**
19
+ * Derives a key from biometric data using HKDF
20
+ * @param biometricData Biometric data from provider
21
+ * @param identifier User's identifier as salt (email, phone number, etc.)
22
+ * @returns Derived key
23
+ */
24
+ export function deriveBiometricKey(
25
+ biometricData: Buffer,
26
+ identifier: string,
27
+ ): Buffer {
28
+ const salt = identifier;
29
+
30
+ return hkdf(biometricData, HKDF_LENGTH, { salt, hash: HKDF_HASH });
31
+ }
32
+
33
+ /**
34
+ * Derives EDV keys from biometric data for the KeyMappingVault
35
+ * @param biometricData Biometric data from the provider
36
+ * @param identifier User's identifier as additional entropy (email, phone number, etc.)
37
+ * @returns Keys for accessing the KeyMappingVault
38
+ */
39
+ export async function deriveKeyMappingVaultKeys(
40
+ biometricData: Buffer,
41
+ identifier: string
42
+ ): Promise<{ hmacKey: string; agreementKey: string; verificationKey: string }> {
43
+ const seedBuffer = deriveBiometricKey(biometricData, identifier);
44
+
45
+ return edvService.deriveKeys(new Uint8Array(seedBuffer));
46
+ }
47
+
48
+ /**
49
+ * Generates a key for encrypting/decrypting the master key
50
+ * @param biometricData Biometric data from provider
51
+ * @param identifier User's identifier as salt (email, phone number, etc.)
52
+ * @returns Encryption key and IV for AES encryption
53
+ */
54
+ export async function deriveBiometricEncryptionKey(
55
+ biometricData: Buffer,
56
+ identifier: string
57
+ ): Promise<{ key: Buffer; iv: Buffer }> {
58
+ const key = deriveBiometricKey(biometricData, identifier);
59
+
60
+ const randomBytes = crypto.getRandomValues(new Uint8Array(16));
61
+ const iv = Buffer.from(randomBytes);
62
+
63
+ return {
64
+ key,
65
+ iv
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Encrypts the master key using a key derived from biometric data
71
+ * @param masterKey The CloudWalletVault master key to encrypt
72
+ * @param encryptionKey Key derived from biometric data
73
+ * @param iv Initialization vector
74
+ * @returns Encrypted master key
75
+ */
76
+ export async function encryptMasterKey(
77
+ masterKey: Uint8Array,
78
+ encryptionKey: Buffer,
79
+ iv: Buffer
80
+ ): Promise<Uint8Array> {
81
+ const keyData = new Uint8Array(encryptionKey);
82
+ const ivData = new Uint8Array(iv);
83
+
84
+ const key = await crypto.subtle.importKey(
85
+ 'raw',
86
+ keyData,
87
+ { name: 'AES-GCM' },
88
+ false,
89
+ ['encrypt']
90
+ );
91
+
92
+ const encryptedBuffer = await crypto.subtle.encrypt(
93
+ { name: 'AES-GCM', iv: ivData },
94
+ key,
95
+ masterKey
96
+ );
97
+
98
+ return new Uint8Array(encryptedBuffer);
99
+ }
100
+
101
+ /**
102
+ * Decrypts the master key using biometric-derived key
103
+ * @param encryptedKey The encrypted master key
104
+ * @param decryptionKey Key derived from biometric data
105
+ * @param iv Initialization vector
106
+ * @returns The decrypted master key
107
+ */
108
+ export async function decryptMasterKey(
109
+ encryptedKey: Uint8Array,
110
+ decryptionKey: Buffer,
111
+ iv: Buffer
112
+ ): Promise<Uint8Array> {
113
+ try {
114
+ const keyData = new Uint8Array(decryptionKey);
115
+ const ivData = new Uint8Array(iv);
116
+
117
+ const key = await crypto.subtle.importKey(
118
+ 'raw',
119
+ keyData,
120
+ { name: 'AES-GCM' },
121
+ false,
122
+ ['decrypt']
123
+ );
124
+
125
+ const decryptedBuffer = await crypto.subtle.decrypt(
126
+ { name: 'AES-GCM', iv: ivData },
127
+ key,
128
+ encryptedKey
129
+ );
130
+
131
+ return new Uint8Array(decryptedBuffer);
132
+ } catch (error) {
133
+ throw new Error('Decryption failed: Invalid key or corrupted data');
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Initializes the KeyMappingVault using biometric data
139
+ * @param edvUrl URL for the edv
140
+ * @param authKey Auth key for the edv
141
+ * @param biometricData User's biometric data
142
+ * @param identifier User's identifier (email, phone number, etc.)
143
+ * @returns Initialized EDV service
144
+ */
145
+ export async function initializeKeyMappingVault(
146
+ edvUrl: string,
147
+ authKey: string,
148
+ biometricData: Buffer,
149
+ identifier: string
150
+ ): Promise<EDVService> {
151
+ const {
152
+ hmacKey,
153
+ agreementKey,
154
+ verificationKey
155
+ } = await deriveKeyMappingVaultKeys(biometricData, identifier);
156
+
157
+ const keyMappingEdvService = new EDVService();
158
+ await keyMappingEdvService.initialize({
159
+ hmacKey,
160
+ agreementKey,
161
+ verificationKey,
162
+ edvUrl,
163
+ authKey
164
+ });
165
+
166
+ return keyMappingEdvService;
167
+ }
168
+
169
+ /**
170
+ * Enrolls a user by creating necessary vaults and keys
171
+ * @param edvUrl URL for the edv
172
+ * @param authKey Auth key for the edv
173
+ * @param biometricData Biometric data from provider
174
+ * @param identifier User's identifier (email, phone number, etc.)
175
+ * @returns The master key and mnemonic for backup
176
+ */
177
+ export async function enrollUserWithBiometrics(
178
+ edvUrl: string,
179
+ authKey: string,
180
+ biometricData: Buffer,
181
+ identifier: string
182
+ ): Promise<{ masterKey: Uint8Array; mnemonic: string }> {
183
+ const keyMappingEdv = await initializeKeyMappingVault(
184
+ edvUrl,
185
+ authKey,
186
+ biometricData,
187
+ identifier
188
+ );
189
+ const { mnemonic, masterKey } = await generateCloudWalletMasterKey();
190
+ const { key: encryptionKey, iv } = await deriveBiometricEncryptionKey(biometricData, identifier);
191
+ const encryptedMasterKey = await encryptMasterKey(masterKey, encryptionKey, iv);
192
+
193
+ const encryptedData = {
194
+ data: Array.from(encryptedMasterKey),
195
+ iv: Array.from(iv)
196
+ };
197
+
198
+ const contentId = `${await keyMappingEdv.getController()}#${MASTER_KEY_SUFFIX}`;
199
+
200
+ await keyMappingEdv.insert({
201
+ document: {
202
+ content: {
203
+ id: contentId,
204
+ type: KEY_MAPPING_TYPE,
205
+ encryptedKey: encryptedData
206
+ }
207
+ }
208
+ });
209
+
210
+ return { masterKey, mnemonic };
211
+ }
212
+
213
+ /**
214
+ * Gets the master key from the key mapping vault using provided decryption keys
215
+ * @param keyMappingEdv Initialized key mapping vault service
216
+ * @param identifier User's identifier (email, phone number, etc.)
217
+ * @param decryptionKey Key for decrypting the master key
218
+ * @param iv Initialization vector for decryption
219
+ * @returns The decrypted master key for CloudWalletVault
220
+ */
221
+ export async function getKeyMappingMasterKey(
222
+ keyMappingEdv: EDVService,
223
+ identifier: string,
224
+ decryptionKey: Buffer,
225
+ ): Promise<Uint8Array> {
226
+ const result = await keyMappingEdv.find({
227
+ equals: {
228
+ 'content.id': identifier
229
+ }
230
+ });
231
+
232
+ if (!result.documents || result.documents.length === 0) {
233
+ throw new Error('Authentication failed: Invalid identifier');
234
+ }
235
+
236
+ // The KeyMappingVault keys are derived from the biometric data so each
237
+ // vault should have a unique key for the user
238
+
239
+ const keyMappingDoc = result.documents[0];
240
+ const { data: encryptedKey, iv: storedIv } = keyMappingDoc.content.encryptedKey;
241
+ const encryptedKeyArray = new Uint8Array(encryptedKey);
242
+ const ivBuffer = Buffer.from(storedIv);
243
+
244
+ try {
245
+ const masterKey = await decryptMasterKey(encryptedKeyArray, decryptionKey, ivBuffer);
246
+
247
+ return masterKey;
248
+ } catch (error) {
249
+ throw new Error('Authentication failed: Invalid decryption key');
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Authenticates a user with biometric data and identifier
255
+ * @param edvUrl URL for the edv
256
+ * @param authKey Auth key for the edv
257
+ * @param biometricData Biometric data from the provider
258
+ * @param identifier User's identifier (email, phone number, etc.)
259
+ * @returns The decrypted master key for CloudWalletVault
260
+ */
261
+ export async function authenticateWithBiometrics(
262
+ edvUrl: string,
263
+ authKey: string,
264
+ biometricData: Buffer,
265
+ identifier: string
266
+ ): Promise<Uint8Array> {
267
+ const keyMappingEdv = await initializeKeyMappingVault(
268
+ edvUrl,
269
+ authKey,
270
+ biometricData,
271
+ identifier
272
+ );
273
+ const { key: decryptionKey } = await deriveBiometricEncryptionKey(biometricData, identifier);
274
+
275
+ const contentId = `${await keyMappingEdv.getController()}#${MASTER_KEY_SUFFIX}`;
276
+
277
+ return getKeyMappingMasterKey(keyMappingEdv, contentId, decryptionKey);
278
+ }
279
+
280
+ /**
281
+ * Initializes the Cloud Wallet using biometric authentication
282
+ * @param edvUrl Cloud wallet vault URL
283
+ * @param authKey Cloud wallet auth key
284
+ * @param biometricData User's biometric data
285
+ * @param identifier User's identifier (email, phone number, etc.)
286
+ * @param dataStore Optional data store for the wallet
287
+ * @returns Initialized cloud wallet
288
+ */
289
+ export async function initializeCloudWalletWithBiometrics(
290
+ edvUrl: string,
291
+ authKey: string,
292
+ biometricData: Buffer,
293
+ identifier: string,
294
+ dataStore?: any
295
+ ): Promise<any> {
296
+ const masterKey = await authenticateWithBiometrics(
297
+ edvUrl,
298
+ authKey,
299
+ biometricData,
300
+ identifier
301
+ );
302
+
303
+ return initializeCloudWallet({
304
+ dataStore,
305
+ edvUrl,
306
+ authKey,
307
+ masterKey
308
+ });
309
+ }
12
310
 
13
311
  interface QueuedOperation {
14
312
  operation: () => Promise<any>;
@@ -21,11 +319,10 @@ interface DocumentQueue {
21
319
  isProcessing: boolean;
22
320
  }
23
321
 
24
- export async function generateCloudWalletMasterKey(): Promise<{ mnemonic: string; masterKey: string }> {
25
- const mnemonic = await utilCryptoService.mnemonicGenerate(12);
322
+ export async function generateCloudWalletMasterKey(): Promise<{ mnemonic: string; masterKey: Uint8Array }> {
323
+ const mnemonic = await utilCryptoService.mnemonicGenerate(MNEMONIC_WORD_COUNT);
26
324
 
27
- const seedBytes = await utilCryptoService.mnemonicToMiniSecret(mnemonic);
28
- const masterKey = base64url.encode(Buffer.from(seedBytes));
325
+ const masterKey = await utilCryptoService.mnemonicToMiniSecret(mnemonic);
29
326
 
30
327
  return {
31
328
  mnemonic,
@@ -33,9 +330,8 @@ export async function generateCloudWalletMasterKey(): Promise<{ mnemonic: string
33
330
  };
34
331
  }
35
332
 
36
- export async function recoverCloudWalletMasterKey(mnemonic: string): Promise<string> {
37
- const seedBytes = await utilCryptoService.mnemonicToMiniSecret(mnemonic);
38
- const masterKey = base64url.encode(Buffer.from(seedBytes));
333
+ export async function recoverCloudWalletMasterKey(mnemonic: string): Promise<Uint8Array> {
334
+ const masterKey = await utilCryptoService.mnemonicToMiniSecret(mnemonic);
39
335
 
40
336
  return masterKey;
41
337
  }
@@ -49,7 +345,7 @@ export async function initializeCloudWallet({
49
345
  dataStore?: DataStore;
50
346
  edvUrl: string;
51
347
  authKey: string;
52
- masterKey: any;
348
+ masterKey: Uint8Array;
53
349
  }) {
54
350
  const {
55
351
  hmacKey,
@@ -174,7 +470,7 @@ export async function initializeCloudWallet({
174
470
  },
175
471
  });
176
472
  logger.debug(`Document added to EDV: ${content.id}`);
177
- } catch(error) {
473
+ } catch (error) {
178
474
  logger.error(`Unable to add document ${content.id}: ${error.message}`);
179
475
  }
180
476
  });
@@ -185,7 +481,7 @@ export async function initializeCloudWallet({
185
481
  try {
186
482
  logger.debug(`Removing document from EDV: ${documentId}`);
187
483
  const edvDocument = await findDocumentByContentId(documentId);
188
- await edvService.delete({document: edvDocument});
484
+ await edvService.delete({ document: edvDocument });
189
485
  // TODO: Remove this once we figure out why the data store is empty after deleting a document
190
486
  await pullDocuments();
191
487
  logger.debug(`Document removed from EDV: ${documentId}`);
@@ -259,7 +555,7 @@ export async function initializeCloudWallet({
259
555
  const allDocs = await edvService.find({});
260
556
 
261
557
  for (const doc of allDocs.documents) {
262
- await edvService.delete({document: doc});
558
+ await edvService.delete({ document: doc });
263
559
  }
264
560
  }
265
561
 
@@ -24,6 +24,7 @@ export interface ICredentialProvider {
24
24
  getCredentialStatus(
25
25
  credential: Credential,
26
26
  ): Promise<{status: string; error?: string}>;
27
+ removeCredential(credential: Credential): Promise<void>;
27
28
  }
28
29
 
29
30
  export function isBBSPlusCredential(credential) {
@@ -275,6 +276,35 @@ async function syncCredentialStatus({
275
276
  return statusDocs;
276
277
  }
277
278
 
279
+ /**
280
+ * Removes a credential and its related documents from the wallet
281
+ * @param param0
282
+ * @returns
283
+ */
284
+ export async function removeCredential({
285
+ wallet,
286
+ credential,
287
+ }: {
288
+ wallet: IWallet;
289
+ credential: Credential | string;
290
+ }): Promise<void> {
291
+ // Allow passing either a credential object or a credential ID
292
+ const credentialId = typeof credential === 'string' ? credential : credential.id;
293
+
294
+ assert(!!credentialId, 'credential ID is required');
295
+
296
+ // Remove the main credential document
297
+ await wallet.removeDocument(credentialId);
298
+
299
+ if (await wallet.getDocumentById(`${credentialId}#witness`)) {
300
+ await wallet.removeDocument(`${credentialId}#witness`);
301
+ }
302
+
303
+ if (await wallet.getDocumentById(`${credentialId}#status`)) {
304
+ await wallet.removeDocument(`${credentialId}#status`);
305
+ }
306
+ }
307
+
278
308
  export function createCredentialProvider({
279
309
  wallet,
280
310
  }: {
@@ -320,6 +350,7 @@ export function createCredentialProvider({
320
350
  return syncCredentialStatus({wallet, ...props});
321
351
  },
322
352
  addCredential: credential => addCredential({wallet, credential}),
353
+ removeCredential: credential => removeCredential({wallet, credential}),
323
354
  // TODO: move import credential from json or URL to this provider
324
355
  };
325
356
  }