@enbox/agent 0.7.6 → 0.7.7
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.
- package/dist/browser.mjs +7 -9
- package/dist/browser.mjs.map +3 -3
- package/dist/esm/hd-identity-vault.js +187 -177
- package/dist/esm/hd-identity-vault.js.map +1 -1
- package/dist/types/hd-identity-vault.d.ts +25 -0
- package/dist/types/hd-identity-vault.d.ts.map +1 -1
- package/dist/types/types/identity-vault.d.ts +9 -0
- package/dist/types/types/identity-vault.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/hd-identity-vault.ts +244 -212
- package/src/types/identity-vault.ts +8 -1
package/src/hd-identity-vault.ts
CHANGED
|
@@ -48,6 +48,30 @@ export type HdIdentityVaultInitializeParams = {
|
|
|
48
48
|
dwnEndpoints?: string[];
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
+
export type HdIdentityVaultResetPasswordWithRecoveryPhraseParams = {
|
|
52
|
+
/** The BIP-39 recovery phrase originally used to initialize the vault. */
|
|
53
|
+
recoveryPhrase: string;
|
|
54
|
+
|
|
55
|
+
/** The new password used to unlock the existing vault from this point forward. */
|
|
56
|
+
password: string;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
type HdIdentityVaultDerivedMaterial = {
|
|
60
|
+
recoveryPhrase: string;
|
|
61
|
+
contentEncryptionKey: Jwk;
|
|
62
|
+
contentEncryptionKeyJwe: string;
|
|
63
|
+
portableDid: PortableDid;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export class HdIdentityVaultRecoveryPhraseMismatchError extends Error {
|
|
67
|
+
public readonly code = 'HD_IDENTITY_VAULT_RECOVERY_PHRASE_MISMATCH';
|
|
68
|
+
|
|
69
|
+
constructor(message = 'HdIdentityVault: Recovery phrase does not match the initialized vault.') {
|
|
70
|
+
super(message);
|
|
71
|
+
this.name = 'HdIdentityVaultRecoveryPhraseMismatchError';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
51
75
|
/**
|
|
52
76
|
* Type guard function to check if a given object is an empty string or a string containing only
|
|
53
77
|
* whitespace.
|
|
@@ -311,22 +335,7 @@ export class HdIdentityVault implements IdentityVault<{ InitializeResult: string
|
|
|
311
335
|
// vault store. This avoids the expensive LevelDB read + JWE decrypt on
|
|
312
336
|
// every call while the vault is unlocked.
|
|
313
337
|
if (!this._cachedPortableDid) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const { plaintext: portableDidBytes } = await CompactJwe.decrypt({
|
|
317
|
-
jwe : didJwe,
|
|
318
|
-
key : this._contentEncryptionKey!,
|
|
319
|
-
crypto : this.crypto,
|
|
320
|
-
keyManager : new LocalKeyManager(),
|
|
321
|
-
options : { minP2cCount: 1 }, // Vault decrypts its own JWEs; no external-input floor needed.
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
const portableDid = Convert.uint8Array(portableDidBytes).toObject();
|
|
325
|
-
if (!isPortableDid(portableDid)) {
|
|
326
|
-
throw new Error('HdIdentityVault: Unable to decode malformed DID in identity vault');
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
this._cachedPortableDid = portableDid;
|
|
338
|
+
this._cachedPortableDid = await this.decryptStoredPortableDid(this._contentEncryptionKey!);
|
|
330
339
|
}
|
|
331
340
|
|
|
332
341
|
// Always return a fresh BearerDid from a deep copy of the cached
|
|
@@ -398,217 +407,61 @@ export class HdIdentityVault implements IdentityVault<{ InitializeResult: string
|
|
|
398
407
|
public async initialize({ password, recoveryPhrase, dwnEndpoints }:
|
|
399
408
|
HdIdentityVaultInitializeParams
|
|
400
409
|
): Promise<string> {
|
|
401
|
-
/**
|
|
402
|
-
* STEP 0: Validate the input parameters and verify the identity vault is not already
|
|
403
|
-
* initialized.
|
|
404
|
-
*/
|
|
405
|
-
|
|
406
410
|
// Verify that the identity vault was not previously initialized.
|
|
407
411
|
if (await this.isInitialized()) {
|
|
408
412
|
throw new Error(`HdIdentityVault: Vault has already been initialized.`);
|
|
409
413
|
}
|
|
410
414
|
|
|
411
|
-
|
|
412
|
-
if (isEmptyString(password)) {
|
|
413
|
-
throw new Error(
|
|
414
|
-
`HdIdentityVault: The password is required and cannot be blank. Please provide a ' +
|
|
415
|
-
'valid, non-empty password.`
|
|
416
|
-
);
|
|
417
|
-
}
|
|
415
|
+
const derivedMaterial = await this.deriveVaultMaterial({ password, recoveryPhrase, dwnEndpoints });
|
|
418
416
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
);
|
|
425
|
-
}
|
|
417
|
+
await this._store.set('contentEncryptionKey', derivedMaterial.contentEncryptionKeyJwe);
|
|
418
|
+
await this._store.set(
|
|
419
|
+
'did',
|
|
420
|
+
await this.encryptPortableDid(derivedMaterial.portableDid, derivedMaterial.contentEncryptionKey)
|
|
421
|
+
);
|
|
426
422
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
*/
|
|
423
|
+
this._contentEncryptionKey = derivedMaterial.contentEncryptionKey;
|
|
424
|
+
this._cachedPortableDid = derivedMaterial.portableDid;
|
|
425
|
+
await this.setStatus({ initialized: true });
|
|
431
426
|
|
|
432
|
-
//
|
|
433
|
-
|
|
427
|
+
// Return the recovery phrase in case it was generated so that it can be displayed to the user
|
|
428
|
+
// for safekeeping.
|
|
429
|
+
return derivedMaterial.recoveryPhrase;
|
|
430
|
+
}
|
|
434
431
|
|
|
435
|
-
|
|
436
|
-
|
|
432
|
+
/**
|
|
433
|
+
* Resets the vault password using the original recovery phrase.
|
|
434
|
+
*
|
|
435
|
+
* The recovery phrase must derive the same vault CEK and agent DID that are already stored in
|
|
436
|
+
* this vault. If it does not, no stored state is changed. On success, only the password-wrapped
|
|
437
|
+
* CEK is replaced; the encrypted DID and all local vault data are preserved.
|
|
438
|
+
*/
|
|
439
|
+
public async resetPasswordWithRecoveryPhrase({
|
|
440
|
+
recoveryPhrase,
|
|
441
|
+
password,
|
|
442
|
+
}: HdIdentityVaultResetPasswordWithRecoveryPhraseParams): Promise<void> {
|
|
443
|
+
if (await this.isInitialized() === false) {
|
|
437
444
|
throw new Error(
|
|
438
|
-
'HdIdentityVault:
|
|
439
|
-
'
|
|
445
|
+
'HdIdentityVault: Unable to reset the vault password because the identity vault has not ' +
|
|
446
|
+
'been initialized.'
|
|
440
447
|
);
|
|
441
448
|
}
|
|
442
449
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* STEP 2: Derive the vault HD key pair from the root key.
|
|
451
|
-
*/
|
|
452
|
-
|
|
453
|
-
// The vault HD key is derived using account 0 and index 0 so that it can be
|
|
454
|
-
// deterministically re-derived. The vault key pair serves as input keying material for:
|
|
455
|
-
// - deriving the vault content encryption key (CEK)
|
|
456
|
-
// - deriving the salt that serves as input to derive the key that encrypts the vault CEK
|
|
457
|
-
const vaultHdKey = rootHdKey.derive(`m/44'/0'/0'/0'/0'`);
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* STEP 3: Derive the vault Content Encryption Key (CEK) from the vault private
|
|
461
|
-
* key and a non-secret static info value.
|
|
462
|
-
*/
|
|
463
|
-
|
|
464
|
-
// A non-secret static info value is combined with the vault private key as input to HKDF
|
|
465
|
-
// (Hash-based Key Derivation Function) to derive a 32-byte content encryption key (CEK).
|
|
466
|
-
const contentEncryptionKey = await this.crypto.deriveKey({
|
|
467
|
-
algorithm : 'HKDF-512', // key derivation function
|
|
468
|
-
baseKeyBytes : vaultHdKey.privateKey, // input keying material
|
|
469
|
-
salt : '', // empty salt because private key is sufficiently random
|
|
470
|
-
info : 'vault_cek', // non-secret application specific information
|
|
471
|
-
derivedKeyAlgorithm : 'A256GCM' // derived key algorithm
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
/**
|
|
475
|
-
* STEP 4: Using the given `password` and a `salt` derived from the vault public key, encrypt
|
|
476
|
-
* the vault CEK and store it in the data store as a compact JWE.
|
|
477
|
-
*/
|
|
478
|
-
|
|
479
|
-
// A non-secret static info value is combined with the vault public key as input to HKDF
|
|
480
|
-
// (Hash-based Key Derivation Function) to derive a new 32-byte salt.
|
|
481
|
-
const saltInput = await this.crypto.deriveKeyBytes({
|
|
482
|
-
algorithm : 'HKDF-512', // key derivation function
|
|
483
|
-
baseKeyBytes : vaultHdKey.publicKey, // input keying material
|
|
484
|
-
salt : '', // empty salt because public key is sufficiently random
|
|
485
|
-
info : 'vault_unlock_salt', // non-secret application specific information
|
|
486
|
-
length : 256, // derived key length, in bits
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
// Construct the JWE header.
|
|
490
|
-
const cekJweProtectedHeader: JweHeaderParams = {
|
|
491
|
-
alg : 'PBES2-HS512+A256KW',
|
|
492
|
-
enc : 'A256GCM',
|
|
493
|
-
cty : 'text/plain',
|
|
494
|
-
p2c : this._keyDerivationWorkFactor,
|
|
495
|
-
p2s : Convert.uint8Array(saltInput).toBase64Url()
|
|
496
|
-
};
|
|
497
|
-
|
|
498
|
-
// Encrypt the vault content encryption key (CEK) to compact JWE format.
|
|
499
|
-
const cekJwe = await CompactJwe.encrypt({
|
|
500
|
-
key : Convert.string(password).toUint8Array(),
|
|
501
|
-
protectedHeader : cekJweProtectedHeader,
|
|
502
|
-
plaintext : Convert.object(contentEncryptionKey).toUint8Array(),
|
|
503
|
-
crypto : this.crypto,
|
|
504
|
-
keyManager : new LocalKeyManager()
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
// Store the compact JWE in the data store.
|
|
508
|
-
await this._store.set('contentEncryptionKey', cekJwe);
|
|
509
|
-
|
|
510
|
-
/**
|
|
511
|
-
* STEP 5: Create a DID using identity, signing, and encryption keys derived from the root key.
|
|
512
|
-
*/
|
|
513
|
-
|
|
514
|
-
// Derive the identity key pair using index 0 and convert to JWK format.
|
|
515
|
-
// Note: The account is set to Unix epoch time so that in the future, the keys for a DID DHT
|
|
516
|
-
// document can be deterministically derived based on the versionId returned in a DID
|
|
517
|
-
// resolution result.
|
|
518
|
-
const identityHdKey = rootHdKey.derive(`m/44'/0'/1708523827'/0'/0'`);
|
|
519
|
-
const identityPrivateKey = await this.crypto.bytesToPrivateKey({
|
|
520
|
-
algorithm : 'Ed25519',
|
|
521
|
-
privateKeyBytes : identityHdKey.privateKey
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
// Derive the signing key using index 1 and convert to JWK format.
|
|
525
|
-
const signingHdKey = rootHdKey.derive(`m/44'/0'/1708523827'/0'/1'`);
|
|
526
|
-
const signingPrivateKey = await this.crypto.bytesToPrivateKey({
|
|
527
|
-
algorithm : 'Ed25519',
|
|
528
|
-
privateKeyBytes : signingHdKey.privateKey
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
// Derive the encryption key using index 2 (X25519 for ECDH-ES JWE encryption).
|
|
532
|
-
const encryptionHdKey = rootHdKey.derive(`m/44'/0'/1708523827'/0'/2'`);
|
|
533
|
-
const encryptionPrivateKey = await this.crypto.bytesToPrivateKey({
|
|
534
|
-
algorithm : 'X25519',
|
|
535
|
-
privateKeyBytes : encryptionHdKey.privateKey
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
// Add the identity, signing, and encryption keys to the deterministic key generator so that
|
|
539
|
-
// when the DID is created it will use the derived keys.
|
|
540
|
-
const deterministicKeyGenerator = new DeterministicKeyGenerator();
|
|
541
|
-
await deterministicKeyGenerator.addPredefinedKeys({
|
|
542
|
-
privateKeys: [identityPrivateKey, signingPrivateKey, encryptionPrivateKey]
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
// Create the DID using the derived identity, signing, and encryption keys.
|
|
546
|
-
const options = {
|
|
547
|
-
verificationMethods: [
|
|
548
|
-
{
|
|
549
|
-
algorithm : 'Ed25519',
|
|
550
|
-
id : 'sig',
|
|
551
|
-
purposes : ['assertionMethod', 'authentication']
|
|
552
|
-
},
|
|
553
|
-
{
|
|
554
|
-
algorithm : 'X25519',
|
|
555
|
-
id : 'enc',
|
|
556
|
-
purposes : ['keyAgreement']
|
|
557
|
-
}
|
|
558
|
-
]
|
|
559
|
-
} as DidDhtCreateOptions<DeterministicKeyGenerator>;
|
|
560
|
-
|
|
561
|
-
if (dwnEndpoints && !!dwnEndpoints.length) {
|
|
562
|
-
options.services = [
|
|
563
|
-
{
|
|
564
|
-
id : 'dwn',
|
|
565
|
-
type : 'DecentralizedWebNode',
|
|
566
|
-
serviceEndpoint : dwnEndpoints,
|
|
567
|
-
}
|
|
568
|
-
];
|
|
450
|
+
const derivedMaterial = await this.deriveVaultMaterial({ password, recoveryPhrase });
|
|
451
|
+
let storedPortableDid: PortableDid;
|
|
452
|
+
try {
|
|
453
|
+
storedPortableDid = await this.decryptStoredPortableDid(derivedMaterial.contentEncryptionKey);
|
|
454
|
+
} catch {
|
|
455
|
+
throw new HdIdentityVaultRecoveryPhraseMismatchError();
|
|
569
456
|
}
|
|
570
457
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
* STEP 6: Convert the DID to portable format and store it in the data store as a
|
|
575
|
-
* compact JWE.
|
|
576
|
-
*/
|
|
577
|
-
|
|
578
|
-
// Convert the DID to a portable format.
|
|
579
|
-
const portableDid = await did.export();
|
|
580
|
-
|
|
581
|
-
// Construct the JWE header.
|
|
582
|
-
const didJweProtectedHeader: JweHeaderParams = {
|
|
583
|
-
alg : 'dir',
|
|
584
|
-
enc : 'A256GCM',
|
|
585
|
-
cty : 'json'
|
|
586
|
-
};
|
|
587
|
-
|
|
588
|
-
// Encrypt the DID to compact JWE format.
|
|
589
|
-
const didJwe = await CompactJwe.encrypt({
|
|
590
|
-
key : contentEncryptionKey,
|
|
591
|
-
plaintext : Convert.object(portableDid).toUint8Array(),
|
|
592
|
-
protectedHeader : didJweProtectedHeader,
|
|
593
|
-
crypto : this.crypto,
|
|
594
|
-
keyManager : new LocalKeyManager()
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
// Store the compact JWE in the data store.
|
|
598
|
-
await this._store.set('did', didJwe);
|
|
599
|
-
|
|
600
|
-
/**
|
|
601
|
-
* STEP 7: Set the vault CEK (effectively unlocking the vault), set the status to initialized,
|
|
602
|
-
* and return the mnemonic used to generate the vault key.
|
|
603
|
-
*/
|
|
604
|
-
|
|
605
|
-
this._contentEncryptionKey = contentEncryptionKey;
|
|
606
|
-
|
|
607
|
-
await this.setStatus({ initialized: true });
|
|
458
|
+
if (storedPortableDid.uri !== derivedMaterial.portableDid.uri) {
|
|
459
|
+
throw new HdIdentityVaultRecoveryPhraseMismatchError();
|
|
460
|
+
}
|
|
608
461
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
462
|
+
await this._store.set('contentEncryptionKey', derivedMaterial.contentEncryptionKeyJwe);
|
|
463
|
+
this._contentEncryptionKey = derivedMaterial.contentEncryptionKey;
|
|
464
|
+
this._cachedPortableDid = storedPortableDid;
|
|
612
465
|
}
|
|
613
466
|
|
|
614
467
|
/**
|
|
@@ -868,6 +721,185 @@ export class HdIdentityVault implements IdentityVault<{ InitializeResult: string
|
|
|
868
721
|
return plaintext;
|
|
869
722
|
}
|
|
870
723
|
|
|
724
|
+
private async deriveVaultMaterial({
|
|
725
|
+
password,
|
|
726
|
+
recoveryPhrase,
|
|
727
|
+
dwnEndpoints,
|
|
728
|
+
}: HdIdentityVaultInitializeParams): Promise<HdIdentityVaultDerivedMaterial> {
|
|
729
|
+
this.validatePassword(password);
|
|
730
|
+
const resolvedRecoveryPhrase = this.resolveRecoveryPhrase(recoveryPhrase);
|
|
731
|
+
|
|
732
|
+
const rootSeed = await mnemonicToSeed(resolvedRecoveryPhrase);
|
|
733
|
+
const rootHdKey = HDKey.fromMasterSeed(rootSeed);
|
|
734
|
+
|
|
735
|
+
// The vault key is deterministic so the same phrase can re-derive both the CEK and unlock salt.
|
|
736
|
+
const vaultHdKey = rootHdKey.derive(`m/44'/0'/0'/0'/0'`);
|
|
737
|
+
const contentEncryptionKey = await this.crypto.deriveKey({
|
|
738
|
+
algorithm : 'HKDF-512',
|
|
739
|
+
baseKeyBytes : vaultHdKey.privateKey,
|
|
740
|
+
salt : '',
|
|
741
|
+
info : 'vault_cek',
|
|
742
|
+
derivedKeyAlgorithm : 'A256GCM',
|
|
743
|
+
});
|
|
744
|
+
const saltInput = await this.crypto.deriveKeyBytes({
|
|
745
|
+
algorithm : 'HKDF-512',
|
|
746
|
+
baseKeyBytes : vaultHdKey.publicKey,
|
|
747
|
+
salt : '',
|
|
748
|
+
info : 'vault_unlock_salt',
|
|
749
|
+
length : 256,
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
return {
|
|
753
|
+
recoveryPhrase : resolvedRecoveryPhrase,
|
|
754
|
+
contentEncryptionKey,
|
|
755
|
+
contentEncryptionKeyJwe : await this.encryptContentEncryptionKey({
|
|
756
|
+
password,
|
|
757
|
+
contentEncryptionKey,
|
|
758
|
+
saltInput,
|
|
759
|
+
}),
|
|
760
|
+
portableDid: await this.derivePortableDid({ rootHdKey, dwnEndpoints }),
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
private validatePassword(password: string): void {
|
|
765
|
+
if (isEmptyString(password)) {
|
|
766
|
+
throw new Error(
|
|
767
|
+
'HdIdentityVault: The password is required and cannot be blank. Please provide a ' +
|
|
768
|
+
'valid, non-empty password.'
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
private resolveRecoveryPhrase(recoveryPhrase?: string): string {
|
|
774
|
+
if (recoveryPhrase !== undefined && isEmptyString(recoveryPhrase)) {
|
|
775
|
+
throw new Error(
|
|
776
|
+
'HdIdentityVault: The recovery phrase is required and cannot be blank. Please provide a ' +
|
|
777
|
+
'valid BIP-39 recovery phrase.'
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const resolvedRecoveryPhrase = recoveryPhrase ?? generateMnemonic(wordlist, 128);
|
|
782
|
+
if (!validateMnemonic(resolvedRecoveryPhrase, wordlist)) {
|
|
783
|
+
throw new Error(
|
|
784
|
+
'HdIdentityVault: The provided recovery phrase is invalid. Please ensure that the ' +
|
|
785
|
+
'recovery phrase is a correctly formatted series of 12 words.'
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
return resolvedRecoveryPhrase;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
private async encryptContentEncryptionKey({
|
|
793
|
+
password,
|
|
794
|
+
contentEncryptionKey,
|
|
795
|
+
saltInput,
|
|
796
|
+
}: {
|
|
797
|
+
password: string;
|
|
798
|
+
contentEncryptionKey: Jwk;
|
|
799
|
+
saltInput: Uint8Array;
|
|
800
|
+
}): Promise<string> {
|
|
801
|
+
const protectedHeader: JweHeaderParams = {
|
|
802
|
+
alg : 'PBES2-HS512+A256KW',
|
|
803
|
+
enc : 'A256GCM',
|
|
804
|
+
cty : 'text/plain',
|
|
805
|
+
p2c : this._keyDerivationWorkFactor,
|
|
806
|
+
p2s : Convert.uint8Array(saltInput).toBase64Url(),
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
return CompactJwe.encrypt({
|
|
810
|
+
key : Convert.string(password).toUint8Array(),
|
|
811
|
+
protectedHeader,
|
|
812
|
+
plaintext : Convert.object(contentEncryptionKey).toUint8Array(),
|
|
813
|
+
crypto : this.crypto,
|
|
814
|
+
keyManager : new LocalKeyManager(),
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
private async derivePortableDid({
|
|
819
|
+
rootHdKey,
|
|
820
|
+
dwnEndpoints,
|
|
821
|
+
}: {
|
|
822
|
+
rootHdKey: HDKey;
|
|
823
|
+
dwnEndpoints?: string[];
|
|
824
|
+
}): Promise<PortableDid> {
|
|
825
|
+
const identityHdKey = rootHdKey.derive(`m/44'/0'/1708523827'/0'/0'`);
|
|
826
|
+
const identityPrivateKey = await this.crypto.bytesToPrivateKey({
|
|
827
|
+
algorithm : 'Ed25519',
|
|
828
|
+
privateKeyBytes : identityHdKey.privateKey,
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
const signingHdKey = rootHdKey.derive(`m/44'/0'/1708523827'/0'/1'`);
|
|
832
|
+
const signingPrivateKey = await this.crypto.bytesToPrivateKey({
|
|
833
|
+
algorithm : 'Ed25519',
|
|
834
|
+
privateKeyBytes : signingHdKey.privateKey,
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
const encryptionHdKey = rootHdKey.derive(`m/44'/0'/1708523827'/0'/2'`);
|
|
838
|
+
const encryptionPrivateKey = await this.crypto.bytesToPrivateKey({
|
|
839
|
+
algorithm : 'X25519',
|
|
840
|
+
privateKeyBytes : encryptionHdKey.privateKey,
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
const deterministicKeyGenerator = new DeterministicKeyGenerator();
|
|
844
|
+
await deterministicKeyGenerator.addPredefinedKeys({
|
|
845
|
+
privateKeys: [identityPrivateKey, signingPrivateKey, encryptionPrivateKey],
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
const options = {
|
|
849
|
+
verificationMethods: [
|
|
850
|
+
{
|
|
851
|
+
algorithm : 'Ed25519',
|
|
852
|
+
id : 'sig',
|
|
853
|
+
purposes : ['assertionMethod', 'authentication'],
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
algorithm : 'X25519',
|
|
857
|
+
id : 'enc',
|
|
858
|
+
purposes : ['keyAgreement'],
|
|
859
|
+
},
|
|
860
|
+
],
|
|
861
|
+
} as DidDhtCreateOptions<DeterministicKeyGenerator>;
|
|
862
|
+
|
|
863
|
+
if (dwnEndpoints && dwnEndpoints.length > 0) {
|
|
864
|
+
options.services = [
|
|
865
|
+
{
|
|
866
|
+
id : 'dwn',
|
|
867
|
+
type : 'DecentralizedWebNode',
|
|
868
|
+
serviceEndpoint : dwnEndpoints,
|
|
869
|
+
},
|
|
870
|
+
];
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
return (await DidDht.create({ keyManager: deterministicKeyGenerator, options })).export();
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
private async encryptPortableDid(portableDid: PortableDid, contentEncryptionKey: Jwk): Promise<string> {
|
|
877
|
+
return CompactJwe.encrypt({
|
|
878
|
+
key : contentEncryptionKey,
|
|
879
|
+
plaintext : Convert.object(portableDid).toUint8Array(),
|
|
880
|
+
protectedHeader : { alg: 'dir', enc: 'A256GCM', cty: 'json' },
|
|
881
|
+
crypto : this.crypto,
|
|
882
|
+
keyManager : new LocalKeyManager(),
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
private async decryptStoredPortableDid(contentEncryptionKey: Jwk): Promise<PortableDid> {
|
|
887
|
+
const { plaintext: portableDidBytes } = await CompactJwe.decrypt({
|
|
888
|
+
jwe : await this.getStoredDid(),
|
|
889
|
+
key : contentEncryptionKey,
|
|
890
|
+
crypto : this.crypto,
|
|
891
|
+
keyManager : new LocalKeyManager(),
|
|
892
|
+
options : { minP2cCount: 1 },
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
const portableDid = Convert.uint8Array(portableDidBytes).toObject();
|
|
896
|
+
if (!isPortableDid(portableDid)) {
|
|
897
|
+
throw new Error('HdIdentityVault: Unable to decode malformed DID in identity vault');
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
return portableDid;
|
|
901
|
+
}
|
|
902
|
+
|
|
871
903
|
/**
|
|
872
904
|
* Retrieves the Decentralized Identifier (DID) associated with the identity vault from the vault
|
|
873
905
|
* store.
|
|
@@ -950,4 +982,4 @@ export class HdIdentityVault implements IdentityVault<{ InitializeResult: string
|
|
|
950
982
|
|
|
951
983
|
return true;
|
|
952
984
|
}
|
|
953
|
-
}
|
|
985
|
+
}
|
|
@@ -88,6 +88,13 @@ export interface IdentityVault<T extends Record<string, any> = { InitializeResul
|
|
|
88
88
|
*/
|
|
89
89
|
initialize(params: { password: string }): Promise<T['InitializeResult']>;
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Resets the vault password by proving knowledge of the original recovery phrase.
|
|
93
|
+
*
|
|
94
|
+
* Implementations must leave existing vault contents unchanged when the phrase does not match.
|
|
95
|
+
*/
|
|
96
|
+
resetPasswordWithRecoveryPhrase(params: { recoveryPhrase: string, password: string }): Promise<void>;
|
|
97
|
+
|
|
91
98
|
/**
|
|
92
99
|
* Returns a boolean indicating whether the IdentityVault has been initialized.
|
|
93
100
|
*/
|
|
@@ -152,4 +159,4 @@ export type IdentityVaultStatus = {
|
|
|
152
159
|
* The timestamp of the last restore.
|
|
153
160
|
*/
|
|
154
161
|
lastRestore: string | null;
|
|
155
|
-
};
|
|
162
|
+
};
|