@metamask-previews/kyc-controller 0.0.0-preview-eb263b0ee → 0.0.0-preview-c61613ed2

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 (64) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/KycController-method-action-types.cjs.map +1 -1
  3. package/dist/KycController-method-action-types.d.cts +4 -2
  4. package/dist/KycController-method-action-types.d.cts.map +1 -1
  5. package/dist/KycController-method-action-types.d.mts +4 -2
  6. package/dist/KycController-method-action-types.d.mts.map +1 -1
  7. package/dist/KycController-method-action-types.mjs.map +1 -1
  8. package/dist/KycController.cjs +35 -10
  9. package/dist/KycController.cjs.map +1 -1
  10. package/dist/KycController.d.cts +4 -2
  11. package/dist/KycController.d.cts.map +1 -1
  12. package/dist/KycController.d.mts +4 -2
  13. package/dist/KycController.d.mts.map +1 -1
  14. package/dist/KycController.mjs +35 -10
  15. package/dist/KycController.mjs.map +1 -1
  16. package/dist/KycService-method-action-types.cjs.map +1 -1
  17. package/dist/KycService-method-action-types.d.cts +25 -8
  18. package/dist/KycService-method-action-types.d.cts.map +1 -1
  19. package/dist/KycService-method-action-types.d.mts +25 -8
  20. package/dist/KycService-method-action-types.d.mts.map +1 -1
  21. package/dist/KycService-method-action-types.mjs.map +1 -1
  22. package/dist/KycService.cjs +65 -29
  23. package/dist/KycService.cjs.map +1 -1
  24. package/dist/KycService.d.cts +48 -16
  25. package/dist/KycService.d.cts.map +1 -1
  26. package/dist/KycService.d.mts +48 -16
  27. package/dist/KycService.d.mts.map +1 -1
  28. package/dist/KycService.mjs +65 -29
  29. package/dist/KycService.mjs.map +1 -1
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +1 -1
  32. package/dist/index.d.cts.map +1 -1
  33. package/dist/index.d.mts +1 -1
  34. package/dist/index.d.mts.map +1 -1
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/ukyc/constants.cjs +2 -2
  37. package/dist/ukyc/constants.cjs.map +1 -1
  38. package/dist/ukyc/constants.d.cts +2 -2
  39. package/dist/ukyc/constants.d.mts +2 -2
  40. package/dist/ukyc/constants.mjs +2 -2
  41. package/dist/ukyc/constants.mjs.map +1 -1
  42. package/dist/ukyc/jwtChain.cjs +1 -1
  43. package/dist/ukyc/jwtChain.cjs.map +1 -1
  44. package/dist/ukyc/jwtChain.d.cts +4 -4
  45. package/dist/ukyc/jwtChain.d.mts +4 -4
  46. package/dist/ukyc/jwtChain.mjs +1 -1
  47. package/dist/ukyc/jwtChain.mjs.map +1 -1
  48. package/dist/ukyc/wrapEncryptionKey.cjs +2 -3
  49. package/dist/ukyc/wrapEncryptionKey.cjs.map +1 -1
  50. package/dist/ukyc/wrapEncryptionKey.d.cts +6 -9
  51. package/dist/ukyc/wrapEncryptionKey.d.cts.map +1 -1
  52. package/dist/ukyc/wrapEncryptionKey.d.mts +6 -9
  53. package/dist/ukyc/wrapEncryptionKey.d.mts.map +1 -1
  54. package/dist/ukyc/wrapEncryptionKey.mjs +2 -3
  55. package/dist/ukyc/wrapEncryptionKey.mjs.map +1 -1
  56. package/package.json +1 -1
  57. package/dist/ukyc/wrapUserKey.cjs +0 -80
  58. package/dist/ukyc/wrapUserKey.cjs.map +0 -1
  59. package/dist/ukyc/wrapUserKey.d.cts +0 -26
  60. package/dist/ukyc/wrapUserKey.d.cts.map +0 -1
  61. package/dist/ukyc/wrapUserKey.d.mts +0 -26
  62. package/dist/ukyc/wrapUserKey.d.mts.map +0 -1
  63. package/dist/ukyc/wrapUserKey.mjs +0 -76
  64. package/dist/ukyc/wrapUserKey.mjs.map +0 -1
@@ -65,8 +65,8 @@ export const UKYC_STORAGE_ACCESS_TOKEN_AUDIENCES = [
65
65
  */
66
66
  export const UKYC_CAPABILITY_AUTH_SCHEME = 'AccessToken';
67
67
  /**
68
- * Standard well-known path where the Fractal encryption service publishes its
69
- * JWKS (the Ed25519 public keys used to sign the `jwtChain`).
68
+ * Standard well-known path where the idOS enclave and the idOS relay publish their JWKS
69
+ * (the Ed25519 public keys used to sign encryption-schema `jwtChain`s).
70
70
  */
71
71
  export const UKYC_JWKS_PATH = '/.well-known/jwks.json';
72
72
  //# sourceMappingURL=constants.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.mjs","sourceRoot":"","sources":["../../src/ukyc/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,wBAAiC,CAAC;AAE7E;;;;GAIG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,EAAE,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,SAAS,EAAE,EAAE;IACb,iBAAiB,EAAE,EAAE;IACrB,UAAU,EAAE,EAAE;IACd,cAAc,EAAE,EAAE;CACV,CAAC;AAEX;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,SAAS,EAAE,qCAAqC;IAChD,iBAAiB,EAAE,8CAA8C;IACjE,UAAU,EAAE,sCAAsC;IAClD,cAAc,EAAE,2CAA2C;CACnD,CAAC;AAEX;;GAEG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,CAAC;AAEnD;;;GAGG;AACH,MAAM,CAAC,MAAM,kCAAkC,GAC7C,4BAAqC,CAAC;AAExC;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,WAAoB,CAAC;AAEvD;;;;GAIG;AACH,MAAM,CAAC,MAAM,mCAAmC,GAAG;IACjD,kCAAkC;IAClC,kBAAkB;CACV,CAAC;AAEX;;;;GAIG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,aAAsB,CAAC;AAElE;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,wBAAwB,CAAC","sourcesContent":["/**\n * Constants for the UKYC client-derived key material and storage-authorization\n * layer. See the architecture doc, section \"Client-Derived Material\".\n */\n\n/**\n * Fully-qualified key path for the `local_user_secret` in Encrypted User\n * Storage.\n */\nexport const UKYC_LOCAL_USER_SECRET_PATH = `ukyc.local_user_secret` as const;\n\n/**\n * Size of the `local_user_secret` in bytes. 32 bytes (256 bits) provides high\n * entropy and matches the input length expected by the HKDF-SHA256 derivations\n * below.\n */\nexport const UKYC_LOCAL_USER_SECRET_SIZE_BYTES = 32;\n\n/**\n * Byte length of each value derived from `local_user_secret`.\n *\n * `signingKey` is 32 bytes because it is used directly as the Ed25519\n * private key (for Ed25519 the 32-byte seed *is* the private key).\n */\nexport const UKYC_DERIVED_KEY_SIZES = {\n storageId: 32,\n dataEncryptionKey: 32,\n signingKey: 32,\n relayTunnelKey: 32,\n} as const;\n\n/**\n * HKDF `info` labels providing domain separation between the values derived\n * from `local_user_secret`.\n */\nexport const UKYC_KDF_INFO = {\n storageId: 'metamask.ukyc.storage.v1.storage_id',\n dataEncryptionKey: 'metamask.ukyc.storage.v1.data_encryption_key',\n signingKey: 'metamask.ukyc.storage.v1.signing_key',\n relayTunnelKey: 'metamask.ukyc.storage.v1.relay_tunnel_key',\n} as const;\n\n/**\n * Version bound into every `storage_access_token` payload.\n */\nexport const UKYC_STORAGE_ACCESS_TOKEN_VERSION = 1;\n\n/**\n * Audience identifying the UKYC user-storage service. Required by UKYC Storage\n * when it verifies a `storage_access_token`.\n */\nexport const UKYC_STORAGE_ACCESS_TOKEN_AUDIENCE =\n 'metamask:user-storage:ukyc' as const;\n\n/**\n * Audience identifying the idOS Kwil credential-registry nodes. Required by\n * idOS Kwil when it verifies a `storage_access_token`.\n */\nexport const UKYC_KWIL_AUDIENCE = 'idos:kwil' as const;\n\n/**\n * Full audience list bound into every `storage_access_token` payload. `aud`\n * lists every verifier that may accept the token, so both UKYC Storage and\n * idOS Kwil can each find their own entry.\n */\nexport const UKYC_STORAGE_ACCESS_TOKEN_AUDIENCES = [\n UKYC_STORAGE_ACCESS_TOKEN_AUDIENCE,\n UKYC_KWIL_AUDIENCE,\n] as const;\n\n/**\n * Authorization scheme under which a signed `storage_access_token` envelope is\n * carried to UKYC Storage: `Authorization: AccessToken <base64url(envelope)>`.\n * The credentials portion is what `encodeStorageAccessTokenForHeader` returns.\n */\nexport const UKYC_CAPABILITY_AUTH_SCHEME = 'AccessToken' as const;\n\n/**\n * Standard well-known path where the Fractal encryption service publishes its\n * JWKS (the Ed25519 public keys used to sign the `jwtChain`).\n */\nexport const UKYC_JWKS_PATH = '/.well-known/jwks.json';\n"]}
1
+ {"version":3,"file":"constants.mjs","sourceRoot":"","sources":["../../src/ukyc/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,wBAAiC,CAAC;AAE7E;;;;GAIG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,EAAE,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,SAAS,EAAE,EAAE;IACb,iBAAiB,EAAE,EAAE;IACrB,UAAU,EAAE,EAAE;IACd,cAAc,EAAE,EAAE;CACV,CAAC;AAEX;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,SAAS,EAAE,qCAAqC;IAChD,iBAAiB,EAAE,8CAA8C;IACjE,UAAU,EAAE,sCAAsC;IAClD,cAAc,EAAE,2CAA2C;CACnD,CAAC;AAEX;;GAEG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,CAAC;AAEnD;;;GAGG;AACH,MAAM,CAAC,MAAM,kCAAkC,GAC7C,4BAAqC,CAAC;AAExC;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,WAAoB,CAAC;AAEvD;;;;GAIG;AACH,MAAM,CAAC,MAAM,mCAAmC,GAAG;IACjD,kCAAkC;IAClC,kBAAkB;CACV,CAAC;AAEX;;;;GAIG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,aAAsB,CAAC;AAElE;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,wBAAwB,CAAC","sourcesContent":["/**\n * Constants for the UKYC client-derived key material and storage-authorization\n * layer. See the architecture doc, section \"Client-Derived Material\".\n */\n\n/**\n * Fully-qualified key path for the `local_user_secret` in Encrypted User\n * Storage.\n */\nexport const UKYC_LOCAL_USER_SECRET_PATH = `ukyc.local_user_secret` as const;\n\n/**\n * Size of the `local_user_secret` in bytes. 32 bytes (256 bits) provides high\n * entropy and matches the input length expected by the HKDF-SHA256 derivations\n * below.\n */\nexport const UKYC_LOCAL_USER_SECRET_SIZE_BYTES = 32;\n\n/**\n * Byte length of each value derived from `local_user_secret`.\n *\n * `signingKey` is 32 bytes because it is used directly as the Ed25519\n * private key (for Ed25519 the 32-byte seed *is* the private key).\n */\nexport const UKYC_DERIVED_KEY_SIZES = {\n storageId: 32,\n dataEncryptionKey: 32,\n signingKey: 32,\n relayTunnelKey: 32,\n} as const;\n\n/**\n * HKDF `info` labels providing domain separation between the values derived\n * from `local_user_secret`.\n */\nexport const UKYC_KDF_INFO = {\n storageId: 'metamask.ukyc.storage.v1.storage_id',\n dataEncryptionKey: 'metamask.ukyc.storage.v1.data_encryption_key',\n signingKey: 'metamask.ukyc.storage.v1.signing_key',\n relayTunnelKey: 'metamask.ukyc.storage.v1.relay_tunnel_key',\n} as const;\n\n/**\n * Version bound into every `storage_access_token` payload.\n */\nexport const UKYC_STORAGE_ACCESS_TOKEN_VERSION = 1;\n\n/**\n * Audience identifying the UKYC user-storage service. Required by UKYC Storage\n * when it verifies a `storage_access_token`.\n */\nexport const UKYC_STORAGE_ACCESS_TOKEN_AUDIENCE =\n 'metamask:user-storage:ukyc' as const;\n\n/**\n * Audience identifying the idOS Kwil credential-registry nodes. Required by\n * idOS Kwil when it verifies a `storage_access_token`.\n */\nexport const UKYC_KWIL_AUDIENCE = 'idos:kwil' as const;\n\n/**\n * Full audience list bound into every `storage_access_token` payload. `aud`\n * lists every verifier that may accept the token, so both UKYC Storage and\n * idOS Kwil can each find their own entry.\n */\nexport const UKYC_STORAGE_ACCESS_TOKEN_AUDIENCES = [\n UKYC_STORAGE_ACCESS_TOKEN_AUDIENCE,\n UKYC_KWIL_AUDIENCE,\n] as const;\n\n/**\n * Authorization scheme under which a signed `storage_access_token` envelope is\n * carried to UKYC Storage: `Authorization: AccessToken <base64url(envelope)>`.\n * The credentials portion is what `encodeStorageAccessTokenForHeader` returns.\n */\nexport const UKYC_CAPABILITY_AUTH_SCHEME = 'AccessToken' as const;\n\n/**\n * Standard well-known path where the idOS enclave and the idOS relay publish their JWKS\n * (the Ed25519 public keys used to sign encryption-schema `jwtChain`s).\n */\nexport const UKYC_JWKS_PATH = '/.well-known/jwks.json';\n"]}
@@ -24,7 +24,7 @@ function decodeJsonSegment(segment, label) {
24
24
  * published Ed25519 signing key and checks the EdDSA signature over the
25
25
  * `header.payload` input. Returns the decoded, verified payload.
26
26
  *
27
- * @param keys - The JWKS keys published by the Fractal encryption service.
27
+ * @param keys - The issuer JWKS keys used to verify the chain.
28
28
  * @param jwtChain - The compact-serialized EdDSA JWT from an encryption schema.
29
29
  * @returns The verified JWT payload.
30
30
  */
@@ -1 +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 in an encryption schema 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 an encryption schema.\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"]}
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 an encryption-schema `jwtChain` against the issuer's published JWKS\n * (idOS enclave for `encryptionDataKey`, idOS relay for `ukycCapabilityToken`).\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 an issuer 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 in an encryption schema 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 issuer JWKS keys used to verify the chain.\n * @param jwtChain - The compact-serialized EdDSA JWT from an encryption schema.\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"]}
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Verifies the `jwtChain` returned by the Fractal encryption service against
3
- * its published JWKS.
2
+ * Verifies an encryption-schema `jwtChain` against the issuer's published JWKS
3
+ * (idOS enclave for `encryptionDataKey`, idOS relay for `ukycCapabilityToken`).
4
4
  *
5
5
  * The signature check is done with `@noble/curves` (rather than WebCrypto
6
6
  * `subtle`) because not every MetaMask runtime exposes a `subtle`
@@ -8,7 +8,7 @@
8
8
  * no `jose` dependency is required.
9
9
  */
10
10
  /**
11
- * A single Ed25519 (OKP) JSON Web Key from the Fractal JWKS.
11
+ * A single Ed25519 (OKP) JSON Web Key from an issuer JWKS.
12
12
  */
13
13
  export type Jwk = {
14
14
  kty: string;
@@ -32,7 +32,7 @@ export type JwtChainPayload = {
32
32
  * published Ed25519 signing key and checks the EdDSA signature over the
33
33
  * `header.payload` input. Returns the decoded, verified payload.
34
34
  *
35
- * @param keys - The JWKS keys published by the Fractal encryption service.
35
+ * @param keys - The issuer JWKS keys used to verify the chain.
36
36
  * @param jwtChain - The compact-serialized EdDSA JWT from an encryption schema.
37
37
  * @returns The verified JWT payload.
38
38
  */
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Verifies the `jwtChain` returned by the Fractal encryption service against
3
- * its published JWKS.
2
+ * Verifies an encryption-schema `jwtChain` against the issuer's published JWKS
3
+ * (idOS enclave for `encryptionDataKey`, idOS relay for `ukycCapabilityToken`).
4
4
  *
5
5
  * The signature check is done with `@noble/curves` (rather than WebCrypto
6
6
  * `subtle`) because not every MetaMask runtime exposes a `subtle`
@@ -8,7 +8,7 @@
8
8
  * no `jose` dependency is required.
9
9
  */
10
10
  /**
11
- * A single Ed25519 (OKP) JSON Web Key from the Fractal JWKS.
11
+ * A single Ed25519 (OKP) JSON Web Key from an issuer JWKS.
12
12
  */
13
13
  export type Jwk = {
14
14
  kty: string;
@@ -32,7 +32,7 @@ export type JwtChainPayload = {
32
32
  * published Ed25519 signing key and checks the EdDSA signature over the
33
33
  * `header.payload` input. Returns the decoded, verified payload.
34
34
  *
35
- * @param keys - The JWKS keys published by the Fractal encryption service.
35
+ * @param keys - The issuer JWKS keys used to verify the chain.
36
36
  * @param jwtChain - The compact-serialized EdDSA JWT from an encryption schema.
37
37
  * @returns The verified JWT payload.
38
38
  */
@@ -21,7 +21,7 @@ function decodeJsonSegment(segment, label) {
21
21
  * published Ed25519 signing key and checks the EdDSA signature over the
22
22
  * `header.payload` input. Returns the decoded, verified payload.
23
23
  *
24
- * @param keys - The JWKS keys published by the Fractal encryption service.
24
+ * @param keys - The issuer JWKS keys used to verify the chain.
25
25
  * @param jwtChain - The compact-serialized EdDSA JWT from an encryption schema.
26
26
  * @returns The verified JWT payload.
27
27
  */
@@ -1 +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 in an encryption schema 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 an encryption schema.\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"]}
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 an encryption-schema `jwtChain` against the issuer's published JWKS\n * (idOS enclave for `encryptionDataKey`, idOS relay for `ukycCapabilityToken`).\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 an issuer 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 in an encryption schema 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 issuer JWKS keys used to verify the chain.\n * @param jwtChain - The compact-serialized EdDSA JWT from an encryption schema.\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"]}
@@ -9,9 +9,8 @@ const encoding_js_1 = require("../encoding.cjs");
9
9
  * The box is sealed with NaCl's `crypto_box`, keyed by the X25519 shared secret
10
10
  * between our session client private key and the session server public key
11
11
  * from an encryption schema (`encryptionDataKey` or `ukycCapabilityToken`)
12
- * returned by `createUkycSession`. The 32-byte client public key is prefixed
13
- * onto `data` so the server can open the box without a prior key-registration
14
- * step.
12
+ * returned by `createUkycSession`. The 32-byte client public key is still
13
+ * prefixed onto `data` so the box is self-describing on the wire.
15
14
  *
16
15
  * @param sessionClientPrivateKey - Our session's X25519 private key.
17
16
  * @param sessionServerPublicKey - The server's X25519 public key (base64url).
@@ -1 +1 @@
1
- {"version":3,"file":"wrapEncryptionKey.cjs","sourceRoot":"","sources":["../../src/ukyc/wrapEncryptionKey.ts"],"names":[],"mappings":";;;AAAA,yCAA6C;AAE7C,iDAA+D;AA0B/D;;;;;;;;;;;;;;;;GAgBG;AACH,SAAgB,iBAAiB,CAC/B,uBAAmC,EACnC,sBAA8B,EAC9B,SAAqB;IAErB,MAAM,eAAe,GAAG,IAAA,8BAAgB,EAAC,sBAAsB,CAAC,CAAC;IACjE,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,GAAG,eAAG,CAAC,OAAO,CAAC,aAAa,CAC9D,uBAAuB,CACxB,CAAC;IACF,MAAM,KAAK,GAAG,IAAA,uBAAW,EAAC,eAAG,CAAC,WAAW,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,IAAA,eAAG,EACpB,SAAS,EACT,KAAK,EACL,eAAe,EACf,uBAAuB,CACxB,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,eAAe,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IACxE,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;IAC7B,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAC7C,OAAO;QACL,IAAI,EAAE,IAAA,yBAAW,EAAC,IAAI,CAAC;QACvB,KAAK,EAAE,IAAA,yBAAW,EAAC,KAAK,CAAC;KAC1B,CAAC;AACJ,CAAC;AAvBD,8CAuBC","sourcesContent":["import { randomBytes, box } from 'tweetnacl';\n\nimport { base64UrlToBytes, toBase64Url } from '../encoding.js';\n\n/**\n * Wraps a secret for the UKYC session server using NaCl's `crypto_box`\n * (X25519 + XSalsa20-Poly1305) established with a per-secret wrapping key\n * returned inside an encryption schema from `createUkycSession`.\n *\n * Unlike {@link wrapUserKey} (which generates a fresh ephemeral keypair per\n * call), this reuses a session client keypair. There is no prior wrapping-key\n * exchange to register that public key, so `data` is\n * `clientPublicKey (32) || ciphertext+tag` and only `{ data, nonce }` need be\n * transmitted. Used for both the `data_encryption_key` and the\n * `ukyc_capability_token`.\n */\n\n/**\n * The transmitted portion of a wrapped secret: the sender public key prefixed\n * to the `crypto_box` ciphertext (which includes the 16-byte Poly1305 auth\n * tag), and the nonce, both unpadded base64url-encoded. Matches the KYC API\n * `CapabilityAuthorization` wire shape.\n */\nexport type WrappedEncryptionKeyParts = {\n data: string;\n nonce: string;\n};\n\n/**\n * Wraps `plaintext` 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 * from an encryption schema (`encryptionDataKey` or `ukycCapabilityToken`)\n * returned by `createUkycSession`. The 32-byte client public key is prefixed\n * onto `data` so the server can open the box without a prior key-registration\n * step.\n *\n * @param sessionClientPrivateKey - Our session's X25519 private key.\n * @param sessionServerPublicKey - The server's X25519 public key (base64url).\n * @param plaintext - The raw bytes to encrypt (e.g. the `data_encryption_key`\n * or the encoded `ukyc_capability_token`).\n * @returns The base64url `data` (`clientPublicKey || ciphertext+tag`) and\n * `nonce`.\n */\nexport function wrapEncryptionKey(\n sessionClientPrivateKey: Uint8Array,\n sessionServerPublicKey: string,\n plaintext: Uint8Array,\n): WrappedEncryptionKeyParts {\n const serverPublicKey = base64UrlToBytes(sessionServerPublicKey);\n const { publicKey: clientPublicKey } = box.keyPair.fromSecretKey(\n sessionClientPrivateKey,\n );\n const nonce = randomBytes(box.nonceLength);\n const ciphertext = box(\n plaintext,\n nonce,\n serverPublicKey,\n sessionClientPrivateKey,\n );\n const data = new Uint8Array(clientPublicKey.length + ciphertext.length);\n data.set(clientPublicKey, 0);\n data.set(ciphertext, clientPublicKey.length);\n return {\n data: toBase64Url(data),\n nonce: toBase64Url(nonce),\n };\n}\n"]}
1
+ {"version":3,"file":"wrapEncryptionKey.cjs","sourceRoot":"","sources":["../../src/ukyc/wrapEncryptionKey.ts"],"names":[],"mappings":";;;AAAA,yCAA6C;AAE7C,iDAA+D;AAwB/D;;;;;;;;;;;;;;;GAeG;AACH,SAAgB,iBAAiB,CAC/B,uBAAmC,EACnC,sBAA8B,EAC9B,SAAqB;IAErB,MAAM,eAAe,GAAG,IAAA,8BAAgB,EAAC,sBAAsB,CAAC,CAAC;IACjE,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,GAAG,eAAG,CAAC,OAAO,CAAC,aAAa,CAC9D,uBAAuB,CACxB,CAAC;IACF,MAAM,KAAK,GAAG,IAAA,uBAAW,EAAC,eAAG,CAAC,WAAW,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,IAAA,eAAG,EACpB,SAAS,EACT,KAAK,EACL,eAAe,EACf,uBAAuB,CACxB,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,eAAe,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IACxE,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;IAC7B,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAC7C,OAAO;QACL,IAAI,EAAE,IAAA,yBAAW,EAAC,IAAI,CAAC;QACvB,KAAK,EAAE,IAAA,yBAAW,EAAC,KAAK,CAAC;KAC1B,CAAC;AACJ,CAAC;AAvBD,8CAuBC","sourcesContent":["import { randomBytes, box } from 'tweetnacl';\n\nimport { base64UrlToBytes, toBase64Url } from '../encoding.js';\n\n/**\n * Wraps a secret for the UKYC session server using NaCl's `crypto_box`\n * (X25519 + XSalsa20-Poly1305) established with a per-secret wrapping key\n * returned inside an encryption schema from `createUkycSession`.\n *\n * Reuses a session client keypair whose public half is registered on\n * `createUkycSession`. `data` is still `clientPublicKey (32) || ciphertext+tag`\n * so the box can be opened from `{ data, nonce }` alone. Used for both the\n * `data_encryption_key` and the `ukyc_capability_token`.\n */\n\n/**\n * The transmitted portion of a wrapped secret: the sender public key prefixed\n * to the `crypto_box` ciphertext (which includes the 16-byte Poly1305 auth\n * tag), and the nonce, both unpadded base64url-encoded. Matches the KYC API\n * `CapabilityAuthorization` wire shape.\n */\nexport type WrappedEncryptionKeyParts = {\n data: string;\n nonce: string;\n};\n\n/**\n * Wraps `plaintext` 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 * from an encryption schema (`encryptionDataKey` or `ukycCapabilityToken`)\n * returned by `createUkycSession`. The 32-byte client public key is still\n * prefixed onto `data` so the box is self-describing on the wire.\n *\n * @param sessionClientPrivateKey - Our session's X25519 private key.\n * @param sessionServerPublicKey - The server's X25519 public key (base64url).\n * @param plaintext - The raw bytes to encrypt (e.g. the `data_encryption_key`\n * or the encoded `ukyc_capability_token`).\n * @returns The base64url `data` (`clientPublicKey || ciphertext+tag`) and\n * `nonce`.\n */\nexport function wrapEncryptionKey(\n sessionClientPrivateKey: Uint8Array,\n sessionServerPublicKey: string,\n plaintext: Uint8Array,\n): WrappedEncryptionKeyParts {\n const serverPublicKey = base64UrlToBytes(sessionServerPublicKey);\n const { publicKey: clientPublicKey } = box.keyPair.fromSecretKey(\n sessionClientPrivateKey,\n );\n const nonce = randomBytes(box.nonceLength);\n const ciphertext = box(\n plaintext,\n nonce,\n serverPublicKey,\n sessionClientPrivateKey,\n );\n const data = new Uint8Array(clientPublicKey.length + ciphertext.length);\n data.set(clientPublicKey, 0);\n data.set(ciphertext, clientPublicKey.length);\n return {\n data: toBase64Url(data),\n nonce: toBase64Url(nonce),\n };\n}\n"]}
@@ -3,12 +3,10 @@
3
3
  * (X25519 + XSalsa20-Poly1305) established with a per-secret wrapping key
4
4
  * returned inside an encryption schema from `createUkycSession`.
5
5
  *
6
- * Unlike {@link wrapUserKey} (which generates a fresh ephemeral keypair per
7
- * call), this reuses a session client keypair. There is no prior wrapping-key
8
- * exchange to register that public key, so `data` is
9
- * `clientPublicKey (32) || ciphertext+tag` and only `{ data, nonce }` need be
10
- * transmitted. Used for both the `data_encryption_key` and the
11
- * `ukyc_capability_token`.
6
+ * Reuses a session client keypair whose public half is registered on
7
+ * `createUkycSession`. `data` is still `clientPublicKey (32) || ciphertext+tag`
8
+ * so the box can be opened from `{ data, nonce }` alone. Used for both the
9
+ * `data_encryption_key` and the `ukyc_capability_token`.
12
10
  */
13
11
  /**
14
12
  * The transmitted portion of a wrapped secret: the sender public key prefixed
@@ -26,9 +24,8 @@ export type WrappedEncryptionKeyParts = {
26
24
  * The box is sealed with NaCl's `crypto_box`, keyed by the X25519 shared secret
27
25
  * between our session client private key and the session server public key
28
26
  * from an encryption schema (`encryptionDataKey` or `ukycCapabilityToken`)
29
- * returned by `createUkycSession`. The 32-byte client public key is prefixed
30
- * onto `data` so the server can open the box without a prior key-registration
31
- * step.
27
+ * returned by `createUkycSession`. The 32-byte client public key is still
28
+ * prefixed onto `data` so the box is self-describing on the wire.
32
29
  *
33
30
  * @param sessionClientPrivateKey - Our session's X25519 private key.
34
31
  * @param sessionServerPublicKey - The server's X25519 public key (base64url).
@@ -1 +1 @@
1
- {"version":3,"file":"wrapEncryptionKey.d.cts","sourceRoot":"","sources":["../../src/ukyc/wrapEncryptionKey.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAC/B,uBAAuB,EAAE,UAAU,EACnC,sBAAsB,EAAE,MAAM,EAC9B,SAAS,EAAE,UAAU,GACpB,yBAAyB,CAmB3B"}
1
+ {"version":3,"file":"wrapEncryptionKey.d.cts","sourceRoot":"","sources":["../../src/ukyc/wrapEncryptionKey.ts"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,uBAAuB,EAAE,UAAU,EACnC,sBAAsB,EAAE,MAAM,EAC9B,SAAS,EAAE,UAAU,GACpB,yBAAyB,CAmB3B"}
@@ -3,12 +3,10 @@
3
3
  * (X25519 + XSalsa20-Poly1305) established with a per-secret wrapping key
4
4
  * returned inside an encryption schema from `createUkycSession`.
5
5
  *
6
- * Unlike {@link wrapUserKey} (which generates a fresh ephemeral keypair per
7
- * call), this reuses a session client keypair. There is no prior wrapping-key
8
- * exchange to register that public key, so `data` is
9
- * `clientPublicKey (32) || ciphertext+tag` and only `{ data, nonce }` need be
10
- * transmitted. Used for both the `data_encryption_key` and the
11
- * `ukyc_capability_token`.
6
+ * Reuses a session client keypair whose public half is registered on
7
+ * `createUkycSession`. `data` is still `clientPublicKey (32) || ciphertext+tag`
8
+ * so the box can be opened from `{ data, nonce }` alone. Used for both the
9
+ * `data_encryption_key` and the `ukyc_capability_token`.
12
10
  */
13
11
  /**
14
12
  * The transmitted portion of a wrapped secret: the sender public key prefixed
@@ -26,9 +24,8 @@ export type WrappedEncryptionKeyParts = {
26
24
  * The box is sealed with NaCl's `crypto_box`, keyed by the X25519 shared secret
27
25
  * between our session client private key and the session server public key
28
26
  * from an encryption schema (`encryptionDataKey` or `ukycCapabilityToken`)
29
- * returned by `createUkycSession`. The 32-byte client public key is prefixed
30
- * onto `data` so the server can open the box without a prior key-registration
31
- * step.
27
+ * returned by `createUkycSession`. The 32-byte client public key is still
28
+ * prefixed onto `data` so the box is self-describing on the wire.
32
29
  *
33
30
  * @param sessionClientPrivateKey - Our session's X25519 private key.
34
31
  * @param sessionServerPublicKey - The server's X25519 public key (base64url).
@@ -1 +1 @@
1
- {"version":3,"file":"wrapEncryptionKey.d.mts","sourceRoot":"","sources":["../../src/ukyc/wrapEncryptionKey.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAC/B,uBAAuB,EAAE,UAAU,EACnC,sBAAsB,EAAE,MAAM,EAC9B,SAAS,EAAE,UAAU,GACpB,yBAAyB,CAmB3B"}
1
+ {"version":3,"file":"wrapEncryptionKey.d.mts","sourceRoot":"","sources":["../../src/ukyc/wrapEncryptionKey.ts"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,uBAAuB,EAAE,UAAU,EACnC,sBAAsB,EAAE,MAAM,EAC9B,SAAS,EAAE,UAAU,GACpB,yBAAyB,CAmB3B"}
@@ -7,9 +7,8 @@ import { base64UrlToBytes, toBase64Url } from "../encoding.mjs";
7
7
  * The box is sealed with NaCl's `crypto_box`, keyed by the X25519 shared secret
8
8
  * between our session client private key and the session server public key
9
9
  * from an encryption schema (`encryptionDataKey` or `ukycCapabilityToken`)
10
- * returned by `createUkycSession`. The 32-byte client public key is prefixed
11
- * onto `data` so the server can open the box without a prior key-registration
12
- * step.
10
+ * returned by `createUkycSession`. The 32-byte client public key is still
11
+ * prefixed onto `data` so the box is self-describing on the wire.
13
12
  *
14
13
  * @param sessionClientPrivateKey - Our session's X25519 private key.
15
14
  * @param sessionServerPublicKey - The server's X25519 public key (base64url).
@@ -1 +1 @@
1
- {"version":3,"file":"wrapEncryptionKey.mjs","sourceRoot":"","sources":["../../src/ukyc/wrapEncryptionKey.ts"],"names":[],"mappings":";;AAEA,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,wBAAuB;AA0B/D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,uBAAmC,EACnC,sBAA8B,EAC9B,SAAqB;IAErB,MAAM,eAAe,GAAG,gBAAgB,CAAC,sBAAsB,CAAC,CAAC;IACjE,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAC9D,uBAAuB,CACxB,CAAC;IACF,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,GAAG,CACpB,SAAS,EACT,KAAK,EACL,eAAe,EACf,uBAAuB,CACxB,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,eAAe,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IACxE,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;IAC7B,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAC7C,OAAO;QACL,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC;QACvB,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 a secret for the UKYC session server using NaCl's `crypto_box`\n * (X25519 + XSalsa20-Poly1305) established with a per-secret wrapping key\n * returned inside an encryption schema from `createUkycSession`.\n *\n * Unlike {@link wrapUserKey} (which generates a fresh ephemeral keypair per\n * call), this reuses a session client keypair. There is no prior wrapping-key\n * exchange to register that public key, so `data` is\n * `clientPublicKey (32) || ciphertext+tag` and only `{ data, nonce }` need be\n * transmitted. Used for both the `data_encryption_key` and the\n * `ukyc_capability_token`.\n */\n\n/**\n * The transmitted portion of a wrapped secret: the sender public key prefixed\n * to the `crypto_box` ciphertext (which includes the 16-byte Poly1305 auth\n * tag), and the nonce, both unpadded base64url-encoded. Matches the KYC API\n * `CapabilityAuthorization` wire shape.\n */\nexport type WrappedEncryptionKeyParts = {\n data: string;\n nonce: string;\n};\n\n/**\n * Wraps `plaintext` 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 * from an encryption schema (`encryptionDataKey` or `ukycCapabilityToken`)\n * returned by `createUkycSession`. The 32-byte client public key is prefixed\n * onto `data` so the server can open the box without a prior key-registration\n * step.\n *\n * @param sessionClientPrivateKey - Our session's X25519 private key.\n * @param sessionServerPublicKey - The server's X25519 public key (base64url).\n * @param plaintext - The raw bytes to encrypt (e.g. the `data_encryption_key`\n * or the encoded `ukyc_capability_token`).\n * @returns The base64url `data` (`clientPublicKey || ciphertext+tag`) and\n * `nonce`.\n */\nexport function wrapEncryptionKey(\n sessionClientPrivateKey: Uint8Array,\n sessionServerPublicKey: string,\n plaintext: Uint8Array,\n): WrappedEncryptionKeyParts {\n const serverPublicKey = base64UrlToBytes(sessionServerPublicKey);\n const { publicKey: clientPublicKey } = box.keyPair.fromSecretKey(\n sessionClientPrivateKey,\n );\n const nonce = randomBytes(box.nonceLength);\n const ciphertext = box(\n plaintext,\n nonce,\n serverPublicKey,\n sessionClientPrivateKey,\n );\n const data = new Uint8Array(clientPublicKey.length + ciphertext.length);\n data.set(clientPublicKey, 0);\n data.set(ciphertext, clientPublicKey.length);\n return {\n data: toBase64Url(data),\n nonce: toBase64Url(nonce),\n };\n}\n"]}
1
+ {"version":3,"file":"wrapEncryptionKey.mjs","sourceRoot":"","sources":["../../src/ukyc/wrapEncryptionKey.ts"],"names":[],"mappings":";;AAEA,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,wBAAuB;AAwB/D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,iBAAiB,CAC/B,uBAAmC,EACnC,sBAA8B,EAC9B,SAAqB;IAErB,MAAM,eAAe,GAAG,gBAAgB,CAAC,sBAAsB,CAAC,CAAC;IACjE,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAC9D,uBAAuB,CACxB,CAAC;IACF,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,GAAG,CACpB,SAAS,EACT,KAAK,EACL,eAAe,EACf,uBAAuB,CACxB,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,eAAe,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IACxE,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;IAC7B,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAC7C,OAAO;QACL,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC;QACvB,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 a secret for the UKYC session server using NaCl's `crypto_box`\n * (X25519 + XSalsa20-Poly1305) established with a per-secret wrapping key\n * returned inside an encryption schema from `createUkycSession`.\n *\n * Reuses a session client keypair whose public half is registered on\n * `createUkycSession`. `data` is still `clientPublicKey (32) || ciphertext+tag`\n * so the box can be opened from `{ data, nonce }` alone. Used for both the\n * `data_encryption_key` and the `ukyc_capability_token`.\n */\n\n/**\n * The transmitted portion of a wrapped secret: the sender public key prefixed\n * to the `crypto_box` ciphertext (which includes the 16-byte Poly1305 auth\n * tag), and the nonce, both unpadded base64url-encoded. Matches the KYC API\n * `CapabilityAuthorization` wire shape.\n */\nexport type WrappedEncryptionKeyParts = {\n data: string;\n nonce: string;\n};\n\n/**\n * Wraps `plaintext` 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 * from an encryption schema (`encryptionDataKey` or `ukycCapabilityToken`)\n * returned by `createUkycSession`. The 32-byte client public key is still\n * prefixed onto `data` so the box is self-describing on the wire.\n *\n * @param sessionClientPrivateKey - Our session's X25519 private key.\n * @param sessionServerPublicKey - The server's X25519 public key (base64url).\n * @param plaintext - The raw bytes to encrypt (e.g. the `data_encryption_key`\n * or the encoded `ukyc_capability_token`).\n * @returns The base64url `data` (`clientPublicKey || ciphertext+tag`) and\n * `nonce`.\n */\nexport function wrapEncryptionKey(\n sessionClientPrivateKey: Uint8Array,\n sessionServerPublicKey: string,\n plaintext: Uint8Array,\n): WrappedEncryptionKeyParts {\n const serverPublicKey = base64UrlToBytes(sessionServerPublicKey);\n const { publicKey: clientPublicKey } = box.keyPair.fromSecretKey(\n sessionClientPrivateKey,\n );\n const nonce = randomBytes(box.nonceLength);\n const ciphertext = box(\n plaintext,\n nonce,\n serverPublicKey,\n sessionClientPrivateKey,\n );\n const data = new Uint8Array(clientPublicKey.length + ciphertext.length);\n data.set(clientPublicKey, 0);\n data.set(ciphertext, clientPublicKey.length);\n return {\n data: toBase64Url(data),\n nonce: toBase64Url(nonce),\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/kyc-controller",
3
- "version": "0.0.0-preview-eb263b0ee",
3
+ "version": "0.0.0-preview-c61613ed2",
4
4
  "description": "Shared KYC / identity verification controller used across MetaMask clients",
5
5
  "keywords": [
6
6
  "Ethereum",
@@ -1,80 +0,0 @@
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
@@ -1 +0,0 @@
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"]}
@@ -1,26 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,26 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,76 +0,0 @@
1
- import { base64ToBytes } from "@metamask/utils";
2
- import { gcm } from "@noble/ciphers/aes";
3
- import { x25519 } from "@noble/curves/ed25519";
4
- import { hkdf } from "@noble/hashes/hkdf";
5
- import { sha256 } from "@noble/hashes/sha2";
6
- import { hexToBytes, randomBytes } from "@noble/hashes/utils";
7
- import { toBase64Url } from "../encoding.mjs";
8
- /**
9
- * Wraps (encrypts) a symmetric key so that only the holder of a given X25519
10
- * private key can recover it — used to seal the `data_encryption_key` for the
11
- * idOS Relay before it is handed to the UKYC API as `wrappedUserKey`.
12
- */
13
- /** X25519 public/private keys and AES-256-GCM keys are all 32 bytes. */
14
- const X25519_KEY_SIZE_BYTES = 32;
15
- /** 96-bit IV, the AES-GCM standard nonce size. */
16
- const IV_SIZE_BYTES = 12;
17
- /**
18
- * Decodes an X25519 public key presented as either hex or base64/base64url.
19
- *
20
- * @param recipientPublicKey - The recipient's 32-byte X25519 public key.
21
- * @returns The raw 32-byte public key.
22
- */
23
- function decodePublicKey(recipientPublicKey) {
24
- const isHex = recipientPublicKey.length === X25519_KEY_SIZE_BYTES * 2 &&
25
- /^[0-9a-fA-F]+$/u.test(recipientPublicKey);
26
- const bytes = isHex
27
- ? hexToBytes(recipientPublicKey)
28
- : base64ToBytes(recipientPublicKey
29
- .replace(/-/gu, '+')
30
- .replace(/_/gu, '/')
31
- .padEnd(recipientPublicKey.length +
32
- ((4 - (recipientPublicKey.length % 4)) % 4), '='));
33
- if (bytes.length !== X25519_KEY_SIZE_BYTES) {
34
- throw new Error(`UKYC: wrappingPublicKey has unexpected length ${bytes.length}, expected ${X25519_KEY_SIZE_BYTES}.`);
35
- }
36
- return bytes;
37
- }
38
- /**
39
- * Wraps `keyToWrap` for the holder of `recipientPublicKey` using an
40
- * ephemeral-static ECDH + AES-256-GCM sealed-box scheme:
41
- *
42
- * ephemeral = fresh X25519 keypair (one per call)
43
- * shared = X25519(ephemeralPrivate, recipientPublic)
44
- * aeadKey = HKDF-SHA256(shared, 32 bytes)
45
- * iv = 12 random bytes
46
- * ct = AES-256-GCM(aeadKey, iv).encrypt(keyToWrap) // ct includes tag
47
- *
48
- * The recipient reverses it with their private key:
49
- *
50
- * shared = X25519(recipientPrivate, ephemeralPublic)
51
- * aeadKey = HKDF-SHA256(shared, 32 bytes)
52
- * key = AES-256-GCM(aeadKey, iv).decrypt(ct)
53
- *
54
- * This mirrors the X25519 + AES-256-GCM/HKDF decryption used for MoonPay
55
- * Check/Auth-frame credentials, so both directions share one primitive.
56
- *
57
- * @param recipientPublicKey - The recipient's X25519 public key (hex or base64).
58
- * @param keyToWrap - The raw symmetric key bytes to encrypt (e.g. the
59
- * `data_encryption_key`).
60
- * @returns Base64url of `ephemeralPublicKey(32) || iv(12) || ciphertext+tag`.
61
- */
62
- export function wrapUserKey(recipientPublicKey, keyToWrap) {
63
- const recipient = decodePublicKey(recipientPublicKey);
64
- const ephemeralPrivateKey = x25519.utils.randomSecretKey();
65
- const ephemeralPublicKey = x25519.getPublicKey(ephemeralPrivateKey);
66
- const shared = x25519.getSharedSecret(ephemeralPrivateKey, recipient);
67
- const aeadKey = hkdf(sha256, shared, undefined, undefined, 32);
68
- const iv = randomBytes(IV_SIZE_BYTES);
69
- const ciphertext = gcm(aeadKey, iv).encrypt(keyToWrap);
70
- const blob = new Uint8Array(ephemeralPublicKey.length + iv.length + ciphertext.length);
71
- blob.set(ephemeralPublicKey, 0);
72
- blob.set(iv, ephemeralPublicKey.length);
73
- blob.set(ciphertext, ephemeralPublicKey.length + iv.length);
74
- return toBase64Url(blob);
75
- }
76
- //# sourceMappingURL=wrapUserKey.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"wrapUserKey.mjs","sourceRoot":"","sources":["../../src/ukyc/wrapUserKey.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,wBAAwB;AAChD,OAAO,EAAE,GAAG,EAAE,2BAA2B;AACzC,OAAO,EAAE,MAAM,EAAE,8BAA8B;AAC/C,OAAO,EAAE,IAAI,EAAE,2BAA2B;AAC1C,OAAO,EAAE,MAAM,EAAE,2BAA2B;AAC5C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,4BAA4B;AAE9D,OAAO,EAAE,WAAW,EAAE,wBAAuB;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,UAAU,CAAC,kBAAkB,CAAC;QAChC,CAAC,CAAC,aAAa,CACX,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,MAAM,UAAU,WAAW,CACzB,kBAA0B,EAC1B,SAAqB;IAErB,MAAM,SAAS,GAAG,eAAe,CAAC,kBAAkB,CAAC,CAAC;IAEtD,MAAM,mBAAmB,GAAG,MAAM,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC;IAC3D,MAAM,kBAAkB,GAAG,MAAM,CAAC,YAAY,CAAC,mBAAmB,CAAC,CAAC;IAEpE,MAAM,MAAM,GAAG,MAAM,CAAC,eAAe,CAAC,mBAAmB,EAAE,SAAS,CAAC,CAAC;IACtE,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;IAE/D,MAAM,EAAE,GAAG,WAAW,CAAC,aAAa,CAAC,CAAC;IACtC,MAAM,UAAU,GAAG,GAAG,CAAC,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,WAAW,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC","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"]}