@oxy.so/contracts 1.1.1 → 1.2.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.
@@ -0,0 +1,522 @@
1
+ /**
2
+ * Web identity carrier contract — "one identity, two carriers".
3
+ *
4
+ * SINGLE SOURCE OF TRUTH for the sealed envelope that lets a browser carry an
5
+ * account's self-custody identity without Oxy ever holding it
6
+ * (`docs/superpowers/specs/2026-09-15-one-identity-two-carriers-design.md`).
7
+ *
8
+ * The identity is a BIP-39 mnemonic whose seed's first 32 bytes are the
9
+ * secp256k1 key — exactly the Commons derivation — so a web identity and a
10
+ * Commons identity are the same thing. On the web it travels as:
11
+ *
12
+ * entropy (16 bytes) ── XChaCha20-Poly1305 under a random DEK ──▶ sealedEntropy
13
+ * DEK ── XChaCha20-Poly1305 under KEK_i ──▶ wraps[i]
14
+ * KEK_i = HKDF(PRF output of passkey i)
15
+ *
16
+ * The server stores the envelope and can open NONE of it: the PRF output never
17
+ * leaves the user's authenticator, and the mnemonic is never uploaded. The AEAD
18
+ * associated data binds every ciphertext to the identity's public key (and each
19
+ * wrap to its credential), so a re-labelled or transplanted envelope fails to
20
+ * open instead of decrypting into the wrong identity.
21
+ *
22
+ * Every hex field is lowercase-or-uppercase hex. Platform-agnostic — zod only,
23
+ * ESM-safe (no `require()`).
24
+ */
25
+ import { z } from 'zod';
26
+ /** The only envelope version. A scheme change is a new literal, never a mutation. */
27
+ export declare const WEB_IDENTITY_ENVELOPE_VERSION: 1;
28
+ /**
29
+ * The identity's secp256k1 public key in Oxy's canonical form: uncompressed SEC1
30
+ * (`04` + 64 bytes), lowercase hex — what `KeyManager.derivePublicKey` produces
31
+ * and `users.public_key` stores.
32
+ */
33
+ export declare const webIdentityPublicKeySchema: z.ZodString;
34
+ /** A WebAuthn credential id, base64url as the browser reports it. */
35
+ export declare const webauthnCredentialIdSchema: z.ZodString;
36
+ /** One passkey's wrap of the envelope's data key. */
37
+ export declare const webIdentityWrapSchema: z.ZodObject<{
38
+ credentialId: z.ZodString;
39
+ /** 24-byte XChaCha20-Poly1305 nonce. */
40
+ nonce: z.ZodString;
41
+ /** The 32-byte DEK sealed under this passkey's KEK, with the 16-byte tag appended (48 bytes). */
42
+ wrappedKey: z.ZodString;
43
+ createdAt: z.ZodString;
44
+ }, "strip", z.ZodTypeAny, {
45
+ credentialId: string;
46
+ createdAt: string;
47
+ nonce: string;
48
+ wrappedKey: string;
49
+ }, {
50
+ credentialId: string;
51
+ createdAt: string;
52
+ nonce: string;
53
+ wrappedKey: string;
54
+ }>;
55
+ /**
56
+ * The sealed identity as it is stored (server copy and local copy alike).
57
+ *
58
+ * `wraps` holds one entry per passkey able to open it; at least one, and a
59
+ * bounded number so an envelope cannot grow without limit.
60
+ */
61
+ export declare const webIdentityEnvelopeSchema: z.ZodObject<{
62
+ version: z.ZodLiteral<1>;
63
+ algorithm: z.ZodLiteral<"xchacha20poly1305">;
64
+ publicKey: z.ZodString;
65
+ /** 24-byte nonce of the entropy seal. */
66
+ entropyNonce: z.ZodString;
67
+ /** The 16-byte BIP-39 entropy sealed under the DEK, tag appended (32 bytes). */
68
+ sealedEntropy: z.ZodString;
69
+ wraps: z.ZodArray<z.ZodObject<{
70
+ credentialId: z.ZodString;
71
+ /** 24-byte XChaCha20-Poly1305 nonce. */
72
+ nonce: z.ZodString;
73
+ /** The 32-byte DEK sealed under this passkey's KEK, with the 16-byte tag appended (48 bytes). */
74
+ wrappedKey: z.ZodString;
75
+ createdAt: z.ZodString;
76
+ }, "strip", z.ZodTypeAny, {
77
+ credentialId: string;
78
+ createdAt: string;
79
+ nonce: string;
80
+ wrappedKey: string;
81
+ }, {
82
+ credentialId: string;
83
+ createdAt: string;
84
+ nonce: string;
85
+ wrappedKey: string;
86
+ }>, "many">;
87
+ }, "strip", z.ZodTypeAny, {
88
+ version: 1;
89
+ publicKey: string;
90
+ algorithm: "xchacha20poly1305";
91
+ entropyNonce: string;
92
+ sealedEntropy: string;
93
+ wraps: {
94
+ credentialId: string;
95
+ createdAt: string;
96
+ nonce: string;
97
+ wrappedKey: string;
98
+ }[];
99
+ }, {
100
+ version: 1;
101
+ publicKey: string;
102
+ algorithm: "xchacha20poly1305";
103
+ entropyNonce: string;
104
+ sealedEntropy: string;
105
+ wraps: {
106
+ credentialId: string;
107
+ createdAt: string;
108
+ nonce: string;
109
+ wrappedKey: string;
110
+ }[];
111
+ }>;
112
+ /**
113
+ * `PUT /identity/web-envelope` — store or replace the caller's envelope.
114
+ *
115
+ * Refused unless `envelope.publicKey` is the identity key already linked to the
116
+ * account: an envelope can only ever carry the account's own identity.
117
+ */
118
+ export declare const webIdentityEnvelopeUploadSchema: z.ZodObject<{
119
+ envelope: z.ZodObject<{
120
+ version: z.ZodLiteral<1>;
121
+ algorithm: z.ZodLiteral<"xchacha20poly1305">;
122
+ publicKey: z.ZodString;
123
+ /** 24-byte nonce of the entropy seal. */
124
+ entropyNonce: z.ZodString;
125
+ /** The 16-byte BIP-39 entropy sealed under the DEK, tag appended (32 bytes). */
126
+ sealedEntropy: z.ZodString;
127
+ wraps: z.ZodArray<z.ZodObject<{
128
+ credentialId: z.ZodString;
129
+ /** 24-byte XChaCha20-Poly1305 nonce. */
130
+ nonce: z.ZodString;
131
+ /** The 32-byte DEK sealed under this passkey's KEK, with the 16-byte tag appended (48 bytes). */
132
+ wrappedKey: z.ZodString;
133
+ createdAt: z.ZodString;
134
+ }, "strip", z.ZodTypeAny, {
135
+ credentialId: string;
136
+ createdAt: string;
137
+ nonce: string;
138
+ wrappedKey: string;
139
+ }, {
140
+ credentialId: string;
141
+ createdAt: string;
142
+ nonce: string;
143
+ wrappedKey: string;
144
+ }>, "many">;
145
+ }, "strip", z.ZodTypeAny, {
146
+ version: 1;
147
+ publicKey: string;
148
+ algorithm: "xchacha20poly1305";
149
+ entropyNonce: string;
150
+ sealedEntropy: string;
151
+ wraps: {
152
+ credentialId: string;
153
+ createdAt: string;
154
+ nonce: string;
155
+ wrappedKey: string;
156
+ }[];
157
+ }, {
158
+ version: 1;
159
+ publicKey: string;
160
+ algorithm: "xchacha20poly1305";
161
+ entropyNonce: string;
162
+ sealedEntropy: string;
163
+ wraps: {
164
+ credentialId: string;
165
+ createdAt: string;
166
+ nonce: string;
167
+ wrappedKey: string;
168
+ }[];
169
+ }>;
170
+ }, "strip", z.ZodTypeAny, {
171
+ envelope: {
172
+ version: 1;
173
+ publicKey: string;
174
+ algorithm: "xchacha20poly1305";
175
+ entropyNonce: string;
176
+ sealedEntropy: string;
177
+ wraps: {
178
+ credentialId: string;
179
+ createdAt: string;
180
+ nonce: string;
181
+ wrappedKey: string;
182
+ }[];
183
+ };
184
+ }, {
185
+ envelope: {
186
+ version: 1;
187
+ publicKey: string;
188
+ algorithm: "xchacha20poly1305";
189
+ entropyNonce: string;
190
+ sealedEntropy: string;
191
+ wraps: {
192
+ credentialId: string;
193
+ createdAt: string;
194
+ nonce: string;
195
+ wrappedKey: string;
196
+ }[];
197
+ };
198
+ }>;
199
+ /** `GET /identity/web-envelope` — the caller's envelope and its recovery-phrase state. */
200
+ export declare const webIdentityEnvelopeResponseSchema: z.ZodObject<{
201
+ envelope: z.ZodNullable<z.ZodObject<{
202
+ version: z.ZodLiteral<1>;
203
+ algorithm: z.ZodLiteral<"xchacha20poly1305">;
204
+ publicKey: z.ZodString;
205
+ /** 24-byte nonce of the entropy seal. */
206
+ entropyNonce: z.ZodString;
207
+ /** The 16-byte BIP-39 entropy sealed under the DEK, tag appended (32 bytes). */
208
+ sealedEntropy: z.ZodString;
209
+ wraps: z.ZodArray<z.ZodObject<{
210
+ credentialId: z.ZodString;
211
+ /** 24-byte XChaCha20-Poly1305 nonce. */
212
+ nonce: z.ZodString;
213
+ /** The 32-byte DEK sealed under this passkey's KEK, with the 16-byte tag appended (48 bytes). */
214
+ wrappedKey: z.ZodString;
215
+ createdAt: z.ZodString;
216
+ }, "strip", z.ZodTypeAny, {
217
+ credentialId: string;
218
+ createdAt: string;
219
+ nonce: string;
220
+ wrappedKey: string;
221
+ }, {
222
+ credentialId: string;
223
+ createdAt: string;
224
+ nonce: string;
225
+ wrappedKey: string;
226
+ }>, "many">;
227
+ }, "strip", z.ZodTypeAny, {
228
+ version: 1;
229
+ publicKey: string;
230
+ algorithm: "xchacha20poly1305";
231
+ entropyNonce: string;
232
+ sealedEntropy: string;
233
+ wraps: {
234
+ credentialId: string;
235
+ createdAt: string;
236
+ nonce: string;
237
+ wrappedKey: string;
238
+ }[];
239
+ }, {
240
+ version: 1;
241
+ publicKey: string;
242
+ algorithm: "xchacha20poly1305";
243
+ entropyNonce: string;
244
+ sealedEntropy: string;
245
+ wraps: {
246
+ credentialId: string;
247
+ createdAt: string;
248
+ nonce: string;
249
+ wrappedKey: string;
250
+ }[];
251
+ }>>;
252
+ /**
253
+ * When the owner confirmed they wrote the recovery phrase down, or `null`.
254
+ * Until then the identity must not be unlocked on a second device, nor used
255
+ * for any operation that needs the key (design decision D2).
256
+ */
257
+ phraseConfirmedAt: z.ZodNullable<z.ZodString>;
258
+ updatedAt: z.ZodNullable<z.ZodString>;
259
+ }, "strip", z.ZodTypeAny, {
260
+ updatedAt: string | null;
261
+ envelope: {
262
+ version: 1;
263
+ publicKey: string;
264
+ algorithm: "xchacha20poly1305";
265
+ entropyNonce: string;
266
+ sealedEntropy: string;
267
+ wraps: {
268
+ credentialId: string;
269
+ createdAt: string;
270
+ nonce: string;
271
+ wrappedKey: string;
272
+ }[];
273
+ } | null;
274
+ phraseConfirmedAt: string | null;
275
+ }, {
276
+ updatedAt: string | null;
277
+ envelope: {
278
+ version: 1;
279
+ publicKey: string;
280
+ algorithm: "xchacha20poly1305";
281
+ entropyNonce: string;
282
+ sealedEntropy: string;
283
+ wraps: {
284
+ credentialId: string;
285
+ createdAt: string;
286
+ nonce: string;
287
+ wrappedKey: string;
288
+ }[];
289
+ } | null;
290
+ phraseConfirmedAt: string | null;
291
+ }>;
292
+ /**
293
+ * `POST /identity/web-envelope/phrase-confirmed` and
294
+ * `DELETE /identity/web-envelope` both prove control of the identity key, not
295
+ * just a bearer: a stolen session must not be able to mark a phrase as saved or
296
+ * destroy the web copy of someone's identity.
297
+ *
298
+ * The signed message is `JSON.stringify({ action, userId, timestamp })` — the
299
+ * same scheme as `link_identity`.
300
+ */
301
+ export declare const webIdentityEnvelopeProofSchema: z.ZodObject<{
302
+ signature: z.ZodString;
303
+ timestamp: z.ZodNumber;
304
+ }, "strip", z.ZodTypeAny, {
305
+ signature: string;
306
+ timestamp: number;
307
+ }, {
308
+ signature: string;
309
+ timestamp: number;
310
+ }>;
311
+ /** `PUT /identity/web-envelope` body: the envelope plus a `web_envelope_put` identity-key proof. */
312
+ export declare const webIdentityEnvelopePutSchema: z.ZodObject<{
313
+ envelope: z.ZodObject<{
314
+ version: z.ZodLiteral<1>;
315
+ algorithm: z.ZodLiteral<"xchacha20poly1305">;
316
+ publicKey: z.ZodString;
317
+ /** 24-byte nonce of the entropy seal. */
318
+ entropyNonce: z.ZodString;
319
+ /** The 16-byte BIP-39 entropy sealed under the DEK, tag appended (32 bytes). */
320
+ sealedEntropy: z.ZodString;
321
+ wraps: z.ZodArray<z.ZodObject<{
322
+ credentialId: z.ZodString;
323
+ /** 24-byte XChaCha20-Poly1305 nonce. */
324
+ nonce: z.ZodString;
325
+ /** The 32-byte DEK sealed under this passkey's KEK, with the 16-byte tag appended (48 bytes). */
326
+ wrappedKey: z.ZodString;
327
+ createdAt: z.ZodString;
328
+ }, "strip", z.ZodTypeAny, {
329
+ credentialId: string;
330
+ createdAt: string;
331
+ nonce: string;
332
+ wrappedKey: string;
333
+ }, {
334
+ credentialId: string;
335
+ createdAt: string;
336
+ nonce: string;
337
+ wrappedKey: string;
338
+ }>, "many">;
339
+ }, "strip", z.ZodTypeAny, {
340
+ version: 1;
341
+ publicKey: string;
342
+ algorithm: "xchacha20poly1305";
343
+ entropyNonce: string;
344
+ sealedEntropy: string;
345
+ wraps: {
346
+ credentialId: string;
347
+ createdAt: string;
348
+ nonce: string;
349
+ wrappedKey: string;
350
+ }[];
351
+ }, {
352
+ version: 1;
353
+ publicKey: string;
354
+ algorithm: "xchacha20poly1305";
355
+ entropyNonce: string;
356
+ sealedEntropy: string;
357
+ wraps: {
358
+ credentialId: string;
359
+ createdAt: string;
360
+ nonce: string;
361
+ wrappedKey: string;
362
+ }[];
363
+ }>;
364
+ } & {
365
+ signature: z.ZodString;
366
+ timestamp: z.ZodNumber;
367
+ }, "strip", z.ZodTypeAny, {
368
+ signature: string;
369
+ timestamp: number;
370
+ envelope: {
371
+ version: 1;
372
+ publicKey: string;
373
+ algorithm: "xchacha20poly1305";
374
+ entropyNonce: string;
375
+ sealedEntropy: string;
376
+ wraps: {
377
+ credentialId: string;
378
+ createdAt: string;
379
+ nonce: string;
380
+ wrappedKey: string;
381
+ }[];
382
+ };
383
+ }, {
384
+ signature: string;
385
+ timestamp: number;
386
+ envelope: {
387
+ version: 1;
388
+ publicKey: string;
389
+ algorithm: "xchacha20poly1305";
390
+ entropyNonce: string;
391
+ sealedEntropy: string;
392
+ wraps: {
393
+ credentialId: string;
394
+ createdAt: string;
395
+ nonce: string;
396
+ wrappedKey: string;
397
+ }[];
398
+ };
399
+ }>;
400
+ /**
401
+ * `POST /identity/web-envelope/establish` body — create an account's FIRST
402
+ * identity on the web: link the key and store its envelope in ONE transaction.
403
+ *
404
+ * Linking and storing as two calls would let a failure (or a closed tab) in
405
+ * between leave the account bound to a key that nothing carries — an identity
406
+ * lost at birth. `link` is a `link_identity` proof and the outer proof a
407
+ * `web_envelope_put` proof, both signed by the envelope's own key.
408
+ */
409
+ export declare const webIdentityEnvelopeEstablishSchema: z.ZodObject<{
410
+ envelope: z.ZodObject<{
411
+ version: z.ZodLiteral<1>;
412
+ algorithm: z.ZodLiteral<"xchacha20poly1305">;
413
+ publicKey: z.ZodString;
414
+ /** 24-byte nonce of the entropy seal. */
415
+ entropyNonce: z.ZodString;
416
+ /** The 16-byte BIP-39 entropy sealed under the DEK, tag appended (32 bytes). */
417
+ sealedEntropy: z.ZodString;
418
+ wraps: z.ZodArray<z.ZodObject<{
419
+ credentialId: z.ZodString;
420
+ /** 24-byte XChaCha20-Poly1305 nonce. */
421
+ nonce: z.ZodString;
422
+ /** The 32-byte DEK sealed under this passkey's KEK, with the 16-byte tag appended (48 bytes). */
423
+ wrappedKey: z.ZodString;
424
+ createdAt: z.ZodString;
425
+ }, "strip", z.ZodTypeAny, {
426
+ credentialId: string;
427
+ createdAt: string;
428
+ nonce: string;
429
+ wrappedKey: string;
430
+ }, {
431
+ credentialId: string;
432
+ createdAt: string;
433
+ nonce: string;
434
+ wrappedKey: string;
435
+ }>, "many">;
436
+ }, "strip", z.ZodTypeAny, {
437
+ version: 1;
438
+ publicKey: string;
439
+ algorithm: "xchacha20poly1305";
440
+ entropyNonce: string;
441
+ sealedEntropy: string;
442
+ wraps: {
443
+ credentialId: string;
444
+ createdAt: string;
445
+ nonce: string;
446
+ wrappedKey: string;
447
+ }[];
448
+ }, {
449
+ version: 1;
450
+ publicKey: string;
451
+ algorithm: "xchacha20poly1305";
452
+ entropyNonce: string;
453
+ sealedEntropy: string;
454
+ wraps: {
455
+ credentialId: string;
456
+ createdAt: string;
457
+ nonce: string;
458
+ wrappedKey: string;
459
+ }[];
460
+ }>;
461
+ } & {
462
+ signature: z.ZodString;
463
+ timestamp: z.ZodNumber;
464
+ } & {
465
+ link: z.ZodObject<{
466
+ signature: z.ZodString;
467
+ timestamp: z.ZodNumber;
468
+ }, "strip", z.ZodTypeAny, {
469
+ signature: string;
470
+ timestamp: number;
471
+ }, {
472
+ signature: string;
473
+ timestamp: number;
474
+ }>;
475
+ }, "strip", z.ZodTypeAny, {
476
+ signature: string;
477
+ timestamp: number;
478
+ envelope: {
479
+ version: 1;
480
+ publicKey: string;
481
+ algorithm: "xchacha20poly1305";
482
+ entropyNonce: string;
483
+ sealedEntropy: string;
484
+ wraps: {
485
+ credentialId: string;
486
+ createdAt: string;
487
+ nonce: string;
488
+ wrappedKey: string;
489
+ }[];
490
+ };
491
+ link: {
492
+ signature: string;
493
+ timestamp: number;
494
+ };
495
+ }, {
496
+ signature: string;
497
+ timestamp: number;
498
+ envelope: {
499
+ version: 1;
500
+ publicKey: string;
501
+ algorithm: "xchacha20poly1305";
502
+ entropyNonce: string;
503
+ sealedEntropy: string;
504
+ wraps: {
505
+ credentialId: string;
506
+ createdAt: string;
507
+ nonce: string;
508
+ wrappedKey: string;
509
+ }[];
510
+ };
511
+ link: {
512
+ signature: string;
513
+ timestamp: number;
514
+ };
515
+ }>;
516
+ export type WebIdentityWrap = z.infer<typeof webIdentityWrapSchema>;
517
+ export type WebIdentityEnvelope = z.infer<typeof webIdentityEnvelopeSchema>;
518
+ export type WebIdentityEnvelopeUpload = z.infer<typeof webIdentityEnvelopeUploadSchema>;
519
+ export type WebIdentityEnvelopeResponse = z.infer<typeof webIdentityEnvelopeResponseSchema>;
520
+ export type WebIdentityEnvelopeProof = z.infer<typeof webIdentityEnvelopeProofSchema>;
521
+ export type WebIdentityEnvelopePut = z.infer<typeof webIdentityEnvelopePutSchema>;
522
+ export type WebIdentityEnvelopeEstablish = z.infer<typeof webIdentityEnvelopeEstablishSchema>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxy.so/contracts",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "OxyHQ API contracts — single source of truth for request/response Zod schemas and inferred types, shared by the backend and the client SDKs",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",