@derec-alliance/react-native 0.0.1

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 (65) hide show
  1. package/DeRec.podspec +25 -0
  2. package/LICENSE +201 -0
  3. package/README.md +222 -0
  4. package/android/CMakeLists.txt +38 -0
  5. package/android/build.gradle +79 -0
  6. package/android/src/main/AndroidManifest.xml +12 -0
  7. package/android/src/main/java/org/derec/DeRecModule.kt +56 -0
  8. package/android/src/main/java/org/derec/DeRecPackage.kt +24 -0
  9. package/android/src/main/jni/OnLoad.cpp +39 -0
  10. package/android/src/main/jniLibs/arm64-v8a/libderec_library.a +0 -0
  11. package/android/src/main/jniLibs/armeabi-v7a/libderec_library.a +0 -0
  12. package/android/src/main/jniLibs/x86_64/libderec_library.a +0 -0
  13. package/cpp/Convert.cpp +50 -0
  14. package/cpp/Convert.h +38 -0
  15. package/cpp/DeRecInstaller.cpp +186 -0
  16. package/cpp/DeRecInstaller.h +28 -0
  17. package/cpp/Invoker.h +20 -0
  18. package/cpp/JsCallbackBridge.cpp +144 -0
  19. package/cpp/JsCallbackBridge.h +148 -0
  20. package/cpp/Primitives.cpp +1193 -0
  21. package/cpp/Primitives.h +39 -0
  22. package/cpp/ProtocolHost.cpp +613 -0
  23. package/cpp/ProtocolHost.h +88 -0
  24. package/cpp/RuntimeInvoker.h +29 -0
  25. package/cpp/StoreCallbacks.cpp +1293 -0
  26. package/cpp/StoreCallbacks.h +111 -0
  27. package/cpp/WorkerThread.cpp +74 -0
  28. package/cpp/WorkerThread.h +54 -0
  29. package/cpp/derec_ffi.h +2145 -0
  30. package/ios/DeRec.h +23 -0
  31. package/ios/DeRec.mm +39 -0
  32. package/ios/DeRecFFI.xcframework/Info.plist +48 -0
  33. package/ios/DeRecFFI.xcframework/ios-arm64/Headers/derec_ffi.h +2145 -0
  34. package/ios/DeRecFFI.xcframework/ios-arm64/libderec_library.a +0 -0
  35. package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/Headers/derec_ffi.h +2145 -0
  36. package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/libderec_library.a +0 -0
  37. package/lib/binary.d.ts +19 -0
  38. package/lib/binary.js +164 -0
  39. package/lib/codec.d.ts +16 -0
  40. package/lib/codec.js +80 -0
  41. package/lib/errors.d.ts +23 -0
  42. package/lib/errors.js +35 -0
  43. package/lib/index.d.ts +4 -0
  44. package/lib/index.js +27 -0
  45. package/lib/messages.d.ts +41 -0
  46. package/lib/messages.js +192 -0
  47. package/lib/native.d.ts +11 -0
  48. package/lib/native.js +40 -0
  49. package/lib/primitives.d.ts +137 -0
  50. package/lib/primitives.js +329 -0
  51. package/lib/protocol.d.ts +188 -0
  52. package/lib/protocol.js +514 -0
  53. package/lib/types.d.ts +1014 -0
  54. package/lib/types.js +63 -0
  55. package/package.json +55 -0
  56. package/react-native.config.js +19 -0
  57. package/src/binary.ts +182 -0
  58. package/src/codec.ts +79 -0
  59. package/src/errors.ts +45 -0
  60. package/src/index.ts +7 -0
  61. package/src/messages.ts +192 -0
  62. package/src/native.ts +52 -0
  63. package/src/primitives.ts +770 -0
  64. package/src/protocol.ts +621 -0
  65. package/src/types.ts +1040 -0
package/lib/types.d.ts ADDED
@@ -0,0 +1,1014 @@
1
+ export interface SecretStore {
2
+ load(secretId: string, channelId: string, kind: 0 | 1 | 2): Promise<Uint8Array | null | undefined>;
3
+ /**
4
+ * Load secrets of the same `kind` for several channels in one call,
5
+ * scoped to `secretId`. Must return an array with one entry per input
6
+ * id, in the same order, using `null` (or `undefined`) for channels
7
+ * with no stored secret of `kind`.
8
+ */
9
+ loadMany(secretId: string, channelIds: string[], kind: 0 | 1 | 2, missingPolicy: "skip" | "fail"): Promise<Array<Uint8Array | null | undefined>>;
10
+ save(secretId: string, channelId: string, kind: 0 | 1 | 2, value: Uint8Array): Promise<void>;
11
+ remove(secretId: string, channelId: string, kind: 0 | 1 | 2): Promise<void>;
12
+ }
13
+ /**
14
+ * Channel-record persistence.
15
+ *
16
+ * A record is addressed by `(channelId, replicaId)`. A `replicaId` of `"0"` —
17
+ * the value the protocol reserves as "absent" — addresses the helper channel
18
+ * at `channelId`.
19
+ *
20
+ * Any other value addresses that member of the replica group, and the member
21
+ * is keyed by **`replicaId` alone**. The accompanying `channelId` is context,
22
+ * not part of the key: a member moves between channels during an admission
23
+ * handover while remaining the same member, and a lookup that required both to
24
+ * match would miss it exactly when the move needs to be observed. Keep two
25
+ * maps — helpers by `channelId`, members by `replicaId` — not one keyed by the
26
+ * pair.
27
+ *
28
+ * `load`/`save` bytes are a JSON-encoded `ChannelRecord`: an externally
29
+ * tagged union carrying exactly one of `Helper` or `Replica`.
30
+ *
31
+ * `listHelpers` and `listReplicas` are **not** arrays of that union — they
32
+ * return a JSON array of the **inner** records with the tag stripped:
33
+ * `[{ channel_id, transport, ... }, ...]`, `HelperChannel` for the first and
34
+ * `ReplicaMember` for the second. Wrapping each element back in
35
+ * `{ "Helper": ... }` will not decode.
36
+ *
37
+ * Build that array by **splicing the stored bytes as text** — the payloads are
38
+ * opaque, so persist and re-emit them verbatim:
39
+ *
40
+ * ```js
41
+ * const inner = rows.map((r) => new TextDecoder().decode(r));
42
+ * return new TextEncoder().encode(`[${inner.join(",")}]`);
43
+ * ```
44
+ *
45
+ * Do not `JSON.parse` and re-serialise. Every id in these records is a `u64`,
46
+ * and `JSON.parse` silently rounds anything above 2^53 — the corruption only
47
+ * appears once a real id happens to be large. `bindings/web` implements this.
48
+ */
49
+ export interface ChannelStore {
50
+ load(secretId: string, channelId: string, replicaId: string): Promise<Uint8Array | null | undefined>;
51
+ save(secretId: string, channelId: string, replicaId: string, bytes: Uint8Array): Promise<void>;
52
+ remove(secretId: string, channelId: string, replicaId: string): Promise<boolean>;
53
+ /** JSON array of the helper channels stored under `secretId`. */
54
+ listHelpers(secretId: string): Promise<Uint8Array | null | undefined>;
55
+ /**
56
+ * JSON array of the replica-group members stored under `secretId`,
57
+ * including this device's own row.
58
+ *
59
+ * The order is significant in exactly one situation. A group has one member
60
+ * holding the `Source` role; when it is removed, the protocol promotes the
61
+ * first element of this array that is neither the departing member nor
62
+ * itself leaving. Ordering this array is therefore how an application
63
+ * chooses its succession policy. The choice is read once, on the single
64
+ * device running the removal, and is then published in the roster, so
65
+ * implementations on different devices need not agree on order. Nothing else
66
+ * consults it.
67
+ *
68
+ * Returning an arbitrary order is correct and simply delegates the choice to
69
+ * the storage — note that a SQL `SELECT` without `ORDER BY` and `Map`
70
+ * insertion order after arbitrary edits are both effectively arbitrary.
71
+ * Order explicitly to make succession predictable.
72
+ */
73
+ listReplicas(secretId: string): Promise<Uint8Array | null | undefined>;
74
+ linkChannel(secretId: string, channelId: string, linkedChannelId: string): Promise<void>;
75
+ linkedChannels(secretId: string, channelId: string): Promise<string[]>;
76
+ }
77
+ export interface Share {
78
+ secretId: string;
79
+ version: number;
80
+ bytes: Uint8Array;
81
+ }
82
+ export interface ShareStore {
83
+ load(secretId: string, channelId: string, versions: number[]): Promise<Share[]>;
84
+ loadMany(secretId: string, channelIds: string[], versions: number[]): Promise<Share[]>;
85
+ loadAll(secretId: string, channelIds: string[]): Promise<Share[]>;
86
+ save(secretId: string, channelId: string, share: Share): Promise<void>;
87
+ latestVersion(secretId: string): Promise<number | null>;
88
+ removeChannel(secretId: string, channelId: string): Promise<void>;
89
+ }
90
+ export interface UserSecretEntry {
91
+ id: Uint8Array;
92
+ name: string;
93
+ data: Uint8Array;
94
+ }
95
+ export interface UserSecrets {
96
+ version: number;
97
+ secrets: UserSecretEntry[];
98
+ description?: string;
99
+ }
100
+ /**
101
+ * Persistence for the user-facing secret contents, keyed by `secretId`.
102
+ * One `secretId` maps to at most one stored snapshot — the most recent
103
+ * `start(ProtectSecret)` value. Read back by the pair-completion
104
+ * auto-publish hook so freshly-paired peers receive the current secret.
105
+ */
106
+ export interface UserSecretStore {
107
+ loadLatest(secretId: string): Promise<UserSecrets | null | undefined>;
108
+ saveLatest(secretId: string, value: UserSecrets): Promise<void>;
109
+ remove(secretId: string): Promise<void>;
110
+ }
111
+ /**
112
+ * In-flight orchestrator state persistence. The library treats item
113
+ * payloads as opaque JSON blobs — `save` writes the blob, `load`
114
+ * returns the exact blob it received, `remove` drops the row, and
115
+ * `loadAll` returns every blob whose `kind` matches the requested
116
+ * category (`0` = PendingVerification, `1` = PendingRecovery,
117
+ * `2` = PendingUnpair, `3` = SharingRound, `4` = PendingSyncCheck).
118
+ *
119
+ * Rows are keyed by `(secretId, StateKey)` — the `keyJson` buffer is
120
+ * a JSON object `{ kind, channel_id?, version? }` matching the `kind`
121
+ * numbering above. The library will `save`/`load`/`remove` under the
122
+ * same key across a session, so implementations can hash the entire
123
+ * `keyJson` buffer or unpack its fields (`kind` + `channel_id` +
124
+ * `version`) as the composite key.
125
+ *
126
+ * Save is full-replacement upsert — accumulator-style state
127
+ * (PendingRecovery and SharingRound) grows via load-modify-save cycles
128
+ * from the library; no per-row append primitive is required.
129
+ */
130
+ export interface StateStore {
131
+ save(secretId: string, itemJson: Uint8Array): Promise<void>;
132
+ load(secretId: string, keyJson: Uint8Array): Promise<Uint8Array | null | undefined>;
133
+ remove(secretId: string, keyJson: Uint8Array): Promise<boolean>;
134
+ loadAll(secretId: string, kind: 0 | 1 | 2 | 3 | 4): Promise<Uint8Array[]>;
135
+ }
136
+ /**
137
+ * Outbound message delivery.
138
+ *
139
+ * This is a mailbox, not a request/response channel: every peer has an
140
+ * address, and a reply is posted to that address rather than returned from
141
+ * `process`. Where both sides are reachable services, a one-way push is all
142
+ * that is needed.
143
+ *
144
+ * A peer that cannot be addressed — a phone, a browser, anything behind NAT —
145
+ * breaks that silently: the reply is handed to `send`, goes nowhere, and
146
+ * nothing reports an error. Such a service must answer on the connection the
147
+ * request arrived on, by building the protocol per request with a `Transport`
148
+ * that collects into a buffer instead of sending, then returning the collected
149
+ * message whose trace id matches the inbound envelope's
150
+ * (`envelope_read_trace_id`). One call can emit several messages, so the rest
151
+ * of the buffer is genuine fan-out and still has to be delivered. See "Serving
152
+ * DeRec over request/response transports" in the Rust SDK README.
153
+ */
154
+ export interface Transport {
155
+ send(endpoint: {
156
+ protocol: string;
157
+ uri: string;
158
+ }, message: Uint8Array): Promise<void>;
159
+ }
160
+ export declare enum SenderKind {
161
+ Owner = 0,
162
+ Helper = 1,
163
+ ReplicaSource = 3,
164
+ ReplicaDestination = 4
165
+ }
166
+ /**
167
+ * Selects how the initiator's public encryption material is delivered in a
168
+ * `ContactMessage`.
169
+ *
170
+ * - `InlineKeys` (default): keys are embedded in the contact itself.
171
+ * - `HashedKeys`: only a SHA-384 commitment to the keys is in the contact;
172
+ * the scanner must fetch the actual keys over the wire via the `PrePair`
173
+ * round-trip and verify them against the commitment before pairing.
174
+ * - `NoKeys`: no key material and no commitment. The contact carries only
175
+ * `channel_id`, `nonce`, and `transport_protocol` — small enough to be
176
+ * hand-typed or dictated. Keys are generated on the fly by the contact
177
+ * creator when the `PrePairRequest` arrives; the scanner accepts them
178
+ * without cryptographic verification. Trust rests entirely on the OOB
179
+ * delivery channel being fully trusted (e.g. a verified email from an
180
+ * already-KYC-authenticated institution). Applications MUST rate-limit
181
+ * inbound `PrePairRequest`s per channel and expire outstanding NoKeys
182
+ * contacts on a short timer.
183
+ *
184
+ * Because nothing binds the published keys to the contact, the channel is
185
+ * held `Pending` until `verifyFingerprint` succeeds on both sides: it is
186
+ * not a publish target, not a recovery source, and inbound messages on it
187
+ * are ignored. A man-in-the-middle on the plaintext `PrePair` leg leaves
188
+ * the two sides with different shared keys and so different fingerprints,
189
+ * which is what the comparison catches — the role `contact_binding_hash`
190
+ * plays for `HashedKeys`.
191
+ */
192
+ export declare enum ContactMode {
193
+ InlineKeys = 0,
194
+ HashedKeys = 1,
195
+ NoKeys = 2
196
+ }
197
+ export declare enum FlowKind {
198
+ Pairing = 0,
199
+ Discovery = 1,
200
+ ProtectSecret = 2,
201
+ VerifyShares = 3,
202
+ RecoverSecret = 4,
203
+ Unpair = 5,
204
+ UpdateChannelInfo = 6,
205
+ /** Ask the replica group whether this device is behind, and catch up if it
206
+ * is. Replica-only, and takes no parameters — the group and this device's
207
+ * own version both come from the stores. */
208
+ SyncCheck = 7,
209
+ /** Remove a member from the replica group. Replica-only. Naming this device
210
+ * is a voluntary departure; naming another is an eviction. Params:
211
+ * `{ replica_id: string; memo?: string }` — `replica_id` is a decimal
212
+ * string so ids above 2^53 survive JS number handling. */
213
+ RemoveReplica = 8
214
+ }
215
+ export type UnpairAck = "required" | "not_required";
216
+ export interface ContactMessage {
217
+ channel_id: bigint;
218
+ /** `ContactMode` numeric value (0 = INLINE_KEYS, 1 = HASHED_KEYS, 2 = NO_KEYS). */
219
+ contact_mode: number;
220
+ transport_protocol?: TransportProtocol;
221
+ nonce: bigint;
222
+ /** Present only when `contact_mode === ContactMode.InlineKeys`. */
223
+ mlkem_encapsulation_key?: Uint8Array;
224
+ /** Present only when `contact_mode === ContactMode.InlineKeys`. */
225
+ ecies_public_key?: Uint8Array;
226
+ /** Present only when `contact_mode === ContactMode.HashedKeys`. SHA-384 digest (48 bytes). */
227
+ contact_binding_hash?: Uint8Array;
228
+ timestamp?: Timestamp;
229
+ }
230
+ export interface UserSecret {
231
+ id: Uint8Array;
232
+ name: string;
233
+ data: Uint8Array;
234
+ }
235
+ export type Target = bigint | bigint[] | null;
236
+ export interface PairingParams {
237
+ kind: SenderKind;
238
+ contact: ContactMessage;
239
+ peerCommunicationInfo?: Record<string, string>;
240
+ }
241
+ export interface DiscoveryParams {
242
+ target?: Target;
243
+ }
244
+ export interface ProtectSecretParams {
245
+ secrets: UserSecret[];
246
+ description?: string;
247
+ }
248
+ export interface VerifySharesParams {
249
+ secretId: bigint | string;
250
+ version: number;
251
+ target?: Target;
252
+ }
253
+ export interface RecoverSecretParams {
254
+ secretId: bigint | string;
255
+ version: number;
256
+ }
257
+ export interface UnpairParams {
258
+ channel_id: string;
259
+ memo?: string;
260
+ }
261
+ export interface UpdateChannelInfoParams {
262
+ target?: Target;
263
+ /** New communication-info map. `null`/absent leaves the peer's stored
264
+ * map untouched; pass an empty object to clear it. */
265
+ communication_info?: Record<string, string>;
266
+ /** New transport endpoint. Absent leaves it untouched. */
267
+ transport_protocol?: {
268
+ uri: string;
269
+ protocol: number;
270
+ };
271
+ }
272
+ /**
273
+ * How long the protocol waits on each thing that can keep it waiting. Every
274
+ * field is optional; omit one to keep the library's default for it.
275
+ */
276
+ export interface Timeouts {
277
+ /** Staleness boundary for inbound envelopes — the replay-defence window.
278
+ * Any message older than this is discarded on receipt, whatever the flow.
279
+ * Lowering it starts refusing legitimately old messages from slow
280
+ * transports or skewed clocks. Library default: 300. */
281
+ inbound_message_secs?: number;
282
+ /** How long a publishing round waits on a peer that has not answered.
283
+ * Bounds how long `SharingComplete` can be delayed by one unreachable
284
+ * peer. Library default: 60. */
285
+ sharing_round_secs?: number;
286
+ /** How long to wait for an unpair acknowledgement before dropping local
287
+ * channel state anyway. Library default: 60. */
288
+ unpair_ack_secs?: number;
289
+ /** Removal of channels still awaiting out-of-band fingerprint
290
+ * confirmation — every replica pairing, and every `NoKeys` pairing.
291
+ * Unlike the others this can be disabled, leaving the sweep to the
292
+ * application via `removeExpiredChannels`. The budget is a **human** one:
293
+ * someone comparing a fingerprint, possibly over the phone. Library
294
+ * default: `{ enabled: true, timeout_in_secs: 300 }`. */
295
+ expired_channels?: {
296
+ enabled: boolean;
297
+ timeout_in_secs: number;
298
+ };
299
+ }
300
+ /** `SyncCheck` takes no parameters: the group and this device's own version
301
+ * are both read from the stores. The argument may be omitted entirely. */
302
+ export type SyncCheckParams = Record<string, never>;
303
+ export interface RemoveReplicaParams {
304
+ /** The member to remove, as a **decimal** `u64` string — the same form
305
+ * `ReplicaPaired.peer_replica_id` hands back. A value naming no current
306
+ * member is rejected; it is not silently ignored. */
307
+ replica_id: string;
308
+ memo?: string;
309
+ }
310
+ export type DeRecEvent = {
311
+ type: "PairingCompleted";
312
+ /** Long-term `channel_id` both peers atomically rotated to at handshake completion. */
313
+ channel_id: string;
314
+ /** Transient `channel_id` used only during pairing (the one that traveled on the ContactMessage). No longer resolves in library state. */
315
+ pairing_channel_id: string;
316
+ kind: SenderKind;
317
+ peer_communication_info?: Record<string, string>;
318
+ } | {
319
+ type: "ActionRequired";
320
+ channel_id: string;
321
+ action: Uint8Array;
322
+ action_kind: string;
323
+ peer_communication_info?: Record<string, string>;
324
+ sender_kind?: SenderKind;
325
+ version?: number;
326
+ share_description?: string;
327
+ share_secret_id?: string;
328
+ } | {
329
+ type: "ShareStored";
330
+ channel_id: string;
331
+ version: number;
332
+ } | {
333
+ type: "ShareConfirmed";
334
+ channel_id: string;
335
+ version: number;
336
+ } | {
337
+ type: "ShareRejected";
338
+ channel_id: string;
339
+ version: number;
340
+ status: number;
341
+ memo: string;
342
+ }
343
+ /** A publishing round finished — every targeted helper confirmed,
344
+ * rejected, or timed out.
345
+ *
346
+ * **A mixed round waits for the replica leg.** The counts here describe
347
+ * helpers only and are known the instant the helpers answer, but the
348
+ * event is withheld until every replica member has also acknowledged,
349
+ * refused, or timed out. One unreachable member therefore delays it by
350
+ * up to the configured timeout, which is easy to mistake for a hang.
351
+ * Nothing is lost — the round always terminates and a silent member is
352
+ * reported in `ReplicaSyncComplete.behind` rather than failing it.
353
+ *
354
+ * Drive per-helper progress from `ShareConfirmed` instead: those land as
355
+ * each helper answers, with no cross-population wait. A helpers-only
356
+ * round is unaffected. */
357
+ | {
358
+ type: "SharingComplete";
359
+ version: number;
360
+ confirmed_count: number;
361
+ failed_count: number;
362
+ threshold_met: boolean;
363
+ }
364
+ /** A group member refused a secret sync. Keyed by `replica_id`, not
365
+ * `channel_id`: every member answers on the one group channel. A
366
+ * `VERSION_CONFLICT` status means the round must be resolved and
367
+ * republished at a new version. */
368
+ | {
369
+ type: "ReplicaSyncRejected";
370
+ replica_id: string;
371
+ secret_id: string;
372
+ version: number;
373
+ status: number;
374
+ memo: string;
375
+ }
376
+ /** A secret sync could not be delivered to a member at all — distinct from
377
+ * `ReplicaSyncRejected`, which is the member answering "no". */
378
+ | {
379
+ type: "ReplicaSyncFailed";
380
+ replica_id: string;
381
+ version: number;
382
+ reason: string;
383
+ }
384
+ /** A member left the group and its roster row was dropped. Fires on the
385
+ * members that remain. */
386
+ | {
387
+ type: "ReplicaRemoved";
388
+ replica_id: string;
389
+ }
390
+ /** The group's source role moved to another member because the previous
391
+ * source is leaving. Fires on the device that chose the successor — which
392
+ * it does by the order its channel store returns members in — and on the
393
+ * successor itself when the roster promoting it arrives. */
394
+ | {
395
+ type: "ReplicaSourceChanged";
396
+ replica_id: string;
397
+ }
398
+ /** This device left the group and dropped its whole `secret_id` partition —
399
+ * group channel, helper channels, shares, secrets and the snapshot. Fires
400
+ * only once it was told to leave *and* has since seen a roster excluding
401
+ * it; absence alone never destroys a copy of the secret. */
402
+ | {
403
+ type: "SelfRemovedFromGroup";
404
+ version: number;
405
+ }
406
+ /** A replica catch-up finished. `fetched_from` is absent when this device
407
+ * was already current, in which case no hydration event follows. */
408
+ | {
409
+ type: "SyncCheckComplete";
410
+ local_version: number;
411
+ group_version: number;
412
+ fetched_from?: string;
413
+ }
414
+ /** The replica leg of a publishing round finished. Reported separately from
415
+ * `SharingComplete`: replicas are best-effort, so a member in `behind` does
416
+ * not fail the round. `behind` is the application's retry list — the
417
+ * library keeps no durable per-member sync state. */
418
+ | {
419
+ type: "ReplicaSyncComplete";
420
+ version: number;
421
+ synced: string[];
422
+ behind: string[];
423
+ } | {
424
+ type: "ShareVerified";
425
+ channel_id: string;
426
+ version: number;
427
+ } | {
428
+ type: "SecretsDiscovered";
429
+ channel_id: string;
430
+ secrets: Array<{
431
+ secret_id: string;
432
+ versions: Array<{
433
+ version: number;
434
+ description: string;
435
+ }>;
436
+ }>;
437
+ } | {
438
+ type: "RecoveryShareReceived";
439
+ channel_id: string;
440
+ shares_received: number;
441
+ } | {
442
+ type: "RecoveryShareError";
443
+ channel_id: string;
444
+ shares_received: number;
445
+ error: string;
446
+ }
447
+ /** Recovery completed — the typed `Secret` snapshot the owner
448
+ * originally protected. Mirrors `ReplicaSecretReceived.secret`:
449
+ * `secrets` is the user-facing `Vec<UserSecret>` the application
450
+ * fed to `start(FlowKind.ProtectSecret)`; `helpers` and `replicas`
451
+ * are the roster snapshot captured at distribution time. The library handles the two-stage
452
+ * `DeRecSecret` → `Secret` protobuf decode internally. */
453
+ | {
454
+ type: "SecretRecovered";
455
+ secret: {
456
+ helpers: Array<{
457
+ channel_id: string;
458
+ transport_uri: string;
459
+ shared_key: Uint8Array;
460
+ communication_info: Record<string, string>;
461
+ }>;
462
+ secrets: Array<{
463
+ id: Uint8Array;
464
+ name: string;
465
+ data: Uint8Array;
466
+ }>;
467
+ /** Replica composite. Absent when this `secret_id` has no
468
+ * replica setup. Carries the full member roster, the one channel
469
+ * they share, and the 32-byte group key. Required by `restore` to
470
+ * rebuild replica state without re-pairing. */
471
+ replicas?: {
472
+ /** The one channel every member is addressed on. */
473
+ channel_id: string;
474
+ /** Every member of the group, including the writer. Exactly one
475
+ * carries `role: "Source"` — that member is where the secret
476
+ * originated, which is why no separate owner field is needed. */
477
+ members: Array<{
478
+ replica_id: string;
479
+ transport_uri: string;
480
+ role: "Source" | "Destination";
481
+ communication_info: Record<string, string>;
482
+ }>;
483
+ shared_key: Uint8Array;
484
+ };
485
+ };
486
+ } | {
487
+ type: "Unpaired";
488
+ channel_id: string;
489
+ } | {
490
+ type: "UnpairRejected";
491
+ channel_id: string;
492
+ status: number;
493
+ memo: string;
494
+ }
495
+ /** Contact creator answered the scanner's `PrePairRequest` with a
496
+ * non-Ok status (HashedKeys flow). Distinct from a cryptographic
497
+ * hash mismatch, which surfaces as a thrown error from `process()`. */
498
+ | {
499
+ type: "PrePairRejected";
500
+ channel_id: string;
501
+ status: number;
502
+ memo: string;
503
+ }
504
+ /** Fires alongside `PairingCompleted` on replica-mode pair handshakes.
505
+ * `peer_replica_id` is the peer's `u64` as a **decimal** string,
506
+ * matching the wire `derec.replica_id` representation and every other
507
+ * id across this boundary. Pass it back verbatim — `RemoveReplica`
508
+ * expects the same decimal form. The local side's role
509
+ * (`ReplicaSource` vs `ReplicaDestination`) is on the persisted
510
+ * channel record — replica pairings are unidirectional, so there is
511
+ * no separate "role in pair" field. */
512
+ | {
513
+ type: "ReplicaPaired";
514
+ channel_id: string;
515
+ peer_replica_id: string;
516
+ }
517
+ /** A `ReplicaSource` peer pushed a secret sync on a
518
+ * `ReplicaDestination` channel. The library decoded the
519
+ * `ReplicaSecretPayload`; the app installs `secret.secrets` and
520
+ * optionally uses `shares` for recovery. `from_replica_id` and the
521
+ * `replica_id` fields inside `secret` are `u64` as **decimal**
522
+ * strings. */
523
+ | {
524
+ type: "ReplicaSecretReceived";
525
+ channel_id: string;
526
+ from_replica_id: string;
527
+ secret_id: string;
528
+ version: number;
529
+ secret: {
530
+ helpers: Array<{
531
+ channel_id: string;
532
+ transport_uri: string;
533
+ shared_key: Uint8Array;
534
+ communication_info: Record<string, string>;
535
+ }>;
536
+ secrets: Array<{
537
+ id: Uint8Array;
538
+ name: string;
539
+ data: Uint8Array;
540
+ }>;
541
+ /** Replica composite. Absent when this `secret_id` has no
542
+ * replica setup. The same shape as `SecretRecovered.secret.replicas`. */
543
+ replicas?: {
544
+ /** The one channel every member is addressed on. */
545
+ channel_id: string;
546
+ /** Every member of the group, including the writer. Exactly one
547
+ * carries `role: "Source"` — that member is where the secret
548
+ * originated, which is why no separate owner field is needed. */
549
+ members: Array<{
550
+ replica_id: string;
551
+ transport_uri: string;
552
+ role: "Source" | "Destination";
553
+ communication_info: Record<string, string>;
554
+ }>;
555
+ shared_key: Uint8Array;
556
+ };
557
+ };
558
+ shares: Array<{
559
+ channel_id: string;
560
+ committed_share: Uint8Array;
561
+ }>;
562
+ }
563
+ /** The first sync for a `secret_id` this device had no snapshot for —
564
+ * the secret now exists here. Same payload as `ReplicaSecretReceived`,
565
+ * which reports a later version of a secret the device already held.
566
+ * Both are written to the stores by the library before the event is
567
+ * delivered; the distinct type is what tells an application the set of
568
+ * secrets on the device changed.
569
+ *
570
+ * This is not a recovery: recovery reconstructs a secret from helper
571
+ * shares and is driven by the application through `restore`. */
572
+ | {
573
+ type: "ReplicaSecretInstalled";
574
+ channel_id: string;
575
+ from_replica_id: string;
576
+ secret_id: string;
577
+ version: number;
578
+ secret: {
579
+ helpers: Array<{
580
+ channel_id: string;
581
+ transport_uri: string;
582
+ shared_key: Uint8Array;
583
+ communication_info: Record<string, string>;
584
+ }>;
585
+ secrets: Array<{
586
+ id: Uint8Array;
587
+ name: string;
588
+ data: Uint8Array;
589
+ }>;
590
+ /** Replica composite. Absent when this `secret_id` has no
591
+ * replica setup. The same shape as `SecretRecovered.secret.replicas`. */
592
+ replicas?: {
593
+ /** The one channel every member is addressed on. */
594
+ channel_id: string;
595
+ /** Every member of the group, including the writer. Exactly one
596
+ * carries `role: "Source"` — that member is where the secret
597
+ * originated, which is why no separate owner field is needed. */
598
+ members: Array<{
599
+ replica_id: string;
600
+ transport_uri: string;
601
+ role: "Source" | "Destination";
602
+ communication_info: Record<string, string>;
603
+ }>;
604
+ shared_key: Uint8Array;
605
+ };
606
+ };
607
+ shares: Array<{
608
+ channel_id: string;
609
+ committed_share: Uint8Array;
610
+ }>;
611
+ }
612
+ /** Peer's ack of a secret sync we sent. `status` is the `StatusEnum`
613
+ * integer (0 = Ok), `memo` is the peer's explanation. */
614
+ | {
615
+ type: "ReplicaSecretAcked";
616
+ channel_id: string;
617
+ from_replica_id: string;
618
+ secret_id: string;
619
+ version: number;
620
+ status: number;
621
+ memo: string;
622
+ }
623
+ /** A peer announced an updated `communication_info` map and/or
624
+ * transport endpoint via `start(FlowKind.UpdateChannelInfo)`.
625
+ * Surfaces on both sides — the initiator sees its own update echo
626
+ * back after the responder accepts. */
627
+ | {
628
+ type: "ChannelInfoUpdated";
629
+ channel_id: string;
630
+ }
631
+ /** The peer answered our outbound `UpdateChannelInfo` with a
632
+ * non-`Ok` status. Local state is not rolled back — the app decides
633
+ * whether to retry. */
634
+ | {
635
+ type: "ChannelInfoUpdateRejected";
636
+ channel_id: string;
637
+ status: number;
638
+ memo: string;
639
+ }
640
+ /** Emitted by `process()` in place of `ActionRequired` when the
641
+ * configured {@link AutoAcceptPolicy} opts in to the inbound
642
+ * action's flow. The same event vec carries the flow's completion
643
+ * events (e.g. `ShareStored`, `PairingCompleted`). Use this purely
644
+ * for observability — no further action is required. `action_kind`
645
+ * is the same label vocabulary as `ActionRequired.action_kind`
646
+ * (`"Pairing"`, `"StoreShare"`, …). */
647
+ | {
648
+ type: "AutoAccepted";
649
+ channel_id: string;
650
+ action_kind: string;
651
+ } | {
652
+ type: "NoOp";
653
+ }
654
+ /** A pairing handshake was dispatched successfully. `kind` is the
655
+ * local party's role — same value the subsequent `PairingCompleted`
656
+ * will carry. Emitted by `start(Pairing)`. */
657
+ | {
658
+ type: "PairingStarted";
659
+ channel_id: string;
660
+ kind: SenderKind;
661
+ }
662
+ /** A discovery request was dispatched to `channel_id`. Emitted per
663
+ * targeted helper by `start(Discovery)`. */
664
+ | {
665
+ type: "DiscoveryStarted";
666
+ channel_id: string;
667
+ }
668
+ /** A discovery request could not be dispatched to `channel_id`. Other
669
+ * targeted channels are unaffected. */
670
+ | {
671
+ type: "DiscoveryFailed";
672
+ channel_id: string;
673
+ error: string;
674
+ }
675
+ /** A share-storage request was dispatched to `channel_id`. Emitted per
676
+ * targeted peer by `start(ProtectSecret)`. */
677
+ | {
678
+ type: "ProtectSecretStarted";
679
+ channel_id: string;
680
+ version: number;
681
+ }
682
+ /** A share-storage request could not be dispatched to `channel_id`. */
683
+ | {
684
+ type: "ProtectSecretFailed";
685
+ channel_id: string;
686
+ version: number;
687
+ error: string;
688
+ }
689
+ /** A verify-share challenge was dispatched to `channel_id`. */
690
+ | {
691
+ type: "VerifySharesStarted";
692
+ channel_id: string;
693
+ version: number;
694
+ }
695
+ /** A verify-share challenge could not be dispatched to `channel_id`. */
696
+ | {
697
+ type: "VerifySharesFailed";
698
+ channel_id: string;
699
+ version: number;
700
+ error: string;
701
+ }
702
+ /** A recovery share request was dispatched to `channel_id`. */
703
+ | {
704
+ type: "RecoverSecretStarted";
705
+ channel_id: string;
706
+ version: number;
707
+ }
708
+ /** A recovery share request could not be dispatched to `channel_id`. */
709
+ | {
710
+ type: "RecoverSecretFailed";
711
+ channel_id: string;
712
+ version: number;
713
+ error: string;
714
+ }
715
+ /** An unpair request was dispatched to `channel_id`. Followed by an
716
+ * `Unpaired` event once the peer acknowledges (or in the same event
717
+ * vec, under `UnpairAck.NotRequired`). */
718
+ | {
719
+ type: "UnpairFailed";
720
+ channel_id: string;
721
+ error: string;
722
+ } | {
723
+ type: "UnpairStarted";
724
+ channel_id: string;
725
+ }
726
+ /** An update-channel-info request was dispatched to `channel_id`. */
727
+ | {
728
+ type: "UpdateChannelInfoStarted";
729
+ channel_id: string;
730
+ }
731
+ /** An update-channel-info request could not be dispatched to
732
+ * `channel_id`. */
733
+ | {
734
+ type: "UpdateChannelInfoFailed";
735
+ channel_id: string;
736
+ error: string;
737
+ };
738
+ /**
739
+ * Per-flow auto-accept policy. When a field is `true`, `process()`
740
+ * internally accepts the matching inbound request and emits
741
+ * `AutoAccepted` in place of `ActionRequired`. Every field defaults
742
+ * to `false`.
743
+ *
744
+ * Per-field caveats (read before enabling in production):
745
+ * - `pairing` — covers standard and replica pairing. Replica pairing
746
+ * remains `Pending` until both sides run `verifyFingerprint()`, so
747
+ * auto-accept is safe for replicas. Standard pairing transitions to
748
+ * `Paired` immediately.
749
+ * - `prePair` — turns the initiator into a request-amplification
750
+ * oracle. Anyone who knows a HashedKeys contact's nonce can elicit a
751
+ * key-publish response. Keep off unless you control both ends of
752
+ * the transport.
753
+ * - `storeShare` — the helper's only admission-control point for
754
+ * inbound shares. The protocol enforces no size, quota or rate limit
755
+ * of its own, and `maxShareSize` is checked for range overlap at
756
+ * pairing time only, never against an actual share. While this is
757
+ * `false`, `ActionRequired` carries the decoded request, so the
758
+ * application can inspect the share and call `reject()` with
759
+ * `StatusEnum.SizeLimitExceeded`. Setting it `true` removes that
760
+ * opportunity entirely: every share from every paired Owner is stored
761
+ * unconditionally, at whatever size it arrives. Keep off in any
762
+ * deployment with per-user storage limits.
763
+ * - `unpair` — destructive. Accepting deletes the local channel
764
+ * record before any UI confirmation.
765
+ * - `updateChannelInfo` — silently overwrites the channel record with
766
+ * the peer's announced transport / communication info.
767
+ */
768
+ export interface AutoAcceptPolicy {
769
+ pairing?: boolean;
770
+ prePair?: boolean;
771
+ storeShare?: boolean;
772
+ verifyShare?: boolean;
773
+ discovery?: boolean;
774
+ getShare?: boolean;
775
+ unpair?: boolean;
776
+ updateChannelInfo?: boolean;
777
+ }
778
+ export interface Timestamp {
779
+ seconds: bigint;
780
+ nanos: number;
781
+ }
782
+ export interface DeRecResult {
783
+ status: number;
784
+ memo: string;
785
+ }
786
+ export interface GetSecretIdsVersionsRequestMessage {
787
+ timestamp?: Timestamp;
788
+ /** Ephemeral endpoint where the requester wants the response routed.
789
+ * Absent means "use the channel's stored peer endpoint". */
790
+ reply_to?: TransportProtocol;
791
+ }
792
+ export interface VersionList {
793
+ secret_id: bigint;
794
+ versions: VersionListEntry[];
795
+ }
796
+ export interface VersionListEntry {
797
+ version: number;
798
+ version_description: string;
799
+ }
800
+ export interface GetSecretIdsVersionsResponseMessage {
801
+ result?: DeRecResult;
802
+ secret_list: VersionList[];
803
+ timestamp?: Timestamp;
804
+ }
805
+ export interface VersionEntry {
806
+ version: number;
807
+ description: string;
808
+ }
809
+ export interface SecretVersionEntry {
810
+ secret_id: bigint;
811
+ versions: VersionEntry[];
812
+ }
813
+ export interface TransportProtocol {
814
+ uri: string;
815
+ protocol: number;
816
+ }
817
+ export interface CommunicationInfoKeyValue {
818
+ key: string;
819
+ string_value: string | null;
820
+ bytes_value: Uint8Array | null;
821
+ }
822
+ export interface CommunicationInfo {
823
+ communication_info_entries: CommunicationInfoKeyValue[];
824
+ }
825
+ export interface ParameterRange {
826
+ min_share_size: bigint;
827
+ max_share_size: bigint;
828
+ min_time_between_verifications: bigint;
829
+ max_time_between_verifications: bigint;
830
+ min_time_between_share_updates: bigint;
831
+ max_time_between_share_updates: bigint;
832
+ min_unresponsive_deletion_timeout: bigint;
833
+ max_unresponsive_deletion_timeout: bigint;
834
+ min_unresponsive_deactivation_timeout: bigint;
835
+ max_unresponsive_deactivation_timeout: bigint;
836
+ }
837
+ export interface PairRequestMessage {
838
+ sender_kind: number;
839
+ mlkem_ciphertext: Uint8Array;
840
+ ecies_public_key: Uint8Array;
841
+ nonce: bigint;
842
+ communication_info?: CommunicationInfo;
843
+ parameter_range?: ParameterRange;
844
+ transport_protocol?: TransportProtocol;
845
+ timestamp?: Timestamp;
846
+ }
847
+ export interface PairResponseMessage {
848
+ result?: DeRecResult;
849
+ nonce: bigint;
850
+ communication_info?: CommunicationInfo;
851
+ parameter_range?: ParameterRange;
852
+ timestamp?: Timestamp;
853
+ /**
854
+ * Post-handshake rekey channel id. Both sides switch their local channel
855
+ * record to this value once the response is accepted. Derived by the
856
+ * responder as `SHA-384(u64_be(originalChannelId) || sharedKey)[..8]`
857
+ * interpreted as big-endian `u64`, and validated by the requester against
858
+ * its own derivation. Zero on rejection (non-Ok `result.status`).
859
+ */
860
+ channel_id: bigint;
861
+ }
862
+ export interface PrePairRequestMessage {
863
+ nonce: bigint;
864
+ transport_protocol?: TransportProtocol;
865
+ timestamp?: Timestamp;
866
+ }
867
+ export interface PrePairResponseMessage {
868
+ result?: DeRecResult;
869
+ /** Present only when `result.status === Ok`. */
870
+ mlkem_encapsulation_key?: Uint8Array;
871
+ /** Present only when `result.status === Ok`. */
872
+ ecies_public_key?: Uint8Array;
873
+ nonce: bigint;
874
+ timestamp?: Timestamp;
875
+ }
876
+ export interface GetShareRequestMessage {
877
+ secret_id: bigint;
878
+ version: number;
879
+ timestamp?: Timestamp;
880
+ /** Ephemeral response endpoint; see `replyTo` semantics. */
881
+ reply_to?: TransportProtocol;
882
+ }
883
+ export interface GetShareResponseMessage {
884
+ share_algorithm: number;
885
+ committed_de_rec_share: Uint8Array;
886
+ result?: DeRecResult;
887
+ timestamp?: Timestamp;
888
+ /** Echoed from the request so the Owner can correlate responses across
889
+ * concurrent recoveries without inspecting the share bytes. */
890
+ secret_id: bigint;
891
+ /** Echoed from the request for the same correlation reasons as `secret_id`. */
892
+ version: number;
893
+ }
894
+ export interface SiblingHash {
895
+ is_left: boolean;
896
+ hash: Uint8Array;
897
+ }
898
+ export interface CommittedDeRecShare {
899
+ de_rec_share: Uint8Array;
900
+ commitment: Uint8Array;
901
+ merkle_path: SiblingHash[];
902
+ }
903
+ export interface StoreShareRequestMessage {
904
+ share: Uint8Array;
905
+ share_algorithm: number;
906
+ version: number;
907
+ keep_list: number[];
908
+ version_description: string;
909
+ timestamp?: Timestamp;
910
+ secret_id: bigint;
911
+ /** Ephemeral response endpoint; see `replyTo` semantics. */
912
+ reply_to?: TransportProtocol;
913
+ }
914
+ export interface StoreShareResponseMessage {
915
+ result?: DeRecResult;
916
+ version: number;
917
+ timestamp?: Timestamp;
918
+ secret_id: bigint;
919
+ }
920
+ export interface UnpairRequestMessage {
921
+ memo: string;
922
+ timestamp?: Timestamp;
923
+ /** Ephemeral response endpoint; see `replyTo` semantics. */
924
+ reply_to?: TransportProtocol;
925
+ }
926
+ export interface UnpairResponseMessage {
927
+ result?: DeRecResult;
928
+ timestamp?: Timestamp;
929
+ }
930
+ export interface VerifyShareRequestMessage {
931
+ secret_id: bigint;
932
+ version: number;
933
+ nonce: bigint;
934
+ timestamp?: Timestamp;
935
+ /** Ephemeral response endpoint; see `replyTo` semantics. */
936
+ reply_to?: TransportProtocol;
937
+ }
938
+ export interface VerifyShareResponseMessage {
939
+ result?: DeRecResult;
940
+ secret_id: bigint;
941
+ version: number;
942
+ nonce: bigint;
943
+ hash: Uint8Array;
944
+ timestamp?: Timestamp;
945
+ }
946
+ export interface ProduceResult {
947
+ envelope: Uint8Array;
948
+ }
949
+ export interface SharingResponseProduceResult extends ProduceResult {
950
+ committed_share: CommittedDeRecShare;
951
+ secret_id: bigint;
952
+ version: number;
953
+ }
954
+ export interface CreateContactResult {
955
+ contact_message: ContactMessage;
956
+ secret_key: Uint8Array;
957
+ }
958
+ export interface PairingRequestProduceResult extends ProduceResult {
959
+ initiator_contact_message: ContactMessage;
960
+ secret_key: Uint8Array;
961
+ }
962
+ export interface PairingResponseProduceResult extends ProduceResult {
963
+ peer_transport_protocol: TransportProtocol;
964
+ shared_key: Uint8Array;
965
+ /**
966
+ * Post-handshake rekey channel id the responder is committing to.
967
+ * Callers MUST atomically rename their local channel record from the
968
+ * pre-rekey id (the one passed to `pairing.response.produce`) to this
969
+ * value as part of accepting the response.
970
+ */
971
+ channel_id: bigint;
972
+ }
973
+ export interface PairingProcessResult {
974
+ shared_key: Uint8Array;
975
+ /**
976
+ * Post-handshake rekey channel id — already validated against the
977
+ * caller's own derivation. Callers MUST atomically rename their local
978
+ * channel record from the pre-rekey id (the one in the contact) to this
979
+ * value.
980
+ */
981
+ channel_id: bigint;
982
+ }
983
+ export interface ProducePrePairResult {
984
+ envelope: Uint8Array;
985
+ }
986
+ export interface PrePairRequestExtractResult {
987
+ request: PrePairRequestMessage;
988
+ }
989
+ export interface PrePairResponseExtractResult {
990
+ response: PrePairResponseMessage;
991
+ }
992
+ export interface ProcessPrePairResult {
993
+ /** Initiator's ML-KEM-768 encapsulation key, validated against the
994
+ * contact's `contactBindingHash`. */
995
+ mlkem_encapsulation_key: Uint8Array;
996
+ /** Initiator's ECIES public key, validated against the contact's
997
+ * `contactBindingHash`. */
998
+ ecies_public_key: Uint8Array;
999
+ /** Nonce echoed from the original `ContactMessage`. */
1000
+ nonce: bigint;
1001
+ }
1002
+ export interface SplitResult {
1003
+ /** Map keyed by channel id (`bigint`). */
1004
+ shares: Map<bigint, CommittedDeRecShare>;
1005
+ }
1006
+ export interface RecoverResult {
1007
+ secret_data: Uint8Array;
1008
+ }
1009
+ export interface UnpairingProcessResult {
1010
+ acknowledged: boolean;
1011
+ }
1012
+ export interface DiscoveryProcessResult {
1013
+ secret_list: SecretVersionEntry[];
1014
+ }