@metamask-previews/kyc-controller 0.0.0-preview-823dcff → 0.0.0-preview-e57e5c3dc
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/CHANGELOG.md +8 -0
- package/README.md +10 -2
- package/dist/KycController-method-action-types.cjs +7 -0
- package/dist/KycController-method-action-types.cjs.map +1 -0
- package/dist/KycController-method-action-types.d.cts +168 -0
- package/dist/KycController-method-action-types.d.cts.map +1 -0
- package/dist/KycController-method-action-types.d.mts +168 -0
- package/dist/KycController-method-action-types.d.mts.map +1 -0
- package/dist/KycController-method-action-types.mjs +6 -0
- package/dist/KycController-method-action-types.mjs.map +1 -0
- package/dist/KycController.cjs +1085 -0
- package/dist/KycController.cjs.map +1 -0
- package/dist/KycController.d.cts +255 -0
- package/dist/KycController.d.cts.map +1 -0
- package/dist/KycController.d.mts +255 -0
- package/dist/KycController.d.mts.map +1 -0
- package/dist/KycController.mjs +1080 -0
- package/dist/KycController.mjs.map +1 -0
- package/dist/KycService-method-action-types.cjs +7 -0
- package/dist/KycService-method-action-types.cjs.map +1 -0
- package/dist/KycService-method-action-types.d.cts +121 -0
- package/dist/KycService-method-action-types.d.cts.map +1 -0
- package/dist/KycService-method-action-types.d.mts +121 -0
- package/dist/KycService-method-action-types.d.mts.map +1 -0
- package/dist/KycService-method-action-types.mjs +6 -0
- package/dist/KycService-method-action-types.mjs.map +1 -0
- package/dist/KycService.cjs +417 -0
- package/dist/KycService.cjs.map +1 -0
- package/dist/KycService.d.cts +307 -0
- package/dist/KycService.d.cts.map +1 -0
- package/dist/KycService.d.mts +307 -0
- package/dist/KycService.d.mts.map +1 -0
- package/dist/KycService.mjs +413 -0
- package/dist/KycService.mjs.map +1 -0
- package/dist/countryCodes.cjs +274 -0
- package/dist/countryCodes.cjs.map +1 -0
- package/dist/countryCodes.d.cts +18 -0
- package/dist/countryCodes.d.cts.map +1 -0
- package/dist/countryCodes.d.mts +18 -0
- package/dist/countryCodes.d.mts.map +1 -0
- package/dist/countryCodes.mjs +270 -0
- package/dist/countryCodes.mjs.map +1 -0
- package/dist/crypto.cjs +154 -0
- package/dist/crypto.cjs.map +1 -0
- package/dist/crypto.d.cts +84 -0
- package/dist/crypto.d.cts.map +1 -0
- package/dist/crypto.d.mts +84 -0
- package/dist/crypto.d.mts.map +1 -0
- package/dist/crypto.mjs +149 -0
- package/dist/crypto.mjs.map +1 -0
- package/dist/encoding.cjs +40 -0
- package/dist/encoding.cjs.map +1 -0
- package/dist/encoding.d.cts +24 -0
- package/dist/encoding.d.cts.map +1 -0
- package/dist/encoding.d.mts +24 -0
- package/dist/encoding.d.mts.map +1 -0
- package/dist/encoding.mjs +35 -0
- package/dist/encoding.mjs.map +1 -0
- package/dist/index.cjs +34 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -7
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +18 -7
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +11 -9
- package/dist/index.mjs.map +1 -1
- package/dist/selectors.cjs +30 -0
- package/dist/selectors.cjs.map +1 -0
- package/dist/selectors.d.cts +24 -0
- package/dist/selectors.d.cts.map +1 -0
- package/dist/selectors.d.mts +24 -0
- package/dist/selectors.d.mts.map +1 -0
- package/dist/selectors.mjs +24 -0
- package/dist/selectors.mjs.map +1 -0
- package/dist/types.cjs +10 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +124 -0
- package/dist/types.d.cts.map +1 -0
- package/dist/types.d.mts +124 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/types.mjs +9 -0
- package/dist/types.mjs.map +1 -0
- package/dist/ukyc/constants.cjs +75 -0
- package/dist/ukyc/constants.cjs.map +1 -0
- package/dist/ukyc/constants.d.cts +69 -0
- package/dist/ukyc/constants.d.cts.map +1 -0
- package/dist/ukyc/constants.d.mts +69 -0
- package/dist/ukyc/constants.d.mts.map +1 -0
- package/dist/ukyc/constants.mjs +72 -0
- package/dist/ukyc/constants.mjs.map +1 -0
- package/dist/ukyc/deriveClientMaterial.cjs +65 -0
- package/dist/ukyc/deriveClientMaterial.cjs.map +1 -0
- package/dist/ukyc/deriveClientMaterial.d.cts +54 -0
- package/dist/ukyc/deriveClientMaterial.d.cts.map +1 -0
- package/dist/ukyc/deriveClientMaterial.d.mts +54 -0
- package/dist/ukyc/deriveClientMaterial.d.mts.map +1 -0
- package/dist/ukyc/deriveClientMaterial.mjs +60 -0
- package/dist/ukyc/deriveClientMaterial.mjs.map +1 -0
- package/dist/ukyc/jwtChain.cjs +54 -0
- package/dist/ukyc/jwtChain.cjs.map +1 -0
- package/dist/ukyc/jwtChain.d.cts +40 -0
- package/dist/ukyc/jwtChain.d.cts.map +1 -0
- package/dist/ukyc/jwtChain.d.mts +40 -0
- package/dist/ukyc/jwtChain.d.mts.map +1 -0
- package/dist/ukyc/jwtChain.mjs +50 -0
- package/dist/ukyc/jwtChain.mjs.map +1 -0
- package/dist/ukyc/localUserSecret.cjs +98 -0
- package/dist/ukyc/localUserSecret.cjs.map +1 -0
- package/dist/ukyc/localUserSecret.d.cts +65 -0
- package/dist/ukyc/localUserSecret.d.cts.map +1 -0
- package/dist/ukyc/localUserSecret.d.mts +65 -0
- package/dist/ukyc/localUserSecret.d.mts.map +1 -0
- package/dist/ukyc/localUserSecret.mjs +92 -0
- package/dist/ukyc/localUserSecret.mjs.map +1 -0
- package/dist/ukyc/storageAccessToken.cjs +139 -0
- package/dist/ukyc/storageAccessToken.cjs.map +1 -0
- package/dist/ukyc/storageAccessToken.d.cts +99 -0
- package/dist/ukyc/storageAccessToken.d.cts.map +1 -0
- package/dist/ukyc/storageAccessToken.d.mts +99 -0
- package/dist/ukyc/storageAccessToken.d.mts.map +1 -0
- package/dist/ukyc/storageAccessToken.mjs +133 -0
- package/dist/ukyc/storageAccessToken.mjs.map +1 -0
- package/dist/ukyc/testToken.cjs +61 -0
- package/dist/ukyc/testToken.cjs.map +1 -0
- package/dist/ukyc/testToken.d.cts +50 -0
- package/dist/ukyc/testToken.d.cts.map +1 -0
- package/dist/ukyc/testToken.d.mts +50 -0
- package/dist/ukyc/testToken.d.mts.map +1 -0
- package/dist/ukyc/testToken.mjs +57 -0
- package/dist/ukyc/testToken.mjs.map +1 -0
- package/dist/ukyc/wrapEncryptionKey.cjs +28 -0
- package/dist/ukyc/wrapEncryptionKey.cjs.map +1 -0
- package/dist/ukyc/wrapEncryptionKey.d.cts +34 -0
- package/dist/ukyc/wrapEncryptionKey.d.cts.map +1 -0
- package/dist/ukyc/wrapEncryptionKey.d.mts +34 -0
- package/dist/ukyc/wrapEncryptionKey.d.mts.map +1 -0
- package/dist/ukyc/wrapEncryptionKey.mjs +25 -0
- package/dist/ukyc/wrapEncryptionKey.mjs.map +1 -0
- package/dist/ukyc/wrapUserKey.cjs +80 -0
- package/dist/ukyc/wrapUserKey.cjs.map +1 -0
- package/dist/ukyc/wrapUserKey.d.cts +26 -0
- package/dist/ukyc/wrapUserKey.d.cts.map +1 -0
- package/dist/ukyc/wrapUserKey.d.mts +26 -0
- package/dist/ukyc/wrapUserKey.d.mts.map +1 -0
- package/dist/ukyc/wrapUserKey.mjs +76 -0
- package/dist/ukyc/wrapUserKey.mjs.map +1 -0
- package/dist/ukyc/wrappedRelayPayload.cjs +32 -0
- package/dist/ukyc/wrappedRelayPayload.cjs.map +1 -0
- package/dist/ukyc/wrappedRelayPayload.d.cts +35 -0
- package/dist/ukyc/wrappedRelayPayload.d.cts.map +1 -0
- package/dist/ukyc/wrappedRelayPayload.d.mts +35 -0
- package/dist/ukyc/wrappedRelayPayload.d.mts.map +1 -0
- package/dist/ukyc/wrappedRelayPayload.mjs +28 -0
- package/dist/ukyc/wrappedRelayPayload.mjs.map +1 -0
- package/package.json +24 -3
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derives UKYC client material from the root `local_user_secret` using
|
|
3
|
+
* HKDF-SHA256 with domain-separated `info` labels — see the architecture doc,
|
|
4
|
+
* section "Client-Derived Material".
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* The set of values derived from `local_user_secret`.
|
|
8
|
+
*/
|
|
9
|
+
export type UkycClientMaterial = {
|
|
10
|
+
/** Opaque lookup key for the encrypted KYC object. */
|
|
11
|
+
storageId: Uint8Array;
|
|
12
|
+
/** Symmetric key that encrypts `encrypted_kyc_data` (or wraps per-blob keys). */
|
|
13
|
+
dataEncryptionKey: Uint8Array;
|
|
14
|
+
/**
|
|
15
|
+
* Ed25519 private key used to sign `storage_access_token` capabilities. For
|
|
16
|
+
* Ed25519 the 32-byte HKDF output *is* the private key. The private half
|
|
17
|
+
* never leaves the device.
|
|
18
|
+
*/
|
|
19
|
+
signingKey: Uint8Array;
|
|
20
|
+
/** Public half of `signingKey`, registered with the object on first write. */
|
|
21
|
+
signingPublicKey: Uint8Array;
|
|
22
|
+
/** Key for establishing/authenticating the encrypted tunnel to idOS, if needed. */
|
|
23
|
+
relayTunnelKey: Uint8Array;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* The same material with byte fields base64url-encoded, matching the wire
|
|
27
|
+
* shapes in the architecture doc (`storage_id` as an opaque id, public key as a
|
|
28
|
+
* "base64url public key"). Secret material is intentionally omitted.
|
|
29
|
+
*/
|
|
30
|
+
export type EncodedUkycClientMaterial = {
|
|
31
|
+
storageId: string;
|
|
32
|
+
signingPublicKey: string;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Derives all UKYC client material from the root `local_user_secret`.
|
|
36
|
+
*
|
|
37
|
+
* This is a pure function of `local_user_secret`: the same input always yields
|
|
38
|
+
* the same outputs, which is what makes `storage_id` and `signing_key` stable
|
|
39
|
+
* across sessions and devices.
|
|
40
|
+
*
|
|
41
|
+
* @param localUserSecret - The `local_user_secret` produced by
|
|
42
|
+
* `getOrCreateLocalUserSecret`.
|
|
43
|
+
* @returns The derived {@link UkycClientMaterial}.
|
|
44
|
+
*/
|
|
45
|
+
export declare function deriveClientMaterial(localUserSecret: Uint8Array): UkycClientMaterial;
|
|
46
|
+
/**
|
|
47
|
+
* Encodes the non-secret client material into the base64url wire shapes used by
|
|
48
|
+
* the UKYC storage API (`storage_id` and `signing_public_key`).
|
|
49
|
+
*
|
|
50
|
+
* @param material - The derived client material.
|
|
51
|
+
* @returns The base64url-encoded, non-secret fields.
|
|
52
|
+
*/
|
|
53
|
+
export declare function encodeClientMaterial(material: UkycClientMaterial): EncodedUkycClientMaterial;
|
|
54
|
+
//# sourceMappingURL=deriveClientMaterial.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deriveClientMaterial.d.mts","sourceRoot":"","sources":["../../src/ukyc/deriveClientMaterial.ts"],"names":[],"mappings":"AAQA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,sDAAsD;IACtD,SAAS,EAAE,UAAU,CAAC;IACtB,iFAAiF;IACjF,iBAAiB,EAAE,UAAU,CAAC;IAC9B;;;;OAIG;IACH,UAAU,EAAE,UAAU,CAAC;IACvB,8EAA8E;IAC9E,gBAAgB,EAAE,UAAU,CAAC;IAC7B,mFAAmF;IACnF,cAAc,EAAE,UAAU,CAAC;CAC5B,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAsBF;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,eAAe,EAAE,UAAU,GAC1B,kBAAkB,CAkCpB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,kBAAkB,GAC3B,yBAAyB,CAK3B"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { stringToBytes } from "@metamask/utils";
|
|
2
|
+
import { ed25519 } from "@noble/curves/ed25519";
|
|
3
|
+
import { hkdf } from "@noble/hashes/hkdf";
|
|
4
|
+
import { sha256 } from "@noble/hashes/sha2";
|
|
5
|
+
import { UKYC_DERIVED_KEY_SIZES, UKYC_KDF_INFO } from "./constants.mjs";
|
|
6
|
+
import { toBase64Url } from "../encoding.mjs";
|
|
7
|
+
/**
|
|
8
|
+
* Derives a single labeled value from `local_user_secret`.
|
|
9
|
+
*
|
|
10
|
+
* No salt is used: `local_user_secret` is already a high-entropy
|
|
11
|
+
* uniformly-random secret, so per-output domain separation comes entirely from
|
|
12
|
+
* the `info` label.
|
|
13
|
+
*
|
|
14
|
+
* @param localUserSecret - The root `local_user_secret` bytes.
|
|
15
|
+
* @param info - Domain-separation label for this output.
|
|
16
|
+
* @param length - Desired output length in bytes.
|
|
17
|
+
* @returns The derived bytes.
|
|
18
|
+
*/
|
|
19
|
+
function deriveLabeled(localUserSecret, info, length) {
|
|
20
|
+
return hkdf(sha256, localUserSecret, undefined, stringToBytes(info), length);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Derives all UKYC client material from the root `local_user_secret`.
|
|
24
|
+
*
|
|
25
|
+
* This is a pure function of `local_user_secret`: the same input always yields
|
|
26
|
+
* the same outputs, which is what makes `storage_id` and `signing_key` stable
|
|
27
|
+
* across sessions and devices.
|
|
28
|
+
*
|
|
29
|
+
* @param localUserSecret - The `local_user_secret` produced by
|
|
30
|
+
* `getOrCreateLocalUserSecret`.
|
|
31
|
+
* @returns The derived {@link UkycClientMaterial}.
|
|
32
|
+
*/
|
|
33
|
+
export function deriveClientMaterial(localUserSecret) {
|
|
34
|
+
const storageId = deriveLabeled(localUserSecret, UKYC_KDF_INFO.storageId, UKYC_DERIVED_KEY_SIZES.storageId);
|
|
35
|
+
const dataEncryptionKey = deriveLabeled(localUserSecret, UKYC_KDF_INFO.dataEncryptionKey, UKYC_DERIVED_KEY_SIZES.dataEncryptionKey);
|
|
36
|
+
const signingKey = deriveLabeled(localUserSecret, UKYC_KDF_INFO.signingKey, UKYC_DERIVED_KEY_SIZES.signingKey);
|
|
37
|
+
const relayTunnelKey = deriveLabeled(localUserSecret, UKYC_KDF_INFO.relayTunnelKey, UKYC_DERIVED_KEY_SIZES.relayTunnelKey);
|
|
38
|
+
const signingPublicKey = ed25519.getPublicKey(signingKey);
|
|
39
|
+
return {
|
|
40
|
+
storageId,
|
|
41
|
+
dataEncryptionKey,
|
|
42
|
+
signingKey,
|
|
43
|
+
signingPublicKey,
|
|
44
|
+
relayTunnelKey,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Encodes the non-secret client material into the base64url wire shapes used by
|
|
49
|
+
* the UKYC storage API (`storage_id` and `signing_public_key`).
|
|
50
|
+
*
|
|
51
|
+
* @param material - The derived client material.
|
|
52
|
+
* @returns The base64url-encoded, non-secret fields.
|
|
53
|
+
*/
|
|
54
|
+
export function encodeClientMaterial(material) {
|
|
55
|
+
return {
|
|
56
|
+
storageId: toBase64Url(material.storageId),
|
|
57
|
+
signingPublicKey: toBase64Url(material.signingPublicKey),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=deriveClientMaterial.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deriveClientMaterial.mjs","sourceRoot":"","sources":["../../src/ukyc/deriveClientMaterial.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,wBAAwB;AAChD,OAAO,EAAE,OAAO,EAAE,8BAA8B;AAChD,OAAO,EAAE,IAAI,EAAE,2BAA2B;AAC1C,OAAO,EAAE,MAAM,EAAE,2BAA2B;AAE5C,OAAO,EAAE,sBAAsB,EAAE,aAAa,EAAE,wBAAuB;AACvE,OAAO,EAAE,WAAW,EAAE,wBAAuB;AAsC7C;;;;;;;;;;;GAWG;AACH,SAAS,aAAa,CACpB,eAA2B,EAC3B,IAAY,EACZ,MAAc;IAEd,OAAO,IAAI,CAAC,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;AAC/E,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAClC,eAA2B;IAE3B,MAAM,SAAS,GAAG,aAAa,CAC7B,eAAe,EACf,aAAa,CAAC,SAAS,EACvB,sBAAsB,CAAC,SAAS,CACjC,CAAC;IAEF,MAAM,iBAAiB,GAAG,aAAa,CACrC,eAAe,EACf,aAAa,CAAC,iBAAiB,EAC/B,sBAAsB,CAAC,iBAAiB,CACzC,CAAC;IAEF,MAAM,UAAU,GAAG,aAAa,CAC9B,eAAe,EACf,aAAa,CAAC,UAAU,EACxB,sBAAsB,CAAC,UAAU,CAClC,CAAC;IAEF,MAAM,cAAc,GAAG,aAAa,CAClC,eAAe,EACf,aAAa,CAAC,cAAc,EAC5B,sBAAsB,CAAC,cAAc,CACtC,CAAC;IAEF,MAAM,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;IAE1D,OAAO;QACL,SAAS;QACT,iBAAiB;QACjB,UAAU;QACV,gBAAgB;QAChB,cAAc;KACf,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,QAA4B;IAE5B,OAAO;QACL,SAAS,EAAE,WAAW,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC1C,gBAAgB,EAAE,WAAW,CAAC,QAAQ,CAAC,gBAAgB,CAAC;KACzD,CAAC;AACJ,CAAC","sourcesContent":["import { stringToBytes } from '@metamask/utils';\nimport { ed25519 } from '@noble/curves/ed25519';\nimport { hkdf } from '@noble/hashes/hkdf';\nimport { sha256 } from '@noble/hashes/sha2';\n\nimport { UKYC_DERIVED_KEY_SIZES, UKYC_KDF_INFO } from './constants.js';\nimport { toBase64Url } from '../encoding.js';\n\n/**\n * Derives UKYC client material from the root `local_user_secret` using\n * HKDF-SHA256 with domain-separated `info` labels — see the architecture doc,\n * section \"Client-Derived Material\".\n */\n\n/**\n * The set of values derived from `local_user_secret`.\n */\nexport type UkycClientMaterial = {\n /** Opaque lookup key for the encrypted KYC object. */\n storageId: Uint8Array;\n /** Symmetric key that encrypts `encrypted_kyc_data` (or wraps per-blob keys). */\n dataEncryptionKey: Uint8Array;\n /**\n * Ed25519 private key used to sign `storage_access_token` capabilities. For\n * Ed25519 the 32-byte HKDF output *is* the private key. The private half\n * never leaves the device.\n */\n signingKey: Uint8Array;\n /** Public half of `signingKey`, registered with the object on first write. */\n signingPublicKey: Uint8Array;\n /** Key for establishing/authenticating the encrypted tunnel to idOS, if needed. */\n relayTunnelKey: Uint8Array;\n};\n\n/**\n * The same material with byte fields base64url-encoded, matching the wire\n * shapes in the architecture doc (`storage_id` as an opaque id, public key as a\n * \"base64url public key\"). Secret material is intentionally omitted.\n */\nexport type EncodedUkycClientMaterial = {\n storageId: string;\n signingPublicKey: string;\n};\n\n/**\n * Derives a single labeled value from `local_user_secret`.\n *\n * No salt is used: `local_user_secret` is already a high-entropy\n * uniformly-random secret, so per-output domain separation comes entirely from\n * the `info` label.\n *\n * @param localUserSecret - The root `local_user_secret` bytes.\n * @param info - Domain-separation label for this output.\n * @param length - Desired output length in bytes.\n * @returns The derived bytes.\n */\nfunction deriveLabeled(\n localUserSecret: Uint8Array,\n info: string,\n length: number,\n): Uint8Array {\n return hkdf(sha256, localUserSecret, undefined, stringToBytes(info), length);\n}\n\n/**\n * Derives all UKYC client material from the root `local_user_secret`.\n *\n * This is a pure function of `local_user_secret`: the same input always yields\n * the same outputs, which is what makes `storage_id` and `signing_key` stable\n * across sessions and devices.\n *\n * @param localUserSecret - The `local_user_secret` produced by\n * `getOrCreateLocalUserSecret`.\n * @returns The derived {@link UkycClientMaterial}.\n */\nexport function deriveClientMaterial(\n localUserSecret: Uint8Array,\n): UkycClientMaterial {\n const storageId = deriveLabeled(\n localUserSecret,\n UKYC_KDF_INFO.storageId,\n UKYC_DERIVED_KEY_SIZES.storageId,\n );\n\n const dataEncryptionKey = deriveLabeled(\n localUserSecret,\n UKYC_KDF_INFO.dataEncryptionKey,\n UKYC_DERIVED_KEY_SIZES.dataEncryptionKey,\n );\n\n const signingKey = deriveLabeled(\n localUserSecret,\n UKYC_KDF_INFO.signingKey,\n UKYC_DERIVED_KEY_SIZES.signingKey,\n );\n\n const relayTunnelKey = deriveLabeled(\n localUserSecret,\n UKYC_KDF_INFO.relayTunnelKey,\n UKYC_DERIVED_KEY_SIZES.relayTunnelKey,\n );\n\n const signingPublicKey = ed25519.getPublicKey(signingKey);\n\n return {\n storageId,\n dataEncryptionKey,\n signingKey,\n signingPublicKey,\n relayTunnelKey,\n };\n}\n\n/**\n * Encodes the non-secret client material into the base64url wire shapes used by\n * the UKYC storage API (`storage_id` and `signing_public_key`).\n *\n * @param material - The derived client material.\n * @returns The base64url-encoded, non-secret fields.\n */\nexport function encodeClientMaterial(\n material: UkycClientMaterial,\n): EncodedUkycClientMaterial {\n return {\n storageId: toBase64Url(material.storageId),\n signingPublicKey: toBase64Url(material.signingPublicKey),\n };\n}\n"]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.verifyJwtChain = void 0;
|
|
4
|
+
const utils_1 = require("@metamask/utils");
|
|
5
|
+
const ed25519_1 = require("@noble/curves/ed25519");
|
|
6
|
+
const encoding_js_1 = require("../encoding.cjs");
|
|
7
|
+
/**
|
|
8
|
+
* Decodes a base64url JWT segment into a parsed JSON object.
|
|
9
|
+
*
|
|
10
|
+
* @param segment - The base64url-encoded segment.
|
|
11
|
+
* @param label - Human-readable segment name for error messages.
|
|
12
|
+
* @returns The parsed JSON object.
|
|
13
|
+
*/
|
|
14
|
+
function decodeJsonSegment(segment, label) {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse((0, utils_1.bytesToString)((0, encoding_js_1.base64UrlToBytes)(segment)));
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
throw new Error(`UKYC: failed to decode jwtChain ${label}: ${String(error)}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Verifies `jwtChain` against `keys`: matches the JWT header `kid` to a
|
|
24
|
+
* published Ed25519 signing key and checks the EdDSA signature over the
|
|
25
|
+
* `header.payload` input. Returns the decoded, verified payload.
|
|
26
|
+
*
|
|
27
|
+
* @param keys - The JWKS keys published by the Fractal encryption service.
|
|
28
|
+
* @param jwtChain - The compact-serialized EdDSA JWT from `getWrappingKey`.
|
|
29
|
+
* @returns The verified JWT payload.
|
|
30
|
+
*/
|
|
31
|
+
function verifyJwtChain(keys, jwtChain) {
|
|
32
|
+
const [headerSegment, payloadSegment, signatureSegment] = jwtChain.split('.');
|
|
33
|
+
if (!headerSegment || !payloadSegment || !signatureSegment) {
|
|
34
|
+
throw new Error('UKYC: jwtChain is not a well-formed JWT (expected 3 segments).');
|
|
35
|
+
}
|
|
36
|
+
const header = decodeJsonSegment(headerSegment, 'header');
|
|
37
|
+
if (header.alg !== 'EdDSA') {
|
|
38
|
+
throw new Error(`UKYC: unsupported jwtChain alg "${String(header.alg)}" (expected EdDSA).`);
|
|
39
|
+
}
|
|
40
|
+
const jwk = keys.find((key) => key.kid === header.kid);
|
|
41
|
+
if (!jwk) {
|
|
42
|
+
throw new Error(`UKYC: no JWKS key matches jwtChain kid "${String(header.kid)}".`);
|
|
43
|
+
}
|
|
44
|
+
if (jwk.kty !== 'OKP' || jwk.crv !== 'Ed25519') {
|
|
45
|
+
throw new Error(`UKYC: JWKS key ${jwk.kid} is not an Ed25519 OKP key (kty=${jwk.kty}, crv=${jwk.crv}).`);
|
|
46
|
+
}
|
|
47
|
+
const isValid = ed25519_1.ed25519.verify((0, encoding_js_1.base64UrlToBytes)(signatureSegment), new TextEncoder().encode(`${headerSegment}.${payloadSegment}`), (0, encoding_js_1.base64UrlToBytes)(jwk.x));
|
|
48
|
+
if (!isValid) {
|
|
49
|
+
throw new Error('UKYC: jwtChain signature verification failed against JWKS.');
|
|
50
|
+
}
|
|
51
|
+
return decodeJsonSegment(payloadSegment, 'payload');
|
|
52
|
+
}
|
|
53
|
+
exports.verifyJwtChain = verifyJwtChain;
|
|
54
|
+
//# sourceMappingURL=jwtChain.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwtChain.cjs","sourceRoot":"","sources":["../../src/ukyc/jwtChain.ts"],"names":[],"mappings":";;;AAAA,2CAAgD;AAChD,mDAAgD;AAEhD,iDAAkD;AA0ClD;;;;;;GAMG;AACH,SAAS,iBAAiB,CAAO,OAAe,EAAE,KAAa;IAC7D,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAA,qBAAa,EAAC,IAAA,8BAAgB,EAAC,OAAO,CAAC,CAAC,CAAS,CAAC;IACtE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,mCAAmC,KAAK,KAAK,MAAM,CAAC,KAAK,CAAC,EAAE,CAC7D,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,cAAc,CAAC,IAAW,EAAE,QAAgB;IAC1D,MAAM,CAAC,aAAa,EAAE,cAAc,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC9E,IAAI,CAAC,aAAa,IAAI,CAAC,cAAc,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CACb,gEAAgE,CACjE,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,iBAAiB,CAAY,aAAa,EAAE,QAAQ,CAAC,CAAC;IACrE,IAAI,MAAM,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,mCAAmC,MAAM,CACvC,MAAM,CAAC,GAAG,CACX,qBAAqB,CACvB,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG,CAAC,CAAC;IACvD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,2CAA2C,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAClE,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,CAAC,GAAG,KAAK,KAAK,IAAI,GAAG,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CACb,kBAAkB,GAAG,CAAC,GAAG,mCAAmC,GAAG,CAAC,GAAG,SAAS,GAAG,CAAC,GAAG,IAAI,CACxF,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,iBAAO,CAAC,MAAM,CAC5B,IAAA,8BAAgB,EAAC,gBAAgB,CAAC,EAClC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,aAAa,IAAI,cAAc,EAAE,CAAC,EAC9D,IAAA,8BAAgB,EAAC,GAAG,CAAC,CAAC,CAAC,CACxB,CAAC;IACF,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,4DAA4D,CAC7D,CAAC;IACJ,CAAC;IAED,OAAO,iBAAiB,CAAkB,cAAc,EAAE,SAAS,CAAC,CAAC;AACvE,CAAC;AAzCD,wCAyCC","sourcesContent":["import { bytesToString } from '@metamask/utils';\nimport { ed25519 } from '@noble/curves/ed25519';\n\nimport { base64UrlToBytes } from '../encoding.js';\n\n/**\n * Verifies the `jwtChain` returned by the Fractal encryption service against\n * its published JWKS.\n *\n * The signature check is done with `@noble/curves` (rather than WebCrypto\n * `subtle`) because not every MetaMask runtime exposes a `subtle`\n * implementation for Ed25519; JWT parsing is a plain base64url/JSON decode, so\n * no `jose` dependency is required.\n */\n\n/**\n * A single Ed25519 (OKP) JSON Web Key from the Fractal JWKS.\n */\nexport type Jwk = {\n kty: string;\n crv: string;\n x: string;\n kid: string;\n use?: string;\n alg?: string;\n};\n\n/**\n * The verified `jwtChain` payload. `sessionServerPublicKeyX` attests the\n * server's X25519 public key so the client can confirm the value returned\n * out-of-band by `getWrappingKey` was not tampered with.\n */\nexport type JwtChainPayload = {\n sessionServerPublicKeyX: string;\n nonce: string;\n};\n\n/**\n * The protected header of a compact JWT.\n */\ntype JwtHeader = {\n alg?: string;\n kid?: string;\n};\n\n/**\n * Decodes a base64url JWT segment into a parsed JSON object.\n *\n * @param segment - The base64url-encoded segment.\n * @param label - Human-readable segment name for error messages.\n * @returns The parsed JSON object.\n */\nfunction decodeJsonSegment<Type>(segment: string, label: string): Type {\n try {\n return JSON.parse(bytesToString(base64UrlToBytes(segment))) as Type;\n } catch (error) {\n throw new Error(\n `UKYC: failed to decode jwtChain ${label}: ${String(error)}`,\n );\n }\n}\n\n/**\n * Verifies `jwtChain` against `keys`: matches the JWT header `kid` to a\n * published Ed25519 signing key and checks the EdDSA signature over the\n * `header.payload` input. Returns the decoded, verified payload.\n *\n * @param keys - The JWKS keys published by the Fractal encryption service.\n * @param jwtChain - The compact-serialized EdDSA JWT from `getWrappingKey`.\n * @returns The verified JWT payload.\n */\nexport function verifyJwtChain(keys: Jwk[], jwtChain: string): JwtChainPayload {\n const [headerSegment, payloadSegment, signatureSegment] = jwtChain.split('.');\n if (!headerSegment || !payloadSegment || !signatureSegment) {\n throw new Error(\n 'UKYC: jwtChain is not a well-formed JWT (expected 3 segments).',\n );\n }\n\n const header = decodeJsonSegment<JwtHeader>(headerSegment, 'header');\n if (header.alg !== 'EdDSA') {\n throw new Error(\n `UKYC: unsupported jwtChain alg \"${String(\n header.alg,\n )}\" (expected EdDSA).`,\n );\n }\n\n const jwk = keys.find((key) => key.kid === header.kid);\n if (!jwk) {\n throw new Error(\n `UKYC: no JWKS key matches jwtChain kid \"${String(header.kid)}\".`,\n );\n }\n if (jwk.kty !== 'OKP' || jwk.crv !== 'Ed25519') {\n throw new Error(\n `UKYC: JWKS key ${jwk.kid} is not an Ed25519 OKP key (kty=${jwk.kty}, crv=${jwk.crv}).`,\n );\n }\n\n const isValid = ed25519.verify(\n base64UrlToBytes(signatureSegment),\n new TextEncoder().encode(`${headerSegment}.${payloadSegment}`),\n base64UrlToBytes(jwk.x),\n );\n if (!isValid) {\n throw new Error(\n 'UKYC: jwtChain signature verification failed against JWKS.',\n );\n }\n\n return decodeJsonSegment<JwtChainPayload>(payloadSegment, 'payload');\n}\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies the `jwtChain` returned by the Fractal encryption service against
|
|
3
|
+
* its published JWKS.
|
|
4
|
+
*
|
|
5
|
+
* The signature check is done with `@noble/curves` (rather than WebCrypto
|
|
6
|
+
* `subtle`) because not every MetaMask runtime exposes a `subtle`
|
|
7
|
+
* implementation for Ed25519; JWT parsing is a plain base64url/JSON decode, so
|
|
8
|
+
* no `jose` dependency is required.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* A single Ed25519 (OKP) JSON Web Key from the Fractal JWKS.
|
|
12
|
+
*/
|
|
13
|
+
export type Jwk = {
|
|
14
|
+
kty: string;
|
|
15
|
+
crv: string;
|
|
16
|
+
x: string;
|
|
17
|
+
kid: string;
|
|
18
|
+
use?: string;
|
|
19
|
+
alg?: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The verified `jwtChain` payload. `sessionServerPublicKeyX` attests the
|
|
23
|
+
* server's X25519 public key so the client can confirm the value returned
|
|
24
|
+
* out-of-band by `getWrappingKey` was not tampered with.
|
|
25
|
+
*/
|
|
26
|
+
export type JwtChainPayload = {
|
|
27
|
+
sessionServerPublicKeyX: string;
|
|
28
|
+
nonce: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Verifies `jwtChain` against `keys`: matches the JWT header `kid` to a
|
|
32
|
+
* published Ed25519 signing key and checks the EdDSA signature over the
|
|
33
|
+
* `header.payload` input. Returns the decoded, verified payload.
|
|
34
|
+
*
|
|
35
|
+
* @param keys - The JWKS keys published by the Fractal encryption service.
|
|
36
|
+
* @param jwtChain - The compact-serialized EdDSA JWT from `getWrappingKey`.
|
|
37
|
+
* @returns The verified JWT payload.
|
|
38
|
+
*/
|
|
39
|
+
export declare function verifyJwtChain(keys: Jwk[], jwtChain: string): JwtChainPayload;
|
|
40
|
+
//# sourceMappingURL=jwtChain.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwtChain.d.cts","sourceRoot":"","sources":["../../src/ukyc/jwtChain.ts"],"names":[],"mappings":"AAKA;;;;;;;;GAQG;AAEH;;GAEG;AACH,MAAM,MAAM,GAAG,GAAG;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,EAAE,MAAM,CAAC;IACV,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,uBAAuB,EAAE,MAAM,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AA2BF;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,eAAe,CAyC7E"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies the `jwtChain` returned by the Fractal encryption service against
|
|
3
|
+
* its published JWKS.
|
|
4
|
+
*
|
|
5
|
+
* The signature check is done with `@noble/curves` (rather than WebCrypto
|
|
6
|
+
* `subtle`) because not every MetaMask runtime exposes a `subtle`
|
|
7
|
+
* implementation for Ed25519; JWT parsing is a plain base64url/JSON decode, so
|
|
8
|
+
* no `jose` dependency is required.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* A single Ed25519 (OKP) JSON Web Key from the Fractal JWKS.
|
|
12
|
+
*/
|
|
13
|
+
export type Jwk = {
|
|
14
|
+
kty: string;
|
|
15
|
+
crv: string;
|
|
16
|
+
x: string;
|
|
17
|
+
kid: string;
|
|
18
|
+
use?: string;
|
|
19
|
+
alg?: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The verified `jwtChain` payload. `sessionServerPublicKeyX` attests the
|
|
23
|
+
* server's X25519 public key so the client can confirm the value returned
|
|
24
|
+
* out-of-band by `getWrappingKey` was not tampered with.
|
|
25
|
+
*/
|
|
26
|
+
export type JwtChainPayload = {
|
|
27
|
+
sessionServerPublicKeyX: string;
|
|
28
|
+
nonce: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Verifies `jwtChain` against `keys`: matches the JWT header `kid` to a
|
|
32
|
+
* published Ed25519 signing key and checks the EdDSA signature over the
|
|
33
|
+
* `header.payload` input. Returns the decoded, verified payload.
|
|
34
|
+
*
|
|
35
|
+
* @param keys - The JWKS keys published by the Fractal encryption service.
|
|
36
|
+
* @param jwtChain - The compact-serialized EdDSA JWT from `getWrappingKey`.
|
|
37
|
+
* @returns The verified JWT payload.
|
|
38
|
+
*/
|
|
39
|
+
export declare function verifyJwtChain(keys: Jwk[], jwtChain: string): JwtChainPayload;
|
|
40
|
+
//# sourceMappingURL=jwtChain.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwtChain.d.mts","sourceRoot":"","sources":["../../src/ukyc/jwtChain.ts"],"names":[],"mappings":"AAKA;;;;;;;;GAQG;AAEH;;GAEG;AACH,MAAM,MAAM,GAAG,GAAG;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,EAAE,MAAM,CAAC;IACV,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,uBAAuB,EAAE,MAAM,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AA2BF;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,eAAe,CAyC7E"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { bytesToString } from "@metamask/utils";
|
|
2
|
+
import { ed25519 } from "@noble/curves/ed25519";
|
|
3
|
+
import { base64UrlToBytes } from "../encoding.mjs";
|
|
4
|
+
/**
|
|
5
|
+
* Decodes a base64url JWT segment into a parsed JSON object.
|
|
6
|
+
*
|
|
7
|
+
* @param segment - The base64url-encoded segment.
|
|
8
|
+
* @param label - Human-readable segment name for error messages.
|
|
9
|
+
* @returns The parsed JSON object.
|
|
10
|
+
*/
|
|
11
|
+
function decodeJsonSegment(segment, label) {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(bytesToString(base64UrlToBytes(segment)));
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
throw new Error(`UKYC: failed to decode jwtChain ${label}: ${String(error)}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Verifies `jwtChain` against `keys`: matches the JWT header `kid` to a
|
|
21
|
+
* published Ed25519 signing key and checks the EdDSA signature over the
|
|
22
|
+
* `header.payload` input. Returns the decoded, verified payload.
|
|
23
|
+
*
|
|
24
|
+
* @param keys - The JWKS keys published by the Fractal encryption service.
|
|
25
|
+
* @param jwtChain - The compact-serialized EdDSA JWT from `getWrappingKey`.
|
|
26
|
+
* @returns The verified JWT payload.
|
|
27
|
+
*/
|
|
28
|
+
export function verifyJwtChain(keys, jwtChain) {
|
|
29
|
+
const [headerSegment, payloadSegment, signatureSegment] = jwtChain.split('.');
|
|
30
|
+
if (!headerSegment || !payloadSegment || !signatureSegment) {
|
|
31
|
+
throw new Error('UKYC: jwtChain is not a well-formed JWT (expected 3 segments).');
|
|
32
|
+
}
|
|
33
|
+
const header = decodeJsonSegment(headerSegment, 'header');
|
|
34
|
+
if (header.alg !== 'EdDSA') {
|
|
35
|
+
throw new Error(`UKYC: unsupported jwtChain alg "${String(header.alg)}" (expected EdDSA).`);
|
|
36
|
+
}
|
|
37
|
+
const jwk = keys.find((key) => key.kid === header.kid);
|
|
38
|
+
if (!jwk) {
|
|
39
|
+
throw new Error(`UKYC: no JWKS key matches jwtChain kid "${String(header.kid)}".`);
|
|
40
|
+
}
|
|
41
|
+
if (jwk.kty !== 'OKP' || jwk.crv !== 'Ed25519') {
|
|
42
|
+
throw new Error(`UKYC: JWKS key ${jwk.kid} is not an Ed25519 OKP key (kty=${jwk.kty}, crv=${jwk.crv}).`);
|
|
43
|
+
}
|
|
44
|
+
const isValid = ed25519.verify(base64UrlToBytes(signatureSegment), new TextEncoder().encode(`${headerSegment}.${payloadSegment}`), base64UrlToBytes(jwk.x));
|
|
45
|
+
if (!isValid) {
|
|
46
|
+
throw new Error('UKYC: jwtChain signature verification failed against JWKS.');
|
|
47
|
+
}
|
|
48
|
+
return decodeJsonSegment(payloadSegment, 'payload');
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=jwtChain.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwtChain.mjs","sourceRoot":"","sources":["../../src/ukyc/jwtChain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,wBAAwB;AAChD,OAAO,EAAE,OAAO,EAAE,8BAA8B;AAEhD,OAAO,EAAE,gBAAgB,EAAE,wBAAuB;AA0ClD;;;;;;GAMG;AACH,SAAS,iBAAiB,CAAO,OAAe,EAAE,KAAa;IAC7D,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAS,CAAC;IACtE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,mCAAmC,KAAK,KAAK,MAAM,CAAC,KAAK,CAAC,EAAE,CAC7D,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,IAAW,EAAE,QAAgB;IAC1D,MAAM,CAAC,aAAa,EAAE,cAAc,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC9E,IAAI,CAAC,aAAa,IAAI,CAAC,cAAc,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CACb,gEAAgE,CACjE,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,iBAAiB,CAAY,aAAa,EAAE,QAAQ,CAAC,CAAC;IACrE,IAAI,MAAM,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,mCAAmC,MAAM,CACvC,MAAM,CAAC,GAAG,CACX,qBAAqB,CACvB,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG,CAAC,CAAC;IACvD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,2CAA2C,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAClE,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,CAAC,GAAG,KAAK,KAAK,IAAI,GAAG,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CACb,kBAAkB,GAAG,CAAC,GAAG,mCAAmC,GAAG,CAAC,GAAG,SAAS,GAAG,CAAC,GAAG,IAAI,CACxF,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAC5B,gBAAgB,CAAC,gBAAgB,CAAC,EAClC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,aAAa,IAAI,cAAc,EAAE,CAAC,EAC9D,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,CACxB,CAAC;IACF,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,4DAA4D,CAC7D,CAAC;IACJ,CAAC;IAED,OAAO,iBAAiB,CAAkB,cAAc,EAAE,SAAS,CAAC,CAAC;AACvE,CAAC","sourcesContent":["import { bytesToString } from '@metamask/utils';\nimport { ed25519 } from '@noble/curves/ed25519';\n\nimport { base64UrlToBytes } from '../encoding.js';\n\n/**\n * Verifies the `jwtChain` returned by the Fractal encryption service against\n * its published JWKS.\n *\n * The signature check is done with `@noble/curves` (rather than WebCrypto\n * `subtle`) because not every MetaMask runtime exposes a `subtle`\n * implementation for Ed25519; JWT parsing is a plain base64url/JSON decode, so\n * no `jose` dependency is required.\n */\n\n/**\n * A single Ed25519 (OKP) JSON Web Key from the Fractal JWKS.\n */\nexport type Jwk = {\n kty: string;\n crv: string;\n x: string;\n kid: string;\n use?: string;\n alg?: string;\n};\n\n/**\n * The verified `jwtChain` payload. `sessionServerPublicKeyX` attests the\n * server's X25519 public key so the client can confirm the value returned\n * out-of-band by `getWrappingKey` was not tampered with.\n */\nexport type JwtChainPayload = {\n sessionServerPublicKeyX: string;\n nonce: string;\n};\n\n/**\n * The protected header of a compact JWT.\n */\ntype JwtHeader = {\n alg?: string;\n kid?: string;\n};\n\n/**\n * Decodes a base64url JWT segment into a parsed JSON object.\n *\n * @param segment - The base64url-encoded segment.\n * @param label - Human-readable segment name for error messages.\n * @returns The parsed JSON object.\n */\nfunction decodeJsonSegment<Type>(segment: string, label: string): Type {\n try {\n return JSON.parse(bytesToString(base64UrlToBytes(segment))) as Type;\n } catch (error) {\n throw new Error(\n `UKYC: failed to decode jwtChain ${label}: ${String(error)}`,\n );\n }\n}\n\n/**\n * Verifies `jwtChain` against `keys`: matches the JWT header `kid` to a\n * published Ed25519 signing key and checks the EdDSA signature over the\n * `header.payload` input. Returns the decoded, verified payload.\n *\n * @param keys - The JWKS keys published by the Fractal encryption service.\n * @param jwtChain - The compact-serialized EdDSA JWT from `getWrappingKey`.\n * @returns The verified JWT payload.\n */\nexport function verifyJwtChain(keys: Jwk[], jwtChain: string): JwtChainPayload {\n const [headerSegment, payloadSegment, signatureSegment] = jwtChain.split('.');\n if (!headerSegment || !payloadSegment || !signatureSegment) {\n throw new Error(\n 'UKYC: jwtChain is not a well-formed JWT (expected 3 segments).',\n );\n }\n\n const header = decodeJsonSegment<JwtHeader>(headerSegment, 'header');\n if (header.alg !== 'EdDSA') {\n throw new Error(\n `UKYC: unsupported jwtChain alg \"${String(\n header.alg,\n )}\" (expected EdDSA).`,\n );\n }\n\n const jwk = keys.find((key) => key.kid === header.kid);\n if (!jwk) {\n throw new Error(\n `UKYC: no JWKS key matches jwtChain kid \"${String(header.kid)}\".`,\n );\n }\n if (jwk.kty !== 'OKP' || jwk.crv !== 'Ed25519') {\n throw new Error(\n `UKYC: JWKS key ${jwk.kid} is not an Ed25519 OKP key (kty=${jwk.kty}, crv=${jwk.crv}).`,\n );\n }\n\n const isValid = ed25519.verify(\n base64UrlToBytes(signatureSegment),\n new TextEncoder().encode(`${headerSegment}.${payloadSegment}`),\n base64UrlToBytes(jwk.x),\n );\n if (!isValid) {\n throw new Error(\n 'UKYC: jwtChain signature verification failed against JWKS.',\n );\n }\n\n return decodeJsonSegment<JwtChainPayload>(payloadSegment, 'payload');\n}\n"]}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hasLocalUserSecret = exports.getOrCreateLocalUserSecret = exports.loadLocalUserSecret = void 0;
|
|
4
|
+
const utils_1 = require("@metamask/utils");
|
|
5
|
+
const utils_2 = require("@noble/hashes/utils");
|
|
6
|
+
const constants_js_1 = require("./constants.cjs");
|
|
7
|
+
/**
|
|
8
|
+
* In-flight `getOrCreateLocalUserSecret` calls, keyed by entropy source.
|
|
9
|
+
* Deduplicates concurrent enrollments in a single client session so we never
|
|
10
|
+
* generate and persist two competing `local_user_secret`s for the same source.
|
|
11
|
+
*/
|
|
12
|
+
const inFlightCreations = new Map();
|
|
13
|
+
/**
|
|
14
|
+
* Loads the persisted `local_user_secret` from Encrypted User Storage, if one
|
|
15
|
+
* exists.
|
|
16
|
+
*
|
|
17
|
+
* @param store - The Encrypted User Storage adapter.
|
|
18
|
+
* @param entropySourceId - Optional HD keyring entropy source id, used to scope
|
|
19
|
+
* the secret to a specific SRP in multi-SRP wallets. Defaults to the primary SRP.
|
|
20
|
+
* @returns The decoded `local_user_secret` bytes, or `null` if none has been
|
|
21
|
+
* enrolled.
|
|
22
|
+
*/
|
|
23
|
+
async function loadLocalUserSecret(store, entropySourceId) {
|
|
24
|
+
const stored = await store.get(constants_js_1.UKYC_LOCAL_USER_SECRET_PATH, entropySourceId);
|
|
25
|
+
if (!stored) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const localUserSecret = (0, utils_1.base64ToBytes)(stored);
|
|
29
|
+
if (localUserSecret.length !== constants_js_1.UKYC_LOCAL_USER_SECRET_SIZE_BYTES) {
|
|
30
|
+
throw new Error(`UKYC: stored local_user_secret has unexpected length ${localUserSecret.length}, expected ${constants_js_1.UKYC_LOCAL_USER_SECRET_SIZE_BYTES}.`);
|
|
31
|
+
}
|
|
32
|
+
return localUserSecret;
|
|
33
|
+
}
|
|
34
|
+
exports.loadLocalUserSecret = loadLocalUserSecret;
|
|
35
|
+
/**
|
|
36
|
+
* Persists a freshly generated `local_user_secret` to Encrypted User Storage.
|
|
37
|
+
*
|
|
38
|
+
* @param store - The Encrypted User Storage adapter.
|
|
39
|
+
* @param localUserSecret - The `local_user_secret` bytes to persist.
|
|
40
|
+
* @param entropySourceId - Optional HD keyring entropy source id.
|
|
41
|
+
*/
|
|
42
|
+
async function persistLocalUserSecret(store, localUserSecret, entropySourceId) {
|
|
43
|
+
await store.set(constants_js_1.UKYC_LOCAL_USER_SECRET_PATH, (0, utils_1.bytesToBase64)(localUserSecret), entropySourceId);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Creates the UKYC `local_user_secret` if it does not already exist, otherwise
|
|
47
|
+
* loads the existing one. This is the single entry point used on UKYC
|
|
48
|
+
* enrollment.
|
|
49
|
+
*
|
|
50
|
+
* The operation is idempotent and safe against concurrent callers in the same
|
|
51
|
+
* session: repeated or parallel calls resolve to the same `local_user_secret`
|
|
52
|
+
* and never generate more than one secret for a given entropy source.
|
|
53
|
+
*
|
|
54
|
+
* @param store - The Encrypted User Storage adapter.
|
|
55
|
+
* @param entropySourceId - Optional HD keyring entropy source id, used to scope
|
|
56
|
+
* the secret to a specific SRP in multi-SRP wallets. Defaults to the primary SRP.
|
|
57
|
+
* @returns The `local_user_secret` bytes (existing or newly created).
|
|
58
|
+
*/
|
|
59
|
+
async function getOrCreateLocalUserSecret(store, entropySourceId) {
|
|
60
|
+
const cacheKey = entropySourceId ?? '';
|
|
61
|
+
const pending = inFlightCreations.get(cacheKey);
|
|
62
|
+
if (pending) {
|
|
63
|
+
return pending;
|
|
64
|
+
}
|
|
65
|
+
const creation = (async () => {
|
|
66
|
+
const existing = await loadLocalUserSecret(store, entropySourceId);
|
|
67
|
+
if (existing) {
|
|
68
|
+
return existing;
|
|
69
|
+
}
|
|
70
|
+
const localUserSecret = (0, utils_2.randomBytes)(constants_js_1.UKYC_LOCAL_USER_SECRET_SIZE_BYTES);
|
|
71
|
+
await persistLocalUserSecret(store, localUserSecret, entropySourceId);
|
|
72
|
+
// Re-read after persisting so that all callers converge on whatever value
|
|
73
|
+
// actually landed in storage (defends against a competing write that may
|
|
74
|
+
// have won the race, e.g. from another device syncing the same feature).
|
|
75
|
+
return ((await loadLocalUserSecret(store, entropySourceId)) ?? localUserSecret);
|
|
76
|
+
})();
|
|
77
|
+
inFlightCreations.set(cacheKey, creation);
|
|
78
|
+
try {
|
|
79
|
+
return await creation;
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
inFlightCreations.delete(cacheKey);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.getOrCreateLocalUserSecret = getOrCreateLocalUserSecret;
|
|
86
|
+
/**
|
|
87
|
+
* Whether a `local_user_secret` has already been enrolled for the given entropy
|
|
88
|
+
* source.
|
|
89
|
+
*
|
|
90
|
+
* @param store - The Encrypted User Storage adapter.
|
|
91
|
+
* @param entropySourceId - Optional HD keyring entropy source id.
|
|
92
|
+
* @returns `true` if a `local_user_secret` exists in Encrypted User Storage.
|
|
93
|
+
*/
|
|
94
|
+
async function hasLocalUserSecret(store, entropySourceId) {
|
|
95
|
+
return (await loadLocalUserSecret(store, entropySourceId)) !== null;
|
|
96
|
+
}
|
|
97
|
+
exports.hasLocalUserSecret = hasLocalUserSecret;
|
|
98
|
+
//# sourceMappingURL=localUserSecret.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localUserSecret.cjs","sourceRoot":"","sources":["../../src/ukyc/localUserSecret.ts"],"names":[],"mappings":";;;AAAA,2CAA+D;AAC/D,+CAAkD;AAElD,kDAGwB;AAiCxB;;;;GAIG;AACH,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAA+B,CAAC;AAEjE;;;;;;;;;GASG;AACI,KAAK,UAAU,mBAAmB,CACvC,KAA+B,EAC/B,eAAwB;IAExB,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,0CAA2B,EAAE,eAAe,CAAC,CAAC;IAE7E,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,eAAe,GAAG,IAAA,qBAAa,EAAC,MAAM,CAAC,CAAC;IAE9C,IAAI,eAAe,CAAC,MAAM,KAAK,gDAAiC,EAAE,CAAC;QACjE,MAAM,IAAI,KAAK,CACb,wDAAwD,eAAe,CAAC,MAAM,cAAc,gDAAiC,GAAG,CACjI,CAAC;IACJ,CAAC;IAED,OAAO,eAAe,CAAC;AACzB,CAAC;AAnBD,kDAmBC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,sBAAsB,CACnC,KAA+B,EAC/B,eAA2B,EAC3B,eAAwB;IAExB,MAAM,KAAK,CAAC,GAAG,CACb,0CAA2B,EAC3B,IAAA,qBAAa,EAAC,eAAe,CAAC,EAC9B,eAAe,CAChB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;GAaG;AACI,KAAK,UAAU,0BAA0B,CAC9C,KAA+B,EAC/B,eAAwB;IAExB,MAAM,QAAQ,GAAG,eAAe,IAAI,EAAE,CAAC;IAEvC,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAChD,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,KAAK,IAAyB,EAAE;QAChD,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;QACnE,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,MAAM,eAAe,GAAG,IAAA,mBAAW,EAAC,gDAAiC,CAAC,CAAC;QACvE,MAAM,sBAAsB,CAAC,KAAK,EAAE,eAAe,EAAE,eAAe,CAAC,CAAC;QAEtE,0EAA0E;QAC1E,yEAAyE;QACzE,yEAAyE;QACzE,OAAO,CACL,CAAC,MAAM,mBAAmB,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,IAAI,eAAe,CACvE,CAAC;IACJ,CAAC,CAAC,EAAE,CAAC;IAEL,iBAAiB,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAE1C,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,CAAC;IACxB,CAAC;YAAS,CAAC;QACT,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;AACH,CAAC;AAnCD,gEAmCC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,kBAAkB,CACtC,KAA+B,EAC/B,eAAwB;IAExB,OAAO,CAAC,MAAM,mBAAmB,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,KAAK,IAAI,CAAC;AACtE,CAAC;AALD,gDAKC","sourcesContent":["import { base64ToBytes, bytesToBase64 } from '@metamask/utils';\nimport { randomBytes } from '@noble/hashes/utils';\n\nimport {\n UKYC_LOCAL_USER_SECRET_PATH,\n UKYC_LOCAL_USER_SECRET_SIZE_BYTES,\n} from './constants.js';\n\n/**\n * Orchestrates creation and loading of the UKYC `local_user_secret`.\n *\n * The `local_user_secret` is the root secret for all UKYC client-derived\n * material. It is generated once, on first enrollment, and persisted to\n * MetaMask Encrypted User Storage. It is never transmitted off the device, not\n * even to the idOS Relay. Every subsequent value (`storage_id`,\n * `data_encryption_key`, `signing_key`, `relay_tunnel_key`) is derived from it\n * via HKDF — see `deriveClientMaterial`.\n *\n * This module is platform-agnostic: the Encrypted User Storage backing is\n * injected as a {@link UkycLocalUserSecretStore} so the controller (which owns\n * the messenger) supplies the concrete `UserStorageController` calls.\n */\n\n/**\n * The Encrypted User Storage operations this module needs. On MetaMask clients\n * these are backed by `UserStorageController:performGetStorage` /\n * `performSetStorage`.\n */\nexport type UkycLocalUserSecretStore = {\n /**\n * Reads the base64 string stored at `path`, or `null` if none exists.\n */\n get: (path: string, entropySourceId?: string) => Promise<string | null>;\n /**\n * Writes the base64 string `value` at `path`.\n */\n set: (path: string, value: string, entropySourceId?: string) => Promise<void>;\n};\n\n/**\n * In-flight `getOrCreateLocalUserSecret` calls, keyed by entropy source.\n * Deduplicates concurrent enrollments in a single client session so we never\n * generate and persist two competing `local_user_secret`s for the same source.\n */\nconst inFlightCreations = new Map<string, Promise<Uint8Array>>();\n\n/**\n * Loads the persisted `local_user_secret` from Encrypted User Storage, if one\n * exists.\n *\n * @param store - The Encrypted User Storage adapter.\n * @param entropySourceId - Optional HD keyring entropy source id, used to scope\n * the secret to a specific SRP in multi-SRP wallets. Defaults to the primary SRP.\n * @returns The decoded `local_user_secret` bytes, or `null` if none has been\n * enrolled.\n */\nexport async function loadLocalUserSecret(\n store: UkycLocalUserSecretStore,\n entropySourceId?: string,\n): Promise<Uint8Array | null> {\n const stored = await store.get(UKYC_LOCAL_USER_SECRET_PATH, entropySourceId);\n\n if (!stored) {\n return null;\n }\n\n const localUserSecret = base64ToBytes(stored);\n\n if (localUserSecret.length !== UKYC_LOCAL_USER_SECRET_SIZE_BYTES) {\n throw new Error(\n `UKYC: stored local_user_secret has unexpected length ${localUserSecret.length}, expected ${UKYC_LOCAL_USER_SECRET_SIZE_BYTES}.`,\n );\n }\n\n return localUserSecret;\n}\n\n/**\n * Persists a freshly generated `local_user_secret` to Encrypted User Storage.\n *\n * @param store - The Encrypted User Storage adapter.\n * @param localUserSecret - The `local_user_secret` bytes to persist.\n * @param entropySourceId - Optional HD keyring entropy source id.\n */\nasync function persistLocalUserSecret(\n store: UkycLocalUserSecretStore,\n localUserSecret: Uint8Array,\n entropySourceId?: string,\n): Promise<void> {\n await store.set(\n UKYC_LOCAL_USER_SECRET_PATH,\n bytesToBase64(localUserSecret),\n entropySourceId,\n );\n}\n\n/**\n * Creates the UKYC `local_user_secret` if it does not already exist, otherwise\n * loads the existing one. This is the single entry point used on UKYC\n * enrollment.\n *\n * The operation is idempotent and safe against concurrent callers in the same\n * session: repeated or parallel calls resolve to the same `local_user_secret`\n * and never generate more than one secret for a given entropy source.\n *\n * @param store - The Encrypted User Storage adapter.\n * @param entropySourceId - Optional HD keyring entropy source id, used to scope\n * the secret to a specific SRP in multi-SRP wallets. Defaults to the primary SRP.\n * @returns The `local_user_secret` bytes (existing or newly created).\n */\nexport async function getOrCreateLocalUserSecret(\n store: UkycLocalUserSecretStore,\n entropySourceId?: string,\n): Promise<Uint8Array> {\n const cacheKey = entropySourceId ?? '';\n\n const pending = inFlightCreations.get(cacheKey);\n if (pending) {\n return pending;\n }\n\n const creation = (async (): Promise<Uint8Array> => {\n const existing = await loadLocalUserSecret(store, entropySourceId);\n if (existing) {\n return existing;\n }\n\n const localUserSecret = randomBytes(UKYC_LOCAL_USER_SECRET_SIZE_BYTES);\n await persistLocalUserSecret(store, localUserSecret, entropySourceId);\n\n // Re-read after persisting so that all callers converge on whatever value\n // actually landed in storage (defends against a competing write that may\n // have won the race, e.g. from another device syncing the same feature).\n return (\n (await loadLocalUserSecret(store, entropySourceId)) ?? localUserSecret\n );\n })();\n\n inFlightCreations.set(cacheKey, creation);\n\n try {\n return await creation;\n } finally {\n inFlightCreations.delete(cacheKey);\n }\n}\n\n/**\n * Whether a `local_user_secret` has already been enrolled for the given entropy\n * source.\n *\n * @param store - The Encrypted User Storage adapter.\n * @param entropySourceId - Optional HD keyring entropy source id.\n * @returns `true` if a `local_user_secret` exists in Encrypted User Storage.\n */\nexport async function hasLocalUserSecret(\n store: UkycLocalUserSecretStore,\n entropySourceId?: string,\n): Promise<boolean> {\n return (await loadLocalUserSecret(store, entropySourceId)) !== null;\n}\n"]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrates creation and loading of the UKYC `local_user_secret`.
|
|
3
|
+
*
|
|
4
|
+
* The `local_user_secret` is the root secret for all UKYC client-derived
|
|
5
|
+
* material. It is generated once, on first enrollment, and persisted to
|
|
6
|
+
* MetaMask Encrypted User Storage. It is never transmitted off the device, not
|
|
7
|
+
* even to the idOS Relay. Every subsequent value (`storage_id`,
|
|
8
|
+
* `data_encryption_key`, `signing_key`, `relay_tunnel_key`) is derived from it
|
|
9
|
+
* via HKDF — see `deriveClientMaterial`.
|
|
10
|
+
*
|
|
11
|
+
* This module is platform-agnostic: the Encrypted User Storage backing is
|
|
12
|
+
* injected as a {@link UkycLocalUserSecretStore} so the controller (which owns
|
|
13
|
+
* the messenger) supplies the concrete `UserStorageController` calls.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The Encrypted User Storage operations this module needs. On MetaMask clients
|
|
17
|
+
* these are backed by `UserStorageController:performGetStorage` /
|
|
18
|
+
* `performSetStorage`.
|
|
19
|
+
*/
|
|
20
|
+
export type UkycLocalUserSecretStore = {
|
|
21
|
+
/**
|
|
22
|
+
* Reads the base64 string stored at `path`, or `null` if none exists.
|
|
23
|
+
*/
|
|
24
|
+
get: (path: string, entropySourceId?: string) => Promise<string | null>;
|
|
25
|
+
/**
|
|
26
|
+
* Writes the base64 string `value` at `path`.
|
|
27
|
+
*/
|
|
28
|
+
set: (path: string, value: string, entropySourceId?: string) => Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Loads the persisted `local_user_secret` from Encrypted User Storage, if one
|
|
32
|
+
* exists.
|
|
33
|
+
*
|
|
34
|
+
* @param store - The Encrypted User Storage adapter.
|
|
35
|
+
* @param entropySourceId - Optional HD keyring entropy source id, used to scope
|
|
36
|
+
* the secret to a specific SRP in multi-SRP wallets. Defaults to the primary SRP.
|
|
37
|
+
* @returns The decoded `local_user_secret` bytes, or `null` if none has been
|
|
38
|
+
* enrolled.
|
|
39
|
+
*/
|
|
40
|
+
export declare function loadLocalUserSecret(store: UkycLocalUserSecretStore, entropySourceId?: string): Promise<Uint8Array | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Creates the UKYC `local_user_secret` if it does not already exist, otherwise
|
|
43
|
+
* loads the existing one. This is the single entry point used on UKYC
|
|
44
|
+
* enrollment.
|
|
45
|
+
*
|
|
46
|
+
* The operation is idempotent and safe against concurrent callers in the same
|
|
47
|
+
* session: repeated or parallel calls resolve to the same `local_user_secret`
|
|
48
|
+
* and never generate more than one secret for a given entropy source.
|
|
49
|
+
*
|
|
50
|
+
* @param store - The Encrypted User Storage adapter.
|
|
51
|
+
* @param entropySourceId - Optional HD keyring entropy source id, used to scope
|
|
52
|
+
* the secret to a specific SRP in multi-SRP wallets. Defaults to the primary SRP.
|
|
53
|
+
* @returns The `local_user_secret` bytes (existing or newly created).
|
|
54
|
+
*/
|
|
55
|
+
export declare function getOrCreateLocalUserSecret(store: UkycLocalUserSecretStore, entropySourceId?: string): Promise<Uint8Array>;
|
|
56
|
+
/**
|
|
57
|
+
* Whether a `local_user_secret` has already been enrolled for the given entropy
|
|
58
|
+
* source.
|
|
59
|
+
*
|
|
60
|
+
* @param store - The Encrypted User Storage adapter.
|
|
61
|
+
* @param entropySourceId - Optional HD keyring entropy source id.
|
|
62
|
+
* @returns `true` if a `local_user_secret` exists in Encrypted User Storage.
|
|
63
|
+
*/
|
|
64
|
+
export declare function hasLocalUserSecret(store: UkycLocalUserSecretStore, entropySourceId?: string): Promise<boolean>;
|
|
65
|
+
//# sourceMappingURL=localUserSecret.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localUserSecret.d.cts","sourceRoot":"","sources":["../../src/ukyc/localUserSecret.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;GAaG;AAEH;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC;;OAEG;IACH,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxE;;OAEG;IACH,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/E,CAAC;AASF;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,wBAAwB,EAC/B,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAgB5B;AAqBD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,wBAAwB,EAC/B,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,UAAU,CAAC,CAgCrB;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,wBAAwB,EAC/B,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,OAAO,CAAC,CAElB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrates creation and loading of the UKYC `local_user_secret`.
|
|
3
|
+
*
|
|
4
|
+
* The `local_user_secret` is the root secret for all UKYC client-derived
|
|
5
|
+
* material. It is generated once, on first enrollment, and persisted to
|
|
6
|
+
* MetaMask Encrypted User Storage. It is never transmitted off the device, not
|
|
7
|
+
* even to the idOS Relay. Every subsequent value (`storage_id`,
|
|
8
|
+
* `data_encryption_key`, `signing_key`, `relay_tunnel_key`) is derived from it
|
|
9
|
+
* via HKDF — see `deriveClientMaterial`.
|
|
10
|
+
*
|
|
11
|
+
* This module is platform-agnostic: the Encrypted User Storage backing is
|
|
12
|
+
* injected as a {@link UkycLocalUserSecretStore} so the controller (which owns
|
|
13
|
+
* the messenger) supplies the concrete `UserStorageController` calls.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The Encrypted User Storage operations this module needs. On MetaMask clients
|
|
17
|
+
* these are backed by `UserStorageController:performGetStorage` /
|
|
18
|
+
* `performSetStorage`.
|
|
19
|
+
*/
|
|
20
|
+
export type UkycLocalUserSecretStore = {
|
|
21
|
+
/**
|
|
22
|
+
* Reads the base64 string stored at `path`, or `null` if none exists.
|
|
23
|
+
*/
|
|
24
|
+
get: (path: string, entropySourceId?: string) => Promise<string | null>;
|
|
25
|
+
/**
|
|
26
|
+
* Writes the base64 string `value` at `path`.
|
|
27
|
+
*/
|
|
28
|
+
set: (path: string, value: string, entropySourceId?: string) => Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Loads the persisted `local_user_secret` from Encrypted User Storage, if one
|
|
32
|
+
* exists.
|
|
33
|
+
*
|
|
34
|
+
* @param store - The Encrypted User Storage adapter.
|
|
35
|
+
* @param entropySourceId - Optional HD keyring entropy source id, used to scope
|
|
36
|
+
* the secret to a specific SRP in multi-SRP wallets. Defaults to the primary SRP.
|
|
37
|
+
* @returns The decoded `local_user_secret` bytes, or `null` if none has been
|
|
38
|
+
* enrolled.
|
|
39
|
+
*/
|
|
40
|
+
export declare function loadLocalUserSecret(store: UkycLocalUserSecretStore, entropySourceId?: string): Promise<Uint8Array | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Creates the UKYC `local_user_secret` if it does not already exist, otherwise
|
|
43
|
+
* loads the existing one. This is the single entry point used on UKYC
|
|
44
|
+
* enrollment.
|
|
45
|
+
*
|
|
46
|
+
* The operation is idempotent and safe against concurrent callers in the same
|
|
47
|
+
* session: repeated or parallel calls resolve to the same `local_user_secret`
|
|
48
|
+
* and never generate more than one secret for a given entropy source.
|
|
49
|
+
*
|
|
50
|
+
* @param store - The Encrypted User Storage adapter.
|
|
51
|
+
* @param entropySourceId - Optional HD keyring entropy source id, used to scope
|
|
52
|
+
* the secret to a specific SRP in multi-SRP wallets. Defaults to the primary SRP.
|
|
53
|
+
* @returns The `local_user_secret` bytes (existing or newly created).
|
|
54
|
+
*/
|
|
55
|
+
export declare function getOrCreateLocalUserSecret(store: UkycLocalUserSecretStore, entropySourceId?: string): Promise<Uint8Array>;
|
|
56
|
+
/**
|
|
57
|
+
* Whether a `local_user_secret` has already been enrolled for the given entropy
|
|
58
|
+
* source.
|
|
59
|
+
*
|
|
60
|
+
* @param store - The Encrypted User Storage adapter.
|
|
61
|
+
* @param entropySourceId - Optional HD keyring entropy source id.
|
|
62
|
+
* @returns `true` if a `local_user_secret` exists in Encrypted User Storage.
|
|
63
|
+
*/
|
|
64
|
+
export declare function hasLocalUserSecret(store: UkycLocalUserSecretStore, entropySourceId?: string): Promise<boolean>;
|
|
65
|
+
//# sourceMappingURL=localUserSecret.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localUserSecret.d.mts","sourceRoot":"","sources":["../../src/ukyc/localUserSecret.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;GAaG;AAEH;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC;;OAEG;IACH,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxE;;OAEG;IACH,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/E,CAAC;AASF;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,wBAAwB,EAC/B,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAgB5B;AAqBD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,wBAAwB,EAC/B,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,UAAU,CAAC,CAgCrB;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,wBAAwB,EAC/B,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,OAAO,CAAC,CAElB"}
|