@interop/wallet-core 0.5.0 → 0.7.0
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/enrollment/enrollment.d.ts +144 -0
- package/dist/enrollment/enrollment.d.ts.map +1 -0
- package/dist/enrollment/enrollment.js +296 -0
- package/dist/enrollment/enrollment.js.map +1 -0
- package/dist/enrollment/index.d.ts +20 -0
- package/dist/enrollment/index.d.ts.map +1 -0
- package/dist/enrollment/index.js +19 -0
- package/dist/enrollment/index.js.map +1 -0
- package/dist/identity/index.d.ts +0 -5
- package/dist/identity/index.d.ts.map +1 -1
- package/dist/identity/index.js +0 -4
- package/dist/identity/index.js.map +1 -1
- package/dist/index.d.ts +16 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -3
- package/dist/index.js.map +1 -1
- package/dist/keyring/fetch.d.ts +22 -0
- package/dist/keyring/fetch.d.ts.map +1 -0
- package/dist/keyring/fetch.js +35 -0
- package/dist/keyring/fetch.js.map +1 -0
- package/dist/keyring/index.d.ts +30 -0
- package/dist/keyring/index.d.ts.map +1 -0
- package/dist/keyring/index.js +28 -0
- package/dist/keyring/index.js.map +1 -0
- package/dist/keyring/kdf.d.ts +102 -0
- package/dist/keyring/kdf.d.ts.map +1 -0
- package/dist/keyring/kdf.js +148 -0
- package/dist/keyring/kdf.js.map +1 -0
- package/dist/keyring/record.d.ts +98 -0
- package/dist/keyring/record.d.ts.map +1 -0
- package/dist/keyring/record.js +119 -0
- package/dist/keyring/record.js.map +1 -0
- package/dist/keyring/unlockSpace.d.ts +101 -0
- package/dist/keyring/unlockSpace.d.ts.map +1 -0
- package/dist/keyring/unlockSpace.js +226 -0
- package/dist/keyring/unlockSpace.js.map +1 -0
- package/dist/keys/index.d.ts +24 -0
- package/dist/keys/index.d.ts.map +1 -0
- package/dist/keys/index.js +22 -0
- package/dist/keys/index.js.map +1 -0
- package/dist/keys/puk.d.ts +42 -0
- package/dist/keys/puk.d.ts.map +1 -0
- package/dist/keys/puk.js +56 -0
- package/dist/keys/puk.js.map +1 -0
- package/dist/keys/pukRoster.d.ts +210 -0
- package/dist/keys/pukRoster.d.ts.map +1 -0
- package/dist/keys/pukRoster.js +263 -0
- package/dist/keys/pukRoster.js.map +1 -0
- package/dist/keys/rosterStore.d.ts +18 -0
- package/dist/keys/rosterStore.d.ts.map +1 -0
- package/dist/keys/rosterStore.js +38 -0
- package/dist/keys/rosterStore.js.map +1 -0
- package/dist/markers/acquire.d.ts +107 -0
- package/dist/markers/acquire.d.ts.map +1 -0
- package/dist/markers/acquire.js +83 -0
- package/dist/markers/acquire.js.map +1 -0
- package/dist/markers/cipher.d.ts +56 -0
- package/dist/markers/cipher.d.ts.map +1 -0
- package/dist/markers/cipher.js +73 -0
- package/dist/markers/cipher.js.map +1 -0
- package/dist/markers/index.d.ts +30 -0
- package/dist/markers/index.d.ts.map +1 -0
- package/dist/markers/index.js +29 -0
- package/dist/markers/index.js.map +1 -0
- package/dist/markers/refresh.d.ts +60 -0
- package/dist/markers/refresh.d.ts.map +1 -0
- package/dist/markers/refresh.js +67 -0
- package/dist/markers/refresh.js.map +1 -0
- package/dist/recovery/index.d.ts +31 -0
- package/dist/recovery/index.d.ts.map +1 -0
- package/dist/recovery/index.js +28 -0
- package/dist/recovery/index.js.map +1 -0
- package/dist/recovery/recoveryCode.d.ts +105 -0
- package/dist/recovery/recoveryCode.d.ts.map +1 -0
- package/dist/recovery/recoveryCode.js +155 -0
- package/dist/recovery/recoveryCode.js.map +1 -0
- package/dist/recovery/recoveryRecord.d.ts +75 -0
- package/dist/recovery/recoveryRecord.d.ts.map +1 -0
- package/dist/recovery/recoveryRecord.js +89 -0
- package/dist/recovery/recoveryRecord.js.map +1 -0
- package/dist/recovery/recoveryWebvh.d.ts +126 -0
- package/dist/recovery/recoveryWebvh.d.ts.map +1 -0
- package/dist/recovery/recoveryWebvh.js +379 -0
- package/dist/recovery/recoveryWebvh.js.map +1 -0
- package/dist/space/collections.d.ts +57 -0
- package/dist/space/collections.d.ts.map +1 -1
- package/dist/space/collections.js +48 -0
- package/dist/space/collections.js.map +1 -1
- package/dist/space/index.d.ts +4 -0
- package/dist/space/index.d.ts.map +1 -1
- package/dist/space/index.js +4 -0
- package/dist/space/index.js.map +1 -1
- package/dist/webvh/didWeb.d.ts +41 -0
- package/dist/webvh/didWeb.d.ts.map +1 -0
- package/dist/webvh/didWeb.js +24 -0
- package/dist/webvh/didWeb.js.map +1 -0
- package/dist/webvh/didWebvh.d.ts +330 -0
- package/dist/webvh/didWebvh.d.ts.map +1 -0
- package/dist/webvh/didWebvh.js +775 -0
- package/dist/webvh/didWebvh.js.map +1 -0
- package/dist/webvh/index.d.ts +27 -0
- package/dist/webvh/index.d.ts.map +1 -0
- package/dist/webvh/index.js +24 -0
- package/dist/webvh/index.js.map +1 -0
- package/dist/webvh/zcap.d.ts +104 -0
- package/dist/webvh/zcap.d.ts.map +1 -0
- package/dist/webvh/zcap.js +117 -0
- package/dist/webvh/zcap.js.map +1 -0
- package/package.json +42 -4
- package/dist/identity/collectionKeys.d.ts +0 -36
- package/dist/identity/collectionKeys.d.ts.map +0 -1
- package/dist/identity/collectionKeys.js +0 -92
- package/dist/identity/collectionKeys.js.map +0 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { deriveUnlockIdentity, KEYRING_KDF } from './kdf.js';
|
|
2
|
+
import { getUnlockKeyring } from './unlockSpace.js';
|
|
3
|
+
import { unwrapKeyringRecord } from './record.js';
|
|
4
|
+
/**
|
|
5
|
+
* Fetches and unwraps the keyring record an unlock secret addresses, or
|
|
6
|
+
* resolves `null` when no record exists there (an unknown secret). The derived
|
|
7
|
+
* unlock Space id is returned alongside the contents -- it is already computed,
|
|
8
|
+
* and callers key their local state on it.
|
|
9
|
+
*
|
|
10
|
+
* @param options {object}
|
|
11
|
+
* @param options.secret {string | Uint8Array} the unlock secret
|
|
12
|
+
* @param [options.kdf] {UnlockKdf} the unlock method's parameter set
|
|
13
|
+
* @param options.storageServerUrl {string} the WAS server origin
|
|
14
|
+
* @returns {Promise<(KeyringRecordContents & { unlockSpaceId: string }) | null>}
|
|
15
|
+
*/
|
|
16
|
+
export async function fetchKeyringRecord({ secret, kdf = KEYRING_KDF, storageServerUrl }) {
|
|
17
|
+
const unlock = await deriveUnlockIdentity({ secret, kdf });
|
|
18
|
+
const record = await getUnlockKeyring({
|
|
19
|
+
storageServerUrl,
|
|
20
|
+
zcapClient: unlock.zcapClient,
|
|
21
|
+
spaceId: unlock.spaceId
|
|
22
|
+
});
|
|
23
|
+
if (record === null) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const contents = await unwrapKeyringRecord({
|
|
27
|
+
record,
|
|
28
|
+
// `id` is always set on the unlock KAK (a controller was supplied at
|
|
29
|
+
// derivation), so it satisfies IKeyAgreementKey's required `id`.
|
|
30
|
+
keyAgreementKey: unlock.keyAgreementKey,
|
|
31
|
+
keyResolver: unlock.keyResolver
|
|
32
|
+
});
|
|
33
|
+
return { ...contents, unlockSpaceId: unlock.spaceId };
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/keyring/fetch.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAGjD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,EACvC,MAAM,EACN,GAAG,GAAG,WAAW,EACjB,gBAAgB,EAKjB;IACC,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;IAC1D,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC;QACpC,gBAAgB;QAChB,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;KACxB,CAAC,CAAA;IACF,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,OAAO,IAAI,CAAA;IACb,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC;QACzC,MAAM;QACN,qEAAqE;QACrE,iEAAiE;QACjE,eAAe,EAAE,MAAM,CAAC,eAAmC;QAC3D,WAAW,EAAE,MAAM,CAAC,WAAW;KAChC,CAAC,CAAA;IACF,OAAO,EAAE,GAAG,QAAQ,EAAE,aAAa,EAAE,MAAM,CAAC,OAAO,EAAE,CAAA;AACvD,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The `@interop/wallet-core/keyring` subpath: the unlock layer -- how an
|
|
6
|
+
* unlock secret (a passphrase, a passkey PRF output) locates an account
|
|
7
|
+
* without authorizing anything against it.
|
|
8
|
+
*
|
|
9
|
+
* - `deriveUnlockIdentity` / `KEYRING_KDF` / `unlockSpaceIdFor` -- the
|
|
10
|
+
* wire-level unlock derivation (implemented over `@noble/hashes`, so it runs
|
|
11
|
+
* unchanged where WebCrypto's `deriveBits` is unavailable) and the unlock
|
|
12
|
+
* Space addressing convention.
|
|
13
|
+
* - `wrapKeyringRecord` / `unwrapKeyringRecord` -- the `{ version, wrapped }`
|
|
14
|
+
* account-pointer record codec.
|
|
15
|
+
* - `ensureUnlockSpace` / `getUnlockKeyring` / `putUnlockKeyring` /
|
|
16
|
+
* `deleteUnlockSpace` / `deleteUnlockSpaceWithCapability` -- the unlock
|
|
17
|
+
* Space's lifecycle and its one resource.
|
|
18
|
+
* - `fetchKeyringRecord` -- the composed lookup (derive, read, unwrap); an
|
|
19
|
+
* app's caching, pinning, and client-key persistence wrap around it.
|
|
20
|
+
*
|
|
21
|
+
* Kept out of the root export: this subpath pulls the webkms-client / ezcap /
|
|
22
|
+
* was-client dependency graph (the same isolation pattern as `./identity`).
|
|
23
|
+
*/
|
|
24
|
+
export { deriveUnlockIdentity, KEYRING_KDF, UNLOCK_HANDLE, UNLOCK_KEY_NAME, unlockSpaceIdFor } from './kdf.js';
|
|
25
|
+
export type { UnlockIdentity, UnlockKdf } from './kdf.js';
|
|
26
|
+
export { KEYRING_RECORD_VERSION, parseRecordPointer, unwrapKeyringRecord, wrapKeyringRecord } from './record.js';
|
|
27
|
+
export type { AccountPointer, KeyringRecordContents } from './record.js';
|
|
28
|
+
export { deleteUnlockSpace, deleteUnlockSpaceWithCapability, ensureUnlockSpace, getUnlockKeyring, putUnlockKeyring, UNLOCK_SPACE_NAME } from './unlockSpace.js';
|
|
29
|
+
export { fetchKeyringRecord } from './fetch.js';
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/keyring/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,aAAa,EACb,eAAe,EACf,gBAAgB,EACjB,MAAM,UAAU,CAAA;AACjB,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAEzD,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,aAAa,CAAA;AACpB,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAExE,OAAO,EACL,iBAAiB,EACjB,+BAA+B,EAC/B,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The `@interop/wallet-core/keyring` subpath: the unlock layer -- how an
|
|
6
|
+
* unlock secret (a passphrase, a passkey PRF output) locates an account
|
|
7
|
+
* without authorizing anything against it.
|
|
8
|
+
*
|
|
9
|
+
* - `deriveUnlockIdentity` / `KEYRING_KDF` / `unlockSpaceIdFor` -- the
|
|
10
|
+
* wire-level unlock derivation (implemented over `@noble/hashes`, so it runs
|
|
11
|
+
* unchanged where WebCrypto's `deriveBits` is unavailable) and the unlock
|
|
12
|
+
* Space addressing convention.
|
|
13
|
+
* - `wrapKeyringRecord` / `unwrapKeyringRecord` -- the `{ version, wrapped }`
|
|
14
|
+
* account-pointer record codec.
|
|
15
|
+
* - `ensureUnlockSpace` / `getUnlockKeyring` / `putUnlockKeyring` /
|
|
16
|
+
* `deleteUnlockSpace` / `deleteUnlockSpaceWithCapability` -- the unlock
|
|
17
|
+
* Space's lifecycle and its one resource.
|
|
18
|
+
* - `fetchKeyringRecord` -- the composed lookup (derive, read, unwrap); an
|
|
19
|
+
* app's caching, pinning, and client-key persistence wrap around it.
|
|
20
|
+
*
|
|
21
|
+
* Kept out of the root export: this subpath pulls the webkms-client / ezcap /
|
|
22
|
+
* was-client dependency graph (the same isolation pattern as `./identity`).
|
|
23
|
+
*/
|
|
24
|
+
export { deriveUnlockIdentity, KEYRING_KDF, UNLOCK_HANDLE, UNLOCK_KEY_NAME, unlockSpaceIdFor } from './kdf.js';
|
|
25
|
+
export { KEYRING_RECORD_VERSION, parseRecordPointer, unwrapKeyringRecord, wrapKeyringRecord } from './record.js';
|
|
26
|
+
export { deleteUnlockSpace, deleteUnlockSpaceWithCapability, ensureUnlockSpace, getUnlockKeyring, putUnlockKeyring, UNLOCK_SPACE_NAME } from './unlockSpace.js';
|
|
27
|
+
export { fetchKeyringRecord } from './fetch.js';
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/keyring/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,aAAa,EACb,eAAe,EACf,gBAAgB,EACjB,MAAM,UAAU,CAAA;AAGjB,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,aAAa,CAAA;AAGpB,OAAO,EACL,iBAAiB,EACjB,+BAA+B,EAC/B,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The unlock derivation: an unlock secret (a passphrase, or a passkey PRF
|
|
6
|
+
* output) to the unlock identity that locates an account's keyring record and
|
|
7
|
+
* wraps/unwraps it. Nothing about the account is derivable from the secret --
|
|
8
|
+
* the identity's only jobs are addressing the minimal unlock Space and holding
|
|
9
|
+
* the key-agreement key the keyring record is encrypted to.
|
|
10
|
+
*
|
|
11
|
+
* The derivation is wire-level: two wallet apps must produce byte-identical
|
|
12
|
+
* output for the same secret and parameter set, or the same passphrase would
|
|
13
|
+
* address two different unlock Spaces. It is therefore implemented over
|
|
14
|
+
* `@noble/hashes` rather than WebCrypto's `crypto.subtle.deriveBits`, which
|
|
15
|
+
* React Native does not provide; PBKDF2 and HKDF are both fully specified
|
|
16
|
+
* (RFC 8018 / RFC 5869), so the two implementations agree bit for bit.
|
|
17
|
+
*/
|
|
18
|
+
import { CapabilityAgent } from '@interop/webkms-client';
|
|
19
|
+
import { ZcapClient } from '@interop/ezcap';
|
|
20
|
+
import { X25519KeyAgreementKey2020 } from '@interop/x25519-key-agreement-key';
|
|
21
|
+
import type { IKeyResolver } from '@interop/data-integrity-core';
|
|
22
|
+
/**
|
|
23
|
+
* The load-bearing `CapabilityAgent` derivation names for an unlock identity
|
|
24
|
+
* (the counterpart of the data identity's bootstrap names): every unlock
|
|
25
|
+
* derivation runs through these exact strings, so they can never change
|
|
26
|
+
* without stranding existing accounts.
|
|
27
|
+
*/
|
|
28
|
+
export declare const UNLOCK_HANDLE = "unlock";
|
|
29
|
+
export declare const UNLOCK_KEY_NAME = "unlock-key";
|
|
30
|
+
/**
|
|
31
|
+
* Unlock-derivation parameters, one variant per KDF family: PBKDF2 stretches
|
|
32
|
+
* a low-entropy passphrase; HKDF expands already-uniform key material (e.g. a
|
|
33
|
+
* passkey PRF output). Each unlock method pins its own parameter set -- and
|
|
34
|
+
* its own salt, so two methods can never derive the same unlock identity.
|
|
35
|
+
* The `version` records which parameter set produced a derivation; the
|
|
36
|
+
* keyring record's own `version` is stamped separately.
|
|
37
|
+
*/
|
|
38
|
+
export type UnlockKdf = {
|
|
39
|
+
version: number;
|
|
40
|
+
algorithm: 'PBKDF2';
|
|
41
|
+
iterations: number;
|
|
42
|
+
hash: string;
|
|
43
|
+
salt: string;
|
|
44
|
+
} | {
|
|
45
|
+
version: number;
|
|
46
|
+
algorithm: 'HKDF';
|
|
47
|
+
hash: string;
|
|
48
|
+
salt: string;
|
|
49
|
+
info: string;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* PBKDF2 parameters for the passphrase unlock derivation
|
|
53
|
+
* (`unlockSeed = PBKDF2(passphrase)`). Version 1 pins exactly these
|
|
54
|
+
* parameters; the keyring record's `version` field records which set produced
|
|
55
|
+
* it, so changing any of them (iterations, hash, salt) requires minting a new
|
|
56
|
+
* record version rather than silently breaking existing unlock derivations.
|
|
57
|
+
* The salt is a fixed app-wide constant -- login stays passphrase-only, with
|
|
58
|
+
* no email (or other) input mixed into the derivation. Every unlock method's
|
|
59
|
+
* KDF carries a distinct salt, so two methods can never derive the same
|
|
60
|
+
* unlock Space.
|
|
61
|
+
*/
|
|
62
|
+
export declare const KEYRING_KDF: UnlockKdf;
|
|
63
|
+
/**
|
|
64
|
+
* Derives the full unlock identity from an unlock secret: the unlock
|
|
65
|
+
* CapabilityAgent, a ZcapClient that can both invoke and delegate (the
|
|
66
|
+
* unlock agent delegates a management zcap on its own Space to the account
|
|
67
|
+
* controller at bind time), the unlock KAK + resolver for wrap/unwrap, and the
|
|
68
|
+
* unlock Space id. Performs no I/O -- the derivation seam for tests and future
|
|
69
|
+
* unlock methods.
|
|
70
|
+
*
|
|
71
|
+
* @param options {object}
|
|
72
|
+
* @param options.secret {string | Uint8Array}
|
|
73
|
+
* @param options.kdf {UnlockKdf}
|
|
74
|
+
* @returns {Promise<object>}
|
|
75
|
+
*/
|
|
76
|
+
export declare function deriveUnlockIdentity({ secret, kdf }: {
|
|
77
|
+
secret: string | Uint8Array;
|
|
78
|
+
kdf: UnlockKdf;
|
|
79
|
+
}): Promise<{
|
|
80
|
+
agent: CapabilityAgent;
|
|
81
|
+
zcapClient: ZcapClient;
|
|
82
|
+
keyAgreementKey: X25519KeyAgreementKey2020;
|
|
83
|
+
keyResolver: IKeyResolver;
|
|
84
|
+
spaceId: string;
|
|
85
|
+
}>;
|
|
86
|
+
/**
|
|
87
|
+
* The unlock Space id an unlock identity addresses:
|
|
88
|
+
* `base64url(SHA-256(did))`, unpadded -- a discovery convention, not an
|
|
89
|
+
* authorization one (holding the id grants nothing).
|
|
90
|
+
*
|
|
91
|
+
* @param options {object}
|
|
92
|
+
* @param options.did {string} the unlock identity's did:key
|
|
93
|
+
* @returns {string}
|
|
94
|
+
*/
|
|
95
|
+
export declare function unlockSpaceIdFor({ did }: {
|
|
96
|
+
did: string;
|
|
97
|
+
}): string;
|
|
98
|
+
/**
|
|
99
|
+
* The derived unlock identity, as `deriveUnlockIdentity` returns it.
|
|
100
|
+
*/
|
|
101
|
+
export type UnlockIdentity = Awaited<ReturnType<typeof deriveUnlockIdentity>>;
|
|
102
|
+
//# sourceMappingURL=kdf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kdf.d.ts","sourceRoot":"","sources":["../../src/keyring/kdf.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAA;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAOhE;;;;;GAKG;AACH,eAAO,MAAM,aAAa,WAAW,CAAA;AACrC,eAAO,MAAM,eAAe,eAAe,CAAA;AAQ3C;;;;;;;GAOG;AACH,MAAM,MAAM,SAAS,GACjB;IACE,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,QAAQ,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb,GACD;IACE,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAEL;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,EAAE,SAMzB,CAAA;AA2DD;;;;;;;;;;;;GAYG;AACH,wBAAsB,oBAAoB,CAAC,EACzC,MAAM,EACN,GAAG,EACJ,EAAE;IACD,MAAM,EAAE,MAAM,GAAG,UAAU,CAAA;IAC3B,GAAG,EAAE,SAAS,CAAA;CACf;;;;;;GAyBA;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,GAAG,EAAE,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAEjE;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The unlock derivation: an unlock secret (a passphrase, or a passkey PRF
|
|
6
|
+
* output) to the unlock identity that locates an account's keyring record and
|
|
7
|
+
* wraps/unwraps it. Nothing about the account is derivable from the secret --
|
|
8
|
+
* the identity's only jobs are addressing the minimal unlock Space and holding
|
|
9
|
+
* the key-agreement key the keyring record is encrypted to.
|
|
10
|
+
*
|
|
11
|
+
* The derivation is wire-level: two wallet apps must produce byte-identical
|
|
12
|
+
* output for the same secret and parameter set, or the same passphrase would
|
|
13
|
+
* address two different unlock Spaces. It is therefore implemented over
|
|
14
|
+
* `@noble/hashes` rather than WebCrypto's `crypto.subtle.deriveBits`, which
|
|
15
|
+
* React Native does not provide; PBKDF2 and HKDF are both fully specified
|
|
16
|
+
* (RFC 8018 / RFC 5869), so the two implementations agree bit for bit.
|
|
17
|
+
*/
|
|
18
|
+
import { CapabilityAgent } from '@interop/webkms-client';
|
|
19
|
+
import { Ed25519Signature2020 } from '@interop/ed25519-signature';
|
|
20
|
+
import { ZcapClient } from '@interop/ezcap';
|
|
21
|
+
import { X25519KeyAgreementKey2020 } from '@interop/x25519-key-agreement-key';
|
|
22
|
+
import { base64urlnopad } from '@scure/base';
|
|
23
|
+
import { hkdf } from '@noble/hashes/hkdf.js';
|
|
24
|
+
import { pbkdf2Async } from '@noble/hashes/pbkdf2.js';
|
|
25
|
+
import { sha256, sha512 } from '@noble/hashes/sha2.js';
|
|
26
|
+
import { singleKeyResolver } from '../identity/keyResolver.js';
|
|
27
|
+
/**
|
|
28
|
+
* The load-bearing `CapabilityAgent` derivation names for an unlock identity
|
|
29
|
+
* (the counterpart of the data identity's bootstrap names): every unlock
|
|
30
|
+
* derivation runs through these exact strings, so they can never change
|
|
31
|
+
* without stranding existing accounts.
|
|
32
|
+
*/
|
|
33
|
+
export const UNLOCK_HANDLE = 'unlock';
|
|
34
|
+
export const UNLOCK_KEY_NAME = 'unlock-key';
|
|
35
|
+
/**
|
|
36
|
+
* The number of bytes an unlock derivation produces (a 32-byte seed, which is
|
|
37
|
+
* what `CapabilityAgent.fromSeed` takes).
|
|
38
|
+
*/
|
|
39
|
+
const UNLOCK_SEED_BYTES = 32;
|
|
40
|
+
/**
|
|
41
|
+
* PBKDF2 parameters for the passphrase unlock derivation
|
|
42
|
+
* (`unlockSeed = PBKDF2(passphrase)`). Version 1 pins exactly these
|
|
43
|
+
* parameters; the keyring record's `version` field records which set produced
|
|
44
|
+
* it, so changing any of them (iterations, hash, salt) requires minting a new
|
|
45
|
+
* record version rather than silently breaking existing unlock derivations.
|
|
46
|
+
* The salt is a fixed app-wide constant -- login stays passphrase-only, with
|
|
47
|
+
* no email (or other) input mixed into the derivation. Every unlock method's
|
|
48
|
+
* KDF carries a distinct salt, so two methods can never derive the same
|
|
49
|
+
* unlock Space.
|
|
50
|
+
*/
|
|
51
|
+
export const KEYRING_KDF = {
|
|
52
|
+
version: 1,
|
|
53
|
+
algorithm: 'PBKDF2',
|
|
54
|
+
iterations: 600_000,
|
|
55
|
+
hash: 'SHA-256',
|
|
56
|
+
salt: 'freewallet/keyring/unlock/v1'
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* The noble hash constructor a WebCrypto hash name selects, so the derivation
|
|
60
|
+
* matches `crypto.subtle.deriveBits` for the same parameters.
|
|
61
|
+
*
|
|
62
|
+
* @param hash {string} a WebCrypto digest name (`SHA-256`, `SHA-512`)
|
|
63
|
+
* @returns {object} the noble hash
|
|
64
|
+
*/
|
|
65
|
+
function nobleHash(hash) {
|
|
66
|
+
if (hash === 'SHA-256') {
|
|
67
|
+
return sha256;
|
|
68
|
+
}
|
|
69
|
+
if (hash === 'SHA-512') {
|
|
70
|
+
return sha512;
|
|
71
|
+
}
|
|
72
|
+
throw new Error(`Unsupported unlock KDF hash "${hash}".`);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Derives the 32-byte unlock seed from an unlock secret, branching on the KDF
|
|
76
|
+
* family: PBKDF2 stretches a passphrase, HKDF expands already-uniform key
|
|
77
|
+
* material such as a passkey PRF output.
|
|
78
|
+
*
|
|
79
|
+
* @param options {object}
|
|
80
|
+
* @param options.secret {string | Uint8Array}
|
|
81
|
+
* @param options.kdf {UnlockKdf}
|
|
82
|
+
* @returns {Promise<Uint8Array>}
|
|
83
|
+
*/
|
|
84
|
+
async function deriveUnlockSeed({ secret, kdf }) {
|
|
85
|
+
// Copy a bytes secret into a fresh buffer: a caller's slice may be a view
|
|
86
|
+
// into a larger buffer, and the codecs below read the whole view.
|
|
87
|
+
const secretBytes = typeof secret === 'string'
|
|
88
|
+
? new TextEncoder().encode(secret)
|
|
89
|
+
: new Uint8Array(secret);
|
|
90
|
+
const hash = nobleHash(kdf.hash);
|
|
91
|
+
const salt = new TextEncoder().encode(kdf.salt);
|
|
92
|
+
if (kdf.algorithm === 'PBKDF2') {
|
|
93
|
+
return pbkdf2Async(hash, secretBytes, salt, {
|
|
94
|
+
c: kdf.iterations,
|
|
95
|
+
dkLen: UNLOCK_SEED_BYTES
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return hkdf(hash, secretBytes, salt, new TextEncoder().encode(kdf.info), UNLOCK_SEED_BYTES);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Derives the full unlock identity from an unlock secret: the unlock
|
|
102
|
+
* CapabilityAgent, a ZcapClient that can both invoke and delegate (the
|
|
103
|
+
* unlock agent delegates a management zcap on its own Space to the account
|
|
104
|
+
* controller at bind time), the unlock KAK + resolver for wrap/unwrap, and the
|
|
105
|
+
* unlock Space id. Performs no I/O -- the derivation seam for tests and future
|
|
106
|
+
* unlock methods.
|
|
107
|
+
*
|
|
108
|
+
* @param options {object}
|
|
109
|
+
* @param options.secret {string | Uint8Array}
|
|
110
|
+
* @param options.kdf {UnlockKdf}
|
|
111
|
+
* @returns {Promise<object>}
|
|
112
|
+
*/
|
|
113
|
+
export async function deriveUnlockIdentity({ secret, kdf }) {
|
|
114
|
+
const seed = await deriveUnlockSeed({ secret, kdf });
|
|
115
|
+
const agent = await CapabilityAgent.fromSeed({
|
|
116
|
+
seed,
|
|
117
|
+
handle: UNLOCK_HANDLE,
|
|
118
|
+
keyName: UNLOCK_KEY_NAME
|
|
119
|
+
});
|
|
120
|
+
const signer = agent.getSigner();
|
|
121
|
+
const zcapClient = new ZcapClient({
|
|
122
|
+
SuiteClass: Ed25519Signature2020,
|
|
123
|
+
invocationSigner: signer,
|
|
124
|
+
delegationSigner: signer
|
|
125
|
+
});
|
|
126
|
+
// The unlock KAK is the Montgomery form of the unlock signing key -- the same
|
|
127
|
+
// derivation the client side uses (`agentsFromSeed`), so a returning user
|
|
128
|
+
// reconstitutes the exact key that wrapped the keyring record.
|
|
129
|
+
const keyAgreementKey = X25519KeyAgreementKey2020.fromEd25519VerificationKey2020({
|
|
130
|
+
keyPair: agent.getVerificationKeyPair()
|
|
131
|
+
});
|
|
132
|
+
const keyResolver = singleKeyResolver({ keyAgreementKey });
|
|
133
|
+
const spaceId = unlockSpaceIdFor({ did: agent.id });
|
|
134
|
+
return { agent, zcapClient, keyAgreementKey, keyResolver, spaceId };
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The unlock Space id an unlock identity addresses:
|
|
138
|
+
* `base64url(SHA-256(did))`, unpadded -- a discovery convention, not an
|
|
139
|
+
* authorization one (holding the id grants nothing).
|
|
140
|
+
*
|
|
141
|
+
* @param options {object}
|
|
142
|
+
* @param options.did {string} the unlock identity's did:key
|
|
143
|
+
* @returns {string}
|
|
144
|
+
*/
|
|
145
|
+
export function unlockSpaceIdFor({ did }) {
|
|
146
|
+
return base64urlnopad.encode(sha256(new TextEncoder().encode(did)));
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=kdf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kdf.js","sourceRoot":"","sources":["../../src/keyring/kdf.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* The keyring record codec: the `{ version, wrapped }` envelope stored as the
|
|
6
|
+
* one resource of an account's unlock Space. Its plaintext carries the account
|
|
7
|
+
* controller, the email captured at bind time, and the account pointer -- and
|
|
8
|
+
* deliberately no key material of any kind, so the record locates an account
|
|
9
|
+
* without authorizing anything against it.
|
|
10
|
+
*
|
|
11
|
+
* The wrap is an EDV document envelope under the unlock key-agreement key,
|
|
12
|
+
* bound to the `keyring` cipher context, so a keyring envelope can never be
|
|
13
|
+
* mistaken for (or swapped with) any other wrapped record.
|
|
14
|
+
*/
|
|
15
|
+
import type { IKeyAgreementKey, IKeyResolver } from '@interop/data-integrity-core';
|
|
16
|
+
/**
|
|
17
|
+
* The version stamped on the stored `{ version, wrapped }` keyring envelope.
|
|
18
|
+
* Version 2 is the account-pointer record; version-1 records (which carried a
|
|
19
|
+
* wrapped account-wide data seed) are refused as unusable -- such accounts are
|
|
20
|
+
* re-provisioned, not migrated.
|
|
21
|
+
*/
|
|
22
|
+
export declare const KEYRING_RECORD_VERSION = 2;
|
|
23
|
+
/**
|
|
24
|
+
* The account pointer a keyring record carries in place of the retired data
|
|
25
|
+
* seed: where the account lives (`spaceId` + `host`, the WAS server origin)
|
|
26
|
+
* and, once provisioning has published it, the account's stable did:webvh id.
|
|
27
|
+
* Discovery only -- holding the pointer authorizes nothing.
|
|
28
|
+
*/
|
|
29
|
+
export interface AccountPointer {
|
|
30
|
+
did?: string;
|
|
31
|
+
spaceId: string;
|
|
32
|
+
host: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The unwrapped contents of a keyring record: the account controller (the
|
|
36
|
+
* first enrolled client's did:key today), the account email captured at bind
|
|
37
|
+
* time (when one was given -- carried so any unlock method recovers it; a
|
|
38
|
+
* passkey login has no login form to ask on), and the account pointer (absent
|
|
39
|
+
* only on no-WAS deployments, where there is no Space to point at).
|
|
40
|
+
*/
|
|
41
|
+
export interface KeyringRecordContents {
|
|
42
|
+
controller: string;
|
|
43
|
+
email?: string;
|
|
44
|
+
pointer?: AccountPointer;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Wraps the account-pointer contents into a keyring record: the controller,
|
|
48
|
+
* email, and pointer (+ timestamp) encrypted under the unlock KAK via the EDV
|
|
49
|
+
* cipher. Deliberately carries no key material of any kind.
|
|
50
|
+
*
|
|
51
|
+
* @param options {object}
|
|
52
|
+
* @param options.controller {string} the account did:key
|
|
53
|
+
* @param [options.email] {string} the account email, when known
|
|
54
|
+
* @param [options.pointer] {AccountPointer} the account pointer (absent on
|
|
55
|
+
* no-WAS deployments)
|
|
56
|
+
* @param options.keyAgreementKey {IKeyAgreementKey} the unlock KAK
|
|
57
|
+
* @param options.keyResolver {IKeyResolver}
|
|
58
|
+
* @returns {Promise<{ version: number, wrapped: unknown }>}
|
|
59
|
+
*/
|
|
60
|
+
export declare function wrapKeyringRecord({ controller, email, pointer, keyAgreementKey, keyResolver }: {
|
|
61
|
+
controller: string;
|
|
62
|
+
email?: string;
|
|
63
|
+
pointer?: AccountPointer;
|
|
64
|
+
keyAgreementKey: IKeyAgreementKey;
|
|
65
|
+
keyResolver: IKeyResolver;
|
|
66
|
+
}): Promise<{
|
|
67
|
+
version: number;
|
|
68
|
+
wrapped: unknown;
|
|
69
|
+
}>;
|
|
70
|
+
/**
|
|
71
|
+
* Unwraps and validates a keyring record. Rejects a record whose `version` is
|
|
72
|
+
* not the current one (version-1 records carried the retired wrapped data
|
|
73
|
+
* seed and are refused -- accounts are re-provisioned, not migrated), and
|
|
74
|
+
* sanity-checks the decrypted plaintext (non-empty controller, well-formed
|
|
75
|
+
* pointer when present).
|
|
76
|
+
*
|
|
77
|
+
* @param options {object}
|
|
78
|
+
* @param options.record {unknown}
|
|
79
|
+
* @param options.keyAgreementKey {IKeyAgreementKey} the unlock KAK
|
|
80
|
+
* @param options.keyResolver {IKeyResolver}
|
|
81
|
+
* @returns {Promise<KeyringRecordContents>}
|
|
82
|
+
*/
|
|
83
|
+
export declare function unwrapKeyringRecord({ record, keyAgreementKey, keyResolver }: {
|
|
84
|
+
record: unknown;
|
|
85
|
+
keyAgreementKey: IKeyAgreementKey;
|
|
86
|
+
keyResolver: IKeyResolver;
|
|
87
|
+
}): Promise<KeyringRecordContents>;
|
|
88
|
+
/**
|
|
89
|
+
* Parses and validates the optional `pointer` member of a keyring record
|
|
90
|
+
* plaintext. An absent member is a no-WAS record (returns undefined); a
|
|
91
|
+
* present-but-malformed one throws -- a record that claims a pointer but
|
|
92
|
+
* cannot state where the account lives is unusable.
|
|
93
|
+
*
|
|
94
|
+
* @param value {unknown} the record's `pointer` member
|
|
95
|
+
* @returns {AccountPointer | undefined}
|
|
96
|
+
*/
|
|
97
|
+
export declare function parseRecordPointer(value: unknown): AccountPointer | undefined;
|
|
98
|
+
//# sourceMappingURL=record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../src/keyring/record.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACb,MAAM,8BAA8B,CAAA;AAIrC;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAA;AAEvC;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,cAAc,CAAA;CACzB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CAAC,EACtC,UAAU,EACV,KAAK,EACL,OAAO,EACP,eAAe,EACf,WAAW,EACZ,EAAE;IACD,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,cAAc,CAAA;IACxB,eAAe,EAAE,gBAAgB,CAAA;IACjC,WAAW,EAAE,YAAY,CAAA;CAC1B,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAuBjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CAAC,EACxC,MAAM,EACN,eAAe,EACf,WAAW,EACZ,EAAE;IACD,MAAM,EAAE,OAAO,CAAA;IACf,eAAe,EAAE,gBAAgB,CAAA;IACjC,WAAW,EAAE,YAAY,CAAA;CAC1B,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAsCjC;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CAsB7E"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { createEdvDocCipher } from '@interop/was-client/edv';
|
|
2
|
+
import { KEYRING_COLLECTION } from '../space/collections.js';
|
|
3
|
+
/**
|
|
4
|
+
* The version stamped on the stored `{ version, wrapped }` keyring envelope.
|
|
5
|
+
* Version 2 is the account-pointer record; version-1 records (which carried a
|
|
6
|
+
* wrapped account-wide data seed) are refused as unusable -- such accounts are
|
|
7
|
+
* re-provisioned, not migrated.
|
|
8
|
+
*/
|
|
9
|
+
export const KEYRING_RECORD_VERSION = 2;
|
|
10
|
+
/**
|
|
11
|
+
* Wraps the account-pointer contents into a keyring record: the controller,
|
|
12
|
+
* email, and pointer (+ timestamp) encrypted under the unlock KAK via the EDV
|
|
13
|
+
* cipher. Deliberately carries no key material of any kind.
|
|
14
|
+
*
|
|
15
|
+
* @param options {object}
|
|
16
|
+
* @param options.controller {string} the account did:key
|
|
17
|
+
* @param [options.email] {string} the account email, when known
|
|
18
|
+
* @param [options.pointer] {AccountPointer} the account pointer (absent on
|
|
19
|
+
* no-WAS deployments)
|
|
20
|
+
* @param options.keyAgreementKey {IKeyAgreementKey} the unlock KAK
|
|
21
|
+
* @param options.keyResolver {IKeyResolver}
|
|
22
|
+
* @returns {Promise<{ version: number, wrapped: unknown }>}
|
|
23
|
+
*/
|
|
24
|
+
export async function wrapKeyringRecord({ controller, email, pointer, keyAgreementKey, keyResolver }) {
|
|
25
|
+
const cipher = await createEdvDocCipher({
|
|
26
|
+
keyAgreementKey,
|
|
27
|
+
keyResolver,
|
|
28
|
+
collectionId: KEYRING_COLLECTION.id
|
|
29
|
+
});
|
|
30
|
+
const { envelope } = await cipher.encrypt({
|
|
31
|
+
data: {
|
|
32
|
+
controller,
|
|
33
|
+
...(email ? { email } : {}),
|
|
34
|
+
...(pointer
|
|
35
|
+
? {
|
|
36
|
+
pointer: {
|
|
37
|
+
...(pointer.did ? { did: pointer.did } : {}),
|
|
38
|
+
spaceId: pointer.spaceId,
|
|
39
|
+
host: pointer.host
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
: {}),
|
|
43
|
+
createdAt: new Date().toISOString()
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return { version: KEYRING_RECORD_VERSION, wrapped: envelope };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Unwraps and validates a keyring record. Rejects a record whose `version` is
|
|
50
|
+
* not the current one (version-1 records carried the retired wrapped data
|
|
51
|
+
* seed and are refused -- accounts are re-provisioned, not migrated), and
|
|
52
|
+
* sanity-checks the decrypted plaintext (non-empty controller, well-formed
|
|
53
|
+
* pointer when present).
|
|
54
|
+
*
|
|
55
|
+
* @param options {object}
|
|
56
|
+
* @param options.record {unknown}
|
|
57
|
+
* @param options.keyAgreementKey {IKeyAgreementKey} the unlock KAK
|
|
58
|
+
* @param options.keyResolver {IKeyResolver}
|
|
59
|
+
* @returns {Promise<KeyringRecordContents>}
|
|
60
|
+
*/
|
|
61
|
+
export async function unwrapKeyringRecord({ record, keyAgreementKey, keyResolver }) {
|
|
62
|
+
if (record === null || typeof record !== 'object') {
|
|
63
|
+
throw new Error('Malformed keyring record.');
|
|
64
|
+
}
|
|
65
|
+
const { version, wrapped } = record;
|
|
66
|
+
if (version !== KEYRING_RECORD_VERSION) {
|
|
67
|
+
throw new Error(`Unsupported keyring record version "${String(version)}".`);
|
|
68
|
+
}
|
|
69
|
+
const cipher = await createEdvDocCipher({
|
|
70
|
+
keyAgreementKey,
|
|
71
|
+
keyResolver,
|
|
72
|
+
collectionId: KEYRING_COLLECTION.id
|
|
73
|
+
});
|
|
74
|
+
const plaintext = (await cipher.decrypt({
|
|
75
|
+
envelope: wrapped
|
|
76
|
+
}));
|
|
77
|
+
if (typeof plaintext.controller !== 'string' || !plaintext.controller) {
|
|
78
|
+
throw new Error('Keyring record is missing a controller.');
|
|
79
|
+
}
|
|
80
|
+
const pointer = parseRecordPointer(plaintext.pointer);
|
|
81
|
+
return {
|
|
82
|
+
controller: plaintext.controller,
|
|
83
|
+
// A record bound without an email simply has no email; anything
|
|
84
|
+
// non-string is ignored, not fatal.
|
|
85
|
+
...(typeof plaintext.email === 'string' && plaintext.email
|
|
86
|
+
? { email: plaintext.email }
|
|
87
|
+
: {}),
|
|
88
|
+
...(pointer ? { pointer } : {})
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Parses and validates the optional `pointer` member of a keyring record
|
|
93
|
+
* plaintext. An absent member is a no-WAS record (returns undefined); a
|
|
94
|
+
* present-but-malformed one throws -- a record that claims a pointer but
|
|
95
|
+
* cannot state where the account lives is unusable.
|
|
96
|
+
*
|
|
97
|
+
* @param value {unknown} the record's `pointer` member
|
|
98
|
+
* @returns {AccountPointer | undefined}
|
|
99
|
+
*/
|
|
100
|
+
export function parseRecordPointer(value) {
|
|
101
|
+
if (value === undefined) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
if (value === null || typeof value !== 'object') {
|
|
105
|
+
throw new Error('Keyring record has a malformed account pointer.');
|
|
106
|
+
}
|
|
107
|
+
const { did, spaceId, host } = value;
|
|
108
|
+
if (typeof spaceId !== 'string' || !spaceId) {
|
|
109
|
+
throw new Error('Keyring record account pointer is missing its spaceId.');
|
|
110
|
+
}
|
|
111
|
+
if (typeof host !== 'string' || !host) {
|
|
112
|
+
throw new Error('Keyring record account pointer is missing its host.');
|
|
113
|
+
}
|
|
114
|
+
if (did !== undefined && (typeof did !== 'string' || !did)) {
|
|
115
|
+
throw new Error('Keyring record account pointer has a malformed did.');
|
|
116
|
+
}
|
|
117
|
+
return { ...(did ? { did } : {}), spaceId, host };
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/keyring/record.ts"],"names":[],"mappings":"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"}
|