@derec-alliance/nodejs 0.0.1-alpha.10

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/index.d.ts ADDED
@@ -0,0 +1,1547 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) 2026 DeRec Alliance. All rights reserved.
3
+
4
+ export interface SecretStore {
5
+ load(
6
+ secretId: string,
7
+ channelId: string,
8
+ kind: 0 | 1 | 2,
9
+ ): Promise<Uint8Array | null | undefined>;
10
+ /**
11
+ * Load secrets of the same `kind` for several channels in one call,
12
+ * scoped to `secretId`. Must return an array with one entry per input
13
+ * id, in the same order, using `null` (or `undefined`) for channels
14
+ * with no stored secret of `kind`.
15
+ */
16
+ loadMany(
17
+ secretId: string,
18
+ channelIds: string[],
19
+ kind: 0 | 1 | 2,
20
+ missingPolicy: "skip" | "fail",
21
+ ): Promise<Array<Uint8Array | null | undefined>>;
22
+ save(
23
+ secretId: string,
24
+ channelId: string,
25
+ kind: 0 | 1 | 2,
26
+ value: Uint8Array,
27
+ ): Promise<void>;
28
+ remove(secretId: string, channelId: string, kind: 0 | 1 | 2): Promise<void>;
29
+ }
30
+
31
+ /**
32
+ * Channel-record persistence.
33
+ *
34
+ * A record is addressed by `(channelId, replicaId)`. A `replicaId` of `"0"` —
35
+ * the value the protocol reserves as "absent" — addresses the helper channel
36
+ * at `channelId`.
37
+ *
38
+ * Any other value addresses that member of the replica group, and the member
39
+ * is keyed by **`replicaId` alone**. The accompanying `channelId` is context,
40
+ * not part of the key: a member moves between channels during an admission
41
+ * handover while remaining the same member, and a lookup that required both to
42
+ * match would miss it exactly when the move needs to be observed. Keep two
43
+ * maps — helpers by `channelId`, members by `replicaId` — not one keyed by the
44
+ * pair.
45
+ *
46
+ * `load`/`save` bytes are a JSON-encoded `ChannelRecord`: an externally
47
+ * tagged union carrying exactly one of `Helper` or `Replica`.
48
+ *
49
+ * `listHelpers` and `listReplicas` are **not** arrays of that union — they
50
+ * return a JSON array of the **inner** records with the tag stripped:
51
+ * `[{ channel_id, transport, ... }, ...]`, `HelperChannel` for the first and
52
+ * `ReplicaMember` for the second. Wrapping each element back in
53
+ * `{ "Helper": ... }` will not decode.
54
+ *
55
+ * Build that array by **splicing the stored bytes as text** — the payloads are
56
+ * opaque, so persist and re-emit them verbatim:
57
+ *
58
+ * ```js
59
+ * const inner = rows.map((r) => new TextDecoder().decode(r));
60
+ * return new TextEncoder().encode(`[${inner.join(",")}]`);
61
+ * ```
62
+ *
63
+ * Do not `JSON.parse` and re-serialise. Every id in these records is a `u64`,
64
+ * and `JSON.parse` silently rounds anything above 2^53 — the corruption only
65
+ * appears once a real id happens to be large. `bindings/web` implements this.
66
+ */
67
+ export interface ChannelStore {
68
+ load(
69
+ secretId: string,
70
+ channelId: string,
71
+ replicaId: string,
72
+ ): Promise<Uint8Array | null | undefined>;
73
+ save(
74
+ secretId: string,
75
+ channelId: string,
76
+ replicaId: string,
77
+ bytes: Uint8Array,
78
+ ): Promise<void>;
79
+ remove(secretId: string, channelId: string, replicaId: string): Promise<boolean>;
80
+ /** JSON array of the helper channels stored under `secretId`. */
81
+ listHelpers(secretId: string): Promise<Uint8Array | null | undefined>;
82
+ /**
83
+ * JSON array of the replica-group members stored under `secretId`,
84
+ * including this device's own row.
85
+ *
86
+ * The order is significant in exactly one situation. A group has one member
87
+ * holding the `Source` role; when it is removed, the protocol promotes the
88
+ * first element of this array that is neither the departing member nor
89
+ * itself leaving. Ordering this array is therefore how an application
90
+ * chooses its succession policy. The choice is read once, on the single
91
+ * device running the removal, and is then published in the roster, so
92
+ * implementations on different devices need not agree on order. Nothing else
93
+ * consults it.
94
+ *
95
+ * Returning an arbitrary order is correct and simply delegates the choice to
96
+ * the storage — note that a SQL `SELECT` without `ORDER BY` and `Map`
97
+ * insertion order after arbitrary edits are both effectively arbitrary.
98
+ * Order explicitly to make succession predictable.
99
+ */
100
+ listReplicas(secretId: string): Promise<Uint8Array | null | undefined>;
101
+ linkChannel(
102
+ secretId: string,
103
+ channelId: string,
104
+ linkedChannelId: string,
105
+ ): Promise<void>;
106
+ linkedChannels(secretId: string, channelId: string): Promise<string[]>;
107
+ }
108
+
109
+ export interface Share {
110
+ secretId: string;
111
+ version: number;
112
+ bytes: Uint8Array;
113
+ }
114
+
115
+ export interface ShareStore {
116
+ load(secretId: string, channelId: string, versions: number[]): Promise<Share[]>;
117
+ loadMany(
118
+ secretId: string,
119
+ channelIds: string[],
120
+ versions: number[],
121
+ ): Promise<Share[]>;
122
+ loadAll(secretId: string, channelIds: string[]): Promise<Share[]>;
123
+ save(secretId: string, channelId: string, share: Share): Promise<void>;
124
+ latestVersion(secretId: string): Promise<number | null>;
125
+ removeChannel(secretId: string, channelId: string): Promise<void>;
126
+ }
127
+
128
+ export interface UserSecretEntry {
129
+ id: Uint8Array;
130
+ name: string;
131
+ data: Uint8Array;
132
+ }
133
+
134
+ export interface UserSecrets {
135
+ version: number;
136
+ secrets: UserSecretEntry[];
137
+ description?: string;
138
+ }
139
+
140
+ /**
141
+ * Persistence for the user-facing secret contents, keyed by `secretId`.
142
+ * One `secretId` maps to at most one stored snapshot — the most recent
143
+ * `start(ProtectSecret)` value. Read back by the pair-completion
144
+ * auto-publish hook so freshly-paired peers receive the current secret.
145
+ */
146
+ export interface UserSecretStore {
147
+ loadLatest(secretId: string): Promise<UserSecrets | null | undefined>;
148
+ saveLatest(secretId: string, value: UserSecrets): Promise<void>;
149
+ remove(secretId: string): Promise<void>;
150
+ }
151
+
152
+ /**
153
+ * In-flight orchestrator state persistence. The library treats item
154
+ * payloads as opaque JSON blobs — `save` writes the blob, `load`
155
+ * returns the exact blob it received, `remove` drops the row, and
156
+ * `loadAll` returns every blob whose `kind` matches the requested
157
+ * category (`0` = PendingVerification, `1` = PendingRecovery,
158
+ * `2` = PendingUnpair, `3` = SharingRound, `4` = PendingSyncCheck).
159
+ *
160
+ * Rows are keyed by `(secretId, StateKey)` — the `keyJson` buffer is
161
+ * a JSON object `{ kind, channel_id?, version? }` matching the `kind`
162
+ * numbering above. The library will `save`/`load`/`remove` under the
163
+ * same key across a session, so implementations can hash the entire
164
+ * `keyJson` buffer or unpack its fields (`kind` + `channel_id` +
165
+ * `version`) as the composite key.
166
+ *
167
+ * Save is full-replacement upsert — accumulator-style state
168
+ * (PendingRecovery and SharingRound) grows via load-modify-save cycles
169
+ * from the library; no per-row append primitive is required.
170
+ */
171
+ export interface StateStore {
172
+ save(secretId: string, itemJson: Uint8Array): Promise<void>;
173
+ load(
174
+ secretId: string,
175
+ keyJson: Uint8Array,
176
+ ): Promise<Uint8Array | null | undefined>;
177
+ remove(secretId: string, keyJson: Uint8Array): Promise<boolean>;
178
+ loadAll(secretId: string, kind: 0 | 1 | 2 | 3 | 4): Promise<Uint8Array[]>;
179
+ }
180
+
181
+ /**
182
+ * Outbound message delivery.
183
+ *
184
+ * This is a mailbox, not a request/response channel: every peer has an
185
+ * address, and a reply is posted to that address rather than returned from
186
+ * `process`. Where both sides are reachable services, a one-way push is all
187
+ * that is needed.
188
+ *
189
+ * A peer that cannot be addressed — a phone, a browser, anything behind NAT —
190
+ * breaks that silently: the reply is handed to `send`, goes nowhere, and
191
+ * nothing reports an error. Such a service must answer on the connection the
192
+ * request arrived on, by building the protocol per request with a `Transport`
193
+ * that collects into a buffer instead of sending, then returning the collected
194
+ * message whose trace id matches the inbound envelope's
195
+ * (`envelope_read_trace_id`). One call can emit several messages, so the rest
196
+ * of the buffer is genuine fan-out and still has to be delivered. See "Serving
197
+ * DeRec over request/response transports" in the Rust SDK README.
198
+ */
199
+ export interface Transport {
200
+ send(endpoint: { protocol: string; uri: string }, message: Uint8Array): Promise<void>;
201
+ }
202
+
203
+ export enum SenderKind {
204
+ Owner = 0,
205
+ Helper = 1,
206
+ ReplicaSource = 3,
207
+ ReplicaDestination = 4,
208
+ }
209
+
210
+ /**
211
+ * Selects how the initiator's public encryption material is delivered in a
212
+ * `ContactMessage`.
213
+ *
214
+ * - `InlineKeys` (default): keys are embedded in the contact itself.
215
+ * - `HashedKeys`: only a SHA-384 commitment to the keys is in the contact;
216
+ * the scanner must fetch the actual keys over the wire via the `PrePair`
217
+ * round-trip and verify them against the commitment before pairing.
218
+ * - `NoKeys`: no key material and no commitment. The contact carries only
219
+ * `channel_id`, `nonce`, and `transport_protocol` — small enough to be
220
+ * hand-typed or dictated. Keys are generated on the fly by the contact
221
+ * creator when the `PrePairRequest` arrives; the scanner accepts them
222
+ * without cryptographic verification. Trust rests entirely on the OOB
223
+ * delivery channel being fully trusted (e.g. a verified email from an
224
+ * already-KYC-authenticated institution). Applications MUST rate-limit
225
+ * inbound `PrePairRequest`s per channel and expire outstanding NoKeys
226
+ * contacts on a short timer.
227
+ *
228
+ * Because nothing binds the published keys to the contact, the channel is
229
+ * held `Pending` until `verifyFingerprint` succeeds on both sides: it is
230
+ * not a publish target, not a recovery source, and inbound messages on it
231
+ * are ignored. A man-in-the-middle on the plaintext `PrePair` leg leaves
232
+ * the two sides with different shared keys and so different fingerprints,
233
+ * which is what the comparison catches — the role `contact_binding_hash`
234
+ * plays for `HashedKeys`.
235
+ */
236
+ export enum ContactMode {
237
+ InlineKeys = 0,
238
+ HashedKeys = 1,
239
+ NoKeys = 2,
240
+ }
241
+
242
+ export enum FlowKind {
243
+ Pairing = 0,
244
+ Discovery = 1,
245
+ ProtectSecret = 2,
246
+ VerifyShares = 3,
247
+ RecoverSecret = 4,
248
+ Unpair = 5,
249
+ UpdateChannelInfo = 6,
250
+ /** Ask the replica group whether this device is behind, and catch up if it
251
+ * is. Replica-only, and takes no parameters — the group and this device's
252
+ * own version both come from the stores. */
253
+ SyncCheck = 7,
254
+ /** Remove a member from the replica group. Replica-only. Naming this device
255
+ * is a voluntary departure; naming another is an eviction. Params:
256
+ * `{ replica_id: string; memo?: string }` — `replica_id` is a decimal
257
+ * string so ids above 2^53 survive JS number handling. */
258
+ RemoveReplica = 8,
259
+ }
260
+
261
+ export type UnpairAck = "required" | "not_required";
262
+
263
+ export interface ContactMessage {
264
+ channel_id: bigint;
265
+ /** `ContactMode` numeric value (0 = INLINE_KEYS, 1 = HASHED_KEYS, 2 = NO_KEYS). */
266
+ contact_mode: number;
267
+ transport_protocol?: TransportProtocol;
268
+ nonce: bigint;
269
+ /** Present only when `contact_mode === ContactMode.InlineKeys`. */
270
+ mlkem_encapsulation_key?: Uint8Array;
271
+ /** Present only when `contact_mode === ContactMode.InlineKeys`. */
272
+ ecies_public_key?: Uint8Array;
273
+ /** Present only when `contact_mode === ContactMode.HashedKeys`. SHA-384 digest (48 bytes). */
274
+ contact_binding_hash?: Uint8Array;
275
+ timestamp?: Timestamp;
276
+ }
277
+
278
+ export interface UserSecret {
279
+
280
+ id: Uint8Array;
281
+ name: string;
282
+ data: Uint8Array;
283
+ }
284
+
285
+ export type Target = bigint | bigint[] | null;
286
+
287
+ export interface PairingParams {
288
+ kind: SenderKind;
289
+ contact: ContactMessage;
290
+
291
+ peerCommunicationInfo?: Record<string, string>;
292
+ }
293
+ export interface DiscoveryParams {
294
+ target?: Target;
295
+ }
296
+ export interface ProtectSecretParams {
297
+ secrets: UserSecret[];
298
+ description?: string;
299
+ }
300
+ export interface VerifySharesParams {
301
+ secretId: bigint | string;
302
+ version: number;
303
+ target?: Target;
304
+ }
305
+ export interface RecoverSecretParams {
306
+
307
+ secretId: bigint | string;
308
+ version: number;
309
+ }
310
+ export interface UnpairParams {
311
+ channel_id: string;
312
+
313
+ memo?: string;
314
+ }
315
+ export interface UpdateChannelInfoParams {
316
+ target?: Target;
317
+
318
+ /** New communication-info map. `null`/absent leaves the peer's stored
319
+ * map untouched; pass an empty object to clear it. */
320
+ communication_info?: Record<string, string>;
321
+
322
+ /** New transport endpoint. Absent leaves it untouched. */
323
+ transport_protocol?: { uri: string; protocol: number };
324
+ }
325
+
326
+ /**
327
+ * How long the protocol waits on each thing that can keep it waiting. Every
328
+ * field is optional; omit one to keep the library's default for it.
329
+ */
330
+ export interface Timeouts {
331
+ /** Staleness boundary for inbound envelopes — the replay-defence window.
332
+ * Any message older than this is discarded on receipt, whatever the flow.
333
+ * Lowering it starts refusing legitimately old messages from slow
334
+ * transports or skewed clocks. Library default: 300. */
335
+ inbound_message_secs?: number;
336
+ /** How long a publishing round waits on a peer that has not answered.
337
+ * Bounds how long `SharingComplete` can be delayed by one unreachable
338
+ * peer. Library default: 60. */
339
+ sharing_round_secs?: number;
340
+ /** How long to wait for an unpair acknowledgement before dropping local
341
+ * channel state anyway. Library default: 60. */
342
+ unpair_ack_secs?: number;
343
+ /** Removal of channels still awaiting out-of-band fingerprint
344
+ * confirmation — every replica pairing, and every `NoKeys` pairing.
345
+ * Unlike the others this can be disabled, leaving the sweep to the
346
+ * application via `removeExpiredChannels`. The budget is a **human** one:
347
+ * someone comparing a fingerprint, possibly over the phone. Library
348
+ * default: `{ enabled: true, timeout_in_secs: 300 }`. */
349
+ expired_channels?: { enabled: boolean; timeout_in_secs: number };
350
+ }
351
+
352
+ /** `SyncCheck` takes no parameters: the group and this device's own version
353
+ * are both read from the stores. The argument may be omitted entirely. */
354
+ export type SyncCheckParams = Record<string, never>;
355
+
356
+ export interface RemoveReplicaParams {
357
+ /** The member to remove, as a **decimal** `u64` string — the same form
358
+ * `ReplicaPaired.peer_replica_id` hands back. A value naming no current
359
+ * member is rejected; it is not silently ignored. */
360
+ replica_id: string;
361
+
362
+ memo?: string;
363
+ }
364
+
365
+ export type DeRecEvent =
366
+ | {
367
+ type: "PairingCompleted";
368
+ /** Long-term `channel_id` both peers atomically rotated to at handshake completion. */
369
+ channel_id: string;
370
+ /** Transient `channel_id` used only during pairing (the one that traveled on the ContactMessage). No longer resolves in library state. */
371
+ pairing_channel_id: string;
372
+ kind: SenderKind;
373
+ peer_communication_info?: Record<string, string>;
374
+ }
375
+ | {
376
+ type: "ActionRequired";
377
+ channel_id: string;
378
+
379
+ action: Uint8Array;
380
+
381
+ action_kind: string;
382
+ peer_communication_info?: Record<string, string>;
383
+
384
+ sender_kind?: SenderKind;
385
+
386
+ version?: number;
387
+ share_description?: string;
388
+
389
+ share_secret_id?: string;
390
+ }
391
+ | { type: "ShareStored"; channel_id: string; version: number }
392
+ | { type: "ShareConfirmed"; channel_id: string; version: number }
393
+ | { type: "ShareRejected"; channel_id: string; version: number; status: number; memo: string }
394
+ /** A publishing round finished — every targeted helper confirmed,
395
+ * rejected, or timed out.
396
+ *
397
+ * **A mixed round waits for the replica leg.** The counts here describe
398
+ * helpers only and are known the instant the helpers answer, but the
399
+ * event is withheld until every replica member has also acknowledged,
400
+ * refused, or timed out. One unreachable member therefore delays it by
401
+ * up to the configured timeout, which is easy to mistake for a hang.
402
+ * Nothing is lost — the round always terminates and a silent member is
403
+ * reported in `ReplicaSyncComplete.behind` rather than failing it.
404
+ *
405
+ * Drive per-helper progress from `ShareConfirmed` instead: those land as
406
+ * each helper answers, with no cross-population wait. A helpers-only
407
+ * round is unaffected. */
408
+ | { type: "SharingComplete"; version: number; confirmed_count: number; failed_count: number; threshold_met: boolean }
409
+ /** A group member refused a secret sync. Keyed by `replica_id`, not
410
+ * `channel_id`: every member answers on the one group channel. A
411
+ * `VERSION_CONFLICT` status means the round must be resolved and
412
+ * republished at a new version. */
413
+ | {
414
+ type: "ReplicaSyncRejected";
415
+ replica_id: string;
416
+ secret_id: string;
417
+ version: number;
418
+ status: number;
419
+ memo: string;
420
+ }
421
+ /** A secret sync could not be delivered to a member at all — distinct from
422
+ * `ReplicaSyncRejected`, which is the member answering "no". */
423
+ | { type: "ReplicaSyncFailed"; replica_id: string; version: number; reason: string }
424
+ /** A member left the group and its roster row was dropped. Fires on the
425
+ * members that remain. */
426
+ | { type: "ReplicaRemoved"; replica_id: string }
427
+ /** The group's source role moved to another member because the previous
428
+ * source is leaving. Fires on the device that chose the successor — which
429
+ * it does by the order its channel store returns members in — and on the
430
+ * successor itself when the roster promoting it arrives. */
431
+ | { type: "ReplicaSourceChanged"; replica_id: string }
432
+ /** This device left the group and dropped its whole `secret_id` partition —
433
+ * group channel, helper channels, shares, secrets and the snapshot. Fires
434
+ * only once it was told to leave *and* has since seen a roster excluding
435
+ * it; absence alone never destroys a copy of the secret. */
436
+ | { type: "SelfRemovedFromGroup"; version: number }
437
+ /** A replica catch-up finished. `fetched_from` is absent when this device
438
+ * was already current, in which case no hydration event follows. */
439
+ | {
440
+ type: "SyncCheckComplete";
441
+ local_version: number;
442
+ group_version: number;
443
+ fetched_from?: string;
444
+ }
445
+ /** The replica leg of a publishing round finished. Reported separately from
446
+ * `SharingComplete`: replicas are best-effort, so a member in `behind` does
447
+ * not fail the round. `behind` is the application's retry list — the
448
+ * library keeps no durable per-member sync state. */
449
+ | { type: "ReplicaSyncComplete"; version: number; synced: string[]; behind: string[] }
450
+ | { type: "ShareVerified"; channel_id: string; version: number }
451
+ | {
452
+ type: "SecretsDiscovered";
453
+ channel_id: string;
454
+
455
+ secrets: Array<{ secret_id: string; versions: Array<{ version: number; description: string }> }>;
456
+ }
457
+ | { type: "RecoveryShareReceived"; channel_id: string; shares_received: number }
458
+ | { type: "RecoveryShareError"; channel_id: string; shares_received: number; error: string }
459
+ /** Recovery completed — the typed `Secret` snapshot the owner
460
+ * originally protected. Mirrors `ReplicaSecretReceived.secret`:
461
+ * `secrets` is the user-facing `Vec<UserSecret>` the application
462
+ * fed to `start(FlowKind.ProtectSecret)`; `helpers` and `replicas`
463
+ * are the roster snapshot captured at distribution time. The library handles the two-stage
464
+ * `DeRecSecret` → `Secret` protobuf decode internally. */
465
+ | {
466
+ type: "SecretRecovered";
467
+ secret: {
468
+ helpers: Array<{
469
+ channel_id: string;
470
+ transport_uri: string;
471
+ shared_key: Uint8Array;
472
+ communication_info: Record<string, string>;
473
+ }>;
474
+ secrets: Array<{
475
+ id: Uint8Array;
476
+ name: string;
477
+ data: Uint8Array;
478
+ }>;
479
+ /** Replica composite. Absent when this `secret_id` has no
480
+ * replica setup. Carries the full member roster, the one channel
481
+ * they share, and the 32-byte group key. Required by `restore` to
482
+ * rebuild replica state without re-pairing. */
483
+ replicas?: {
484
+ /** The one channel every member is addressed on. */
485
+ channel_id: string;
486
+ /** Every member of the group, including the writer. Exactly one
487
+ * carries `role: "Source"` — that member is where the secret
488
+ * originated, which is why no separate owner field is needed. */
489
+ members: Array<{
490
+ replica_id: string;
491
+ transport_uri: string;
492
+ role: "Source" | "Destination";
493
+ communication_info: Record<string, string>;
494
+ }>;
495
+ shared_key: Uint8Array;
496
+ };
497
+ };
498
+ }
499
+
500
+ | { type: "Unpaired"; channel_id: string }
501
+
502
+ | { type: "UnpairRejected"; channel_id: string; status: number; memo: string }
503
+
504
+ /** Contact creator answered the scanner's `PrePairRequest` with a
505
+ * non-Ok status (HashedKeys flow). Distinct from a cryptographic
506
+ * hash mismatch, which surfaces as a thrown error from `process()`. */
507
+ | { type: "PrePairRejected"; channel_id: string; status: number; memo: string }
508
+
509
+ /** Fires alongside `PairingCompleted` on replica-mode pair handshakes.
510
+ * `peer_replica_id` is the peer's `u64` as a **decimal** string,
511
+ * matching the wire `derec.replica_id` representation and every other
512
+ * id across this boundary. Pass it back verbatim — `RemoveReplica`
513
+ * expects the same decimal form. The local side's role
514
+ * (`ReplicaSource` vs `ReplicaDestination`) is on the persisted
515
+ * channel record — replica pairings are unidirectional, so there is
516
+ * no separate "role in pair" field. */
517
+ | {
518
+ type: "ReplicaPaired";
519
+ channel_id: string;
520
+ peer_replica_id: string;
521
+ }
522
+ /** A `ReplicaSource` peer pushed a secret sync on a
523
+ * `ReplicaDestination` channel. The library decoded the
524
+ * `ReplicaSecretPayload`; the app installs `secret.secrets` and
525
+ * optionally uses `shares` for recovery. `from_replica_id` and the
526
+ * `replica_id` fields inside `secret` are `u64` as **decimal**
527
+ * strings. */
528
+ | {
529
+ type: "ReplicaSecretReceived";
530
+ channel_id: string;
531
+ from_replica_id: string;
532
+ secret_id: string;
533
+ version: number;
534
+ secret: {
535
+ helpers: Array<{
536
+ channel_id: string;
537
+ transport_uri: string;
538
+ shared_key: Uint8Array;
539
+ communication_info: Record<string, string>;
540
+ }>;
541
+ secrets: Array<{
542
+ id: Uint8Array;
543
+ name: string;
544
+ data: Uint8Array;
545
+ }>;
546
+ /** Replica composite. Absent when this `secret_id` has no
547
+ * replica setup. The same shape as `SecretRecovered.secret.replicas`. */
548
+ replicas?: {
549
+ /** The one channel every member is addressed on. */
550
+ channel_id: string;
551
+ /** Every member of the group, including the writer. Exactly one
552
+ * carries `role: "Source"` — that member is where the secret
553
+ * originated, which is why no separate owner field is needed. */
554
+ members: Array<{
555
+ replica_id: string;
556
+ transport_uri: string;
557
+ role: "Source" | "Destination";
558
+ communication_info: Record<string, string>;
559
+ }>;
560
+ shared_key: Uint8Array;
561
+ };
562
+ };
563
+ shares: Array<{
564
+ channel_id: string;
565
+ committed_share: Uint8Array;
566
+ }>;
567
+ }
568
+ /** The first sync for a `secret_id` this device had no snapshot for —
569
+ * the secret now exists here. Same payload as `ReplicaSecretReceived`,
570
+ * which reports a later version of a secret the device already held.
571
+ * Both are written to the stores by the library before the event is
572
+ * delivered; the distinct type is what tells an application the set of
573
+ * secrets on the device changed.
574
+ *
575
+ * This is not a recovery: recovery reconstructs a secret from helper
576
+ * shares and is driven by the application through `restore`. */
577
+ | {
578
+ type: "ReplicaSecretInstalled";
579
+ channel_id: string;
580
+ from_replica_id: string;
581
+ secret_id: string;
582
+ version: number;
583
+ secret: {
584
+ helpers: Array<{
585
+ channel_id: string;
586
+ transport_uri: string;
587
+ shared_key: Uint8Array;
588
+ communication_info: Record<string, string>;
589
+ }>;
590
+ secrets: Array<{
591
+ id: Uint8Array;
592
+ name: string;
593
+ data: Uint8Array;
594
+ }>;
595
+ /** Replica composite. Absent when this `secret_id` has no
596
+ * replica setup. The same shape as `SecretRecovered.secret.replicas`. */
597
+ replicas?: {
598
+ /** The one channel every member is addressed on. */
599
+ channel_id: string;
600
+ /** Every member of the group, including the writer. Exactly one
601
+ * carries `role: "Source"` — that member is where the secret
602
+ * originated, which is why no separate owner field is needed. */
603
+ members: Array<{
604
+ replica_id: string;
605
+ transport_uri: string;
606
+ role: "Source" | "Destination";
607
+ communication_info: Record<string, string>;
608
+ }>;
609
+ shared_key: Uint8Array;
610
+ };
611
+ };
612
+ shares: Array<{
613
+ channel_id: string;
614
+ committed_share: Uint8Array;
615
+ }>;
616
+ }
617
+ /** Peer's ack of a secret sync we sent. `status` is the `StatusEnum`
618
+ * integer (0 = Ok), `memo` is the peer's explanation. */
619
+ | {
620
+ type: "ReplicaSecretAcked";
621
+ channel_id: string;
622
+ from_replica_id: string;
623
+ secret_id: string;
624
+ version: number;
625
+ status: number;
626
+ memo: string;
627
+ }
628
+ /** A peer announced an updated `communication_info` map and/or
629
+ * transport endpoint via `start(FlowKind.UpdateChannelInfo)`.
630
+ * Surfaces on both sides — the initiator sees its own update echo
631
+ * back after the responder accepts. */
632
+ | {
633
+ type: "ChannelInfoUpdated";
634
+ channel_id: string;
635
+ }
636
+ /** The peer answered our outbound `UpdateChannelInfo` with a
637
+ * non-`Ok` status. Local state is not rolled back — the app decides
638
+ * whether to retry. */
639
+ | {
640
+ type: "ChannelInfoUpdateRejected";
641
+ channel_id: string;
642
+ status: number;
643
+ memo: string;
644
+ }
645
+ /** Emitted by `process()` in place of `ActionRequired` when the
646
+ * configured {@link AutoAcceptPolicy} opts in to the inbound
647
+ * action's flow. The same event vec carries the flow's completion
648
+ * events (e.g. `ShareStored`, `PairingCompleted`). Use this purely
649
+ * for observability — no further action is required. `action_kind`
650
+ * is the same label vocabulary as `ActionRequired.action_kind`
651
+ * (`"Pairing"`, `"StoreShare"`, …). */
652
+ | { type: "AutoAccepted"; channel_id: string; action_kind: string }
653
+ | { type: "NoOp" }
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
+ | { type: "PairingStarted"; channel_id: string; kind: SenderKind }
658
+ /** A discovery request was dispatched to `channel_id`. Emitted per
659
+ * targeted helper by `start(Discovery)`. */
660
+ | { type: "DiscoveryStarted"; channel_id: string }
661
+ /** A discovery request could not be dispatched to `channel_id`. Other
662
+ * targeted channels are unaffected. */
663
+ | { type: "DiscoveryFailed"; channel_id: string; error: string }
664
+ /** A share-storage request was dispatched to `channel_id`. Emitted per
665
+ * targeted peer by `start(ProtectSecret)`. */
666
+ | { type: "ProtectSecretStarted"; channel_id: string; version: number }
667
+ /** A share-storage request could not be dispatched to `channel_id`. */
668
+ | {
669
+ type: "ProtectSecretFailed";
670
+ channel_id: string;
671
+ version: number;
672
+ error: string;
673
+ }
674
+ /** A verify-share challenge was dispatched to `channel_id`. */
675
+ | { type: "VerifySharesStarted"; channel_id: string; version: number }
676
+ /** A verify-share challenge could not be dispatched to `channel_id`. */
677
+ | {
678
+ type: "VerifySharesFailed";
679
+ channel_id: string;
680
+ version: number;
681
+ error: string;
682
+ }
683
+ /** A recovery share request was dispatched to `channel_id`. */
684
+ | { type: "RecoverSecretStarted"; channel_id: string; version: number }
685
+ /** A recovery share request could not be dispatched to `channel_id`. */
686
+ | {
687
+ type: "RecoverSecretFailed";
688
+ channel_id: string;
689
+ version: number;
690
+ error: string;
691
+ }
692
+ /** An unpair request was dispatched to `channel_id`. Followed by an
693
+ * `Unpaired` event once the peer acknowledges (or in the same event
694
+ * vec, under `UnpairAck.NotRequired`). */
695
+ | { type: "UnpairFailed"; channel_id: string; error: string }
696
+ | { type: "UnpairStarted"; channel_id: string }
697
+ /** An update-channel-info request was dispatched to `channel_id`. */
698
+ | { type: "UpdateChannelInfoStarted"; channel_id: string }
699
+ /** An update-channel-info request could not be dispatched to
700
+ * `channel_id`. */
701
+ | { type: "UpdateChannelInfoFailed"; channel_id: string; error: string };
702
+
703
+ /**
704
+ * Per-flow auto-accept policy. When a field is `true`, `process()`
705
+ * internally accepts the matching inbound request and emits
706
+ * `AutoAccepted` in place of `ActionRequired`. Every field defaults
707
+ * to `false`.
708
+ *
709
+ * Per-field caveats (read before enabling in production):
710
+ * - `pairing` — covers standard and replica pairing. Replica pairing
711
+ * remains `Pending` until both sides run `verifyFingerprint()`, so
712
+ * auto-accept is safe for replicas. Standard pairing transitions to
713
+ * `Paired` immediately.
714
+ * - `prePair` — turns the initiator into a request-amplification
715
+ * oracle. Anyone who knows a HashedKeys contact's nonce can elicit a
716
+ * key-publish response. Keep off unless you control both ends of
717
+ * the transport.
718
+ * - `storeShare` — the helper's only admission-control point for
719
+ * inbound shares. The protocol enforces no size, quota or rate limit
720
+ * of its own, and `maxShareSize` is checked for range overlap at
721
+ * pairing time only, never against an actual share. While this is
722
+ * `false`, `ActionRequired` carries the decoded request, so the
723
+ * application can inspect the share and call `reject()` with
724
+ * `StatusEnum.SizeLimitExceeded`. Setting it `true` removes that
725
+ * opportunity entirely: every share from every paired Owner is stored
726
+ * unconditionally, at whatever size it arrives. Keep off in any
727
+ * deployment with per-user storage limits.
728
+ * - `unpair` — destructive. Accepting deletes the local channel
729
+ * record before any UI confirmation.
730
+ * - `updateChannelInfo` — silently overwrites the channel record with
731
+ * the peer's announced transport / communication info.
732
+ */
733
+ export interface AutoAcceptPolicy {
734
+ pairing?: boolean;
735
+ prePair?: boolean;
736
+ storeShare?: boolean;
737
+ verifyShare?: boolean;
738
+ discovery?: boolean;
739
+ getShare?: boolean;
740
+ unpair?: boolean;
741
+ updateChannelInfo?: boolean;
742
+ }
743
+
744
+ /**
745
+ * Fluent builder for {@link DeRecProtocol}. Mirrors the Rust
746
+ * `DeRecProtocolBuilder` and the dotnet `DeRecProtocolBuilder`
747
+ * method-for-method so a developer who already knows one SDK can move
748
+ * between them without reaching for reference docs.
749
+ *
750
+ * Required setters: `withChannelStore`, `withShareStore`,
751
+ * `withSecretStore`, `withTransport`, `withOwnTransport`. Calling
752
+ * `build()` without all five throws.
753
+ */
754
+ export declare class DeRecProtocolBuilder {
755
+ /**
756
+ * Construct a builder bound to a specific secret. `secretId`
757
+ * identifies the single secret this protocol instance manages.
758
+ * Apps that juggle multiple secrets instantiate one
759
+ * {@link DeRecProtocol} per id.
760
+ */
761
+ constructor(secretId: bigint | number);
762
+
763
+ withChannelStore(store: ChannelStore): DeRecProtocolBuilder;
764
+ withShareStore(store: ShareStore): DeRecProtocolBuilder;
765
+ withSecretStore(store: SecretStore): DeRecProtocolBuilder;
766
+ withUserSecretStore(store: UserSecretStore): DeRecProtocolBuilder;
767
+ withStateStore(store: StateStore): DeRecProtocolBuilder;
768
+ withTransport(transport: Transport): DeRecProtocolBuilder;
769
+ withOwnTransport(endpoint: { uri: string; protocol: string }): DeRecProtocolBuilder;
770
+
771
+ /** Default: 3. */
772
+ withThreshold(threshold: number): DeRecProtocolBuilder;
773
+ /** Default: 3. */
774
+ withKeepVersionsCount(count: number): DeRecProtocolBuilder;
775
+ /**
776
+ * Configure how long the protocol waits on each thing that can keep it
777
+ * waiting. Every field is optional and **absent means "keep the library
778
+ * default"**; not calling this at all leaves all four at their defaults.
779
+ *
780
+ * These were one setting until it became clear they answer different
781
+ * questions. `inbound_message_secs` is a **security** boundary — how stale
782
+ * a message may be and still be accepted — so it has to tolerate transport
783
+ * latency and clock skew. The other three are **liveness** budgets: how
784
+ * long to keep hoping a peer will answer.
785
+ *
786
+ * Values are forwarded verbatim; clamping, and the meaning of a disabled
787
+ * `expired_channels`, are library decisions rather than this binding's.
788
+ */
789
+ withTimeouts(timeouts: Timeouts): DeRecProtocolBuilder;
790
+
791
+ /**
792
+ * Accept plaintext `http://` transport endpoints. **Development only.**
793
+ * Default: `false`.
794
+ *
795
+ * With `false`, plaintext is accepted in exactly one situation: an endpoint
796
+ * this device configured for **itself** that names loopback (`localhost`,
797
+ * `127.0.0.1`, `::1`). A local dev server therefore needs no configuration
798
+ * at all.
799
+ *
800
+ * With `true`, plaintext is accepted for **any host on any path**,
801
+ * including endpoints a peer supplies. That is what makes the LAN case
802
+ * work — a phone talking to a laptop, where neither side is loopback — and
803
+ * why the name is blunt.
804
+ *
805
+ * This is a guardrail, not transport security. The SDK opens no sockets;
806
+ * delivery is your `Transport`. Nothing here stops an application sending
807
+ * plaintext — it governs which endpoints the protocol will record,
808
+ * propagate to peers, and reply to.
809
+ */
810
+ withUnsafeHttp(allow: boolean): DeRecProtocolBuilder;
811
+ /** Default: empty. */
812
+ withCommunicationInfo(info: Record<string, string>): DeRecProtocolBuilder;
813
+ /** Default: false. */
814
+ withAutoRespondOnFailure(enabled: boolean): DeRecProtocolBuilder;
815
+ /** Default: "required". */
816
+ withUnpairAck(ack: UnpairAck): DeRecProtocolBuilder;
817
+ /**
818
+ * When `true`, every outbound channel-mode request stamps
819
+ * `request.replyTo = ownTransport` so the responder routes its reply
820
+ * back here even if the channel's stored peer endpoint points
821
+ * elsewhere. Default: false.
822
+ */
823
+ withAutoReplyTo(enabled: boolean): DeRecProtocolBuilder;
824
+ /**
825
+ * Per-flow auto-accept policy. When a field is `true`, `process()`
826
+ * internally accepts the matching inbound request and emits
827
+ * `AutoAccepted` in place of `ActionRequired`. See
828
+ * {@link AutoAcceptPolicy} for per-field caveats.
829
+ *
830
+ * Default: empty policy (every flow off).
831
+ */
832
+ withAutoAccept(policy: AutoAcceptPolicy): DeRecProtocolBuilder;
833
+ /**
834
+ * Stable per-device replica id. Required to participate in any
835
+ * `ReplicaSource` / `ReplicaDestination` pairing. The id must be
836
+ * stable across restarts. Default: unset.
837
+ */
838
+ withReplicaId(id: bigint | number): DeRecProtocolBuilder;
839
+
840
+ /**
841
+ * Finalize the configuration. Throws if any of the required setters
842
+ * was not called.
843
+ */
844
+ build(): DeRecProtocol;
845
+ }
846
+
847
+ export declare class DeRecProtocol {
848
+ /** Use {@link DeRecProtocolBuilder} to construct instances. */
849
+ private constructor();
850
+
851
+ /** The secret identifier this protocol instance is bound to. */
852
+ secretId(): bigint;
853
+
854
+ /**
855
+ * Generate an out-of-band contact message used to bootstrap pairing.
856
+ *
857
+ * @param channelId Optional channel identifier. Pass `null` /
858
+ * `undefined` to have the library generate one.
859
+ * @param contactMode `ContactMode.InlineKeys` embeds the public keys
860
+ * directly in the contact. `ContactMode.HashedKeys`
861
+ * embeds only a SHA-384 binding hash (keys are
862
+ * fetched later via the `PrePair` round-trip).
863
+ * `HashedKeys` requires `ownTransportUri` to be
864
+ * ephemeral.
865
+ */
866
+ /**
867
+ * Single entry point for all three `ContactMode` variants.
868
+ *
869
+ * @param channelId `null`/`undefined` lets the library mint a random id.
870
+ * @param contactMode `InlineKeys` embeds keys directly; `HashedKeys`
871
+ * embeds only a SHA-384 commitment (keys fetched via `PrePair`);
872
+ * `NoKeys` carries no key material — the creator generates keys on the
873
+ * fly when the `PrePairRequest` arrives. Only appropriate for `NoKeys`
874
+ * when the OOB delivery channel is fully trusted.
875
+ * @param nonce `null`/`undefined` lets the library generate a fresh
876
+ * random `bigint`. Required for `NoKeys` where callers typically pick
877
+ * a small human-typable value.
878
+ */
879
+ createContact(
880
+ channelId: bigint | null | undefined,
881
+ contactMode: ContactMode,
882
+ nonce?: bigint | null,
883
+ ): Promise<ContactMessage>;
884
+
885
+ start(flowKind: FlowKind.Pairing, params: PairingParams): Promise<DeRecEvent[]>;
886
+ start(flowKind: FlowKind.Discovery, params: DiscoveryParams): Promise<DeRecEvent[]>;
887
+ start(flowKind: FlowKind.ProtectSecret, params: ProtectSecretParams): Promise<DeRecEvent[]>;
888
+ start(flowKind: FlowKind.VerifyShares, params: VerifySharesParams): Promise<DeRecEvent[]>;
889
+ start(flowKind: FlowKind.RecoverSecret, params: RecoverSecretParams): Promise<DeRecEvent[]>;
890
+ start(flowKind: FlowKind.Unpair, params: UnpairParams): Promise<DeRecEvent[]>;
891
+ start(flowKind: FlowKind.UpdateChannelInfo, params: UpdateChannelInfoParams): Promise<DeRecEvent[]>;
892
+ start(flowKind: FlowKind.SyncCheck, params?: SyncCheckParams): Promise<DeRecEvent[]>;
893
+
894
+ /** Announce a member's removal. This does **not** remove anything on its
895
+ * own and emits no `ReplicaRemoved`: it tells every member and flags the
896
+ * target locally. The removal completes only once the application
897
+ * publishes a roster omitting that member — an ordinary
898
+ * `start(FlowKind.ProtectSecret)` — at which point `ReplicaRemoved`
899
+ * fires. A group with no secret to publish therefore cannot complete a
900
+ * removal. */
901
+ start(flowKind: FlowKind.RemoveReplica, params: RemoveReplicaParams): Promise<DeRecEvent[]>;
902
+
903
+ /**
904
+ * Replace this node's local <c>communication_info</c> map. Does not
905
+ * contact peers — follow up with
906
+ * <c>start(FlowKind.UpdateChannelInfo, ...)</c> to propagate.
907
+ */
908
+ setCommunicationInfo(info: Record<string, string>): void;
909
+
910
+ /**
911
+ * Replace this node's local transport endpoint. IMPORTANT: keep the
912
+ * old endpoint operational during the changeover (see the Rust docs
913
+ * on the matching setter for the discipline).
914
+ */
915
+ setOwnTransport(uri: string, protocol: string): void;
916
+
917
+ process(message: Uint8Array): Promise<DeRecEvent[]>;
918
+
919
+ /**
920
+ * Advance time-driven state without an inbound message.
921
+ *
922
+ * Timeouts are otherwise only evaluated by `process`, so a publish whose
923
+ * helpers all go quiet has nothing left to close it: the round stays open
924
+ * and no `SharingComplete` is ever emitted. Call this from a timer —
925
+ * `setInterval`, a service-worker alarm, a job runner — at an interval
926
+ * shorter than the configured timeout.
927
+ *
928
+ * Safe to call at any time; with nothing in flight it resolves to an empty
929
+ * array. It mutates the same round state an inbound response does, so it
930
+ * must be serialized against `process` for the same `secretId`.
931
+ */
932
+ tick(): Promise<DeRecEvent[]>;
933
+
934
+ accept(actionBytes: Uint8Array): Promise<DeRecEvent[]>;
935
+
936
+ reject(actionBytes: Uint8Array, status: number, memo: string): Promise<void>;
937
+
938
+ /**
939
+ * Derive the human-readable fingerprint for a paired channel. Both sides
940
+ * compute the same value from the shared key — users compare them out of
941
+ * band before calling `verifyFingerprint`. Required for every replica
942
+ * pairing and every `NoKeys` pairing, which stay unusable until it
943
+ * succeeds.
944
+ */
945
+ getFingerprint(channelId: bigint | number): Promise<string>;
946
+
947
+ /**
948
+ * Verify `fingerprint` against the channel's locally-derived one. On
949
+ * match, the channel transitions from `Pending` to `Paired`. Returns
950
+ * `true` on confirmation, `false` on mismatch.
951
+ */
952
+ verifyFingerprint(channelId: bigint | number, fingerprint: string): Promise<boolean>;
953
+ /**
954
+ * Remove `Pending` channels older than `olderThanSecs`, along with their
955
+ * pairing keys. Resolves to the removed channel ids as decimal strings.
956
+ *
957
+ * Independent of the configured cleanup policy — this sweeps at the
958
+ * threshold given even when that policy is disabled. The age comparison
959
+ * is strict, so a channel created within the current second survives
960
+ * even `0`.
961
+ */
962
+ removeExpiredChannels(olderThanSecs: number): Promise<string[]>;
963
+
964
+ /**
965
+ * Rebuild this protocol's `secret_id` namespace from a recovered
966
+ * `Secret`. Mirrors the Rust `DeRecProtocol::restore` — pass the
967
+ * typed `secret` carried by the `SecretRecovered` event verbatim.
968
+ *
969
+ * Errors surface as structured objects with a `code` field:
970
+ *
971
+ * | code | meaning |
972
+ * |--------------------|------------------------------------------------------------------|
973
+ * | `ALREADY_RESTORED` | A user-secret snapshot already exists for this `secret_id`. |
974
+ * | `CONFLICT` | Channels live at canonical helper / replica ids. The error |
975
+ * | | carries `channel_ids: string[]` listing the collisions. |
976
+ * | `INVARIANT` | The recovered `Secret` is internally inconsistent. |
977
+ * | `STORAGE` | A store I/O call failed mid-restore. |
978
+ */
979
+ restore(
980
+ recoveredSecret: Extract<DeRecEvent, { type: "SecretRecovered" }>["secret"],
981
+ version: number,
982
+ ): Promise<DeRecEvent[]>;
983
+ }
984
+
985
+ /**
986
+ * Envelope-level helpers that operate on raw `DeRecMessage` bytes without
987
+ * touching the encrypted inner payload. Useful for primitive-only consumers
988
+ * that need to set or read the `traceId` correlation token themselves
989
+ * (`DeRecProtocol` handles trace_id end-to-end automatically).
990
+ */
991
+ export declare const envelope: {
992
+ /**
993
+ * Re-stamp `traceId` on an outbound envelope. Returns the re-encoded
994
+ * bytes. The encrypted inner message is untouched.
995
+ */
996
+ apply_trace_id(envelope_bytes: Uint8Array, trace_id: bigint): Uint8Array;
997
+
998
+ /**
999
+ * Read `traceId` off an inbound envelope. Returns `0n` when unset (the
1000
+ * protobuf default is indistinguishable from an explicit zero).
1001
+ */
1002
+ read_trace_id(envelope_bytes: Uint8Array): bigint;
1003
+ };
1004
+
1005
+ export interface Timestamp {
1006
+
1007
+ seconds: bigint;
1008
+ nanos: number;
1009
+ }
1010
+
1011
+ export interface DeRecResult {
1012
+ status: number;
1013
+ memo: string;
1014
+ }
1015
+
1016
+ export interface GetSecretIdsVersionsRequestMessage {
1017
+ timestamp?: Timestamp;
1018
+ /** Ephemeral endpoint where the requester wants the response routed.
1019
+ * Absent means "use the channel's stored peer endpoint". */
1020
+ reply_to?: TransportProtocol;
1021
+ }
1022
+
1023
+ export interface VersionList {
1024
+ secret_id: bigint;
1025
+ versions: VersionListEntry[];
1026
+ }
1027
+
1028
+ export interface VersionListEntry {
1029
+ version: number;
1030
+ version_description: string;
1031
+ }
1032
+
1033
+ export interface GetSecretIdsVersionsResponseMessage {
1034
+ result?: DeRecResult;
1035
+ secret_list: VersionList[];
1036
+ timestamp?: Timestamp;
1037
+ }
1038
+
1039
+ export interface VersionEntry {
1040
+ version: number;
1041
+ description: string;
1042
+ }
1043
+
1044
+ export interface SecretVersionEntry {
1045
+ secret_id: bigint;
1046
+ versions: VersionEntry[];
1047
+ }
1048
+
1049
+ export interface TransportProtocol {
1050
+ uri: string;
1051
+
1052
+ protocol: number;
1053
+ }
1054
+
1055
+ export interface CommunicationInfoKeyValue {
1056
+ key: string;
1057
+ string_value: string | null;
1058
+ bytes_value: Uint8Array | null;
1059
+ }
1060
+
1061
+ export interface CommunicationInfo {
1062
+ communication_info_entries: CommunicationInfoKeyValue[];
1063
+ }
1064
+
1065
+ // `ContactMessage` is defined once above (line ~87) and covers both
1066
+ // `INLINE_KEYS` and `HASHED_KEYS` modes.
1067
+
1068
+
1069
+ export interface ParameterRange {
1070
+ min_share_size: bigint;
1071
+ max_share_size: bigint;
1072
+ min_time_between_verifications: bigint;
1073
+ max_time_between_verifications: bigint;
1074
+ min_time_between_share_updates: bigint;
1075
+ max_time_between_share_updates: bigint;
1076
+ min_unresponsive_deletion_timeout: bigint;
1077
+ max_unresponsive_deletion_timeout: bigint;
1078
+ min_unresponsive_deactivation_timeout: bigint;
1079
+ max_unresponsive_deactivation_timeout: bigint;
1080
+ }
1081
+
1082
+ export interface PairRequestMessage {
1083
+ sender_kind: number;
1084
+ mlkem_ciphertext: Uint8Array;
1085
+ ecies_public_key: Uint8Array;
1086
+ nonce: bigint;
1087
+ communication_info?: CommunicationInfo;
1088
+ parameter_range?: ParameterRange;
1089
+ transport_protocol?: TransportProtocol;
1090
+ timestamp?: Timestamp;
1091
+ }
1092
+
1093
+ export interface PairResponseMessage {
1094
+ result?: DeRecResult;
1095
+ nonce: bigint;
1096
+ communication_info?: CommunicationInfo;
1097
+ parameter_range?: ParameterRange;
1098
+ timestamp?: Timestamp;
1099
+ /**
1100
+ * Post-handshake rekey channel id. Both sides switch their local channel
1101
+ * record to this value once the response is accepted. Derived by the
1102
+ * responder as `SHA-384(u64_be(originalChannelId) || sharedKey)[..8]`
1103
+ * interpreted as big-endian `u64`, and validated by the requester against
1104
+ * its own derivation. Zero on rejection (non-Ok `result.status`).
1105
+ */
1106
+ channel_id: bigint;
1107
+ }
1108
+
1109
+ export interface PrePairRequestMessage {
1110
+ nonce: bigint;
1111
+ transport_protocol?: TransportProtocol;
1112
+ timestamp?: Timestamp;
1113
+ }
1114
+
1115
+ export interface PrePairResponseMessage {
1116
+ result?: DeRecResult;
1117
+ /** Present only when `result.status === Ok`. */
1118
+ mlkem_encapsulation_key?: Uint8Array;
1119
+ /** Present only when `result.status === Ok`. */
1120
+ ecies_public_key?: Uint8Array;
1121
+ nonce: bigint;
1122
+ timestamp?: Timestamp;
1123
+ }
1124
+
1125
+ export interface GetShareRequestMessage {
1126
+ secret_id: bigint;
1127
+ version: number;
1128
+ timestamp?: Timestamp;
1129
+ /** Ephemeral response endpoint; see `replyTo` semantics. */
1130
+ reply_to?: TransportProtocol;
1131
+ }
1132
+
1133
+ export interface GetShareResponseMessage {
1134
+ share_algorithm: number;
1135
+
1136
+ committed_de_rec_share: Uint8Array;
1137
+ result?: DeRecResult;
1138
+ timestamp?: Timestamp;
1139
+ /** Echoed from the request so the Owner can correlate responses across
1140
+ * concurrent recoveries without inspecting the share bytes. */
1141
+ secret_id: bigint;
1142
+ /** Echoed from the request for the same correlation reasons as `secret_id`. */
1143
+ version: number;
1144
+ }
1145
+
1146
+ export interface SiblingHash {
1147
+ is_left: boolean;
1148
+ hash: Uint8Array;
1149
+ }
1150
+
1151
+ export interface CommittedDeRecShare {
1152
+
1153
+ de_rec_share: Uint8Array;
1154
+ commitment: Uint8Array;
1155
+ merkle_path: SiblingHash[];
1156
+ }
1157
+
1158
+ export interface StoreShareRequestMessage {
1159
+
1160
+ share: Uint8Array;
1161
+ share_algorithm: number;
1162
+ version: number;
1163
+ keep_list: number[];
1164
+ version_description: string;
1165
+ timestamp?: Timestamp;
1166
+ secret_id: bigint;
1167
+ /** Ephemeral response endpoint; see `replyTo` semantics. */
1168
+ reply_to?: TransportProtocol;
1169
+ }
1170
+
1171
+ export interface StoreShareResponseMessage {
1172
+ result?: DeRecResult;
1173
+ version: number;
1174
+ timestamp?: Timestamp;
1175
+ secret_id: bigint;
1176
+ }
1177
+
1178
+ export interface UnpairRequestMessage {
1179
+ memo: string;
1180
+ timestamp?: Timestamp;
1181
+ /** Ephemeral response endpoint; see `replyTo` semantics. */
1182
+ reply_to?: TransportProtocol;
1183
+ }
1184
+
1185
+ export interface UnpairResponseMessage {
1186
+ result?: DeRecResult;
1187
+ timestamp?: Timestamp;
1188
+ }
1189
+
1190
+ export interface VerifyShareRequestMessage {
1191
+ secret_id: bigint;
1192
+ version: number;
1193
+ nonce: bigint;
1194
+ timestamp?: Timestamp;
1195
+ /** Ephemeral response endpoint; see `replyTo` semantics. */
1196
+ reply_to?: TransportProtocol;
1197
+ }
1198
+
1199
+ export interface VerifyShareResponseMessage {
1200
+ result?: DeRecResult;
1201
+ secret_id: bigint;
1202
+ version: number;
1203
+ nonce: bigint;
1204
+ hash: Uint8Array;
1205
+ timestamp?: Timestamp;
1206
+ }
1207
+
1208
+ export interface ProduceResult {
1209
+
1210
+ envelope: Uint8Array;
1211
+ }
1212
+
1213
+ export interface SharingResponseProduceResult extends ProduceResult {
1214
+ committed_share: CommittedDeRecShare;
1215
+ secret_id: bigint;
1216
+ version: number;
1217
+ }
1218
+
1219
+ export interface CreateContactResult {
1220
+ contact_message: ContactMessage;
1221
+
1222
+ secret_key: Uint8Array;
1223
+ }
1224
+
1225
+ export interface PairingRequestProduceResult extends ProduceResult {
1226
+ initiator_contact_message: ContactMessage;
1227
+
1228
+ secret_key: Uint8Array;
1229
+ }
1230
+
1231
+ export interface PairingResponseProduceResult extends ProduceResult {
1232
+ peer_transport_protocol: TransportProtocol;
1233
+
1234
+ shared_key: Uint8Array;
1235
+
1236
+ /**
1237
+ * Post-handshake rekey channel id the responder is committing to.
1238
+ * Callers MUST atomically rename their local channel record from the
1239
+ * pre-rekey id (the one passed to `pairing.response.produce`) to this
1240
+ * value as part of accepting the response.
1241
+ */
1242
+ channel_id: bigint;
1243
+ }
1244
+
1245
+ export interface PairingProcessResult {
1246
+
1247
+ shared_key: Uint8Array;
1248
+
1249
+ /**
1250
+ * Post-handshake rekey channel id — already validated against the
1251
+ * caller's own derivation. Callers MUST atomically rename their local
1252
+ * channel record from the pre-rekey id (the one in the contact) to this
1253
+ * value.
1254
+ */
1255
+ channel_id: bigint;
1256
+ }
1257
+
1258
+ export interface ProducePrePairResult {
1259
+
1260
+ envelope: Uint8Array;
1261
+ }
1262
+
1263
+ export interface PrePairRequestExtractResult {
1264
+
1265
+ request: PrePairRequestMessage;
1266
+ }
1267
+
1268
+ export interface PrePairResponseExtractResult {
1269
+
1270
+ response: PrePairResponseMessage;
1271
+ }
1272
+
1273
+ export interface ProcessPrePairResult {
1274
+
1275
+ /** Initiator's ML-KEM-768 encapsulation key, validated against the
1276
+ * contact's `contactBindingHash`. */
1277
+ mlkem_encapsulation_key: Uint8Array;
1278
+
1279
+ /** Initiator's ECIES public key, validated against the contact's
1280
+ * `contactBindingHash`. */
1281
+ ecies_public_key: Uint8Array;
1282
+
1283
+ /** Nonce echoed from the original `ContactMessage`. */
1284
+ nonce: bigint;
1285
+ }
1286
+
1287
+ export interface SplitResult {
1288
+ /** Map keyed by channel id (`bigint`). */
1289
+ shares: Map<bigint, CommittedDeRecShare>;
1290
+ }
1291
+
1292
+ export interface RecoverResult {
1293
+ secret_data: Uint8Array;
1294
+ }
1295
+
1296
+ export interface UnpairingProcessResult {
1297
+ acknowledged: boolean;
1298
+ }
1299
+
1300
+ export interface DiscoveryProcessResult {
1301
+ secret_list: SecretVersionEntry[];
1302
+ }
1303
+
1304
+ export type DeRecErrorCategory =
1305
+ | "pairing"
1306
+ | "recovery"
1307
+ | "discovery"
1308
+ | "sharing"
1309
+ | "verification"
1310
+ | "unpairing"
1311
+ | "derec_message"
1312
+ | "secret_store"
1313
+ | "channel_store"
1314
+ | "share_store"
1315
+ | "input"
1316
+ | "protobuf"
1317
+ | "invariant"
1318
+ | "wasm";
1319
+
1320
+ export interface DeRecError {
1321
+ category: DeRecErrorCategory;
1322
+ code: string;
1323
+ message: string;
1324
+ status?: number;
1325
+ memo?: string;
1326
+ expected?: number;
1327
+ got?: number;
1328
+ }
1329
+
1330
+ export declare const primitives: {
1331
+ discovery: {
1332
+ request: {
1333
+ /**
1334
+ * @param reply_to Optional ephemeral response endpoint. `null` /
1335
+ * `undefined` means "no override" (the responder
1336
+ * routes to the channel's stored peer endpoint).
1337
+ */
1338
+ produce(
1339
+ channel_id: bigint,
1340
+ shared_key: Uint8Array,
1341
+ reply_to?: TransportProtocol | null,
1342
+ ): ProduceResult;
1343
+ extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { request: GetSecretIdsVersionsRequestMessage };
1344
+ };
1345
+ response: {
1346
+ produce(channel_id: bigint, secret_list: SecretVersionEntry[], shared_key: Uint8Array): ProduceResult;
1347
+ extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { response: GetSecretIdsVersionsResponseMessage };
1348
+ process(response: GetSecretIdsVersionsResponseMessage): DiscoveryProcessResult;
1349
+ };
1350
+ };
1351
+ pairing: {
1352
+ request: {
1353
+ /**
1354
+ * Creates an out-of-band `ContactMessage` to bootstrap pairing.
1355
+ *
1356
+ * @param channel_id Identifier for the local pairing session.
1357
+ * @param contact_mode `ContactMode.InlineKeys` embeds the keys directly;
1358
+ * `ContactMode.HashedKeys` embeds only a SHA-384
1359
+ * commitment and the scanner must complete a
1360
+ * `PrePair` round-trip first.
1361
+ * @param transport_protocol Endpoint the scanner uses to talk back. For
1362
+ * `HashedKeys` mode it MUST be ephemeral.
1363
+ */
1364
+ create_contact(
1365
+ channel_id: bigint,
1366
+ contact_mode: ContactMode | number,
1367
+ transport_protocol: TransportProtocol,
1368
+ ): CreateContactResult;
1369
+ encode_contact(contact_message: ContactMessage): Uint8Array;
1370
+ decode_contact(bytes: Uint8Array): ContactMessage;
1371
+ produce(
1372
+ kind: SenderKind,
1373
+ transport_protocol: TransportProtocol,
1374
+ contact_message: ContactMessage,
1375
+ communication_info: CommunicationInfo | null,
1376
+ parameter_range: ParameterRange | null,
1377
+ ): PairingRequestProduceResult;
1378
+
1379
+ extract(envelope_bytes: Uint8Array, secret_key: Uint8Array): { request: PairRequestMessage };
1380
+
1381
+ /**
1382
+ * Scanner-side: build a plaintext `PrePairRequest` envelope when the
1383
+ * contact was sent in `HashedKeys` mode. The keys obtained via the
1384
+ * matching `PrePairResponse` MUST be checked against the contact's
1385
+ * binding hash with `pairing.response.process_pre_pair` before
1386
+ * proceeding to a normal `produce`.
1387
+ */
1388
+ produce_pre_pair(
1389
+ transport_protocol: TransportProtocol,
1390
+ contact_message: ContactMessage,
1391
+ ): ProducePrePairResult;
1392
+
1393
+ /**
1394
+ * Initiator-side: decode an inbound plaintext `PrePairRequest`
1395
+ * envelope.
1396
+ */
1397
+ extract_pre_pair(envelope_bytes: Uint8Array): PrePairRequestExtractResult;
1398
+ };
1399
+ response: {
1400
+ produce(
1401
+ channel_id: bigint,
1402
+ request: PairRequestMessage,
1403
+ secret_key: Uint8Array,
1404
+ communication_info: CommunicationInfo | null,
1405
+ parameter_range: ParameterRange | null,
1406
+ ): PairingResponseProduceResult;
1407
+
1408
+ extract(envelope_bytes: Uint8Array, secret_key: Uint8Array): { response: PairResponseMessage };
1409
+ process(
1410
+ contact_message: ContactMessage,
1411
+ response: PairResponseMessage,
1412
+ secret_key: Uint8Array,
1413
+ ): PairingProcessResult;
1414
+
1415
+ /**
1416
+ * Contact-creator side: publish the actual public keys back to the
1417
+ * scanner in response to a `PrePairRequest`.
1418
+ */
1419
+ produce_pre_pair(
1420
+ channel_id: bigint,
1421
+ request: PrePairRequestMessage,
1422
+ secret_key: Uint8Array,
1423
+ ): ProducePrePairResult;
1424
+
1425
+ /**
1426
+ * Scanner-side: decode an inbound plaintext `PrePairResponse`
1427
+ * envelope.
1428
+ */
1429
+ extract_pre_pair(envelope_bytes: Uint8Array): PrePairResponseExtractResult;
1430
+
1431
+ /**
1432
+ * Scanner-side: validate the `PrePairResponse` against the contact's
1433
+ * SHA-384 binding hash. Returns the validated public keys + echoed
1434
+ * nonce on match; throws on mismatch.
1435
+ */
1436
+ process_pre_pair(
1437
+ contact_message: ContactMessage,
1438
+ response: PrePairResponseMessage,
1439
+ ): ProcessPrePairResult;
1440
+ };
1441
+ };
1442
+ recovery: {
1443
+ request: {
1444
+ produce(
1445
+ channel_id: bigint,
1446
+ secret_id: bigint,
1447
+ version: number,
1448
+ shared_key: Uint8Array,
1449
+ /** See `discovery.request.produce.reply_to`. */
1450
+ reply_to?: TransportProtocol | null,
1451
+ ): ProduceResult;
1452
+ extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { request: GetShareRequestMessage };
1453
+ };
1454
+ response: {
1455
+ produce(
1456
+ channel_id: bigint,
1457
+ request: GetShareRequestMessage,
1458
+ stored_share_request: StoreShareRequestMessage,
1459
+ shared_key: Uint8Array,
1460
+ ): ProduceResult;
1461
+ extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { response: GetShareResponseMessage };
1462
+ recover(secret_id: bigint, version: number, responses: GetShareResponseMessage[]): RecoverResult;
1463
+ };
1464
+ };
1465
+ sharing: {
1466
+ request: {
1467
+ split(
1468
+ channels: bigint[],
1469
+ secret_id: bigint,
1470
+ version: number,
1471
+ secret_data: Uint8Array,
1472
+ threshold: number,
1473
+ ): SplitResult;
1474
+ produce(
1475
+ channel_id: bigint,
1476
+ version: number,
1477
+ secret_id: bigint,
1478
+ committed_share: CommittedDeRecShare,
1479
+ keep_list: number[],
1480
+ description: string,
1481
+ shared_key: Uint8Array,
1482
+ /** See `discovery.request.produce.reply_to`. */
1483
+ reply_to?: TransportProtocol | null,
1484
+ ): ProduceResult;
1485
+ extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { request: StoreShareRequestMessage };
1486
+ };
1487
+ response: {
1488
+ produce(
1489
+ channel_id: bigint,
1490
+ request: StoreShareRequestMessage,
1491
+ shared_key: Uint8Array,
1492
+ ): SharingResponseProduceResult;
1493
+ extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { response: StoreShareResponseMessage };
1494
+ process(version: number, response: StoreShareResponseMessage): void;
1495
+ };
1496
+ };
1497
+ unpairing: {
1498
+ request: {
1499
+ produce(
1500
+ channel_id: bigint,
1501
+ memo: string,
1502
+ shared_key: Uint8Array,
1503
+ /** See `discovery.request.produce.reply_to`. */
1504
+ reply_to?: TransportProtocol | null,
1505
+ ): ProduceResult;
1506
+ extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { request: UnpairRequestMessage };
1507
+ };
1508
+ response: {
1509
+ produce(channel_id: bigint, shared_key: Uint8Array): ProduceResult;
1510
+ extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { response: UnpairResponseMessage };
1511
+ process(response: UnpairResponseMessage): UnpairingProcessResult;
1512
+ };
1513
+ };
1514
+ verification: {
1515
+ request: {
1516
+ produce(
1517
+ channel_id: bigint,
1518
+ secret_id: bigint,
1519
+ version: number,
1520
+ shared_key: Uint8Array,
1521
+ /** See `discovery.request.produce.reply_to`. */
1522
+ reply_to?: TransportProtocol | null,
1523
+ ): ProduceResult;
1524
+ extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { request: VerifyShareRequestMessage };
1525
+ };
1526
+ response: {
1527
+ produce(
1528
+ channel_id: bigint,
1529
+ request: VerifyShareRequestMessage,
1530
+ shared_key: Uint8Array,
1531
+ share_content: Uint8Array,
1532
+ ): ProduceResult;
1533
+ extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { response: VerifyShareResponseMessage };
1534
+
1535
+ /** `request` must be the request the owner previously produced
1536
+ * for this challenge (kept by the caller in a per-channel
1537
+ * pending-verification map). Responses whose
1538
+ * `(nonce, secret_id, version)` triple doesn't match are
1539
+ * rejected — that's the anti-replay gate. */
1540
+ process(
1541
+ request: VerifyShareRequestMessage,
1542
+ response: VerifyShareResponseMessage,
1543
+ share_content: Uint8Array,
1544
+ ): boolean;
1545
+ };
1546
+ };
1547
+ };