@oxyhq/core 4.0.1 → 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 (109) 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/index.js +4 -8
  8. package/dist/cjs/mixins/OxyServices.accounts.js +54 -0
  9. package/dist/cjs/mixins/OxyServices.assets.js +2 -2
  10. package/dist/cjs/mixins/OxyServices.auth.js +3 -3
  11. package/dist/cjs/mixins/OxyServices.civic.js +3 -3
  12. package/dist/cjs/mixins/OxyServices.language.js +2 -2
  13. package/dist/cjs/mixins/OxyServices.utility.js +7 -95
  14. package/dist/cjs/utils/cacheKey.js +17 -19
  15. package/dist/cjs/utils/deviceManager.js +2 -2
  16. package/dist/cjs/utils/platform.js +0 -14
  17. package/dist/esm/.tsbuildinfo +1 -1
  18. package/dist/esm/HttpService.js +6 -18
  19. package/dist/esm/OxyServices.base.js +0 -21
  20. package/dist/esm/crypto/keyManager.js +4 -4
  21. package/dist/esm/crypto/polyfill.js +5 -4
  22. package/dist/esm/crypto/signatureService.js +39 -214
  23. package/dist/esm/index.js +1 -2
  24. package/dist/esm/mixins/OxyServices.accounts.js +54 -0
  25. package/dist/esm/mixins/OxyServices.assets.js +1 -1
  26. package/dist/esm/mixins/OxyServices.auth.js +1 -1
  27. package/dist/esm/mixins/OxyServices.civic.js +3 -3
  28. package/dist/esm/mixins/OxyServices.language.js +1 -1
  29. package/dist/esm/mixins/OxyServices.utility.js +6 -94
  30. package/dist/esm/utils/cacheKey.js +17 -19
  31. package/dist/esm/utils/deviceManager.js +1 -1
  32. package/dist/esm/utils/platform.js +0 -12
  33. package/dist/types/.tsbuildinfo +1 -1
  34. package/dist/types/HttpService.d.ts +3 -6
  35. package/dist/types/OxyServices.base.d.ts +0 -17
  36. package/dist/types/crypto/polyfill.d.ts +2 -2
  37. package/dist/types/crypto/signatureService.d.ts +18 -84
  38. package/dist/types/index.d.ts +3 -4
  39. package/dist/types/mixins/OxyServices.accounts.d.ts +57 -5
  40. package/dist/types/mixins/OxyServices.analytics.d.ts +0 -2
  41. package/dist/types/mixins/OxyServices.appData.d.ts +0 -2
  42. package/dist/types/mixins/OxyServices.assets.d.ts +0 -2
  43. package/dist/types/mixins/OxyServices.auth.d.ts +0 -2
  44. package/dist/types/mixins/OxyServices.civic.d.ts +3 -5
  45. package/dist/types/mixins/OxyServices.connectedApps.d.ts +0 -2
  46. package/dist/types/mixins/OxyServices.contacts.d.ts +0 -2
  47. package/dist/types/mixins/OxyServices.devices.d.ts +0 -2
  48. package/dist/types/mixins/OxyServices.features.d.ts +0 -2
  49. package/dist/types/mixins/OxyServices.fedcm.d.ts +0 -2
  50. package/dist/types/mixins/OxyServices.identity.d.ts +8 -5
  51. package/dist/types/mixins/OxyServices.language.d.ts +0 -2
  52. package/dist/types/mixins/OxyServices.links.d.ts +0 -2
  53. package/dist/types/mixins/OxyServices.location.d.ts +0 -2
  54. package/dist/types/mixins/OxyServices.nodes.d.ts +0 -44
  55. package/dist/types/mixins/OxyServices.payment.d.ts +0 -2
  56. package/dist/types/mixins/OxyServices.privacy.d.ts +0 -2
  57. package/dist/types/mixins/OxyServices.redirect.d.ts +0 -2
  58. package/dist/types/mixins/OxyServices.reputation.d.ts +0 -2
  59. package/dist/types/mixins/OxyServices.security.d.ts +0 -2
  60. package/dist/types/mixins/OxyServices.silent.d.ts +0 -2
  61. package/dist/types/mixins/OxyServices.sso.d.ts +0 -2
  62. package/dist/types/mixins/OxyServices.topics.d.ts +0 -2
  63. package/dist/types/mixins/OxyServices.user.d.ts +0 -2
  64. package/dist/types/mixins/OxyServices.utility.d.ts +0 -32
  65. package/dist/types/server/auth.d.ts +0 -6
  66. package/dist/types/server/index.d.ts +1 -1
  67. package/dist/types/utils/cacheKey.d.ts +6 -7
  68. package/dist/types/utils/platform.d.ts +0 -8
  69. package/package.json +4 -7
  70. package/src/HttpService.ts +6 -22
  71. package/src/OxyServices.base.ts +0 -23
  72. package/src/__tests__/httpServiceCache.test.ts +0 -19
  73. package/src/crypto/__tests__/keyManager.atomicity.test.ts +2 -1
  74. package/src/crypto/__tests__/keyManager.test.ts +9 -7
  75. package/src/crypto/__tests__/signChallengeShared.test.ts +2 -1
  76. package/src/crypto/__tests__/signedRecord.test.ts +37 -150
  77. package/src/crypto/keyManager.ts +28 -17
  78. package/src/crypto/polyfill.ts +5 -4
  79. package/src/crypto/signatureService.ts +67 -255
  80. package/src/index.ts +3 -3
  81. package/src/mixins/OxyServices.accounts.ts +91 -3
  82. package/src/mixins/OxyServices.assets.ts +1 -1
  83. package/src/mixins/OxyServices.auth.ts +1 -1
  84. package/src/mixins/OxyServices.civic.ts +6 -17
  85. package/src/mixins/OxyServices.identity.ts +8 -2
  86. package/src/mixins/OxyServices.language.ts +1 -1
  87. package/src/mixins/OxyServices.nodes.ts +1 -12
  88. package/src/mixins/OxyServices.utility.ts +6 -119
  89. package/src/mixins/__tests__/OxyServices.civic.test.ts +2 -2
  90. package/src/mixins/__tests__/accounts.test.ts +70 -0
  91. package/src/server/auth.ts +0 -7
  92. package/src/server/index.ts +0 -1
  93. package/src/utils/__tests__/cacheKey.test.ts +0 -0
  94. package/src/utils/cacheKey.ts +16 -21
  95. package/src/utils/deviceManager.ts +1 -1
  96. package/src/utils/platform.ts +0 -14
  97. package/dist/cjs/crypto/canonicalJson.js +0 -107
  98. package/dist/cjs/utils/platformCrypto.js +0 -165
  99. package/dist/cjs/utils/platformCrypto.native.js +0 -123
  100. package/dist/esm/crypto/canonicalJson.js +0 -104
  101. package/dist/esm/utils/platformCrypto.js +0 -125
  102. package/dist/esm/utils/platformCrypto.native.js +0 -80
  103. package/dist/types/crypto/canonicalJson.d.ts +0 -44
  104. package/dist/types/utils/platformCrypto.d.ts +0 -87
  105. package/dist/types/utils/platformCrypto.native.d.ts +0 -54
  106. package/src/crypto/__tests__/canonicalJson.test.ts +0 -116
  107. package/src/crypto/canonicalJson.ts +0 -120
  108. package/src/utils/platformCrypto.native.ts +0 -101
  109. package/src/utils/platformCrypto.ts +0 -145
@@ -1,28 +1,37 @@
1
1
  /**
2
- * Signed-record envelope tests.
2
+ * Signed-record envelope tests — the DEVICE-key-bound path in `@oxyhq/core`.
3
3
  *
4
- * Exercises the full client-side path: `SignatureService.signRecord` builds an
5
- * envelope whose signature covers the canonical JSON of every field EXCEPT
6
- * `publicKey`/`signature`, and `SignatureService.verifyRecord` round-trips it.
7
- * We mock `KeyManager`'s key access with a REAL elliptic secp256k1 keypair, so
8
- * the signing/verification is genuine cryptography (not a stub).
4
+ * `SignatureService.signRecord` / `signRecordV2` read the device key from
5
+ * `KeyManager` and delegate the cryptography to `@oxyhq/protocol`'s
6
+ * `signEnvelope`. These tests prove the orchestration: an envelope built from a
7
+ * stored key round-trips through the protocol's `verifyEnvelopeSignature`, and
8
+ * tampering breaks it. The pure canonical-bytes / `computeRecordId` guards live
9
+ * in `@oxyhq/protocol`'s own suite.
10
+ *
11
+ * We mock `KeyManager.getPrivateKey` with a REAL elliptic secp256k1 private key,
12
+ * so the signing/verification is genuine cryptography (not a stub).
9
13
  */
10
14
 
11
15
  import { ec as EC } from 'elliptic';
12
16
  import type { SignedRecordEnvelope } from '@oxyhq/contracts';
17
+ import {
18
+ canonicalize,
19
+ signedRecordSigningInput,
20
+ verifySignature,
21
+ verifyEnvelopeSignature,
22
+ } from '@oxyhq/protocol';
13
23
  import { KeyManager } from '../keyManager';
14
- import { SignatureService, signedRecordSigningInput, computeRecordId } from '../signatureService';
15
- import { canonicalize } from '../canonicalJson';
24
+ import { SignatureService } from '../signatureService';
16
25
 
17
26
  const ec = new EC('secp256k1');
18
27
 
19
- describe('SignatureService.signRecord / verifyRecord', () => {
28
+ describe('SignatureService.signRecord / verifyEnvelopeSignature', () => {
20
29
  const keyPair = ec.genKeyPair();
21
30
  const publicKey = keyPair.getPublic('hex');
31
+ const privateKey = keyPair.getPrivate('hex').padStart(64, '0');
22
32
 
23
33
  beforeEach(() => {
24
- jest.spyOn(KeyManager, 'getPublicKey').mockResolvedValue(publicKey);
25
- jest.spyOn(KeyManager, 'getKeyPairObject').mockResolvedValue(keyPair);
34
+ jest.spyOn(KeyManager, 'getPrivateKey').mockResolvedValue(privateKey);
26
35
  });
27
36
 
28
37
  afterEach(() => {
@@ -40,7 +49,7 @@ describe('SignatureService.signRecord / verifyRecord', () => {
40
49
  expect(envelope.subject).toBe(subject);
41
50
  expect(envelope.issuer).toBe(subject); // self-issued
42
51
  expect(envelope.record).toEqual(record);
43
- expect(envelope.publicKey).toBe(publicKey);
52
+ expect(envelope.publicKey).toBe(publicKey); // derived from the stored private key
44
53
  expect(envelope.alg).toBe('ES256K-DER-SHA256');
45
54
  expect(typeof envelope.signature).toBe('string');
46
55
  expect(envelope.signature.length).toBeGreaterThan(0);
@@ -52,7 +61,7 @@ describe('SignatureService.signRecord / verifyRecord', () => {
52
61
  handle: '@nate',
53
62
  nested: { a: 1, b: [2, 3] },
54
63
  });
55
- await expect(SignatureService.verifyRecord(envelope)).resolves.toBe(true);
64
+ await expect(verifyEnvelopeSignature(envelope)).resolves.toBe(true);
56
65
  });
57
66
 
58
67
  it('signs the canonical JSON of every field except publicKey + signature', async () => {
@@ -77,7 +86,7 @@ describe('SignatureService.signRecord / verifyRecord', () => {
77
86
 
78
87
  // The signature verifies against that exact input + the embedded key.
79
88
  await expect(
80
- SignatureService.verify(expectedInput, envelope.signature, envelope.publicKey),
89
+ verifySignature(expectedInput, envelope.signature, envelope.publicKey),
81
90
  ).resolves.toBe(true);
82
91
  });
83
92
 
@@ -96,86 +105,41 @@ describe('SignatureService.signRecord / verifyRecord', () => {
96
105
  ...envelope,
97
106
  record: { ...envelope.record, displayName: 'Tampered' },
98
107
  };
99
- await expect(SignatureService.verifyRecord(tampered)).resolves.toBe(false);
108
+ await expect(verifyEnvelopeSignature(tampered)).resolves.toBe(false);
100
109
  });
101
110
 
102
111
  it('rejects a changed subject', async () => {
103
112
  const tampered: SignedRecordEnvelope = { ...envelope, subject: 'did:web:oxy.so:u:evil' };
104
- await expect(SignatureService.verifyRecord(tampered)).resolves.toBe(false);
113
+ await expect(verifyEnvelopeSignature(tampered)).resolves.toBe(false);
105
114
  });
106
115
 
107
116
  it('rejects a changed issuedAt', async () => {
108
117
  const tampered: SignedRecordEnvelope = { ...envelope, issuedAt: envelope.issuedAt + 1 };
109
- await expect(SignatureService.verifyRecord(tampered)).resolves.toBe(false);
118
+ await expect(verifyEnvelopeSignature(tampered)).resolves.toBe(false);
110
119
  });
111
120
 
112
121
  it('rejects verification against an unrelated public key', async () => {
113
122
  const otherKey = ec.genKeyPair().getPublic('hex');
114
123
  const tampered: SignedRecordEnvelope = { ...envelope, publicKey: otherKey };
115
- await expect(SignatureService.verifyRecord(tampered)).resolves.toBe(false);
124
+ await expect(verifyEnvelopeSignature(tampered)).resolves.toBe(false);
116
125
  });
117
126
  });
118
127
 
119
128
  it('throws when no identity is stored', async () => {
120
- jest.spyOn(KeyManager, 'getPublicKey').mockResolvedValue(null);
129
+ jest.spyOn(KeyManager, 'getPrivateKey').mockResolvedValue(null);
121
130
  await expect(
122
131
  SignatureService.signRecord('identity', 'did:web:oxy.so:u:u4', {}),
123
132
  ).rejects.toThrow(/No identity found/);
124
133
  });
125
134
  });
126
135
 
127
- /**
128
- * Critical regression guard: the v1 signing input MUST be byte-identical to the
129
- * original scheme. Every `identity`/`profile` record already in production was
130
- * signed over exactly these bytes, so any change to {@link signedRecordSigningInput}
131
- * for v1 would invalidate them all. This locks the exact canonical string.
132
- */
133
- describe('signedRecordSigningInput — v1 byte stability (regression guard)', () => {
134
- it('produces the exact original canonical bytes for a v1 record', () => {
135
- const input = signedRecordSigningInput({
136
- version: 1,
137
- type: 'identity',
138
- subject: 'did:web:oxy.so:u:u1',
139
- issuer: 'did:web:oxy.so:u:u1',
140
- record: { handle: '@nate' },
141
- issuedAt: 1750000000000,
142
- });
143
- expect(input).toBe(
144
- '{"issuedAt":1750000000000,"issuer":"did:web:oxy.so:u:u1","record":{"handle":"@nate"},"subject":"did:web:oxy.so:u:u1","type":"identity","version":1}',
145
- );
146
- });
147
-
148
- it('does NOT include any v2 chain fields for a v1 record even if they are passed', () => {
149
- const input = signedRecordSigningInput({
150
- version: 1,
151
- type: 'identity',
152
- subject: 'did:web:oxy.so:u:u1',
153
- issuer: 'did:web:oxy.so:u:u1',
154
- record: { handle: '@nate' },
155
- issuedAt: 1750000000000,
156
- // These must be ignored by the v1 branch — proving v1 bytes are immutable.
157
- seq: 5,
158
- prev: 'deadbeef',
159
- collection: 'app.oxy.identity',
160
- rkey: 'self',
161
- });
162
- expect(input).not.toContain('seq');
163
- expect(input).not.toContain('prev');
164
- expect(input).not.toContain('collection');
165
- expect(input).not.toContain('rkey');
166
- expect(input).toBe(
167
- '{"issuedAt":1750000000000,"issuer":"did:web:oxy.so:u:u1","record":{"handle":"@nate"},"subject":"did:web:oxy.so:u:u1","type":"identity","version":1}',
168
- );
169
- });
170
- });
171
-
172
- describe('SignatureService.signRecordV2 / verifyRecord (v2 hash chain)', () => {
136
+ describe('SignatureService.signRecordV2 / verifyEnvelopeSignature (v2 hash chain)', () => {
173
137
  const keyPair = ec.genKeyPair();
174
138
  const publicKey = keyPair.getPublic('hex');
139
+ const privateKey = keyPair.getPrivate('hex').padStart(64, '0');
175
140
 
176
141
  beforeEach(() => {
177
- jest.spyOn(KeyManager, 'getPublicKey').mockResolvedValue(publicKey);
178
- jest.spyOn(KeyManager, 'getKeyPairObject').mockResolvedValue(keyPair);
142
+ jest.spyOn(KeyManager, 'getPrivateKey').mockResolvedValue(privateKey);
179
143
  });
180
144
 
181
145
  afterEach(() => {
@@ -218,7 +182,7 @@ describe('SignatureService.signRecordV2 / verifyRecord (v2 hash chain)', () => {
218
182
  { verdict: 'approve' },
219
183
  { seq: 3, prev: 'a'.repeat(64), collection: 'app.oxy.validation', rkey: 'req_9' },
220
184
  );
221
- await expect(SignatureService.verifyRecord(envelope)).resolves.toBe(true);
185
+ await expect(verifyEnvelopeSignature(envelope)).resolves.toBe(true);
222
186
  });
223
187
 
224
188
  it('covers the chain fields in the signed bytes (tampering breaks verify)', async () => {
@@ -229,18 +193,14 @@ describe('SignatureService.signRecordV2 / verifyRecord (v2 hash chain)', () => {
229
193
  chain,
230
194
  );
231
195
 
196
+ await expect(verifyEnvelopeSignature({ ...envelope, seq: 1 })).resolves.toBe(false);
232
197
  await expect(
233
- SignatureService.verifyRecord({ ...envelope, seq: 1 }),
234
- ).resolves.toBe(false);
235
- await expect(
236
- SignatureService.verifyRecord({ ...envelope, prev: 'b'.repeat(64) }),
237
- ).resolves.toBe(false);
238
- await expect(
239
- SignatureService.verifyRecord({ ...envelope, collection: 'app.evil' }),
198
+ verifyEnvelopeSignature({ ...envelope, prev: 'b'.repeat(64) }),
240
199
  ).resolves.toBe(false);
241
200
  await expect(
242
- SignatureService.verifyRecord({ ...envelope, rkey: 'other' }),
201
+ verifyEnvelopeSignature({ ...envelope, collection: 'app.evil' }),
243
202
  ).resolves.toBe(false);
203
+ await expect(verifyEnvelopeSignature({ ...envelope, rkey: 'other' })).resolves.toBe(false);
244
204
  });
245
205
 
246
206
  it('a v2 envelope signs over DIFFERENT bytes than the same base fields as v1', async () => {
@@ -264,82 +224,9 @@ describe('SignatureService.signRecordV2 / verifyRecord (v2 hash chain)', () => {
264
224
  });
265
225
 
266
226
  it('throws when no identity is stored', async () => {
267
- jest.spyOn(KeyManager, 'getPublicKey').mockResolvedValue(null);
227
+ jest.spyOn(KeyManager, 'getPrivateKey').mockResolvedValue(null);
268
228
  await expect(
269
229
  SignatureService.signRecordV2('identity', 'did:web:oxy.so:u:u4', {}, chain),
270
230
  ).rejects.toThrow(/No identity found/);
271
231
  });
272
232
  });
273
-
274
- describe('computeRecordId — deterministic content address', () => {
275
- const base: SignedRecordEnvelope = {
276
- version: 2,
277
- type: 'reputation_attestation',
278
- subject: 'did:web:oxy.so:u:rid',
279
- issuer: 'did:web:oxy.so',
280
- record: { points: 25 },
281
- issuedAt: 1750000000000,
282
- seq: 0,
283
- prev: null,
284
- collection: 'app.oxy.reputation',
285
- rkey: 'rt_1',
286
- publicKey: '03oxykey',
287
- alg: 'ES256K-DER-SHA256',
288
- signature: 'sig-is-not-part-of-the-id',
289
- };
290
-
291
- it('is a 64-char lowercase hex SHA-256 digest', async () => {
292
- const id = await computeRecordId(base);
293
- expect(id).toMatch(/^[0-9a-f]{64}$/);
294
- });
295
-
296
- it('is deterministic for identical signing fields', async () => {
297
- const a = await computeRecordId(base);
298
- const b = await computeRecordId(base);
299
- expect(a).toBe(b);
300
- });
301
-
302
- it('equals sha256 of the canonical signing input (content address, excludes publicKey/signature)', async () => {
303
- const id = await computeRecordId(base);
304
- const expected = await SignatureService.hashMessage(signedRecordSigningInput(base));
305
- expect(id).toBe(expected);
306
- // Changing only signature/publicKey does NOT change the recordId.
307
- const sameId = await computeRecordId({
308
- ...base,
309
- signature: 'totally-different',
310
- publicKey: 'different-key',
311
- });
312
- expect(sameId).toBe(id);
313
- });
314
-
315
- it('changes when any signed field changes', async () => {
316
- const id = await computeRecordId(base);
317
- expect(await computeRecordId({ ...base, seq: 1 })).not.toBe(id);
318
- expect(await computeRecordId({ ...base, record: { points: 26 } })).not.toBe(id);
319
- expect(await computeRecordId({ ...base, prev: 'a'.repeat(64) })).not.toBe(id);
320
- });
321
-
322
- it('a v1 record and a v2 record with the same base fields have different recordIds', async () => {
323
- const v1Id = await computeRecordId({
324
- version: 1,
325
- type: 'profile',
326
- subject: 'did:web:oxy.so:u:rid',
327
- issuer: 'did:web:oxy.so:u:rid',
328
- record: { points: 25 },
329
- issuedAt: 1750000000000,
330
- });
331
- const v2Id = await computeRecordId({
332
- version: 2,
333
- type: 'profile',
334
- subject: 'did:web:oxy.so:u:rid',
335
- issuer: 'did:web:oxy.so:u:rid',
336
- record: { points: 25 },
337
- issuedAt: 1750000000000,
338
- seq: 0,
339
- prev: null,
340
- collection: 'app.oxy.profile',
341
- rkey: 'self',
342
- });
343
- expect(v1Id).not.toBe(v2Id);
344
- });
345
- });
@@ -7,27 +7,38 @@
7
7
 
8
8
  import { ec as EC } from 'elliptic';
9
9
  import type { ECKeyPair } from 'elliptic';
10
- import type { SecureStoreOptions } from 'expo-secure-store';
11
- import { isWeb, isIOS, isAndroid, isReactNative, isNodeJS } from '../utils/platform';
12
- import { loadExpoCrypto, loadNodeCrypto, loadSecureStore } from '../utils/platformCrypto';
10
+ import { isWeb, isIOS, isAndroid } from '../utils/platform';
11
+ import { type ExpoCryptoLike, type ExpoSecureStoreLike, isReactNative, isNodeJS, loadExpoCrypto, loadNodeCrypto, loadSecureStore } from '@oxyhq/protocol';
13
12
  import { logger } from '../utils/loggerUtils';
14
13
  import { isDev } from '../shared/utils/debugUtils';
15
14
 
16
15
  /**
17
- * Extended SecureStoreOptions that explicitly includes `keychainAccessGroup`.
16
+ * Options for expo-secure-store calls made by KeyManager.
18
17
  *
19
- * The shipped `expo-secure-store` types in this repo (see
20
- * `src/types/expo-secure-store.d.ts`) already declare `keychainAccessGroup`,
21
- * but we redeclare it here as an `interface extends ...` so the field stays
22
- * type-safe even when the upstream package types drift. Older versions of
23
- * `@types/expo-secure-store` omitted this field, which is why the code base
24
- * used to fall back to `as any` that escape hatch is now removed.
18
+ * Defined as a standalone interface (not extending expo-secure-store's
19
+ * `SecureStoreOptions`) so that server / Node.js consumers of @oxyhq/core
20
+ * do not transitively pull in expo-modules-core's type declarations under
21
+ * NodeNext module resolution (which would cause NodeJS.Timeout / number
22
+ * pollution across all timer APIs). The fields mirror SecureStoreOptions
23
+ * exactly any object satisfying this interface is safe to pass to the
24
+ * real expo-secure-store methods (which accept `options?: object`).
25
25
  */
26
- interface OxySecureStoreOptions extends SecureStoreOptions {
26
+ interface OxySecureStoreOptions {
27
+ /** Keychain service name (Android keystore / iOS keychain tag). */
28
+ keychainService?: string;
29
+ /**
30
+ * Keychain / Keystore item accessibility. Use the constants from the
31
+ * `ExpoSecureStoreLike` handle returned by `initSecureStore()`, e.g.
32
+ * `store.WHEN_UNLOCKED_THIS_DEVICE_ONLY`.
33
+ */
34
+ keychainAccessible?: number;
35
+ /** Prompt shown to the user for biometric/auth-protected items. */
36
+ authenticationPrompt?: string;
37
+ /** Whether Face ID / Touch ID / biometric auth is required to read the item. */
38
+ requireAuthentication?: boolean;
27
39
  /**
28
40
  * iOS Keychain access group. Required for sharing identity material across
29
- * apps in the Oxy ecosystem via Keychain Sharing entitlements. The
30
- * underlying `expo-secure-store` runtime supports this option.
41
+ * apps in the Oxy ecosystem via Keychain Sharing entitlements.
31
42
  */
32
43
  keychainAccessGroup?: string;
33
44
  }
@@ -98,13 +109,13 @@ const ANDROID_ACCOUNT_TYPE = 'com.oxy.account';
98
109
  /**
99
110
  * Initialize React Native specific modules
100
111
  *
101
- * Delegates to `platformCrypto`, which is a per-platform module
102
- * (`platformCrypto.ts` vs `platformCrypto.react-native.ts`) selected by the
112
+ * Delegates to `@oxyhq/protocol`'s `platform/crypto`, a per-platform module
113
+ * (`crypto.ts` vs `crypto.native.ts`) selected by the
103
114
  * consumer's bundler. On RN it returns a statically-imported handle to
104
115
  * `expo-secure-store`; off RN it throws (and is never called because every
105
116
  * caller is gated by `isWebPlatform()` / native-only paths).
106
117
  */
107
- async function initSecureStore(): Promise<typeof import('expo-secure-store')> {
118
+ async function initSecureStore(): Promise<ExpoSecureStoreLike> {
108
119
  try {
109
120
  return await loadSecureStore();
110
121
  } catch (error) {
@@ -124,7 +135,7 @@ function isWebPlatform(): boolean {
124
135
  return isWeb();
125
136
  }
126
137
 
127
- async function initExpoCrypto(): Promise<typeof import('expo-crypto')> {
138
+ async function initExpoCrypto(): Promise<ExpoCryptoLike> {
128
139
  // Same per-platform delegation as initSecureStore — see comment there.
129
140
  return loadExpoCrypto();
130
141
  }
@@ -6,12 +6,12 @@
6
6
  *
7
7
  * - Browser/Node.js: Uses native crypto
8
8
  * - React Native: Uses expo-crypto (statically imported via the
9
- * per-platform `platformCrypto` module — see that file's doc-comment for
10
- * how platform routing works).
9
+ * per-platform `platform/crypto` module in `@oxyhq/protocol` — see that
10
+ * file's doc-comment for how platform routing works).
11
11
  */
12
12
 
13
13
  import { Buffer } from 'buffer';
14
- import { getRandomBytesRN } from '../utils/platformCrypto';
14
+ import { getRandomBytesRN } from '@oxyhq/protocol';
15
15
 
16
16
  const getGlobalObject = (): typeof globalThis => {
17
17
  if (typeof globalThis !== 'undefined') return globalThis;
@@ -35,7 +35,8 @@ type CryptoLike = {
35
35
  /**
36
36
  * Synchronous random-bytes shim. On RN, this delegates to
37
37
  * `expo-crypto.getRandomBytes` (statically imported by the RN variant of
38
- * `platformCrypto`, so available without any async warm-up). On Node /
38
+ * `@oxyhq/protocol`'s `platform/crypto`, so available without any async
39
+ * warm-up). On Node /
39
40
  * browser, this throws — but is never called there because both platforms
40
41
  * already provide `globalThis.crypto.getRandomValues` natively.
41
42
  */