@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.
Files changed (155) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +10 -2
  3. package/dist/KycController-method-action-types.cjs +7 -0
  4. package/dist/KycController-method-action-types.cjs.map +1 -0
  5. package/dist/KycController-method-action-types.d.cts +168 -0
  6. package/dist/KycController-method-action-types.d.cts.map +1 -0
  7. package/dist/KycController-method-action-types.d.mts +168 -0
  8. package/dist/KycController-method-action-types.d.mts.map +1 -0
  9. package/dist/KycController-method-action-types.mjs +6 -0
  10. package/dist/KycController-method-action-types.mjs.map +1 -0
  11. package/dist/KycController.cjs +1085 -0
  12. package/dist/KycController.cjs.map +1 -0
  13. package/dist/KycController.d.cts +255 -0
  14. package/dist/KycController.d.cts.map +1 -0
  15. package/dist/KycController.d.mts +255 -0
  16. package/dist/KycController.d.mts.map +1 -0
  17. package/dist/KycController.mjs +1080 -0
  18. package/dist/KycController.mjs.map +1 -0
  19. package/dist/KycService-method-action-types.cjs +7 -0
  20. package/dist/KycService-method-action-types.cjs.map +1 -0
  21. package/dist/KycService-method-action-types.d.cts +121 -0
  22. package/dist/KycService-method-action-types.d.cts.map +1 -0
  23. package/dist/KycService-method-action-types.d.mts +121 -0
  24. package/dist/KycService-method-action-types.d.mts.map +1 -0
  25. package/dist/KycService-method-action-types.mjs +6 -0
  26. package/dist/KycService-method-action-types.mjs.map +1 -0
  27. package/dist/KycService.cjs +417 -0
  28. package/dist/KycService.cjs.map +1 -0
  29. package/dist/KycService.d.cts +307 -0
  30. package/dist/KycService.d.cts.map +1 -0
  31. package/dist/KycService.d.mts +307 -0
  32. package/dist/KycService.d.mts.map +1 -0
  33. package/dist/KycService.mjs +413 -0
  34. package/dist/KycService.mjs.map +1 -0
  35. package/dist/countryCodes.cjs +274 -0
  36. package/dist/countryCodes.cjs.map +1 -0
  37. package/dist/countryCodes.d.cts +18 -0
  38. package/dist/countryCodes.d.cts.map +1 -0
  39. package/dist/countryCodes.d.mts +18 -0
  40. package/dist/countryCodes.d.mts.map +1 -0
  41. package/dist/countryCodes.mjs +270 -0
  42. package/dist/countryCodes.mjs.map +1 -0
  43. package/dist/crypto.cjs +154 -0
  44. package/dist/crypto.cjs.map +1 -0
  45. package/dist/crypto.d.cts +84 -0
  46. package/dist/crypto.d.cts.map +1 -0
  47. package/dist/crypto.d.mts +84 -0
  48. package/dist/crypto.d.mts.map +1 -0
  49. package/dist/crypto.mjs +149 -0
  50. package/dist/crypto.mjs.map +1 -0
  51. package/dist/encoding.cjs +40 -0
  52. package/dist/encoding.cjs.map +1 -0
  53. package/dist/encoding.d.cts +24 -0
  54. package/dist/encoding.d.cts.map +1 -0
  55. package/dist/encoding.d.mts +24 -0
  56. package/dist/encoding.d.mts.map +1 -0
  57. package/dist/encoding.mjs +35 -0
  58. package/dist/encoding.mjs.map +1 -0
  59. package/dist/index.cjs +34 -10
  60. package/dist/index.cjs.map +1 -1
  61. package/dist/index.d.cts +18 -7
  62. package/dist/index.d.cts.map +1 -1
  63. package/dist/index.d.mts +18 -7
  64. package/dist/index.d.mts.map +1 -1
  65. package/dist/index.mjs +11 -9
  66. package/dist/index.mjs.map +1 -1
  67. package/dist/selectors.cjs +30 -0
  68. package/dist/selectors.cjs.map +1 -0
  69. package/dist/selectors.d.cts +24 -0
  70. package/dist/selectors.d.cts.map +1 -0
  71. package/dist/selectors.d.mts +24 -0
  72. package/dist/selectors.d.mts.map +1 -0
  73. package/dist/selectors.mjs +24 -0
  74. package/dist/selectors.mjs.map +1 -0
  75. package/dist/types.cjs +10 -0
  76. package/dist/types.cjs.map +1 -0
  77. package/dist/types.d.cts +124 -0
  78. package/dist/types.d.cts.map +1 -0
  79. package/dist/types.d.mts +124 -0
  80. package/dist/types.d.mts.map +1 -0
  81. package/dist/types.mjs +9 -0
  82. package/dist/types.mjs.map +1 -0
  83. package/dist/ukyc/constants.cjs +75 -0
  84. package/dist/ukyc/constants.cjs.map +1 -0
  85. package/dist/ukyc/constants.d.cts +69 -0
  86. package/dist/ukyc/constants.d.cts.map +1 -0
  87. package/dist/ukyc/constants.d.mts +69 -0
  88. package/dist/ukyc/constants.d.mts.map +1 -0
  89. package/dist/ukyc/constants.mjs +72 -0
  90. package/dist/ukyc/constants.mjs.map +1 -0
  91. package/dist/ukyc/deriveClientMaterial.cjs +65 -0
  92. package/dist/ukyc/deriveClientMaterial.cjs.map +1 -0
  93. package/dist/ukyc/deriveClientMaterial.d.cts +54 -0
  94. package/dist/ukyc/deriveClientMaterial.d.cts.map +1 -0
  95. package/dist/ukyc/deriveClientMaterial.d.mts +54 -0
  96. package/dist/ukyc/deriveClientMaterial.d.mts.map +1 -0
  97. package/dist/ukyc/deriveClientMaterial.mjs +60 -0
  98. package/dist/ukyc/deriveClientMaterial.mjs.map +1 -0
  99. package/dist/ukyc/jwtChain.cjs +54 -0
  100. package/dist/ukyc/jwtChain.cjs.map +1 -0
  101. package/dist/ukyc/jwtChain.d.cts +40 -0
  102. package/dist/ukyc/jwtChain.d.cts.map +1 -0
  103. package/dist/ukyc/jwtChain.d.mts +40 -0
  104. package/dist/ukyc/jwtChain.d.mts.map +1 -0
  105. package/dist/ukyc/jwtChain.mjs +50 -0
  106. package/dist/ukyc/jwtChain.mjs.map +1 -0
  107. package/dist/ukyc/localUserSecret.cjs +98 -0
  108. package/dist/ukyc/localUserSecret.cjs.map +1 -0
  109. package/dist/ukyc/localUserSecret.d.cts +65 -0
  110. package/dist/ukyc/localUserSecret.d.cts.map +1 -0
  111. package/dist/ukyc/localUserSecret.d.mts +65 -0
  112. package/dist/ukyc/localUserSecret.d.mts.map +1 -0
  113. package/dist/ukyc/localUserSecret.mjs +92 -0
  114. package/dist/ukyc/localUserSecret.mjs.map +1 -0
  115. package/dist/ukyc/storageAccessToken.cjs +139 -0
  116. package/dist/ukyc/storageAccessToken.cjs.map +1 -0
  117. package/dist/ukyc/storageAccessToken.d.cts +99 -0
  118. package/dist/ukyc/storageAccessToken.d.cts.map +1 -0
  119. package/dist/ukyc/storageAccessToken.d.mts +99 -0
  120. package/dist/ukyc/storageAccessToken.d.mts.map +1 -0
  121. package/dist/ukyc/storageAccessToken.mjs +133 -0
  122. package/dist/ukyc/storageAccessToken.mjs.map +1 -0
  123. package/dist/ukyc/testToken.cjs +61 -0
  124. package/dist/ukyc/testToken.cjs.map +1 -0
  125. package/dist/ukyc/testToken.d.cts +50 -0
  126. package/dist/ukyc/testToken.d.cts.map +1 -0
  127. package/dist/ukyc/testToken.d.mts +50 -0
  128. package/dist/ukyc/testToken.d.mts.map +1 -0
  129. package/dist/ukyc/testToken.mjs +57 -0
  130. package/dist/ukyc/testToken.mjs.map +1 -0
  131. package/dist/ukyc/wrapEncryptionKey.cjs +28 -0
  132. package/dist/ukyc/wrapEncryptionKey.cjs.map +1 -0
  133. package/dist/ukyc/wrapEncryptionKey.d.cts +34 -0
  134. package/dist/ukyc/wrapEncryptionKey.d.cts.map +1 -0
  135. package/dist/ukyc/wrapEncryptionKey.d.mts +34 -0
  136. package/dist/ukyc/wrapEncryptionKey.d.mts.map +1 -0
  137. package/dist/ukyc/wrapEncryptionKey.mjs +25 -0
  138. package/dist/ukyc/wrapEncryptionKey.mjs.map +1 -0
  139. package/dist/ukyc/wrapUserKey.cjs +80 -0
  140. package/dist/ukyc/wrapUserKey.cjs.map +1 -0
  141. package/dist/ukyc/wrapUserKey.d.cts +26 -0
  142. package/dist/ukyc/wrapUserKey.d.cts.map +1 -0
  143. package/dist/ukyc/wrapUserKey.d.mts +26 -0
  144. package/dist/ukyc/wrapUserKey.d.mts.map +1 -0
  145. package/dist/ukyc/wrapUserKey.mjs +76 -0
  146. package/dist/ukyc/wrapUserKey.mjs.map +1 -0
  147. package/dist/ukyc/wrappedRelayPayload.cjs +32 -0
  148. package/dist/ukyc/wrappedRelayPayload.cjs.map +1 -0
  149. package/dist/ukyc/wrappedRelayPayload.d.cts +35 -0
  150. package/dist/ukyc/wrappedRelayPayload.d.cts.map +1 -0
  151. package/dist/ukyc/wrappedRelayPayload.d.mts +35 -0
  152. package/dist/ukyc/wrappedRelayPayload.d.mts.map +1 -0
  153. package/dist/ukyc/wrappedRelayPayload.mjs +28 -0
  154. package/dist/ukyc/wrappedRelayPayload.mjs.map +1 -0
  155. package/package.json +24 -3
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mintUkycTestToken = void 0;
4
+ const utils_1 = require("@noble/hashes/utils");
5
+ const constants_js_1 = require("./constants.cjs");
6
+ const deriveClientMaterial_js_1 = require("./deriveClientMaterial.cjs");
7
+ const storageAccessToken_js_1 = require("./storageAccessToken.cjs");
8
+ /**
9
+ * Mints ready-to-use UKYC `storage_access_token`s for testing UKYC Storage.
10
+ *
11
+ * This composes the same pure functions the MetaMask client uses in production
12
+ * (`deriveClientMaterial` + `signStorageAccessToken`), so a third party such as
13
+ * idOS can produce a valid, signed token without deriving one on-device. No
14
+ * server "fills in" the `signing_public_key`: it is derived from a
15
+ * `local_user_secret` the caller controls and registered on first write, so
16
+ * reusing the same secret yields a stable `storage_id` and controlling key.
17
+ */
18
+ /** Default token lifetime when `expiresAt` is not supplied (4 hours). */
19
+ const DEFAULT_TOKEN_LIFETIME_MS = 4 * 60 * 60 * 1000;
20
+ /**
21
+ * Resolves the caller-supplied secret into raw bytes, generating a random one
22
+ * when none is provided.
23
+ *
24
+ * @param secret - Raw 32 bytes, a hex string, or undefined for a random secret.
25
+ * @returns The `local_user_secret` bytes.
26
+ */
27
+ function resolveLocalUserSecret(secret) {
28
+ if (secret === undefined) {
29
+ return (0, utils_1.randomBytes)(constants_js_1.UKYC_LOCAL_USER_SECRET_SIZE_BYTES);
30
+ }
31
+ return typeof secret === 'string' ? (0, utils_1.hexToBytes)(secret) : secret;
32
+ }
33
+ /**
34
+ * Mints a signed UKYC `storage_access_token` for testing.
35
+ *
36
+ * @param params - See {@link MintUkycTestTokenParams}.
37
+ * @returns The token, its `Authorization` header, and the derived identifiers.
38
+ */
39
+ function mintUkycTestToken(params = {}) {
40
+ const { operations = ['read'], presenter, sessionId, issuedAt = new Date(), expiresAt = new Date(issuedAt.getTime() + DEFAULT_TOKEN_LIFETIME_MS), } = params;
41
+ const localUserSecret = resolveLocalUserSecret(params.localUserSecret);
42
+ const material = (0, deriveClientMaterial_js_1.deriveClientMaterial)(localUserSecret);
43
+ const token = (0, storageAccessToken_js_1.signStorageAccessToken)({
44
+ material,
45
+ operations,
46
+ presenter,
47
+ sessionId,
48
+ issuedAt,
49
+ expiresAt,
50
+ });
51
+ const { storageId, signingPublicKey } = (0, deriveClientMaterial_js_1.encodeClientMaterial)(material);
52
+ return {
53
+ localUserSecret: (0, utils_1.bytesToHex)(localUserSecret),
54
+ storageId,
55
+ signingPublicKey,
56
+ token,
57
+ authorizationHeader: `${constants_js_1.UKYC_CAPABILITY_AUTH_SCHEME} ${(0, storageAccessToken_js_1.encodeStorageAccessTokenForHeader)(token)}`,
58
+ };
59
+ }
60
+ exports.mintUkycTestToken = mintUkycTestToken;
61
+ //# sourceMappingURL=testToken.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testToken.cjs","sourceRoot":"","sources":["../../src/ukyc/testToken.ts"],"names":[],"mappings":";;;AAAA,+CAA0E;AAE1E,kDAGwB;AACxB,wEAGmC;AACnC,oEAGiC;AAOjC;;;;;;;;;GASG;AAEH,yEAAyE;AACzE,MAAM,yBAAyB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AA6CrD;;;;;;GAMG;AACH,SAAS,sBAAsB,CAAC,MAA4B;IAC1D,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,IAAA,mBAAW,EAAC,gDAAiC,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAA,kBAAU,EAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AAClE,CAAC;AAED;;;;;GAKG;AACH,SAAgB,iBAAiB,CAC/B,SAAkC,EAAE;IAEpC,MAAM,EACJ,UAAU,GAAG,CAAC,MAAM,CAAC,EACrB,SAAS,EACT,SAAS,EACT,QAAQ,GAAG,IAAI,IAAI,EAAE,EACrB,SAAS,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,yBAAyB,CAAC,GACrE,GAAG,MAAM,CAAC;IAEX,MAAM,eAAe,GAAG,sBAAsB,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IACvE,MAAM,QAAQ,GAAG,IAAA,8CAAoB,EAAC,eAAe,CAAC,CAAC;IAEvD,MAAM,KAAK,GAAG,IAAA,8CAAsB,EAAC;QACnC,QAAQ;QACR,UAAU;QACV,SAAS;QACT,SAAS;QACT,QAAQ;QACR,SAAS;KACV,CAAC,CAAC;IAEH,MAAM,EAAE,SAAS,EAAE,gBAAgB,EAAE,GAAG,IAAA,8CAAoB,EAAC,QAAQ,CAAC,CAAC;IAEvE,OAAO;QACL,eAAe,EAAE,IAAA,kBAAU,EAAC,eAAe,CAAC;QAC5C,SAAS;QACT,gBAAgB;QAChB,KAAK;QACL,mBAAmB,EAAE,GAAG,0CAA2B,IAAI,IAAA,yDAAiC,EACtF,KAAK,CACN,EAAE;KACJ,CAAC;AACJ,CAAC;AAlCD,8CAkCC","sourcesContent":["import { bytesToHex, hexToBytes, randomBytes } from '@noble/hashes/utils';\n\nimport {\n UKYC_CAPABILITY_AUTH_SCHEME,\n UKYC_LOCAL_USER_SECRET_SIZE_BYTES,\n} from './constants.js';\nimport {\n deriveClientMaterial,\n encodeClientMaterial,\n} from './deriveClientMaterial.js';\nimport {\n encodeStorageAccessTokenForHeader,\n signStorageAccessToken,\n} from './storageAccessToken.js';\nimport type {\n UkycStorageAccessToken,\n UkycStorageOperation,\n UkycTokenPresenter,\n} from './storageAccessToken.js';\n\n/**\n * Mints ready-to-use UKYC `storage_access_token`s for testing UKYC Storage.\n *\n * This composes the same pure functions the MetaMask client uses in production\n * (`deriveClientMaterial` + `signStorageAccessToken`), so a third party such as\n * idOS can produce a valid, signed token without deriving one on-device. No\n * server \"fills in\" the `signing_public_key`: it is derived from a\n * `local_user_secret` the caller controls and registered on first write, so\n * reusing the same secret yields a stable `storage_id` and controlling key.\n */\n\n/** Default token lifetime when `expiresAt` is not supplied (4 hours). */\nconst DEFAULT_TOKEN_LIFETIME_MS = 4 * 60 * 60 * 1000;\n\n/**\n * Inputs for {@link mintUkycTestToken}. All fields are optional; the only value\n * a caller usually pins is `localUserSecret`, so `storage_id` and the signing\n * key stay stable across runs.\n */\nexport type MintUkycTestTokenParams = {\n /**\n * The root `local_user_secret`, as raw 32 bytes or a hex string. When omitted\n * a fresh random secret is generated (returned in the result so it can be\n * reused).\n */\n localUserSecret?: Uint8Array | string;\n /** Operations the token authorizes. Defaults to `['read']`. */\n operations?: UkycStorageOperation[];\n /** Who will present the token. Defaults to `client`. */\n presenter?: UkycTokenPresenter;\n /** UKYC session id. Required when `presenter` is `idos-relay`. */\n sessionId?: string;\n /** Token issue time. Defaults to now. */\n issuedAt?: Date;\n /** Token expiry. Defaults to `issuedAt` + 4 hours. */\n expiresAt?: Date;\n};\n\n/**\n * The minted token plus everything needed to exercise UKYC Storage with it.\n */\nexport type MintedUkycTestToken = {\n /** The `local_user_secret` used, hex-encoded, so the caller can reuse it. */\n localUserSecret: string;\n /** base64url `storage_id` — use it as the `{storage_id}` path segment. */\n storageId: string;\n /** base64url `signing_public_key` registered on first write. */\n signingPublicKey: string;\n /** The signed token envelope (payload + signature). */\n token: UkycStorageAccessToken;\n /**\n * The full `Authorization` header value, e.g.\n * `AccessToken <base64url(envelope)>`, ready to send to UKYC Storage.\n */\n authorizationHeader: string;\n};\n\n/**\n * Resolves the caller-supplied secret into raw bytes, generating a random one\n * when none is provided.\n *\n * @param secret - Raw 32 bytes, a hex string, or undefined for a random secret.\n * @returns The `local_user_secret` bytes.\n */\nfunction resolveLocalUserSecret(secret?: Uint8Array | string): Uint8Array {\n if (secret === undefined) {\n return randomBytes(UKYC_LOCAL_USER_SECRET_SIZE_BYTES);\n }\n return typeof secret === 'string' ? hexToBytes(secret) : secret;\n}\n\n/**\n * Mints a signed UKYC `storage_access_token` for testing.\n *\n * @param params - See {@link MintUkycTestTokenParams}.\n * @returns The token, its `Authorization` header, and the derived identifiers.\n */\nexport function mintUkycTestToken(\n params: MintUkycTestTokenParams = {},\n): MintedUkycTestToken {\n const {\n operations = ['read'],\n presenter,\n sessionId,\n issuedAt = new Date(),\n expiresAt = new Date(issuedAt.getTime() + DEFAULT_TOKEN_LIFETIME_MS),\n } = params;\n\n const localUserSecret = resolveLocalUserSecret(params.localUserSecret);\n const material = deriveClientMaterial(localUserSecret);\n\n const token = signStorageAccessToken({\n material,\n operations,\n presenter,\n sessionId,\n issuedAt,\n expiresAt,\n });\n\n const { storageId, signingPublicKey } = encodeClientMaterial(material);\n\n return {\n localUserSecret: bytesToHex(localUserSecret),\n storageId,\n signingPublicKey,\n token,\n authorizationHeader: `${UKYC_CAPABILITY_AUTH_SCHEME} ${encodeStorageAccessTokenForHeader(\n token,\n )}`,\n };\n}\n"]}
@@ -0,0 +1,50 @@
1
+ import type { UkycStorageAccessToken, UkycStorageOperation, UkycTokenPresenter } from "./storageAccessToken.cjs";
2
+ /**
3
+ * Inputs for {@link mintUkycTestToken}. All fields are optional; the only value
4
+ * a caller usually pins is `localUserSecret`, so `storage_id` and the signing
5
+ * key stay stable across runs.
6
+ */
7
+ export type MintUkycTestTokenParams = {
8
+ /**
9
+ * The root `local_user_secret`, as raw 32 bytes or a hex string. When omitted
10
+ * a fresh random secret is generated (returned in the result so it can be
11
+ * reused).
12
+ */
13
+ localUserSecret?: Uint8Array | string;
14
+ /** Operations the token authorizes. Defaults to `['read']`. */
15
+ operations?: UkycStorageOperation[];
16
+ /** Who will present the token. Defaults to `client`. */
17
+ presenter?: UkycTokenPresenter;
18
+ /** UKYC session id. Required when `presenter` is `idos-relay`. */
19
+ sessionId?: string;
20
+ /** Token issue time. Defaults to now. */
21
+ issuedAt?: Date;
22
+ /** Token expiry. Defaults to `issuedAt` + 4 hours. */
23
+ expiresAt?: Date;
24
+ };
25
+ /**
26
+ * The minted token plus everything needed to exercise UKYC Storage with it.
27
+ */
28
+ export type MintedUkycTestToken = {
29
+ /** The `local_user_secret` used, hex-encoded, so the caller can reuse it. */
30
+ localUserSecret: string;
31
+ /** base64url `storage_id` — use it as the `{storage_id}` path segment. */
32
+ storageId: string;
33
+ /** base64url `signing_public_key` registered on first write. */
34
+ signingPublicKey: string;
35
+ /** The signed token envelope (payload + signature). */
36
+ token: UkycStorageAccessToken;
37
+ /**
38
+ * The full `Authorization` header value, e.g.
39
+ * `AccessToken <base64url(envelope)>`, ready to send to UKYC Storage.
40
+ */
41
+ authorizationHeader: string;
42
+ };
43
+ /**
44
+ * Mints a signed UKYC `storage_access_token` for testing.
45
+ *
46
+ * @param params - See {@link MintUkycTestTokenParams}.
47
+ * @returns The token, its `Authorization` header, and the derived identifiers.
48
+ */
49
+ export declare function mintUkycTestToken(params?: MintUkycTestTokenParams): MintedUkycTestToken;
50
+ //# sourceMappingURL=testToken.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testToken.d.cts","sourceRoot":"","sources":["../../src/ukyc/testToken.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EACnB,iCAAgC;AAgBjC;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC;;;;OAIG;IACH,eAAe,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IACtC,+DAA+D;IAC/D,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACpC,wDAAwD;IACxD,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,sDAAsD;IACtD,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,6EAA6E;IAC7E,eAAe,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,KAAK,EAAE,sBAAsB,CAAC;IAC9B;;;OAGG;IACH,mBAAmB,EAAE,MAAM,CAAC;CAC7B,CAAC;AAgBF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,GAAE,uBAA4B,GACnC,mBAAmB,CAgCrB"}
@@ -0,0 +1,50 @@
1
+ import type { UkycStorageAccessToken, UkycStorageOperation, UkycTokenPresenter } from "./storageAccessToken.mjs";
2
+ /**
3
+ * Inputs for {@link mintUkycTestToken}. All fields are optional; the only value
4
+ * a caller usually pins is `localUserSecret`, so `storage_id` and the signing
5
+ * key stay stable across runs.
6
+ */
7
+ export type MintUkycTestTokenParams = {
8
+ /**
9
+ * The root `local_user_secret`, as raw 32 bytes or a hex string. When omitted
10
+ * a fresh random secret is generated (returned in the result so it can be
11
+ * reused).
12
+ */
13
+ localUserSecret?: Uint8Array | string;
14
+ /** Operations the token authorizes. Defaults to `['read']`. */
15
+ operations?: UkycStorageOperation[];
16
+ /** Who will present the token. Defaults to `client`. */
17
+ presenter?: UkycTokenPresenter;
18
+ /** UKYC session id. Required when `presenter` is `idos-relay`. */
19
+ sessionId?: string;
20
+ /** Token issue time. Defaults to now. */
21
+ issuedAt?: Date;
22
+ /** Token expiry. Defaults to `issuedAt` + 4 hours. */
23
+ expiresAt?: Date;
24
+ };
25
+ /**
26
+ * The minted token plus everything needed to exercise UKYC Storage with it.
27
+ */
28
+ export type MintedUkycTestToken = {
29
+ /** The `local_user_secret` used, hex-encoded, so the caller can reuse it. */
30
+ localUserSecret: string;
31
+ /** base64url `storage_id` — use it as the `{storage_id}` path segment. */
32
+ storageId: string;
33
+ /** base64url `signing_public_key` registered on first write. */
34
+ signingPublicKey: string;
35
+ /** The signed token envelope (payload + signature). */
36
+ token: UkycStorageAccessToken;
37
+ /**
38
+ * The full `Authorization` header value, e.g.
39
+ * `AccessToken <base64url(envelope)>`, ready to send to UKYC Storage.
40
+ */
41
+ authorizationHeader: string;
42
+ };
43
+ /**
44
+ * Mints a signed UKYC `storage_access_token` for testing.
45
+ *
46
+ * @param params - See {@link MintUkycTestTokenParams}.
47
+ * @returns The token, its `Authorization` header, and the derived identifiers.
48
+ */
49
+ export declare function mintUkycTestToken(params?: MintUkycTestTokenParams): MintedUkycTestToken;
50
+ //# sourceMappingURL=testToken.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testToken.d.mts","sourceRoot":"","sources":["../../src/ukyc/testToken.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EACnB,iCAAgC;AAgBjC;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC;;;;OAIG;IACH,eAAe,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IACtC,+DAA+D;IAC/D,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACpC,wDAAwD;IACxD,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,sDAAsD;IACtD,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,6EAA6E;IAC7E,eAAe,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,KAAK,EAAE,sBAAsB,CAAC;IAC9B;;;OAGG;IACH,mBAAmB,EAAE,MAAM,CAAC;CAC7B,CAAC;AAgBF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,GAAE,uBAA4B,GACnC,mBAAmB,CAgCrB"}
@@ -0,0 +1,57 @@
1
+ import { bytesToHex, hexToBytes, randomBytes } from "@noble/hashes/utils";
2
+ import { UKYC_CAPABILITY_AUTH_SCHEME, UKYC_LOCAL_USER_SECRET_SIZE_BYTES } from "./constants.mjs";
3
+ import { deriveClientMaterial, encodeClientMaterial } from "./deriveClientMaterial.mjs";
4
+ import { encodeStorageAccessTokenForHeader, signStorageAccessToken } from "./storageAccessToken.mjs";
5
+ /**
6
+ * Mints ready-to-use UKYC `storage_access_token`s for testing UKYC Storage.
7
+ *
8
+ * This composes the same pure functions the MetaMask client uses in production
9
+ * (`deriveClientMaterial` + `signStorageAccessToken`), so a third party such as
10
+ * idOS can produce a valid, signed token without deriving one on-device. No
11
+ * server "fills in" the `signing_public_key`: it is derived from a
12
+ * `local_user_secret` the caller controls and registered on first write, so
13
+ * reusing the same secret yields a stable `storage_id` and controlling key.
14
+ */
15
+ /** Default token lifetime when `expiresAt` is not supplied (4 hours). */
16
+ const DEFAULT_TOKEN_LIFETIME_MS = 4 * 60 * 60 * 1000;
17
+ /**
18
+ * Resolves the caller-supplied secret into raw bytes, generating a random one
19
+ * when none is provided.
20
+ *
21
+ * @param secret - Raw 32 bytes, a hex string, or undefined for a random secret.
22
+ * @returns The `local_user_secret` bytes.
23
+ */
24
+ function resolveLocalUserSecret(secret) {
25
+ if (secret === undefined) {
26
+ return randomBytes(UKYC_LOCAL_USER_SECRET_SIZE_BYTES);
27
+ }
28
+ return typeof secret === 'string' ? hexToBytes(secret) : secret;
29
+ }
30
+ /**
31
+ * Mints a signed UKYC `storage_access_token` for testing.
32
+ *
33
+ * @param params - See {@link MintUkycTestTokenParams}.
34
+ * @returns The token, its `Authorization` header, and the derived identifiers.
35
+ */
36
+ export function mintUkycTestToken(params = {}) {
37
+ const { operations = ['read'], presenter, sessionId, issuedAt = new Date(), expiresAt = new Date(issuedAt.getTime() + DEFAULT_TOKEN_LIFETIME_MS), } = params;
38
+ const localUserSecret = resolveLocalUserSecret(params.localUserSecret);
39
+ const material = deriveClientMaterial(localUserSecret);
40
+ const token = signStorageAccessToken({
41
+ material,
42
+ operations,
43
+ presenter,
44
+ sessionId,
45
+ issuedAt,
46
+ expiresAt,
47
+ });
48
+ const { storageId, signingPublicKey } = encodeClientMaterial(material);
49
+ return {
50
+ localUserSecret: bytesToHex(localUserSecret),
51
+ storageId,
52
+ signingPublicKey,
53
+ token,
54
+ authorizationHeader: `${UKYC_CAPABILITY_AUTH_SCHEME} ${encodeStorageAccessTokenForHeader(token)}`,
55
+ };
56
+ }
57
+ //# sourceMappingURL=testToken.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testToken.mjs","sourceRoot":"","sources":["../../src/ukyc/testToken.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,4BAA4B;AAE1E,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EAClC,wBAAuB;AACxB,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACrB,mCAAkC;AACnC,OAAO,EACL,iCAAiC,EACjC,sBAAsB,EACvB,iCAAgC;AAOjC;;;;;;;;;GASG;AAEH,yEAAyE;AACzE,MAAM,yBAAyB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AA6CrD;;;;;;GAMG;AACH,SAAS,sBAAsB,CAAC,MAA4B;IAC1D,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,WAAW,CAAC,iCAAiC,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AAClE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,SAAkC,EAAE;IAEpC,MAAM,EACJ,UAAU,GAAG,CAAC,MAAM,CAAC,EACrB,SAAS,EACT,SAAS,EACT,QAAQ,GAAG,IAAI,IAAI,EAAE,EACrB,SAAS,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,yBAAyB,CAAC,GACrE,GAAG,MAAM,CAAC;IAEX,MAAM,eAAe,GAAG,sBAAsB,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IACvE,MAAM,QAAQ,GAAG,oBAAoB,CAAC,eAAe,CAAC,CAAC;IAEvD,MAAM,KAAK,GAAG,sBAAsB,CAAC;QACnC,QAAQ;QACR,UAAU;QACV,SAAS;QACT,SAAS;QACT,QAAQ;QACR,SAAS;KACV,CAAC,CAAC;IAEH,MAAM,EAAE,SAAS,EAAE,gBAAgB,EAAE,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAEvE,OAAO;QACL,eAAe,EAAE,UAAU,CAAC,eAAe,CAAC;QAC5C,SAAS;QACT,gBAAgB;QAChB,KAAK;QACL,mBAAmB,EAAE,GAAG,2BAA2B,IAAI,iCAAiC,CACtF,KAAK,CACN,EAAE;KACJ,CAAC;AACJ,CAAC","sourcesContent":["import { bytesToHex, hexToBytes, randomBytes } from '@noble/hashes/utils';\n\nimport {\n UKYC_CAPABILITY_AUTH_SCHEME,\n UKYC_LOCAL_USER_SECRET_SIZE_BYTES,\n} from './constants.js';\nimport {\n deriveClientMaterial,\n encodeClientMaterial,\n} from './deriveClientMaterial.js';\nimport {\n encodeStorageAccessTokenForHeader,\n signStorageAccessToken,\n} from './storageAccessToken.js';\nimport type {\n UkycStorageAccessToken,\n UkycStorageOperation,\n UkycTokenPresenter,\n} from './storageAccessToken.js';\n\n/**\n * Mints ready-to-use UKYC `storage_access_token`s for testing UKYC Storage.\n *\n * This composes the same pure functions the MetaMask client uses in production\n * (`deriveClientMaterial` + `signStorageAccessToken`), so a third party such as\n * idOS can produce a valid, signed token without deriving one on-device. No\n * server \"fills in\" the `signing_public_key`: it is derived from a\n * `local_user_secret` the caller controls and registered on first write, so\n * reusing the same secret yields a stable `storage_id` and controlling key.\n */\n\n/** Default token lifetime when `expiresAt` is not supplied (4 hours). */\nconst DEFAULT_TOKEN_LIFETIME_MS = 4 * 60 * 60 * 1000;\n\n/**\n * Inputs for {@link mintUkycTestToken}. All fields are optional; the only value\n * a caller usually pins is `localUserSecret`, so `storage_id` and the signing\n * key stay stable across runs.\n */\nexport type MintUkycTestTokenParams = {\n /**\n * The root `local_user_secret`, as raw 32 bytes or a hex string. When omitted\n * a fresh random secret is generated (returned in the result so it can be\n * reused).\n */\n localUserSecret?: Uint8Array | string;\n /** Operations the token authorizes. Defaults to `['read']`. */\n operations?: UkycStorageOperation[];\n /** Who will present the token. Defaults to `client`. */\n presenter?: UkycTokenPresenter;\n /** UKYC session id. Required when `presenter` is `idos-relay`. */\n sessionId?: string;\n /** Token issue time. Defaults to now. */\n issuedAt?: Date;\n /** Token expiry. Defaults to `issuedAt` + 4 hours. */\n expiresAt?: Date;\n};\n\n/**\n * The minted token plus everything needed to exercise UKYC Storage with it.\n */\nexport type MintedUkycTestToken = {\n /** The `local_user_secret` used, hex-encoded, so the caller can reuse it. */\n localUserSecret: string;\n /** base64url `storage_id` — use it as the `{storage_id}` path segment. */\n storageId: string;\n /** base64url `signing_public_key` registered on first write. */\n signingPublicKey: string;\n /** The signed token envelope (payload + signature). */\n token: UkycStorageAccessToken;\n /**\n * The full `Authorization` header value, e.g.\n * `AccessToken <base64url(envelope)>`, ready to send to UKYC Storage.\n */\n authorizationHeader: string;\n};\n\n/**\n * Resolves the caller-supplied secret into raw bytes, generating a random one\n * when none is provided.\n *\n * @param secret - Raw 32 bytes, a hex string, or undefined for a random secret.\n * @returns The `local_user_secret` bytes.\n */\nfunction resolveLocalUserSecret(secret?: Uint8Array | string): Uint8Array {\n if (secret === undefined) {\n return randomBytes(UKYC_LOCAL_USER_SECRET_SIZE_BYTES);\n }\n return typeof secret === 'string' ? hexToBytes(secret) : secret;\n}\n\n/**\n * Mints a signed UKYC `storage_access_token` for testing.\n *\n * @param params - See {@link MintUkycTestTokenParams}.\n * @returns The token, its `Authorization` header, and the derived identifiers.\n */\nexport function mintUkycTestToken(\n params: MintUkycTestTokenParams = {},\n): MintedUkycTestToken {\n const {\n operations = ['read'],\n presenter,\n sessionId,\n issuedAt = new Date(),\n expiresAt = new Date(issuedAt.getTime() + DEFAULT_TOKEN_LIFETIME_MS),\n } = params;\n\n const localUserSecret = resolveLocalUserSecret(params.localUserSecret);\n const material = deriveClientMaterial(localUserSecret);\n\n const token = signStorageAccessToken({\n material,\n operations,\n presenter,\n sessionId,\n issuedAt,\n expiresAt,\n });\n\n const { storageId, signingPublicKey } = encodeClientMaterial(material);\n\n return {\n localUserSecret: bytesToHex(localUserSecret),\n storageId,\n signingPublicKey,\n token,\n authorizationHeader: `${UKYC_CAPABILITY_AUTH_SCHEME} ${encodeStorageAccessTokenForHeader(\n token,\n )}`,\n };\n}\n"]}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.wrapEncryptionKey = void 0;
4
+ const tweetnacl_1 = require("tweetnacl");
5
+ const encoding_js_1 = require("../encoding.cjs");
6
+ /**
7
+ * Wraps `keyToWrap` for the UKYC session server.
8
+ *
9
+ * The box is sealed with NaCl's `crypto_box`, keyed by the X25519 shared secret
10
+ * between our session client private key and the session server public key
11
+ * returned by `getWrappingKey`.
12
+ *
13
+ * @param sessionClientPrivateKey - Our session's X25519 private key.
14
+ * @param sessionServerPublicKey - The server's X25519 public key (base64url).
15
+ * @param keyToWrap - The raw symmetric key bytes to encrypt.
16
+ * @returns The base64url `encryptedKey` (ciphertext + tag) and `nonce`.
17
+ */
18
+ function wrapEncryptionKey(sessionClientPrivateKey, sessionServerPublicKey, keyToWrap) {
19
+ const serverPublicKey = (0, encoding_js_1.base64UrlToBytes)(sessionServerPublicKey);
20
+ const nonce = (0, tweetnacl_1.randomBytes)(tweetnacl_1.box.nonceLength);
21
+ const encryptedKey = (0, tweetnacl_1.box)(keyToWrap, nonce, serverPublicKey, sessionClientPrivateKey);
22
+ return {
23
+ encryptedKey: (0, encoding_js_1.toBase64Url)(encryptedKey),
24
+ nonce: (0, encoding_js_1.toBase64Url)(nonce),
25
+ };
26
+ }
27
+ exports.wrapEncryptionKey = wrapEncryptionKey;
28
+ //# sourceMappingURL=wrapEncryptionKey.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapEncryptionKey.cjs","sourceRoot":"","sources":["../../src/ukyc/wrapEncryptionKey.ts"],"names":[],"mappings":";;;AAAA,yCAA6C;AAE7C,iDAA+D;AAwB/D;;;;;;;;;;;GAWG;AACH,SAAgB,iBAAiB,CAC/B,uBAAmC,EACnC,sBAA8B,EAC9B,SAAqB;IAErB,MAAM,eAAe,GAAG,IAAA,8BAAgB,EAAC,sBAAsB,CAAC,CAAC;IACjE,MAAM,KAAK,GAAG,IAAA,uBAAW,EAAC,eAAG,CAAC,WAAW,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,IAAA,eAAG,EACtB,SAAS,EACT,KAAK,EACL,eAAe,EACf,uBAAuB,CACxB,CAAC;IACF,OAAO;QACL,YAAY,EAAE,IAAA,yBAAW,EAAC,YAAY,CAAC;QACvC,KAAK,EAAE,IAAA,yBAAW,EAAC,KAAK,CAAC;KAC1B,CAAC;AACJ,CAAC;AAjBD,8CAiBC","sourcesContent":["import { randomBytes, box } from 'tweetnacl';\n\nimport { base64UrlToBytes, toBase64Url } from '../encoding.js';\n\n/**\n * Wraps the `data_encryption_key` for the UKYC session server using NaCl's\n * `crypto_box` (X25519 + XSalsa20-Poly1305) established with the server's\n * per-session wrapping key.\n *\n * Unlike {@link wrapUserKey} (which generates a fresh ephemeral keypair per\n * call), this uses the session client keypair whose public half was already\n * handed to the server via `POST /wrapping-key`. The server therefore already\n * knows our public key and can derive the same shared secret from its session\n * private key, so only `{ encryptedKey, nonce }` need be transmitted.\n */\n\n/**\n * The transmitted portion of a wrapped encryption key: the `crypto_box`\n * ciphertext (which includes the 16-byte Poly1305 auth tag) and the nonce,\n * both unpadded base64url-encoded.\n */\nexport type WrappedEncryptionKeyParts = {\n encryptedKey: string;\n nonce: string;\n};\n\n/**\n * Wraps `keyToWrap` for the UKYC session server.\n *\n * The box is sealed with NaCl's `crypto_box`, keyed by the X25519 shared secret\n * between our session client private key and the session server public key\n * returned by `getWrappingKey`.\n *\n * @param sessionClientPrivateKey - Our session's X25519 private key.\n * @param sessionServerPublicKey - The server's X25519 public key (base64url).\n * @param keyToWrap - The raw symmetric key bytes to encrypt.\n * @returns The base64url `encryptedKey` (ciphertext + tag) and `nonce`.\n */\nexport function wrapEncryptionKey(\n sessionClientPrivateKey: Uint8Array,\n sessionServerPublicKey: string,\n keyToWrap: Uint8Array,\n): WrappedEncryptionKeyParts {\n const serverPublicKey = base64UrlToBytes(sessionServerPublicKey);\n const nonce = randomBytes(box.nonceLength);\n const encryptedKey = box(\n keyToWrap,\n nonce,\n serverPublicKey,\n sessionClientPrivateKey,\n );\n return {\n encryptedKey: toBase64Url(encryptedKey),\n nonce: toBase64Url(nonce),\n };\n}\n"]}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Wraps the `data_encryption_key` for the UKYC session server using NaCl's
3
+ * `crypto_box` (X25519 + XSalsa20-Poly1305) established with the server's
4
+ * per-session wrapping key.
5
+ *
6
+ * Unlike {@link wrapUserKey} (which generates a fresh ephemeral keypair per
7
+ * call), this uses the session client keypair whose public half was already
8
+ * handed to the server via `POST /wrapping-key`. The server therefore already
9
+ * knows our public key and can derive the same shared secret from its session
10
+ * private key, so only `{ encryptedKey, nonce }` need be transmitted.
11
+ */
12
+ /**
13
+ * The transmitted portion of a wrapped encryption key: the `crypto_box`
14
+ * ciphertext (which includes the 16-byte Poly1305 auth tag) and the nonce,
15
+ * both unpadded base64url-encoded.
16
+ */
17
+ export type WrappedEncryptionKeyParts = {
18
+ encryptedKey: string;
19
+ nonce: string;
20
+ };
21
+ /**
22
+ * Wraps `keyToWrap` for the UKYC session server.
23
+ *
24
+ * The box is sealed with NaCl's `crypto_box`, keyed by the X25519 shared secret
25
+ * between our session client private key and the session server public key
26
+ * returned by `getWrappingKey`.
27
+ *
28
+ * @param sessionClientPrivateKey - Our session's X25519 private key.
29
+ * @param sessionServerPublicKey - The server's X25519 public key (base64url).
30
+ * @param keyToWrap - The raw symmetric key bytes to encrypt.
31
+ * @returns The base64url `encryptedKey` (ciphertext + tag) and `nonce`.
32
+ */
33
+ export declare function wrapEncryptionKey(sessionClientPrivateKey: Uint8Array, sessionServerPublicKey: string, keyToWrap: Uint8Array): WrappedEncryptionKeyParts;
34
+ //# sourceMappingURL=wrapEncryptionKey.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapEncryptionKey.d.cts","sourceRoot":"","sources":["../../src/ukyc/wrapEncryptionKey.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,uBAAuB,EAAE,UAAU,EACnC,sBAAsB,EAAE,MAAM,EAC9B,SAAS,EAAE,UAAU,GACpB,yBAAyB,CAa3B"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Wraps the `data_encryption_key` for the UKYC session server using NaCl's
3
+ * `crypto_box` (X25519 + XSalsa20-Poly1305) established with the server's
4
+ * per-session wrapping key.
5
+ *
6
+ * Unlike {@link wrapUserKey} (which generates a fresh ephemeral keypair per
7
+ * call), this uses the session client keypair whose public half was already
8
+ * handed to the server via `POST /wrapping-key`. The server therefore already
9
+ * knows our public key and can derive the same shared secret from its session
10
+ * private key, so only `{ encryptedKey, nonce }` need be transmitted.
11
+ */
12
+ /**
13
+ * The transmitted portion of a wrapped encryption key: the `crypto_box`
14
+ * ciphertext (which includes the 16-byte Poly1305 auth tag) and the nonce,
15
+ * both unpadded base64url-encoded.
16
+ */
17
+ export type WrappedEncryptionKeyParts = {
18
+ encryptedKey: string;
19
+ nonce: string;
20
+ };
21
+ /**
22
+ * Wraps `keyToWrap` for the UKYC session server.
23
+ *
24
+ * The box is sealed with NaCl's `crypto_box`, keyed by the X25519 shared secret
25
+ * between our session client private key and the session server public key
26
+ * returned by `getWrappingKey`.
27
+ *
28
+ * @param sessionClientPrivateKey - Our session's X25519 private key.
29
+ * @param sessionServerPublicKey - The server's X25519 public key (base64url).
30
+ * @param keyToWrap - The raw symmetric key bytes to encrypt.
31
+ * @returns The base64url `encryptedKey` (ciphertext + tag) and `nonce`.
32
+ */
33
+ export declare function wrapEncryptionKey(sessionClientPrivateKey: Uint8Array, sessionServerPublicKey: string, keyToWrap: Uint8Array): WrappedEncryptionKeyParts;
34
+ //# sourceMappingURL=wrapEncryptionKey.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapEncryptionKey.d.mts","sourceRoot":"","sources":["../../src/ukyc/wrapEncryptionKey.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,uBAAuB,EAAE,UAAU,EACnC,sBAAsB,EAAE,MAAM,EAC9B,SAAS,EAAE,UAAU,GACpB,yBAAyB,CAa3B"}
@@ -0,0 +1,25 @@
1
+ import $tweetnacl from "tweetnacl";
2
+ const { randomBytes, box } = $tweetnacl;
3
+ import { base64UrlToBytes, toBase64Url } from "../encoding.mjs";
4
+ /**
5
+ * Wraps `keyToWrap` for the UKYC session server.
6
+ *
7
+ * The box is sealed with NaCl's `crypto_box`, keyed by the X25519 shared secret
8
+ * between our session client private key and the session server public key
9
+ * returned by `getWrappingKey`.
10
+ *
11
+ * @param sessionClientPrivateKey - Our session's X25519 private key.
12
+ * @param sessionServerPublicKey - The server's X25519 public key (base64url).
13
+ * @param keyToWrap - The raw symmetric key bytes to encrypt.
14
+ * @returns The base64url `encryptedKey` (ciphertext + tag) and `nonce`.
15
+ */
16
+ export function wrapEncryptionKey(sessionClientPrivateKey, sessionServerPublicKey, keyToWrap) {
17
+ const serverPublicKey = base64UrlToBytes(sessionServerPublicKey);
18
+ const nonce = randomBytes(box.nonceLength);
19
+ const encryptedKey = box(keyToWrap, nonce, serverPublicKey, sessionClientPrivateKey);
20
+ return {
21
+ encryptedKey: toBase64Url(encryptedKey),
22
+ nonce: toBase64Url(nonce),
23
+ };
24
+ }
25
+ //# sourceMappingURL=wrapEncryptionKey.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapEncryptionKey.mjs","sourceRoot":"","sources":["../../src/ukyc/wrapEncryptionKey.ts"],"names":[],"mappings":";;AAEA,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,wBAAuB;AAwB/D;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAC/B,uBAAmC,EACnC,sBAA8B,EAC9B,SAAqB;IAErB,MAAM,eAAe,GAAG,gBAAgB,CAAC,sBAAsB,CAAC,CAAC;IACjE,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,GAAG,CACtB,SAAS,EACT,KAAK,EACL,eAAe,EACf,uBAAuB,CACxB,CAAC;IACF,OAAO;QACL,YAAY,EAAE,WAAW,CAAC,YAAY,CAAC;QACvC,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC;KAC1B,CAAC;AACJ,CAAC","sourcesContent":["import { randomBytes, box } from 'tweetnacl';\n\nimport { base64UrlToBytes, toBase64Url } from '../encoding.js';\n\n/**\n * Wraps the `data_encryption_key` for the UKYC session server using NaCl's\n * `crypto_box` (X25519 + XSalsa20-Poly1305) established with the server's\n * per-session wrapping key.\n *\n * Unlike {@link wrapUserKey} (which generates a fresh ephemeral keypair per\n * call), this uses the session client keypair whose public half was already\n * handed to the server via `POST /wrapping-key`. The server therefore already\n * knows our public key and can derive the same shared secret from its session\n * private key, so only `{ encryptedKey, nonce }` need be transmitted.\n */\n\n/**\n * The transmitted portion of a wrapped encryption key: the `crypto_box`\n * ciphertext (which includes the 16-byte Poly1305 auth tag) and the nonce,\n * both unpadded base64url-encoded.\n */\nexport type WrappedEncryptionKeyParts = {\n encryptedKey: string;\n nonce: string;\n};\n\n/**\n * Wraps `keyToWrap` for the UKYC session server.\n *\n * The box is sealed with NaCl's `crypto_box`, keyed by the X25519 shared secret\n * between our session client private key and the session server public key\n * returned by `getWrappingKey`.\n *\n * @param sessionClientPrivateKey - Our session's X25519 private key.\n * @param sessionServerPublicKey - The server's X25519 public key (base64url).\n * @param keyToWrap - The raw symmetric key bytes to encrypt.\n * @returns The base64url `encryptedKey` (ciphertext + tag) and `nonce`.\n */\nexport function wrapEncryptionKey(\n sessionClientPrivateKey: Uint8Array,\n sessionServerPublicKey: string,\n keyToWrap: Uint8Array,\n): WrappedEncryptionKeyParts {\n const serverPublicKey = base64UrlToBytes(sessionServerPublicKey);\n const nonce = randomBytes(box.nonceLength);\n const encryptedKey = box(\n keyToWrap,\n nonce,\n serverPublicKey,\n sessionClientPrivateKey,\n );\n return {\n encryptedKey: toBase64Url(encryptedKey),\n nonce: toBase64Url(nonce),\n };\n}\n"]}
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.wrapUserKey = void 0;
4
+ const utils_1 = require("@metamask/utils");
5
+ const aes_1 = require("@noble/ciphers/aes");
6
+ const ed25519_1 = require("@noble/curves/ed25519");
7
+ const hkdf_1 = require("@noble/hashes/hkdf");
8
+ const sha2_1 = require("@noble/hashes/sha2");
9
+ const utils_2 = require("@noble/hashes/utils");
10
+ const encoding_js_1 = require("../encoding.cjs");
11
+ /**
12
+ * Wraps (encrypts) a symmetric key so that only the holder of a given X25519
13
+ * private key can recover it — used to seal the `data_encryption_key` for the
14
+ * idOS Relay before it is handed to the UKYC API as `wrappedUserKey`.
15
+ */
16
+ /** X25519 public/private keys and AES-256-GCM keys are all 32 bytes. */
17
+ const X25519_KEY_SIZE_BYTES = 32;
18
+ /** 96-bit IV, the AES-GCM standard nonce size. */
19
+ const IV_SIZE_BYTES = 12;
20
+ /**
21
+ * Decodes an X25519 public key presented as either hex or base64/base64url.
22
+ *
23
+ * @param recipientPublicKey - The recipient's 32-byte X25519 public key.
24
+ * @returns The raw 32-byte public key.
25
+ */
26
+ function decodePublicKey(recipientPublicKey) {
27
+ const isHex = recipientPublicKey.length === X25519_KEY_SIZE_BYTES * 2 &&
28
+ /^[0-9a-fA-F]+$/u.test(recipientPublicKey);
29
+ const bytes = isHex
30
+ ? (0, utils_2.hexToBytes)(recipientPublicKey)
31
+ : (0, utils_1.base64ToBytes)(recipientPublicKey
32
+ .replace(/-/gu, '+')
33
+ .replace(/_/gu, '/')
34
+ .padEnd(recipientPublicKey.length +
35
+ ((4 - (recipientPublicKey.length % 4)) % 4), '='));
36
+ if (bytes.length !== X25519_KEY_SIZE_BYTES) {
37
+ throw new Error(`UKYC: wrappingPublicKey has unexpected length ${bytes.length}, expected ${X25519_KEY_SIZE_BYTES}.`);
38
+ }
39
+ return bytes;
40
+ }
41
+ /**
42
+ * Wraps `keyToWrap` for the holder of `recipientPublicKey` using an
43
+ * ephemeral-static ECDH + AES-256-GCM sealed-box scheme:
44
+ *
45
+ * ephemeral = fresh X25519 keypair (one per call)
46
+ * shared = X25519(ephemeralPrivate, recipientPublic)
47
+ * aeadKey = HKDF-SHA256(shared, 32 bytes)
48
+ * iv = 12 random bytes
49
+ * ct = AES-256-GCM(aeadKey, iv).encrypt(keyToWrap) // ct includes tag
50
+ *
51
+ * The recipient reverses it with their private key:
52
+ *
53
+ * shared = X25519(recipientPrivate, ephemeralPublic)
54
+ * aeadKey = HKDF-SHA256(shared, 32 bytes)
55
+ * key = AES-256-GCM(aeadKey, iv).decrypt(ct)
56
+ *
57
+ * This mirrors the X25519 + AES-256-GCM/HKDF decryption used for MoonPay
58
+ * Check/Auth-frame credentials, so both directions share one primitive.
59
+ *
60
+ * @param recipientPublicKey - The recipient's X25519 public key (hex or base64).
61
+ * @param keyToWrap - The raw symmetric key bytes to encrypt (e.g. the
62
+ * `data_encryption_key`).
63
+ * @returns Base64url of `ephemeralPublicKey(32) || iv(12) || ciphertext+tag`.
64
+ */
65
+ function wrapUserKey(recipientPublicKey, keyToWrap) {
66
+ const recipient = decodePublicKey(recipientPublicKey);
67
+ const ephemeralPrivateKey = ed25519_1.x25519.utils.randomSecretKey();
68
+ const ephemeralPublicKey = ed25519_1.x25519.getPublicKey(ephemeralPrivateKey);
69
+ const shared = ed25519_1.x25519.getSharedSecret(ephemeralPrivateKey, recipient);
70
+ const aeadKey = (0, hkdf_1.hkdf)(sha2_1.sha256, shared, undefined, undefined, 32);
71
+ const iv = (0, utils_2.randomBytes)(IV_SIZE_BYTES);
72
+ const ciphertext = (0, aes_1.gcm)(aeadKey, iv).encrypt(keyToWrap);
73
+ const blob = new Uint8Array(ephemeralPublicKey.length + iv.length + ciphertext.length);
74
+ blob.set(ephemeralPublicKey, 0);
75
+ blob.set(iv, ephemeralPublicKey.length);
76
+ blob.set(ciphertext, ephemeralPublicKey.length + iv.length);
77
+ return (0, encoding_js_1.toBase64Url)(blob);
78
+ }
79
+ exports.wrapUserKey = wrapUserKey;
80
+ //# sourceMappingURL=wrapUserKey.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapUserKey.cjs","sourceRoot":"","sources":["../../src/ukyc/wrapUserKey.ts"],"names":[],"mappings":";;;AAAA,2CAAgD;AAChD,4CAAyC;AACzC,mDAA+C;AAC/C,6CAA0C;AAC1C,6CAA4C;AAC5C,+CAA8D;AAE9D,iDAA6C;AAE7C;;;;GAIG;AAEH,wEAAwE;AACxE,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAEjC,kDAAkD;AAClD,MAAM,aAAa,GAAG,EAAE,CAAC;AAEzB;;;;;GAKG;AACH,SAAS,eAAe,CAAC,kBAA0B;IACjD,MAAM,KAAK,GACT,kBAAkB,CAAC,MAAM,KAAK,qBAAqB,GAAG,CAAC;QACvD,iBAAiB,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAE7C,MAAM,KAAK,GAAG,KAAK;QACjB,CAAC,CAAC,IAAA,kBAAU,EAAC,kBAAkB,CAAC;QAChC,CAAC,CAAC,IAAA,qBAAa,EACX,kBAAkB;aACf,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;aACnB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;aACnB,MAAM,CACL,kBAAkB,CAAC,MAAM;YACvB,CAAC,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAC7C,GAAG,CACJ,CACJ,CAAC;IAEN,IAAI,KAAK,CAAC,MAAM,KAAK,qBAAqB,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CACb,iDAAiD,KAAK,CAAC,MAAM,cAAc,qBAAqB,GAAG,CACpG,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,SAAgB,WAAW,CACzB,kBAA0B,EAC1B,SAAqB;IAErB,MAAM,SAAS,GAAG,eAAe,CAAC,kBAAkB,CAAC,CAAC;IAEtD,MAAM,mBAAmB,GAAG,gBAAM,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC;IAC3D,MAAM,kBAAkB,GAAG,gBAAM,CAAC,YAAY,CAAC,mBAAmB,CAAC,CAAC;IAEpE,MAAM,MAAM,GAAG,gBAAM,CAAC,eAAe,CAAC,mBAAmB,EAAE,SAAS,CAAC,CAAC;IACtE,MAAM,OAAO,GAAG,IAAA,WAAI,EAAC,aAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;IAE/D,MAAM,EAAE,GAAG,IAAA,mBAAW,EAAC,aAAa,CAAC,CAAC;IACtC,MAAM,UAAU,GAAG,IAAA,SAAG,EAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEvD,MAAM,IAAI,GAAG,IAAI,UAAU,CACzB,kBAAkB,CAAC,MAAM,GAAG,EAAE,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAC1D,CAAC;IACF,IAAI,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC;IAChC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,kBAAkB,CAAC,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC;IAE5D,OAAO,IAAA,yBAAW,EAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAvBD,kCAuBC","sourcesContent":["import { base64ToBytes } from '@metamask/utils';\nimport { gcm } from '@noble/ciphers/aes';\nimport { x25519 } from '@noble/curves/ed25519';\nimport { hkdf } from '@noble/hashes/hkdf';\nimport { sha256 } from '@noble/hashes/sha2';\nimport { hexToBytes, randomBytes } from '@noble/hashes/utils';\n\nimport { toBase64Url } from '../encoding.js';\n\n/**\n * Wraps (encrypts) a symmetric key so that only the holder of a given X25519\n * private key can recover it — used to seal the `data_encryption_key` for the\n * idOS Relay before it is handed to the UKYC API as `wrappedUserKey`.\n */\n\n/** X25519 public/private keys and AES-256-GCM keys are all 32 bytes. */\nconst X25519_KEY_SIZE_BYTES = 32;\n\n/** 96-bit IV, the AES-GCM standard nonce size. */\nconst IV_SIZE_BYTES = 12;\n\n/**\n * Decodes an X25519 public key presented as either hex or base64/base64url.\n *\n * @param recipientPublicKey - The recipient's 32-byte X25519 public key.\n * @returns The raw 32-byte public key.\n */\nfunction decodePublicKey(recipientPublicKey: string): Uint8Array {\n const isHex =\n recipientPublicKey.length === X25519_KEY_SIZE_BYTES * 2 &&\n /^[0-9a-fA-F]+$/u.test(recipientPublicKey);\n\n const bytes = isHex\n ? hexToBytes(recipientPublicKey)\n : base64ToBytes(\n recipientPublicKey\n .replace(/-/gu, '+')\n .replace(/_/gu, '/')\n .padEnd(\n recipientPublicKey.length +\n ((4 - (recipientPublicKey.length % 4)) % 4),\n '=',\n ),\n );\n\n if (bytes.length !== X25519_KEY_SIZE_BYTES) {\n throw new Error(\n `UKYC: wrappingPublicKey has unexpected length ${bytes.length}, expected ${X25519_KEY_SIZE_BYTES}.`,\n );\n }\n return bytes;\n}\n\n/**\n * Wraps `keyToWrap` for the holder of `recipientPublicKey` using an\n * ephemeral-static ECDH + AES-256-GCM sealed-box scheme:\n *\n * ephemeral = fresh X25519 keypair (one per call)\n * shared = X25519(ephemeralPrivate, recipientPublic)\n * aeadKey = HKDF-SHA256(shared, 32 bytes)\n * iv = 12 random bytes\n * ct = AES-256-GCM(aeadKey, iv).encrypt(keyToWrap) // ct includes tag\n *\n * The recipient reverses it with their private key:\n *\n * shared = X25519(recipientPrivate, ephemeralPublic)\n * aeadKey = HKDF-SHA256(shared, 32 bytes)\n * key = AES-256-GCM(aeadKey, iv).decrypt(ct)\n *\n * This mirrors the X25519 + AES-256-GCM/HKDF decryption used for MoonPay\n * Check/Auth-frame credentials, so both directions share one primitive.\n *\n * @param recipientPublicKey - The recipient's X25519 public key (hex or base64).\n * @param keyToWrap - The raw symmetric key bytes to encrypt (e.g. the\n * `data_encryption_key`).\n * @returns Base64url of `ephemeralPublicKey(32) || iv(12) || ciphertext+tag`.\n */\nexport function wrapUserKey(\n recipientPublicKey: string,\n keyToWrap: Uint8Array,\n): string {\n const recipient = decodePublicKey(recipientPublicKey);\n\n const ephemeralPrivateKey = x25519.utils.randomSecretKey();\n const ephemeralPublicKey = x25519.getPublicKey(ephemeralPrivateKey);\n\n const shared = x25519.getSharedSecret(ephemeralPrivateKey, recipient);\n const aeadKey = hkdf(sha256, shared, undefined, undefined, 32);\n\n const iv = randomBytes(IV_SIZE_BYTES);\n const ciphertext = gcm(aeadKey, iv).encrypt(keyToWrap);\n\n const blob = new Uint8Array(\n ephemeralPublicKey.length + iv.length + ciphertext.length,\n );\n blob.set(ephemeralPublicKey, 0);\n blob.set(iv, ephemeralPublicKey.length);\n blob.set(ciphertext, ephemeralPublicKey.length + iv.length);\n\n return toBase64Url(blob);\n}\n"]}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Wraps `keyToWrap` for the holder of `recipientPublicKey` using an
3
+ * ephemeral-static ECDH + AES-256-GCM sealed-box scheme:
4
+ *
5
+ * ephemeral = fresh X25519 keypair (one per call)
6
+ * shared = X25519(ephemeralPrivate, recipientPublic)
7
+ * aeadKey = HKDF-SHA256(shared, 32 bytes)
8
+ * iv = 12 random bytes
9
+ * ct = AES-256-GCM(aeadKey, iv).encrypt(keyToWrap) // ct includes tag
10
+ *
11
+ * The recipient reverses it with their private key:
12
+ *
13
+ * shared = X25519(recipientPrivate, ephemeralPublic)
14
+ * aeadKey = HKDF-SHA256(shared, 32 bytes)
15
+ * key = AES-256-GCM(aeadKey, iv).decrypt(ct)
16
+ *
17
+ * This mirrors the X25519 + AES-256-GCM/HKDF decryption used for MoonPay
18
+ * Check/Auth-frame credentials, so both directions share one primitive.
19
+ *
20
+ * @param recipientPublicKey - The recipient's X25519 public key (hex or base64).
21
+ * @param keyToWrap - The raw symmetric key bytes to encrypt (e.g. the
22
+ * `data_encryption_key`).
23
+ * @returns Base64url of `ephemeralPublicKey(32) || iv(12) || ciphertext+tag`.
24
+ */
25
+ export declare function wrapUserKey(recipientPublicKey: string, keyToWrap: Uint8Array): string;
26
+ //# sourceMappingURL=wrapUserKey.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapUserKey.d.cts","sourceRoot":"","sources":["../../src/ukyc/wrapUserKey.ts"],"names":[],"mappings":"AAqDA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CACzB,kBAAkB,EAAE,MAAM,EAC1B,SAAS,EAAE,UAAU,GACpB,MAAM,CAoBR"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Wraps `keyToWrap` for the holder of `recipientPublicKey` using an
3
+ * ephemeral-static ECDH + AES-256-GCM sealed-box scheme:
4
+ *
5
+ * ephemeral = fresh X25519 keypair (one per call)
6
+ * shared = X25519(ephemeralPrivate, recipientPublic)
7
+ * aeadKey = HKDF-SHA256(shared, 32 bytes)
8
+ * iv = 12 random bytes
9
+ * ct = AES-256-GCM(aeadKey, iv).encrypt(keyToWrap) // ct includes tag
10
+ *
11
+ * The recipient reverses it with their private key:
12
+ *
13
+ * shared = X25519(recipientPrivate, ephemeralPublic)
14
+ * aeadKey = HKDF-SHA256(shared, 32 bytes)
15
+ * key = AES-256-GCM(aeadKey, iv).decrypt(ct)
16
+ *
17
+ * This mirrors the X25519 + AES-256-GCM/HKDF decryption used for MoonPay
18
+ * Check/Auth-frame credentials, so both directions share one primitive.
19
+ *
20
+ * @param recipientPublicKey - The recipient's X25519 public key (hex or base64).
21
+ * @param keyToWrap - The raw symmetric key bytes to encrypt (e.g. the
22
+ * `data_encryption_key`).
23
+ * @returns Base64url of `ephemeralPublicKey(32) || iv(12) || ciphertext+tag`.
24
+ */
25
+ export declare function wrapUserKey(recipientPublicKey: string, keyToWrap: Uint8Array): string;
26
+ //# sourceMappingURL=wrapUserKey.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapUserKey.d.mts","sourceRoot":"","sources":["../../src/ukyc/wrapUserKey.ts"],"names":[],"mappings":"AAqDA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CACzB,kBAAkB,EAAE,MAAM,EAC1B,SAAS,EAAE,UAAU,GACpB,MAAM,CAoBR"}