@oxyhq/core 4.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/HttpService.js +6 -18
  3. package/dist/cjs/OxyServices.base.js +0 -21
  4. package/dist/cjs/crypto/keyManager.js +7 -7
  5. package/dist/cjs/crypto/polyfill.js +6 -5
  6. package/dist/cjs/crypto/signatureService.js +44 -220
  7. package/dist/cjs/i18n/locales/en-US.json +391 -17
  8. package/dist/cjs/i18n/locales/es-ES.json +391 -17
  9. package/dist/cjs/i18n/locales/locales/en-US.json +391 -17
  10. package/dist/cjs/i18n/locales/locales/es-ES.json +391 -17
  11. package/dist/cjs/index.js +4 -8
  12. package/dist/cjs/mixins/OxyServices.accounts.js +54 -0
  13. package/dist/cjs/mixins/OxyServices.assets.js +2 -2
  14. package/dist/cjs/mixins/OxyServices.auth.js +3 -3
  15. package/dist/cjs/mixins/OxyServices.civic.js +3 -3
  16. package/dist/cjs/mixins/OxyServices.language.js +2 -2
  17. package/dist/cjs/mixins/OxyServices.utility.js +7 -95
  18. package/dist/cjs/utils/cacheKey.js +17 -19
  19. package/dist/cjs/utils/deviceManager.js +2 -2
  20. package/dist/cjs/utils/platform.js +0 -14
  21. package/dist/esm/.tsbuildinfo +1 -1
  22. package/dist/esm/HttpService.js +6 -18
  23. package/dist/esm/OxyServices.base.js +0 -21
  24. package/dist/esm/crypto/keyManager.js +4 -4
  25. package/dist/esm/crypto/polyfill.js +5 -4
  26. package/dist/esm/crypto/signatureService.js +39 -214
  27. package/dist/esm/i18n/locales/en-US.json +391 -17
  28. package/dist/esm/i18n/locales/es-ES.json +391 -17
  29. package/dist/esm/i18n/locales/locales/en-US.json +391 -17
  30. package/dist/esm/i18n/locales/locales/es-ES.json +391 -17
  31. package/dist/esm/index.js +1 -2
  32. package/dist/esm/mixins/OxyServices.accounts.js +54 -0
  33. package/dist/esm/mixins/OxyServices.assets.js +1 -1
  34. package/dist/esm/mixins/OxyServices.auth.js +1 -1
  35. package/dist/esm/mixins/OxyServices.civic.js +3 -3
  36. package/dist/esm/mixins/OxyServices.language.js +1 -1
  37. package/dist/esm/mixins/OxyServices.utility.js +6 -94
  38. package/dist/esm/utils/cacheKey.js +17 -19
  39. package/dist/esm/utils/deviceManager.js +1 -1
  40. package/dist/esm/utils/platform.js +0 -12
  41. package/dist/types/.tsbuildinfo +1 -1
  42. package/dist/types/HttpService.d.ts +3 -6
  43. package/dist/types/OxyServices.base.d.ts +0 -17
  44. package/dist/types/crypto/polyfill.d.ts +2 -2
  45. package/dist/types/crypto/signatureService.d.ts +18 -84
  46. package/dist/types/index.d.ts +3 -4
  47. package/dist/types/mixins/OxyServices.accounts.d.ts +57 -5
  48. package/dist/types/mixins/OxyServices.analytics.d.ts +0 -2
  49. package/dist/types/mixins/OxyServices.appData.d.ts +0 -2
  50. package/dist/types/mixins/OxyServices.assets.d.ts +0 -2
  51. package/dist/types/mixins/OxyServices.auth.d.ts +0 -2
  52. package/dist/types/mixins/OxyServices.civic.d.ts +3 -5
  53. package/dist/types/mixins/OxyServices.connectedApps.d.ts +0 -2
  54. package/dist/types/mixins/OxyServices.contacts.d.ts +0 -2
  55. package/dist/types/mixins/OxyServices.devices.d.ts +0 -2
  56. package/dist/types/mixins/OxyServices.features.d.ts +0 -2
  57. package/dist/types/mixins/OxyServices.fedcm.d.ts +0 -2
  58. package/dist/types/mixins/OxyServices.identity.d.ts +8 -5
  59. package/dist/types/mixins/OxyServices.language.d.ts +0 -2
  60. package/dist/types/mixins/OxyServices.links.d.ts +0 -2
  61. package/dist/types/mixins/OxyServices.location.d.ts +0 -2
  62. package/dist/types/mixins/OxyServices.nodes.d.ts +0 -44
  63. package/dist/types/mixins/OxyServices.payment.d.ts +0 -2
  64. package/dist/types/mixins/OxyServices.privacy.d.ts +0 -2
  65. package/dist/types/mixins/OxyServices.redirect.d.ts +0 -2
  66. package/dist/types/mixins/OxyServices.reputation.d.ts +0 -2
  67. package/dist/types/mixins/OxyServices.security.d.ts +0 -2
  68. package/dist/types/mixins/OxyServices.silent.d.ts +0 -2
  69. package/dist/types/mixins/OxyServices.sso.d.ts +0 -2
  70. package/dist/types/mixins/OxyServices.topics.d.ts +0 -2
  71. package/dist/types/mixins/OxyServices.user.d.ts +0 -2
  72. package/dist/types/mixins/OxyServices.utility.d.ts +0 -32
  73. package/dist/types/server/auth.d.ts +0 -6
  74. package/dist/types/server/index.d.ts +1 -1
  75. package/dist/types/utils/cacheKey.d.ts +6 -7
  76. package/dist/types/utils/platform.d.ts +0 -8
  77. package/package.json +4 -7
  78. package/src/HttpService.ts +6 -22
  79. package/src/OxyServices.base.ts +0 -23
  80. package/src/__tests__/httpServiceCache.test.ts +0 -19
  81. package/src/crypto/__tests__/keyManager.atomicity.test.ts +2 -1
  82. package/src/crypto/__tests__/keyManager.test.ts +9 -7
  83. package/src/crypto/__tests__/signChallengeShared.test.ts +2 -1
  84. package/src/crypto/__tests__/signedRecord.test.ts +37 -150
  85. package/src/crypto/keyManager.ts +28 -17
  86. package/src/crypto/polyfill.ts +5 -4
  87. package/src/crypto/signatureService.ts +67 -255
  88. package/src/i18n/locales/en-US.json +391 -17
  89. package/src/i18n/locales/es-ES.json +391 -17
  90. package/src/index.ts +3 -3
  91. package/src/mixins/OxyServices.accounts.ts +91 -3
  92. package/src/mixins/OxyServices.assets.ts +1 -1
  93. package/src/mixins/OxyServices.auth.ts +1 -1
  94. package/src/mixins/OxyServices.civic.ts +6 -17
  95. package/src/mixins/OxyServices.identity.ts +8 -2
  96. package/src/mixins/OxyServices.language.ts +1 -1
  97. package/src/mixins/OxyServices.nodes.ts +1 -12
  98. package/src/mixins/OxyServices.utility.ts +6 -119
  99. package/src/mixins/__tests__/OxyServices.civic.test.ts +2 -2
  100. package/src/mixins/__tests__/accounts.test.ts +70 -0
  101. package/src/server/auth.ts +0 -7
  102. package/src/server/index.ts +0 -1
  103. package/src/utils/__tests__/cacheKey.test.ts +0 -0
  104. package/src/utils/cacheKey.ts +16 -21
  105. package/src/utils/deviceManager.ts +1 -1
  106. package/src/utils/platform.ts +0 -14
  107. package/dist/cjs/crypto/canonicalJson.js +0 -107
  108. package/dist/cjs/utils/platformCrypto.js +0 -165
  109. package/dist/cjs/utils/platformCrypto.native.js +0 -123
  110. package/dist/esm/crypto/canonicalJson.js +0 -104
  111. package/dist/esm/utils/platformCrypto.js +0 -125
  112. package/dist/esm/utils/platformCrypto.native.js +0 -80
  113. package/dist/types/crypto/canonicalJson.d.ts +0 -44
  114. package/dist/types/utils/platformCrypto.d.ts +0 -87
  115. package/dist/types/utils/platformCrypto.native.d.ts +0 -54
  116. package/src/crypto/__tests__/canonicalJson.test.ts +0 -116
  117. package/src/crypto/canonicalJson.ts +0 -120
  118. package/src/utils/platformCrypto.native.ts +0 -101
  119. package/src/utils/platformCrypto.ts +0 -145
@@ -21,6 +21,7 @@ const asyncUtils_1 = require("./utils/asyncUtils");
21
21
  const errorUtils_1 = require("./utils/errorUtils");
22
22
  const jwt_decode_1 = require("jwt-decode");
23
23
  const platform_1 = require("./utils/platform");
24
+ const protocol_1 = require("@oxyhq/protocol");
24
25
  const cacheKey_1 = require("./utils/cacheKey");
25
26
  /**
26
27
  * Check if we're running in a native app environment (React Native, not web)
@@ -149,8 +150,6 @@ class HttpService {
149
150
  * Each listener receives the resulting access token, or `null` when cleared.
150
151
  */
151
152
  this._tokenChangeListeners = new Set();
152
- // Acting-as identity for managed accounts
153
- this._actingAsUserId = null;
154
153
  // Performance monitoring
155
154
  this.requestMetrics = {
156
155
  totalRequests: 0,
@@ -324,10 +323,6 @@ class HttpService {
324
323
  hasNativeAppHeader: headers['X-Native-App'] === 'true',
325
324
  });
326
325
  }
327
- // Add X-Acting-As header for managed account identity delegation
328
- if (this._actingAsUserId) {
329
- headers['X-Acting-As'] = this._actingAsUserId;
330
- }
331
326
  // Merge custom headers if provided
332
327
  if (config.headers) {
333
328
  Object.entries(config.headers).forEach(([key, value]) => {
@@ -349,7 +344,7 @@ class HttpService {
349
344
  // RN's native XMLHttpRequest handles those descriptors correctly, so we
350
345
  // route multipart uploads through XHR on RN only. JSON, text, etc. still
351
346
  // use fetch on every platform.
352
- const useXhrForUpload = isFormData && (0, platform_1.isReactNative)() && typeof XMLHttpRequest !== 'undefined';
347
+ const useXhrForUpload = isFormData && (0, protocol_1.isReactNative)() && typeof XMLHttpRequest !== 'undefined';
353
348
  const response = useXhrForUpload
354
349
  ? await this.uploadViaXHR(fullUrl, method, headers, bodyValue, controller.signal, timeout, this.shouldSendCredentials(fullUrl))
355
350
  : await fetch(fullUrl, {
@@ -633,12 +628,12 @@ class HttpService {
633
628
  * Derive a stable, non-sensitive identity discriminator for cache scoping.
634
629
  *
635
630
  * Thin instance wrapper over the pure {@link computeIdentityTag} helper —
636
- * binds it to this instance's live access token and acting-as id. See that
637
- * function's docs for the full resolution contract (anon fallback, decoded
638
- * `userId || id`, token-hash fallback for undecodable tokens).
631
+ * binds it to this instance's live access token. See that function's docs for
632
+ * the full resolution contract (anon fallback, decoded `userId || id`,
633
+ * token-hash fallback for undecodable tokens).
639
634
  */
640
635
  computeIdentityTag() {
641
- return (0, cacheKey_1.computeIdentityTag)(this.tokenStore.getAccessToken(), this._actingAsUserId);
636
+ return (0, cacheKey_1.computeIdentityTag)(this.tokenStore.getAccessToken());
642
637
  }
643
638
  /**
644
639
  * Generate cache key efficiently
@@ -885,13 +880,6 @@ class HttpService {
885
880
  async delete(url, config) {
886
881
  return this.request({ method: 'DELETE', url, ...config });
887
882
  }
888
- // Acting-as identity management (managed accounts)
889
- setActingAs(userId) {
890
- this._actingAsUserId = userId;
891
- }
892
- getActingAs() {
893
- return this._actingAsUserId;
894
- }
895
883
  // Token management
896
884
  setTokens(accessToken) {
897
885
  this.tokenStore.setTokens(accessToken);
@@ -278,27 +278,6 @@ class OxyServicesBase {
278
278
  return null;
279
279
  }
280
280
  }
281
- /**
282
- * Set the acting-as identity for managed accounts.
283
- *
284
- * When set, all subsequent API requests will include the `X-Acting-As` header,
285
- * causing the server to attribute actions to the managed account. The
286
- * authenticated user must be an authorized manager of the target account.
287
- *
288
- * Pass `null` to clear and revert to the authenticated user's own identity.
289
- *
290
- * @param userId - The managed account user ID, or null to clear
291
- */
292
- setActingAs(userId) {
293
- this.httpService.setActingAs(userId);
294
- }
295
- /**
296
- * Get the current acting-as identity (managed account user ID), or null
297
- * if operating as the authenticated user's own identity.
298
- */
299
- getActingAs() {
300
- return this.httpService.getActingAs();
301
- }
302
281
  /**
303
282
  * Wait for authentication to be ready
304
283
  *
@@ -9,7 +9,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.KeyManager = exports.IdentityPersistError = exports.IdentityAlreadyExistsError = void 0;
10
10
  const elliptic_1 = require("elliptic");
11
11
  const platform_1 = require("../utils/platform");
12
- const platformCrypto_1 = require("../utils/platformCrypto");
12
+ const protocol_1 = require("@oxyhq/protocol");
13
13
  const loggerUtils_1 = require("../utils/loggerUtils");
14
14
  const debugUtils_1 = require("../shared/utils/debugUtils");
15
15
  /**
@@ -72,15 +72,15 @@ const ANDROID_ACCOUNT_TYPE = 'com.oxy.account';
72
72
  /**
73
73
  * Initialize React Native specific modules
74
74
  *
75
- * Delegates to `platformCrypto`, which is a per-platform module
76
- * (`platformCrypto.ts` vs `platformCrypto.react-native.ts`) selected by the
75
+ * Delegates to `@oxyhq/protocol`'s `platform/crypto`, a per-platform module
76
+ * (`crypto.ts` vs `crypto.native.ts`) selected by the
77
77
  * consumer's bundler. On RN it returns a statically-imported handle to
78
78
  * `expo-secure-store`; off RN it throws (and is never called because every
79
79
  * caller is gated by `isWebPlatform()` / native-only paths).
80
80
  */
81
81
  async function initSecureStore() {
82
82
  try {
83
- return await (0, platformCrypto_1.loadSecureStore)();
83
+ return await (0, protocol_1.loadSecureStore)();
84
84
  }
85
85
  catch (error) {
86
86
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -97,7 +97,7 @@ function isWebPlatform() {
97
97
  }
98
98
  async function initExpoCrypto() {
99
99
  // Same per-platform delegation as initSecureStore — see comment there.
100
- return (0, platformCrypto_1.loadExpoCrypto)();
100
+ return (0, protocol_1.loadExpoCrypto)();
101
101
  }
102
102
  /**
103
103
  * Convert Uint8Array to hexadecimal string
@@ -113,7 +113,7 @@ function uint8ArrayToHex(bytes) {
113
113
  */
114
114
  async function getSecureRandomBytes(length) {
115
115
  // In React Native, always use expo-crypto
116
- if ((0, platform_1.isReactNative)() || !(0, platform_1.isNodeJS)()) {
116
+ if ((0, protocol_1.isReactNative)() || !(0, protocol_1.isNodeJS)()) {
117
117
  const Crypto = await initExpoCrypto();
118
118
  return Crypto.getRandomBytes(length);
119
119
  }
@@ -123,7 +123,7 @@ async function getSecureRandomBytes(length) {
123
123
  // `await import('crypto')`, the RN variant throws (and we'd never reach
124
124
  // here on RN because the early-return above caught it).
125
125
  try {
126
- const nodeCrypto = await (0, platformCrypto_1.loadNodeCrypto)();
126
+ const nodeCrypto = await (0, protocol_1.loadNodeCrypto)();
127
127
  return new Uint8Array(nodeCrypto.randomBytes(length));
128
128
  }
129
129
  catch (error) {
@@ -7,14 +7,14 @@
7
7
  *
8
8
  * - Browser/Node.js: Uses native crypto
9
9
  * - React Native: Uses expo-crypto (statically imported via the
10
- * per-platform `platformCrypto` module — see that file's doc-comment for
11
- * how platform routing works).
10
+ * per-platform `platform/crypto` module in `@oxyhq/protocol` — see that
11
+ * file's doc-comment for how platform routing works).
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.Buffer = void 0;
15
15
  const buffer_1 = require("buffer");
16
16
  Object.defineProperty(exports, "Buffer", { enumerable: true, get: function () { return buffer_1.Buffer; } });
17
- const platformCrypto_1 = require("../utils/platformCrypto");
17
+ const protocol_1 = require("@oxyhq/protocol");
18
18
  const getGlobalObject = () => {
19
19
  if (typeof globalThis !== 'undefined')
20
20
  return globalThis;
@@ -34,7 +34,8 @@ if (!globalObject.Buffer) {
34
34
  /**
35
35
  * Synchronous random-bytes shim. On RN, this delegates to
36
36
  * `expo-crypto.getRandomBytes` (statically imported by the RN variant of
37
- * `platformCrypto`, so available without any async warm-up). On Node /
37
+ * `@oxyhq/protocol`'s `platform/crypto`, so available without any async
38
+ * warm-up). On Node /
38
39
  * browser, this throws — but is never called there because both platforms
39
40
  * already provide `globalThis.crypto.getRandomValues` natively.
40
41
  */
@@ -43,7 +44,7 @@ function getRandomBytesSync(byteCount) {
43
44
  // function is only ever called as a fallback when the native
44
45
  // `globalThis.crypto.getRandomValues` is missing, which on a normal
45
46
  // Node/browser host never happens.
46
- return (0, platformCrypto_1.getRandomBytesRN)(byteCount);
47
+ return (0, protocol_1.getRandomBytesRN)(byteCount);
47
48
  }
48
49
  const cryptoPolyfill = {
49
50
  getRandomValues(array) {
@@ -1,87 +1,18 @@
1
1
  "use strict";
2
2
  /**
3
- * Signature Service - ECDSA Digital Signatures
3
+ * Signature Service - ECDSA Digital Signatures (device-key bound)
4
4
  *
5
- * Handles signing and verification of messages using ECDSA secp256k1.
6
- * Used for authenticating requests and proving identity ownership.
5
+ * Handles signing and verification of messages with the user's DEVICE identity
6
+ * key (read from {@link KeyManager} / secure storage). All cryptography itself —
7
+ * canonical signing input, SHA-256, secp256k1 sign/verify, envelope assembly —
8
+ * is delegated to `@oxyhq/protocol`; this service only resolves the key from
9
+ * storage and orchestrates the protocol primitives.
7
10
  */
8
11
  Object.defineProperty(exports, "__esModule", { value: true });
9
12
  exports.SignatureService = void 0;
10
- exports.signedRecordSigningInput = signedRecordSigningInput;
11
- exports.computeRecordId = computeRecordId;
12
- const elliptic_1 = require("elliptic");
13
+ const protocol_1 = require("@oxyhq/protocol");
13
14
  const keyManager_1 = require("./keyManager");
14
- const canonicalJson_1 = require("./canonicalJson");
15
- const platform_1 = require("../utils/platform");
16
- const platformCrypto_1 = require("../utils/platformCrypto");
17
15
  const loggerUtils_1 = require("../utils/loggerUtils");
18
- const debugUtils_1 = require("../shared/utils/debugUtils");
19
- const ec = new elliptic_1.ec('secp256k1');
20
- /**
21
- * Compute the canonical signing input for a signed-record envelope.
22
- *
23
- * This is the single definition of "what the signature covers". `@oxyhq/core`
24
- * (client signing) and `@oxyhq/api` (server verification) both call this, so a
25
- * record signed by a client and verified by the server cannot drift.
26
- *
27
- * - **v1**: the canonical JSON of `{version, type, subject, issuer, record,
28
- * issuedAt}` — BYTE-IDENTICAL to the original scheme, so every signature
29
- * already in production keeps verifying.
30
- * - **v2**: the canonical JSON additionally includes the hash-chain fields
31
- * `{seq, prev, collection, rkey}`. Because {@link canonicalize} sorts keys,
32
- * the on-the-wire field order is irrelevant; the resulting canonical key
33
- * order is `collection, issuedAt, issuer, prev, record, rkey, seq, subject,
34
- * type, version`. `prev` is `null` at genesis (serialized as `null`, not
35
- * omitted), so it is always part of the signed bytes.
36
- */
37
- function signedRecordSigningInput(fields) {
38
- const { version, type, subject, issuer, record, issuedAt } = fields;
39
- if (version === 2) {
40
- const { seq, prev, collection, rkey } = fields;
41
- return (0, canonicalJson_1.canonicalize)({ version, type, subject, issuer, record, issuedAt, seq, prev, collection, rkey });
42
- }
43
- return (0, canonicalJson_1.canonicalize)({ version, type, subject, issuer, record, issuedAt });
44
- }
45
- /**
46
- * Compute the `recordId` (content address) of a signed record: the SHA-256 hex
47
- * digest of its canonical {@link signedRecordSigningInput}.
48
- *
49
- * Deterministic and stable across runtimes (it reuses the same canonicalization
50
- * + SHA-256 the signature itself is built on). The recordId is what `prev`
51
- * references in the per-subject hash chain, so `@oxyhq/core` (client) and
52
- * `@oxyhq/api` (server) MUST compute it identically — both call this function.
53
- * It is taken over the SIGNING input (excluding `publicKey`/`signature`), so it
54
- * is a pure content address of the record's meaning, independent of who signed.
55
- */
56
- async function computeRecordId(fields) {
57
- return sha256(signedRecordSigningInput(fields));
58
- }
59
- /**
60
- * Compute SHA-256 hash of a string
61
- */
62
- async function sha256(message) {
63
- // In React Native, use expo-crypto
64
- if ((0, platform_1.isReactNative)()) {
65
- const Crypto = await (0, platformCrypto_1.loadExpoCrypto)();
66
- return Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, message);
67
- }
68
- if ((0, platform_1.isNodeJS)()) {
69
- try {
70
- const nodeCrypto = await (0, platformCrypto_1.loadNodeCrypto)();
71
- return nodeCrypto.createHash('sha256').update(message).digest('hex');
72
- }
73
- catch (error) {
74
- // Node crypto failed to load — log and fall through to Web Crypto API
75
- loggerUtils_1.logger.warn('[oxy.crypto] Node crypto unavailable, falling back to Web Crypto', { component: 'SignatureService' }, error);
76
- }
77
- }
78
- // Browser: use Web Crypto API
79
- const encoder = new TextEncoder();
80
- const data = encoder.encode(message);
81
- const hashBuffer = await globalThis.crypto.subtle.digest('SHA-256', data);
82
- const hashArray = Array.from(new Uint8Array(hashBuffer));
83
- return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
84
- }
85
16
  class SignatureService {
86
17
  /**
87
18
  * Generate a random challenge string (for offline use)
@@ -89,16 +20,16 @@ class SignatureService {
89
20
  */
90
21
  static async generateChallenge() {
91
22
  // In React Native, use expo-crypto
92
- if ((0, platform_1.isReactNative)()) {
93
- const Crypto = await (0, platformCrypto_1.loadExpoCrypto)();
23
+ if ((0, protocol_1.isReactNative)()) {
24
+ const Crypto = await (0, protocol_1.loadExpoCrypto)();
94
25
  const randomBytes = await Crypto.getRandomBytesAsync(32);
95
26
  return Array.from(new Uint8Array(randomBytes))
96
27
  .map((b) => b.toString(16).padStart(2, '0'))
97
28
  .join('');
98
29
  }
99
- if ((0, platform_1.isNodeJS)()) {
30
+ if ((0, protocol_1.isNodeJS)()) {
100
31
  try {
101
- const nodeCrypto = await (0, platformCrypto_1.loadNodeCrypto)();
32
+ const nodeCrypto = await (0, protocol_1.loadNodeCrypto)();
102
33
  return nodeCrypto.randomBytes(32).toString('hex');
103
34
  }
104
35
  catch (error) {
@@ -117,79 +48,18 @@ class SignatureService {
117
48
  * Hash a message using SHA-256
118
49
  */
119
50
  static async hashMessage(message) {
120
- return sha256(message);
51
+ return (0, protocol_1.sha256)(message);
121
52
  }
122
53
  /**
123
- * Sign a message using the stored private key
54
+ * Sign a message using the stored device private key
124
55
  * Returns the signature in DER format (hex encoded)
125
56
  */
126
57
  static async sign(message) {
127
- const keyPair = await keyManager_1.KeyManager.getKeyPairObject();
128
- if (!keyPair) {
58
+ const privateKey = await keyManager_1.KeyManager.getPrivateKey();
59
+ if (!privateKey) {
129
60
  throw new Error('No identity found. Please create or import an identity first.');
130
61
  }
131
- const messageHash = await sha256(message);
132
- const signature = keyPair.sign(messageHash);
133
- return signature.toDER('hex');
134
- }
135
- /**
136
- * Sign a message with an explicit private key (without storing)
137
- * Useful for one-time operations or testing
138
- */
139
- static async signWithKey(message, privateKey) {
140
- const keyPair = ec.keyFromPrivate(privateKey);
141
- const messageHash = await sha256(message);
142
- const signature = keyPair.sign(messageHash);
143
- return signature.toDER('hex');
144
- }
145
- /**
146
- * Verify a signature against a message and public key
147
- *
148
- * Returns false on any error (invalid signature, malformed input, etc.).
149
- * Errors are logged at debug level so they're available when troubleshooting
150
- * signature mismatches but don't surface to the caller.
151
- */
152
- static async verify(message, signature, publicKey) {
153
- try {
154
- const key = ec.keyFromPublic(publicKey, 'hex');
155
- const messageHash = await sha256(message);
156
- return key.verify(messageHash, signature);
157
- }
158
- catch (error) {
159
- if ((0, debugUtils_1.isDev)()) {
160
- loggerUtils_1.logger.debug('[oxy.crypto] verify() returned false', { component: 'SignatureService' }, error);
161
- }
162
- return false;
163
- }
164
- }
165
- /**
166
- * Synchronous verification (for Node.js backend)
167
- * Uses crypto module directly for hashing
168
- * Note: This method should only be used in Node.js environments
169
- */
170
- static verifySync(message, signature, publicKey) {
171
- try {
172
- if (!(0, platform_1.isNodeJS)()) {
173
- // In React Native, use async verify instead
174
- throw new Error('verifySync should only be used in Node.js. Use verify() in React Native.');
175
- }
176
- // Intentionally using Function constructor here: this method is synchronous by design
177
- // (Node.js backend hot-path) so we cannot use `await import()`. The Function constructor
178
- // prevents Metro/bundlers from statically resolving the require. This is acceptable because
179
- // verifySync is gated by isNodeJS() and will never execute in browser/RN environments.
180
- // eslint-disable-next-line @typescript-eslint/no-implied-eval
181
- const getCrypto = new Function('return require("crypto")');
182
- const crypto = getCrypto();
183
- const key = ec.keyFromPublic(publicKey, 'hex');
184
- const messageHash = crypto.createHash('sha256').update(message).digest('hex');
185
- return key.verify(messageHash, signature);
186
- }
187
- catch (error) {
188
- if ((0, debugUtils_1.isDev)()) {
189
- loggerUtils_1.logger.debug('[oxy.crypto] verifySync() returned false', { component: 'SignatureService' }, error);
190
- }
191
- return false;
192
- }
62
+ return (0, protocol_1.signMessage)(message, privateKey);
193
63
  }
194
64
  /**
195
65
  * Create a signed message object with metadata
@@ -223,7 +93,7 @@ class SignatureService {
223
93
  }
224
94
  // Verify signature
225
95
  const messageWithTimestamp = `${message}:${timestamp}`;
226
- return SignatureService.verify(messageWithTimestamp, signature, publicKey);
96
+ return (0, protocol_1.verifySignature)(messageWithTimestamp, signature, publicKey);
227
97
  }
228
98
  /**
229
99
  * Create a signed authentication challenge response
@@ -251,9 +121,10 @@ class SignatureService {
251
121
  * Mirrors {@link signChallenge} exactly — same message format
252
122
  * (`auth:${publicKey}:${challenge}:${timestamp}`) so the server verification
253
123
  * path is unchanged — but sources the shared public/private key from
254
- * `KeyManager` and signs with `signWithKey`. Used by "Sign in with Oxy"
255
- * same-device shared-keychain SSO (Mechanism A): a sibling native app proves
256
- * control of the shared identity to mint its own session.
124
+ * `KeyManager` and signs with the protocol's explicit-key {@link signMessage}.
125
+ * Used by "Sign in with Oxy" same-device shared-keychain SSO (Mechanism A): a
126
+ * sibling native app proves control of the shared identity to mint its own
127
+ * session.
257
128
  *
258
129
  * Throws if no shared identity exists (native-only; the shared keychain is
259
130
  * unavailable on web).
@@ -266,7 +137,7 @@ class SignatureService {
266
137
  }
267
138
  const timestamp = Date.now();
268
139
  const message = `auth:${publicKey}:${challenge}:${timestamp}`;
269
- const signature = await SignatureService.signWithKey(message, privateKey);
140
+ const signature = await (0, protocol_1.signMessage)(message, privateKey);
270
141
  return {
271
142
  challenge: signature,
272
143
  publicKey,
@@ -284,7 +155,7 @@ class SignatureService {
284
155
  return false;
285
156
  }
286
157
  const message = `auth:${publicKey}:${originalChallenge}:${timestamp}`;
287
- return SignatureService.verify(message, signature, publicKey);
158
+ return (0, protocol_1.verifySignature)(message, signature, publicKey);
288
159
  }
289
160
  /**
290
161
  * Create a registration signature
@@ -332,9 +203,11 @@ class SignatureService {
332
203
  *
333
204
  * The envelope is self-issued: `issuer` equals `subject` (the signer's DID).
334
205
  * The signature covers the canonical JSON of every field EXCEPT `publicKey`
335
- * and `signature` (see {@link signedRecordSigningInput}); `alg` is
336
- * `ES256K-DER-SHA256` (secp256k1 over the SHA-256 of the canonical bytes,
337
- * DER-encoded), the same scheme this service uses everywhere else.
206
+ * and `signature`; `alg` is `ES256K-DER-SHA256` (secp256k1 over the SHA-256 of
207
+ * the canonical bytes, DER-encoded). The cryptography is delegated to the
208
+ * protocol's {@link signEnvelope}, which derives the (uncompressed-hex)
209
+ * `publicKey` from the stored device key — identical to the registered
210
+ * verification method.
338
211
  *
339
212
  * Requires a stored identity (native secure storage); throws if none exists.
340
213
  *
@@ -343,41 +216,26 @@ class SignatureService {
343
216
  * @param record - The arbitrary record payload to attest to.
344
217
  */
345
218
  static async signRecord(type, subject, record) {
346
- const publicKey = await keyManager_1.KeyManager.getPublicKey();
347
- if (!publicKey) {
219
+ const privateKey = await keyManager_1.KeyManager.getPrivateKey();
220
+ if (!privateKey) {
348
221
  throw new Error('No identity found. Please create or import an identity first.');
349
222
  }
350
- const version = 1;
351
- const issuer = subject;
352
- const issuedAt = Date.now();
353
- const signingInput = signedRecordSigningInput({
354
- version,
355
- type,
356
- subject,
357
- issuer,
358
- record,
359
- issuedAt,
360
- });
361
- const signature = await SignatureService.sign(signingInput);
362
- return {
363
- version,
223
+ return (0, protocol_1.signEnvelope)({
224
+ version: 1,
364
225
  type,
365
226
  subject,
366
- issuer,
227
+ issuer: subject,
367
228
  record,
368
- issuedAt,
369
- publicKey,
370
- alg: 'ES256K-DER-SHA256',
371
- signature,
372
- };
229
+ issuedAt: Date.now(),
230
+ }, privateKey);
373
231
  }
374
232
  /**
375
233
  * Build a signed-record envelope (v2) carrying the per-subject hash-chain
376
234
  * fields.
377
235
  *
378
236
  * Identical to {@link signRecord} (self-issued: `issuer === subject`; same
379
- * `ES256K-DER-SHA256` scheme over {@link signedRecordSigningInput}) but
380
- * `version` is `2` and the signed bytes additionally cover the chain fields:
237
+ * `ES256K-DER-SHA256` scheme) but `version` is `2` and the signed bytes
238
+ * additionally cover the chain fields:
381
239
  *
382
240
  * @param type - The record category.
383
241
  * @param subject - The subject DID the record is about (also the issuer).
@@ -392,57 +250,23 @@ class SignatureService {
392
250
  * none exists.
393
251
  */
394
252
  static async signRecordV2(type, subject, record, chain) {
395
- const publicKey = await keyManager_1.KeyManager.getPublicKey();
396
- if (!publicKey) {
253
+ const privateKey = await keyManager_1.KeyManager.getPrivateKey();
254
+ if (!privateKey) {
397
255
  throw new Error('No identity found. Please create or import an identity first.');
398
256
  }
399
- const version = 2;
400
- const issuer = subject;
401
- const issuedAt = Date.now();
402
257
  const { seq, prev, collection, rkey } = chain;
403
- const signingInput = signedRecordSigningInput({
404
- version,
258
+ return (0, protocol_1.signEnvelope)({
259
+ version: 2,
405
260
  type,
406
261
  subject,
407
- issuer,
262
+ issuer: subject,
408
263
  record,
409
- issuedAt,
264
+ issuedAt: Date.now(),
410
265
  seq,
411
266
  prev,
412
267
  collection,
413
268
  rkey,
414
- });
415
- const signature = await SignatureService.sign(signingInput);
416
- return {
417
- version,
418
- type,
419
- subject,
420
- issuer,
421
- record,
422
- issuedAt,
423
- seq,
424
- prev,
425
- collection,
426
- rkey,
427
- publicKey,
428
- alg: 'ES256K-DER-SHA256',
429
- signature,
430
- };
431
- }
432
- /**
433
- * Verify a signed-record envelope: recompute the canonical signing input from
434
- * the envelope's own fields and check the signature against the envelope's
435
- * `publicKey`.
436
- *
437
- * Note: this confirms the signature is internally consistent with the
438
- * embedded `publicKey`. It does NOT establish that `publicKey` is an
439
- * authorized verification method for `subject` — that authorization check is
440
- * the server's responsibility (it asserts the key is a current verification
441
- * method on the subject's DID).
442
- */
443
- static async verifyRecord(envelope) {
444
- const signingInput = signedRecordSigningInput(envelope);
445
- return SignatureService.verify(signingInput, envelope.signature, envelope.publicKey);
269
+ }, privateKey);
446
270
  }
447
271
  }
448
272
  exports.SignatureService = SignatureService;