@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.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/HttpService.js +6 -18
- package/dist/cjs/OxyServices.base.js +0 -21
- package/dist/cjs/crypto/keyManager.js +7 -7
- package/dist/cjs/crypto/polyfill.js +6 -5
- package/dist/cjs/crypto/signatureService.js +44 -220
- package/dist/cjs/i18n/locales/en-US.json +391 -17
- package/dist/cjs/i18n/locales/es-ES.json +391 -17
- package/dist/cjs/i18n/locales/locales/en-US.json +391 -17
- package/dist/cjs/i18n/locales/locales/es-ES.json +391 -17
- package/dist/cjs/index.js +4 -8
- package/dist/cjs/mixins/OxyServices.accounts.js +54 -0
- package/dist/cjs/mixins/OxyServices.assets.js +2 -2
- package/dist/cjs/mixins/OxyServices.auth.js +3 -3
- package/dist/cjs/mixins/OxyServices.civic.js +3 -3
- package/dist/cjs/mixins/OxyServices.language.js +2 -2
- package/dist/cjs/mixins/OxyServices.utility.js +7 -95
- package/dist/cjs/utils/cacheKey.js +17 -19
- package/dist/cjs/utils/deviceManager.js +2 -2
- package/dist/cjs/utils/platform.js +0 -14
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +6 -18
- package/dist/esm/OxyServices.base.js +0 -21
- package/dist/esm/crypto/keyManager.js +4 -4
- package/dist/esm/crypto/polyfill.js +5 -4
- package/dist/esm/crypto/signatureService.js +39 -214
- package/dist/esm/i18n/locales/en-US.json +391 -17
- package/dist/esm/i18n/locales/es-ES.json +391 -17
- package/dist/esm/i18n/locales/locales/en-US.json +391 -17
- package/dist/esm/i18n/locales/locales/es-ES.json +391 -17
- package/dist/esm/index.js +1 -2
- package/dist/esm/mixins/OxyServices.accounts.js +54 -0
- package/dist/esm/mixins/OxyServices.assets.js +1 -1
- package/dist/esm/mixins/OxyServices.auth.js +1 -1
- package/dist/esm/mixins/OxyServices.civic.js +3 -3
- package/dist/esm/mixins/OxyServices.language.js +1 -1
- package/dist/esm/mixins/OxyServices.utility.js +6 -94
- package/dist/esm/utils/cacheKey.js +17 -19
- package/dist/esm/utils/deviceManager.js +1 -1
- package/dist/esm/utils/platform.js +0 -12
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +3 -6
- package/dist/types/OxyServices.base.d.ts +0 -17
- package/dist/types/crypto/polyfill.d.ts +2 -2
- package/dist/types/crypto/signatureService.d.ts +18 -84
- package/dist/types/index.d.ts +3 -4
- package/dist/types/mixins/OxyServices.accounts.d.ts +57 -5
- package/dist/types/mixins/OxyServices.analytics.d.ts +0 -2
- package/dist/types/mixins/OxyServices.appData.d.ts +0 -2
- package/dist/types/mixins/OxyServices.assets.d.ts +0 -2
- package/dist/types/mixins/OxyServices.auth.d.ts +0 -2
- package/dist/types/mixins/OxyServices.civic.d.ts +3 -5
- package/dist/types/mixins/OxyServices.connectedApps.d.ts +0 -2
- package/dist/types/mixins/OxyServices.contacts.d.ts +0 -2
- package/dist/types/mixins/OxyServices.devices.d.ts +0 -2
- package/dist/types/mixins/OxyServices.features.d.ts +0 -2
- package/dist/types/mixins/OxyServices.fedcm.d.ts +0 -2
- package/dist/types/mixins/OxyServices.identity.d.ts +8 -5
- package/dist/types/mixins/OxyServices.language.d.ts +0 -2
- package/dist/types/mixins/OxyServices.links.d.ts +0 -2
- package/dist/types/mixins/OxyServices.location.d.ts +0 -2
- package/dist/types/mixins/OxyServices.nodes.d.ts +0 -44
- package/dist/types/mixins/OxyServices.payment.d.ts +0 -2
- package/dist/types/mixins/OxyServices.privacy.d.ts +0 -2
- package/dist/types/mixins/OxyServices.redirect.d.ts +0 -2
- package/dist/types/mixins/OxyServices.reputation.d.ts +0 -2
- package/dist/types/mixins/OxyServices.security.d.ts +0 -2
- package/dist/types/mixins/OxyServices.silent.d.ts +0 -2
- package/dist/types/mixins/OxyServices.sso.d.ts +0 -2
- package/dist/types/mixins/OxyServices.topics.d.ts +0 -2
- package/dist/types/mixins/OxyServices.user.d.ts +0 -2
- package/dist/types/mixins/OxyServices.utility.d.ts +0 -32
- package/dist/types/server/auth.d.ts +0 -6
- package/dist/types/server/index.d.ts +1 -1
- package/dist/types/utils/cacheKey.d.ts +6 -7
- package/dist/types/utils/platform.d.ts +0 -8
- package/package.json +4 -7
- package/src/HttpService.ts +6 -22
- package/src/OxyServices.base.ts +0 -23
- package/src/__tests__/httpServiceCache.test.ts +0 -19
- package/src/crypto/__tests__/keyManager.atomicity.test.ts +2 -1
- package/src/crypto/__tests__/keyManager.test.ts +9 -7
- package/src/crypto/__tests__/signChallengeShared.test.ts +2 -1
- package/src/crypto/__tests__/signedRecord.test.ts +37 -150
- package/src/crypto/keyManager.ts +28 -17
- package/src/crypto/polyfill.ts +5 -4
- package/src/crypto/signatureService.ts +67 -255
- package/src/i18n/locales/en-US.json +391 -17
- package/src/i18n/locales/es-ES.json +391 -17
- package/src/index.ts +3 -3
- package/src/mixins/OxyServices.accounts.ts +91 -3
- package/src/mixins/OxyServices.assets.ts +1 -1
- package/src/mixins/OxyServices.auth.ts +1 -1
- package/src/mixins/OxyServices.civic.ts +6 -17
- package/src/mixins/OxyServices.identity.ts +8 -2
- package/src/mixins/OxyServices.language.ts +1 -1
- package/src/mixins/OxyServices.nodes.ts +1 -12
- package/src/mixins/OxyServices.utility.ts +6 -119
- package/src/mixins/__tests__/OxyServices.civic.test.ts +2 -2
- package/src/mixins/__tests__/accounts.test.ts +70 -0
- package/src/server/auth.ts +0 -7
- package/src/server/index.ts +0 -1
- package/src/utils/__tests__/cacheKey.test.ts +0 -0
- package/src/utils/cacheKey.ts +16 -21
- package/src/utils/deviceManager.ts +1 -1
- package/src/utils/platform.ts +0 -14
- package/dist/cjs/crypto/canonicalJson.js +0 -107
- package/dist/cjs/utils/platformCrypto.js +0 -165
- package/dist/cjs/utils/platformCrypto.native.js +0 -123
- package/dist/esm/crypto/canonicalJson.js +0 -104
- package/dist/esm/utils/platformCrypto.js +0 -125
- package/dist/esm/utils/platformCrypto.native.js +0 -80
- package/dist/types/crypto/canonicalJson.d.ts +0 -44
- package/dist/types/utils/platformCrypto.d.ts +0 -87
- package/dist/types/utils/platformCrypto.native.d.ts +0 -54
- package/src/crypto/__tests__/canonicalJson.test.ts +0 -116
- package/src/crypto/canonicalJson.ts +0 -120
- package/src/utils/platformCrypto.native.ts +0 -101
- 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
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
|
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 /
|
|
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, '
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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, '
|
|
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, '
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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, '
|
|
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
|
-
});
|
package/src/crypto/keyManager.ts
CHANGED
|
@@ -7,27 +7,38 @@
|
|
|
7
7
|
|
|
8
8
|
import { ec as EC } from 'elliptic';
|
|
9
9
|
import type { ECKeyPair } from 'elliptic';
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
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
|
-
*
|
|
16
|
+
* Options for expo-secure-store calls made by KeyManager.
|
|
18
17
|
*
|
|
19
|
-
*
|
|
20
|
-
* `
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
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
|
|
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.
|
|
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 `
|
|
102
|
-
* (`
|
|
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<
|
|
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<
|
|
138
|
+
async function initExpoCrypto(): Promise<ExpoCryptoLike> {
|
|
128
139
|
// Same per-platform delegation as initSecureStore — see comment there.
|
|
129
140
|
return loadExpoCrypto();
|
|
130
141
|
}
|
package/src/crypto/polyfill.ts
CHANGED
|
@@ -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 `
|
|
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 '
|
|
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
|
-
* `
|
|
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
|
*/
|