@derec-alliance/nodejs 0.0.5 → 0.0.7

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 CHANGED
@@ -9,15 +9,15 @@ export interface SecretStore {
9
9
  ): Promise<Uint8Array | null | undefined>;
10
10
  /**
11
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`.
12
+ * scoped to `secretId`. Must return an array with exactly one entry per
13
+ * input id, in the same order, using `null` (or `undefined`) for channels
14
+ * with no stored secret of `kind`. Whether a missing entry is an error is
15
+ * decided by the library.
15
16
  */
16
17
  loadMany(
17
18
  secretId: string,
18
19
  channelIds: string[],
19
20
  kind: 0 | 1 | 2,
20
- missingPolicy: "skip" | "fail",
21
21
  ): Promise<Array<Uint8Array | null | undefined>>;
22
22
  save(
23
23
  secretId: string,
@@ -123,22 +123,15 @@ export declare function channelFilterMatches(
123
123
  /**
124
124
  * The endpoints a peer-supplied message advertises, in the peer's own order.
125
125
  *
126
- * Yields `supported_transports` when it is non-empty, and otherwise the
127
- * singular `transport_protocol` — which is how every implementation predating
128
- * the offer list advertises, and the reason this is a function rather than a
129
- * field read. Reading `transport_protocol` directly is a bug: its meaning
130
- * narrowed to "one entry of a list, and possibly absent", so a peer that has
131
- * moved past it looks unreachable to a reader that was correct before 0.0.3.
132
- *
133
126
  * Reports what was advertised, not what is acceptable — nothing here is
134
127
  * validated, and the protocol still applies its own transport policy to
135
128
  * whatever it records.
136
129
  */
137
130
  export declare function advertisedEndpoints(
138
131
  message:
139
- | Pick<ContactMessage, "transport_protocol" | "supported_transports">
140
- | Pick<PairRequestMessage, "transport_protocol" | "supported_transports">
141
- | Pick<PrePairRequestMessage, "transport_protocol" | "supported_transports">
132
+ | Pick<ContactMessage, "supported_transports">
133
+ | Pick<PairRequestMessage, "supported_transports">
134
+ | Pick<PrePairRequestMessage, "supported_transports">
142
135
  | null
143
136
  | undefined,
144
137
  ): TransportProtocol[];
@@ -228,11 +221,7 @@ export interface ChannelStore {
228
221
  secretId: string,
229
222
  filter: ReplicaFilter,
230
223
  ): Promise<Uint8Array | null | undefined>;
231
- linkChannel(
232
- secretId: string,
233
- channelId: string,
234
- linkedChannelId: string,
235
- ): Promise<void>;
224
+ linkChannel(secretId: string, a: string, b: string): Promise<void>;
236
225
  linkedChannels(secretId: string, channelId: string): Promise<string[]>;
237
226
  }
238
227
 
@@ -264,6 +253,35 @@ export interface ShareStore {
264
253
  save(secretId: string, channelId: string, share: Share): Promise<void>;
265
254
  latestVersion(secretId: string): Promise<number | null>;
266
255
  removeChannel(secretId: string, channelId: string): Promise<void>;
256
+ /**
257
+ * Drop the shares stored under `(secretId, channelId)` at each of
258
+ * `versions`. Idempotent: a version that is not stored is skipped, and
259
+ * an empty array is a no-op.
260
+ *
261
+ * A helper calls this to apply `StoreShareRequestMessage.keepList`, the
262
+ * complete set of versions the owner wants retained: every stored
263
+ * version outside it is removed once the incoming share is persisted.
264
+ * Shares under other channels or partitions must be left untouched.
265
+ */
266
+ removeVersions(secretId: string, channelId: string, versions: number[]): Promise<void>;
267
+ /**
268
+ * Owner only: the versions every helper keeps after the owner distributes
269
+ * `version`. Asked once per sharing round, before anything is sent,
270
+ * including the rounds the library starts itself; the answer becomes
271
+ * `keepList` for every helper.
272
+ *
273
+ * Return `null` or `undefined` to send no `keepList` (it goes out empty):
274
+ * helpers then keep every version they hold. An app that wants to cap how
275
+ * many versions helpers retain returns that cap here. A returned list is
276
+ * used as is, plus `version`, which the library always adds. Helpers
277
+ * delete every version that is not listed, so list every version that
278
+ * could still become the latest: those that committed (for example, whose
279
+ * `SharingComplete` reported `threshold_met`) and those whose round is
280
+ * still open. Leave out only versions whose round failed or that the user
281
+ * rolled back; a list that leaves out too much can make the secret
282
+ * unrecoverable.
283
+ */
284
+ keepList(secretId: string, version: number): Promise<number[] | null | undefined>;
267
285
  }
268
286
 
269
287
  export interface UserSecretEntry {
@@ -276,6 +294,10 @@ export interface UserSecrets {
276
294
  version: number;
277
295
  secrets: UserSecretEntry[];
278
296
  description?: string;
297
+ /** Decimal `replica_id` of the member that published `version`. Absent
298
+ * when none is recorded. Store and return it unchanged: replica members
299
+ * compare it to detect a conflicting copy of the same version. */
300
+ author_replica_id?: string;
279
301
  }
280
302
 
281
303
  /**
@@ -319,6 +341,18 @@ export interface StateStore {
319
341
  loadAll(secretId: string, kind: 0 | 1 | 2 | 3 | 4): Promise<Uint8Array[]>;
320
342
  }
321
343
 
344
+ /** A transport protocol, by name. */
345
+ export type TransportProtocolName = "https" | "grpc";
346
+
347
+ /**
348
+ * One endpoint a node serves or a peer advertised, as every app-facing call
349
+ * and event carries it.
350
+ */
351
+ export interface Endpoint {
352
+ uri: string;
353
+ protocol: TransportProtocolName;
354
+ }
355
+
322
356
  /**
323
357
  * Outbound message delivery.
324
358
  *
@@ -364,7 +398,7 @@ export interface Transport {
364
398
  * with {@link singleEndpointTransport} rather than by indexing.
365
399
  */
366
400
  send(
367
- endpoints: ReadonlyArray<{ protocol: string; uri: string }>,
401
+ endpoints: ReadonlyArray<Endpoint>,
368
402
  message: Uint8Array,
369
403
  ): Promise<void>;
370
404
  }
@@ -384,10 +418,25 @@ export interface Transport {
384
418
  * a round trip; skipping one that would have worked costs the delivery.
385
419
  */
386
420
  export type SendOne = (
387
- endpoint: { protocol: string; uri: string },
421
+ endpoint: Endpoint,
388
422
  message: Uint8Array,
389
423
  ) => Promise<void>;
390
424
 
425
+ /**
426
+ * The DeRec protocol version this build speaks — the `protocolVersionMajor` /
427
+ * `protocolVersionMinor` it writes into every envelope it produces. Not the
428
+ * package version.
429
+ */
430
+ export declare function protocol_version(): { major: number; minor: number };
431
+
432
+ /**
433
+ * A fresh replica identity, generated by the core — never `0`. Persist it
434
+ * once per device and pass the same value to `withReplicaId` on every
435
+ * protocol init: a device whose id changes cannot be re-identified by its
436
+ * group.
437
+ */
438
+ export declare function generate_replica_id(): bigint;
439
+
391
440
  /**
392
441
  * Builds a {@link Transport} that tries each endpoint in the order the peer
393
442
  * offered it and stops at the first success.
@@ -432,7 +481,7 @@ export enum SenderKind {
432
481
  * the scanner must fetch the actual keys over the wire via the `PrePair`
433
482
  * round-trip and verify them against the commitment before pairing.
434
483
  * - `NoKeys`: no key material and no commitment. The contact carries only
435
- * `channel_id`, `nonce`, and `transport_protocol` — small enough to be
484
+ * `channel_id`, `nonce`, and `supported_transports` — small enough to be
436
485
  * hand-typed or dictated. Keys are generated on the fly by the contact
437
486
  * creator when the `PrePairRequest` arrives; the scanner accepts them
438
487
  * without cryptographic verification. Trust rests entirely on the OOB
@@ -474,20 +523,33 @@ export enum FlowKind {
474
523
  UnpairReplica = 8,
475
524
  }
476
525
 
526
+ /** Result status carried in every protocol response (`result.proto`). Passed
527
+ * to `reject()` to say why an inbound request was refused. */
528
+ export enum StatusEnum {
529
+ Ok = 0,
530
+ Partial = 1,
531
+ Fail = 2,
532
+ SizeLimitExceeded = 3,
533
+ TooFrequent = 4,
534
+ UnknownSecretId = 5,
535
+ UnknownShareVersion = 6,
536
+ DecryptionFailed = 7,
537
+ VerificationFailed = 8,
538
+ FormatError = 9,
539
+ Rejected = 10,
540
+ IncompatibleParameterRange = 11,
541
+ UnsupportedTransportProtocol = 12,
542
+ VersionConflict = 13,
543
+ ReplicaIdConflict = 14,
544
+ RequestToClose = 99,
545
+ }
546
+
477
547
  export type UnpairAck = "required" | "not_required";
478
548
 
479
549
  export interface ContactMessage {
480
550
  channel_id: bigint;
481
551
  /** `ContactMode` numeric value (0 = INLINE_KEYS, 1 = HASHED_KEYS, 2 = NO_KEYS). */
482
552
  contact_mode: number;
483
- /**
484
- * @deprecated Reading this field directly is incorrect: its meaning narrowed
485
- * to "one entry of a list, and possibly absent", so a peer advertising only
486
- * `supported_transports` looks unreachable to a reader that was correct
487
- * before 0.0.3. Call {@link advertisedEndpoints}, which resolves both
488
- * spellings. Removed at 0.0.5.
489
- */
490
- transport_protocol?: TransportProtocol;
491
553
  nonce: bigint;
492
554
  /** Present only when `contact_mode === ContactMode.InlineKeys`. */
493
555
  mlkem_encapsulation_key?: Uint8Array;
@@ -497,8 +559,7 @@ export interface ContactMessage {
497
559
  contact_binding_hash?: Uint8Array;
498
560
  timestamp?: Timestamp;
499
561
  /** Every transport endpoint the creator of this contact can be reached
500
- * on, in its own preference order. Empty means "only
501
- * `transport_protocol` is offered". */
562
+ * on, in its own preference order. */
502
563
  supported_transports: TransportProtocol[];
503
564
  }
504
565
 
@@ -546,20 +607,11 @@ export interface UpdateChannelInfoParams {
546
607
  * map untouched; pass an empty object to clear it. */
547
608
  communication_info?: Record<string, string>;
548
609
 
549
- /**
550
- * New transport endpoint. Absent leaves it untouched.
551
- *
552
- * @deprecated Use `own_transports`, which carries every endpoint this node
553
- * now serves; its first entry also fills this field for peers predating the
554
- * list. Removed at 0.0.5.
555
- */
556
- transport_protocol?: { uri: string; protocol: number };
557
610
  /**
558
611
  * Every endpoint this node now serves, in its own preference order.
559
- * Omitted leaves the target(s)' stored set untouched. Takes precedence
560
- * over `transport_protocol`, whose first entry it also fills.
612
+ * Omitted leaves the target(s)' stored set untouched.
561
613
  */
562
- own_transports?: TransportProtocol[];
614
+ own_transports?: Endpoint[];
563
615
  }
564
616
 
565
617
  /**
@@ -592,6 +644,15 @@ export interface Timeouts {
592
644
  * are both read from the stores. The argument may be omitted entirely. */
593
645
  export type ReplicaDiscoveryParams = Record<string, never>;
594
646
 
647
+ /** Any member may remove any member, the source included: a lost or stolen
648
+ * source must be removable by the devices that remain, and the library
649
+ * checks no role. Ask the user before starting this flow, above all when it
650
+ * names the source. Removing the source promotes the first remaining member
651
+ * in the order the channel store's `listReplicas` returns. The removed
652
+ * member is not asked and gets no event when told to leave: when a roster
653
+ * excluding it arrives it drops its whole `secret_id` partition and emits
654
+ * `SelfRemovedFromGroup`. The secret survives on the remaining members and
655
+ * the helpers. */
595
656
  export interface UnpairReplicaParams {
596
657
  /** The member to remove, as a **decimal** `u64` string — the same form
597
658
  * `ReplicaPaired.peer_replica_id` hands back. A value naming no current
@@ -618,18 +679,34 @@ export type DeRecEvent =
618
679
  action: Uint8Array;
619
680
 
620
681
  action_kind: PendingActionKind;
682
+ /** Correlation token of the inbound request, decimal-encoded. */
683
+ trace_id: string;
684
+ /** Pairing only. */
621
685
  peer_communication_info?: Record<string, string>;
622
-
686
+ /** `sender_kind` of the inbound pair request (Pairing only). */
623
687
  sender_kind?: SenderKind;
624
-
688
+ /** Share version (StoreShare / VerifyShare / GetShare). */
625
689
  version?: number;
690
+ /** Description of the secret version (StoreShare only). */
626
691
  share_description?: string;
627
-
692
+ /** Secret identifier, decimal-encoded (StoreShare / VerifyShare /
693
+ * GetShare). On GetShare, with `version`, names the requested share. */
628
694
  share_secret_id?: string;
695
+ /** Length in bytes of the share the helper would store (StoreShare
696
+ * only) — what a size or quota decision is made on. */
697
+ share_size?: number;
698
+ /** The peer's memo (Unpair only). */
699
+ unpair_memo?: string;
700
+ /** Communication info the peer replaces its stored map with
701
+ * (UpdateChannelInfo only). Absent: unchanged. Empty: cleared. */
702
+ updated_communication_info?: Record<string, string>;
703
+ /** Endpoints the peer is moving to (UpdateChannelInfo only). Absent:
704
+ * unchanged. */
705
+ updated_transports?: Endpoint[];
629
706
  }
630
707
  | { type: "ShareStored"; channel_id: string; version: number }
631
708
  | { type: "ShareConfirmed"; channel_id: string; version: number }
632
- | { type: "ShareRejected"; channel_id: string; version: number; status: number; memo: string }
709
+ | { type: "ShareRejected"; channel_id: string; version: number; status: StatusEnum; memo: string }
633
710
  /** A publishing round finished — every targeted helper confirmed,
634
711
  * rejected, or timed out.
635
712
  *
@@ -647,14 +724,17 @@ export type DeRecEvent =
647
724
  | { type: "SharingComplete"; version: number; confirmed_count: number; failed_count: number; threshold_met: boolean }
648
725
  /** A group member refused a secret sync. Keyed by `replica_id`, not
649
726
  * `channel_id`: every member answers on the one group channel. A
650
- * `VERSION_CONFLICT` status means the round must be resolved and
651
- * republished at a new version. */
727
+ * `VERSION_CONFLICT` status means another member holds a different copy
728
+ * of this version: do not publish from this device again until the
729
+ * conflict is resolved. Run `start(FlowKind.ReplicaDiscovery)` to receive
730
+ * the group's copy as `ReplicaVersionConflict`, merge, and publish the
731
+ * result once with `start(FlowKind.ProtectSecret)`. */
652
732
  | {
653
733
  type: "ReplicaSyncRejected";
654
734
  replica_id: string;
655
735
  secret_id: string;
656
736
  version: number;
657
- status: number;
737
+ status: StatusEnum;
658
738
  memo: string;
659
739
  }
660
740
  /** A secret sync could not be delivered to a member at all — distinct from
@@ -671,7 +751,8 @@ export type DeRecEvent =
671
751
  /** This device left the group and dropped its whole `secret_id` partition —
672
752
  * group channel, helper channels, shares, secrets and the snapshot. Fires
673
753
  * only once it was told to leave *and* has since seen a roster excluding
674
- * it; absence alone never destroys a copy of the secret. */
754
+ * it; absence alone never destroys a copy of the secret. The teardown is
755
+ * automatic: this device is not asked first and gets no earlier event. */
675
756
  | { type: "SelfRemovedFromGroup"; version: number }
676
757
  /** A replica catch-up finished. `fetched_from` is absent when this device
677
758
  * was already current, in which case no hydration event follows. */
@@ -687,6 +768,10 @@ export type DeRecEvent =
687
768
  * library keeps no durable per-member sync state. */
688
769
  | { type: "ReplicaSyncComplete"; version: number; synced: string[]; behind: string[] }
689
770
  | { type: "ShareVerified"; channel_id: string; version: number }
771
+ /** A helper refused a verification challenge: its response carried a
772
+ * non-OK `status` instead of a proof. The challenge is spent; a new
773
+ * `VerifyShares` round challenges the helper again. */
774
+ | { type: "ShareVerifyRejected"; channel_id: string; version: number; status: StatusEnum; memo: string }
690
775
  | {
691
776
  type: "SecretsDiscovered";
692
777
  channel_id: string;
@@ -695,6 +780,22 @@ export type DeRecEvent =
695
780
  }
696
781
  | { type: "RecoveryShareReceived"; channel_id: string; shares_received: number }
697
782
  | { type: "RecoveryShareError"; channel_id: string; shares_received: number; error: string }
783
+ /** A helper refused a recovery share request: its response carried a
784
+ * non-OK `status` (e.g. `UNKNOWN_SHARE_VERSION`) instead of a share. The
785
+ * refusal is not collected — it does not count towards `shares_received`
786
+ * and the recovery stays open for the other helpers' shares — but it does
787
+ * answer that helper's `RecoverSecretStarted`. */
788
+ | { type: "RecoveryShareRefused"; channel_id: string; version: number; status: StatusEnum; memo: string }
789
+ /** A helper answered with a share that cannot be part of the secret;
790
+ * `reason` says how it failed. `Malformed` and `InvalidProof` are judged
791
+ * on arrival; `Inconsistent` (valid on its own but disagreeing with the
792
+ * shares the secret was rebuilt from) is reported alongside
793
+ * `SecretRecovered`, once per helper. The share is set aside — it does
794
+ * not count towards `shares_received` and never blocks the recovery. An
795
+ * honest helper never sends one, so the app may treat it as a sign of a
796
+ * damaged or compromised helper, e.g. offer to unpair it. It also
797
+ * answers that helper's `RecoverSecretStarted`. */
798
+ | { type: "RecoveryShareCorrupted"; channel_id: string; version: number; reason: CorruptionReason }
698
799
  /** Recovery completed — the typed `Secret` snapshot the owner
699
800
  * originally protected. Mirrors `ReplicaSecretReceived.secret`:
700
801
  * `secrets` is the user-facing `Vec<UserSecret>` the application
@@ -707,9 +808,9 @@ export type DeRecEvent =
707
808
  helpers: Array<{
708
809
  channel_id: string;
709
810
  /** Every endpoint this peer advertised, in the order it offered them. */
710
- transports: Array<{ uri: string; protocol: number }>;
811
+ transports: Endpoint[];
711
812
  shared_key: Uint8Array;
712
- communication_info: Record<string, string>;
813
+ communication_info?: Record<string, string>;
713
814
  }>;
714
815
  secrets: Array<{
715
816
  id: Uint8Array;
@@ -729,9 +830,9 @@ export type DeRecEvent =
729
830
  members: Array<{
730
831
  replica_id: string;
731
832
  /** Every endpoint this peer advertised, in the order it offered them. */
732
- transports: Array<{ uri: string; protocol: number }>;
833
+ transports: Endpoint[];
733
834
  role: "Source" | "Destination";
734
- communication_info: Record<string, string>;
835
+ communication_info?: Record<string, string>;
735
836
  }>;
736
837
  shared_key: Uint8Array;
737
838
  };
@@ -740,12 +841,12 @@ export type DeRecEvent =
740
841
 
741
842
  | { type: "Unpaired"; channel_id: string }
742
843
 
743
- | { type: "UnpairRejected"; channel_id: string; status: number; memo: string }
844
+ | { type: "UnpairRejected"; channel_id: string; status: StatusEnum; memo: string }
744
845
 
745
846
  /** Contact creator answered the scanner's `PrePairRequest` with a
746
847
  * non-Ok status (HashedKeys flow). Distinct from a cryptographic
747
848
  * hash mismatch, which surfaces as a thrown error from `process()`. */
748
- | { type: "PrePairRejected"; channel_id: string; status: number; memo: string }
849
+ | { type: "PrePairRejected"; channel_id: string; status: StatusEnum; memo: string }
749
850
 
750
851
  /** Fires alongside `PairingCompleted` on replica-mode pair handshakes.
751
852
  * `peer_replica_id` is the peer's `u64` as a **decimal** string,
@@ -763,22 +864,28 @@ export type DeRecEvent =
763
864
  /** A `ReplicaSource` peer pushed a secret sync on a
764
865
  * `ReplicaDestination` channel. The library decoded the
765
866
  * `ReplicaSecretPayload`; the app installs `secret.secrets` and
766
- * optionally uses `shares` for recovery. `from_replica_id` and the
767
- * `replica_id` fields inside `secret` are `u64` as **decimal**
768
- * strings. */
867
+ * optionally uses `shares` for recovery. `from_replica_id`,
868
+ * `author_replica_id` and the `replica_id` fields inside `secret` are
869
+ * `u64` as **decimal** strings.
870
+ *
871
+ * `from_replica_id` is the member this copy came from: the publisher on
872
+ * a push, the serving member on a catch-up. `author_replica_id` is the
873
+ * member that published `version`, or `null` when the serving member's
874
+ * snapshot records none. */
769
875
  | {
770
876
  type: "ReplicaSecretReceived";
771
877
  channel_id: string;
772
878
  from_replica_id: string;
879
+ author_replica_id: string | null;
773
880
  secret_id: string;
774
881
  version: number;
775
882
  secret: {
776
883
  helpers: Array<{
777
884
  channel_id: string;
778
885
  /** Every endpoint this peer advertised, in the order it offered them. */
779
- transports: Array<{ uri: string; protocol: number }>;
886
+ transports: Endpoint[];
780
887
  shared_key: Uint8Array;
781
- communication_info: Record<string, string>;
888
+ communication_info?: Record<string, string>;
782
889
  }>;
783
890
  secrets: Array<{
784
891
  id: Uint8Array;
@@ -796,9 +903,9 @@ export type DeRecEvent =
796
903
  members: Array<{
797
904
  replica_id: string;
798
905
  /** Every endpoint this peer advertised, in the order it offered them. */
799
- transports: Array<{ uri: string; protocol: number }>;
906
+ transports: Endpoint[];
800
907
  role: "Source" | "Destination";
801
- communication_info: Record<string, string>;
908
+ communication_info?: Record<string, string>;
802
909
  }>;
803
910
  shared_key: Uint8Array;
804
911
  };
@@ -821,15 +928,16 @@ export type DeRecEvent =
821
928
  type: "ReplicaSecretInstalled";
822
929
  channel_id: string;
823
930
  from_replica_id: string;
931
+ author_replica_id: string | null;
824
932
  secret_id: string;
825
933
  version: number;
826
934
  secret: {
827
935
  helpers: Array<{
828
936
  channel_id: string;
829
937
  /** Every endpoint this peer advertised, in the order it offered them. */
830
- transports: Array<{ uri: string; protocol: number }>;
938
+ transports: Endpoint[];
831
939
  shared_key: Uint8Array;
832
- communication_info: Record<string, string>;
940
+ communication_info?: Record<string, string>;
833
941
  }>;
834
942
  secrets: Array<{
835
943
  id: Uint8Array;
@@ -847,9 +955,9 @@ export type DeRecEvent =
847
955
  members: Array<{
848
956
  replica_id: string;
849
957
  /** Every endpoint this peer advertised, in the order it offered them. */
850
- transports: Array<{ uri: string; protocol: number }>;
958
+ transports: Endpoint[];
851
959
  role: "Source" | "Destination";
852
- communication_info: Record<string, string>;
960
+ communication_info?: Record<string, string>;
853
961
  }>;
854
962
  shared_key: Uint8Array;
855
963
  };
@@ -859,6 +967,59 @@ export type DeRecEvent =
859
967
  committed_share: Uint8Array;
860
968
  }>;
861
969
  }
970
+ /** A member offered a different copy of the version this device holds.
971
+ * Nothing was written: this device keeps its own copy and refuses the
972
+ * incoming one with `VERSION_CONFLICT`, so the publisher sees
973
+ * `ReplicaSyncRejected`. Both copies are complete states — the held one
974
+ * is in the local stores, the incoming one is `secret`. Resolve by
975
+ * publishing the chosen state with `start(FlowKind.ProtectSecret)`; the
976
+ * next version supersedes both on every member and helper. Until then,
977
+ * do not publish from this device: any further `ProtectSecret` is a
978
+ * higher version that every other member applies over its own copy,
979
+ * losing the change it never merged.
980
+ *
981
+ * `held_author_replica_id` / `incoming_author_replica_id` are the
982
+ * decimal `replica_id` of each copy's publisher, or `null` when that
983
+ * copy records none. */
984
+ | {
985
+ type: "ReplicaVersionConflict";
986
+ channel_id: string;
987
+ from_replica_id: string;
988
+ secret_id: string;
989
+ version: number;
990
+ held_author_replica_id: string | null;
991
+ incoming_author_replica_id: string | null;
992
+ secret: {
993
+ helpers: Array<{
994
+ channel_id: string;
995
+ /** Every endpoint this peer advertised, in the order it offered them. */
996
+ transports: Endpoint[];
997
+ shared_key: Uint8Array;
998
+ communication_info?: Record<string, string>;
999
+ }>;
1000
+ secrets: Array<{
1001
+ id: Uint8Array;
1002
+ name: string;
1003
+ data: Uint8Array;
1004
+ }>;
1005
+ /** The same shape as `SecretRecovered.secret.replicas`. */
1006
+ replicas?: {
1007
+ /** The one channel every member is addressed on. */
1008
+ channel_id: string;
1009
+ /** Every member of the group, including the writer. Exactly one
1010
+ * carries `role: "Source"` — that member is where the secret
1011
+ * originated, which is why no separate owner field is needed. */
1012
+ members: Array<{
1013
+ replica_id: string;
1014
+ /** Every endpoint this peer advertised, in the order it offered them. */
1015
+ transports: Endpoint[];
1016
+ role: "Source" | "Destination";
1017
+ communication_info?: Record<string, string>;
1018
+ }>;
1019
+ shared_key: Uint8Array;
1020
+ };
1021
+ };
1022
+ }
862
1023
  /** Peer's ack of a secret sync we sent. `status` is the `StatusEnum`
863
1024
  * integer (0 = Ok), `memo` is the peer's explanation. */
864
1025
  | {
@@ -867,7 +1028,7 @@ export type DeRecEvent =
867
1028
  from_replica_id: string;
868
1029
  secret_id: string;
869
1030
  version: number;
870
- status: number;
1031
+ status: StatusEnum;
871
1032
  memo: string;
872
1033
  }
873
1034
  /** A peer announced an updated `communication_info` map and/or
@@ -884,7 +1045,7 @@ export type DeRecEvent =
884
1045
  | {
885
1046
  type: "ChannelInfoUpdateRejected";
886
1047
  channel_id: string;
887
- status: number;
1048
+ status: StatusEnum;
888
1049
  memo: string;
889
1050
  }
890
1051
  /** Emitted by `process()` in place of `ActionRequired` when the
@@ -896,6 +1057,38 @@ export type DeRecEvent =
896
1057
  * (`"Pairing"`, `"StoreShare"`, …). */
897
1058
  | { type: "AutoAccepted"; channel_id: string; action_kind: PendingActionKind }
898
1059
  | { type: "NoOp" }
1060
+ /** An inbound message was dropped untouched: no store was written and
1061
+ * nothing was sent back. `reason` says why.
1062
+ *
1063
+ * `"PendingVerification"` means the peer sent something before this
1064
+ * device confirmed the channel's fingerprint — typically a replica source
1065
+ * pushing its first copy while this destination still shows the code.
1066
+ * Confirming does not replay it: after `verifyFingerprint` succeeds, a
1067
+ * replica destination calls `start(FlowKind.ReplicaDiscovery)` to pull
1068
+ * the copy itself. `"Expired"` means the message was older than the
1069
+ * inbound timeout. `trace_id` matches the peer's `*Started` event for
1070
+ * the same round (`"0"` when the sender set none). */
1071
+ | {
1072
+ type: "MessageIgnored";
1073
+ channel_id: string;
1074
+ reason: IgnoreReason;
1075
+ trace_id: string;
1076
+ }
1077
+ /** Returned by `restore`: a roster entry got no channel, so this device
1078
+ * cannot reach that peer. Every other entry and the user-secret snapshot
1079
+ * were restored; `reason` says why this one was not. `"NoTransports"`
1080
+ * means the recovered roster names no endpoint for it.
1081
+ *
1082
+ * For a helper, `channel_id` is its channel and `replica_id` is absent.
1083
+ * For a replica group member, `channel_id` is the group's channel and
1084
+ * `replica_id` names the member. The peer itself is untouched — a helper
1085
+ * still holds its share — and pairing with it again makes it reachable. */
1086
+ | {
1087
+ type: "PeerNotRestored";
1088
+ channel_id: string;
1089
+ replica_id?: string;
1090
+ reason: NotRestoredReason;
1091
+ }
899
1092
  /** A pairing handshake was dispatched successfully. `kind` is the
900
1093
  * local party's role — same value the subsequent `PairingCompleted`
901
1094
  * will carry. Emitted by `start(Pairing)`. */
@@ -964,8 +1157,8 @@ export type DeRecEvent =
964
1157
  * inbound shares. The protocol enforces no size, quota or rate limit
965
1158
  * of its own, and `maxShareSize` is checked for range overlap at
966
1159
  * pairing time only, never against an actual share. While this is
967
- * `false`, `ActionRequired` carries the decoded request, so the
968
- * application can inspect the share and call `reject()` with
1160
+ * `false`, `ActionRequired` carries `share_size`, so the application
1161
+ * can compare it to its quota and call `reject()` with
969
1162
  * `StatusEnum.SizeLimitExceeded`. Setting it `true` removes that
970
1163
  * opportunity entirely: every share from every paired Owner is stored
971
1164
  * unconditionally, at whatever size it arrives. Keep off in any
@@ -993,10 +1186,23 @@ export interface AutoAcceptPolicy {
993
1186
  * between them without reaching for reference docs.
994
1187
  *
995
1188
  * Required setters: `withChannelStore`, `withShareStore`,
996
- * `withSecretStore`, `withTransport`, and either `withOwnTransport` or
997
- * `withOwnTransports`. Calling `build()` without all five throws.
1189
+ * `withSecretStore`, `withUserSecretStore`, `withStateStore`,
1190
+ * `withTransport`, and `withOwnTransports`. Calling `build()` without all
1191
+ * seven throws.
1192
+ *
1193
+ * An optional setter that is never called leaves the library's default in
1194
+ * force.
998
1195
  */
999
1196
  export declare class DeRecProtocolBuilder {
1197
+ /**
1198
+ * Release the builder's memory in the library. `build()` already releases
1199
+ * it; call this only for a builder that is discarded unbuilt. Safe to call
1200
+ * more than once.
1201
+ */
1202
+ free(): void;
1203
+ /** Same as {@link DeRecProtocolBuilder.free}, for `using`. */
1204
+ [Symbol.dispose](): void;
1205
+
1000
1206
  /**
1001
1207
  * Construct a builder bound to a specific secret. `secretId`
1002
1208
  * identifies the single secret this protocol instance manages.
@@ -1011,33 +1217,21 @@ export declare class DeRecProtocolBuilder {
1011
1217
  withUserSecretStore(store: UserSecretStore): DeRecProtocolBuilder;
1012
1218
  withStateStore(store: StateStore): DeRecProtocolBuilder;
1013
1219
  withTransport(transport: Transport): DeRecProtocolBuilder;
1014
- /**
1015
- * @deprecated Use {@link withOwnTransports}, which takes the whole
1016
- * preference list — `withOwnTransports([endpoint])` is the direct
1017
- * replacement. Removed at 0.0.5.
1018
- */
1019
- withOwnTransport(endpoint: { uri: string; protocol: string }): DeRecProtocolBuilder;
1020
1220
  /**
1021
1221
  * Set every transport endpoint this application serves, in preference
1022
1222
  * order. `protocol` is `"https"` or `"grpc"` (case-insensitive) per
1023
- * entry, same as {@link withOwnTransport}.
1223
+ * entry.
1024
1224
  *
1025
1225
  * The order is this application's own preference and decides which of
1026
1226
  * a peer's offered endpoints is used; it is not sorted, deduplicated,
1027
1227
  * or reordered. Every listed transport must actually be served,
1028
1228
  * because delivery is push-only — listing an endpoint this application
1029
1229
  * does not serve makes pairing succeed and replies vanish.
1030
- *
1031
- * Supersedes {@link withOwnTransport} for applications serving more
1032
- * than one transport; the single-endpoint setter remains fully
1033
- * supported.
1034
1230
  */
1035
- withOwnTransports(transports: { uri: string; protocol: string }[]): DeRecProtocolBuilder;
1231
+ withOwnTransports(transports: Endpoint[]): DeRecProtocolBuilder;
1036
1232
 
1037
- /** Default: 3. */
1233
+ /** Minimum number of shares required to reconstruct the secret. Default: 3. */
1038
1234
  withThreshold(threshold: number): DeRecProtocolBuilder;
1039
- /** Default: 3. */
1040
- withKeepVersionsCount(count: number): DeRecProtocolBuilder;
1041
1235
  /**
1042
1236
  * Configure how long the protocol waits on each thing that can keep it
1043
1237
  * waiting. Every field is optional and **absent means "keep the library
@@ -1055,8 +1249,8 @@ export declare class DeRecProtocolBuilder {
1055
1249
  withTimeouts(timeouts: Timeouts): DeRecProtocolBuilder;
1056
1250
 
1057
1251
  /**
1058
- * Accept plaintext `http://` transport endpoints. **Development only.**
1059
- * Default: `false`.
1252
+ * Accept plaintext `http://` and `grpc://` transport endpoints.
1253
+ * **Development only.** Default: `false`.
1060
1254
  *
1061
1255
  * With `false`, plaintext is accepted in exactly one situation: an endpoint
1062
1256
  * this device configured for **itself** that names loopback (`localhost`,
@@ -1072,33 +1266,20 @@ export declare class DeRecProtocolBuilder {
1072
1266
  * delivery is your `Transport`. Nothing here stops an application sending
1073
1267
  * plaintext — it governs which endpoints the protocol will record,
1074
1268
  * propagate to peers, and reply to.
1075
- *
1076
- * @deprecated Use {@link withUnsafeConnection}, which names both gated
1077
- * schemes. Removed at 0.0.5.
1078
- */
1079
- withUnsafeHttp(allow: boolean): DeRecProtocolBuilder;
1080
- /**
1081
- * Accept plaintext `http://` and `grpc://` transport endpoints.
1082
- * **Development only.** Default: `false`. Supersedes
1083
- * {@link withUnsafeHttp}, which names only the HTTP scheme.
1084
- *
1085
- * Either flag alone is honored. Setting both to disagreeing values fails
1086
- * construction with the error code `CONFLICTING_PLAINTEXT_OPT_IN` rather
1087
- * than resolving silently, because precedence would hand the decision to
1088
- * the flag being removed.
1089
1269
  */
1090
1270
  withUnsafeConnection(allow: boolean): DeRecProtocolBuilder;
1091
1271
  /** Default: empty. */
1092
1272
  withCommunicationInfo(info: Record<string, string>): DeRecProtocolBuilder;
1093
1273
  /** Default: false. */
1094
1274
  withAutoRespondOnFailure(enabled: boolean): DeRecProtocolBuilder;
1095
- /** Default: "required". */
1275
+ /** Exactly `"required"` or `"not_required"`; any other string throws
1276
+ * `invalid_unpair_ack`. Default: `"required"`. */
1096
1277
  withUnpairAck(ack: UnpairAck): DeRecProtocolBuilder;
1097
1278
  /**
1098
1279
  * When `true`, every outbound channel-mode request stamps
1099
- * `request.replyTo = ownTransport` so the responder routes its reply
1100
- * back here even if the channel's stored peer endpoint points
1101
- * elsewhere. Default: false.
1280
+ * `request.reply_to` with this node's own transports so the responder
1281
+ * routes its reply back here even if the channel's stored peer endpoint
1282
+ * points elsewhere. Default: false.
1102
1283
  */
1103
1284
  withAutoReplyTo(enabled: boolean): DeRecProtocolBuilder;
1104
1285
  /**
@@ -1113,7 +1294,8 @@ export declare class DeRecProtocolBuilder {
1113
1294
  /**
1114
1295
  * Stable per-device replica id. Required to participate in any
1115
1296
  * `ReplicaSource` / `ReplicaDestination` pairing. The id must be
1116
- * stable across restarts. Default: unset.
1297
+ * stable across restarts: mint it once with {@link generate_replica_id}
1298
+ * and persist it. Default: unset.
1117
1299
  */
1118
1300
  withReplicaId(id: bigint | number): DeRecProtocolBuilder;
1119
1301
  /**
@@ -1136,10 +1318,33 @@ export declare class DeRecProtocolBuilder {
1136
1318
  build(): DeRecProtocol;
1137
1319
  }
1138
1320
 
1321
+ /**
1322
+ * Every method that touches protocol state returns a `Promise` and runs under
1323
+ * one lock per instance, so overlapping calls on the same instance — a `tick`
1324
+ * timer firing while `process` handles an inbound message — queue and run one
1325
+ * at a time, in the order they were made. Distinct instances do not share the
1326
+ * lock: two instances bound to the same `secretId` and the same stores must
1327
+ * still be serialized by the caller.
1328
+ *
1329
+ * A store or transport callback must not await a call on the instance that
1330
+ * invoked it: that call queues behind the one waiting on the callback, and
1331
+ * neither settles. Calling `free()` while a call is in flight throws.
1332
+ */
1139
1333
  export declare class DeRecProtocol {
1140
1334
  /** Use {@link DeRecProtocolBuilder} to construct instances. */
1141
1335
  private constructor();
1142
1336
 
1337
+ /**
1338
+ * Release this protocol's memory in the library, and its references to the
1339
+ * stores and transport. The library's memory is not reclaimed by the
1340
+ * JavaScript garbage collector in time to rely on, so call this when done.
1341
+ * Safe to call more than once; any other method called afterwards throws,
1342
+ * as does calling this while a call is in flight.
1343
+ */
1344
+ free(): void;
1345
+ /** Same as {@link DeRecProtocol.free}, for `using`. */
1346
+ [Symbol.dispose](): void;
1347
+
1143
1348
  /** The secret identifier this protocol instance is bound to. */
1144
1349
  secretId(): bigint;
1145
1350
 
@@ -1152,8 +1357,8 @@ export declare class DeRecProtocol {
1152
1357
  * directly in the contact. `ContactMode.HashedKeys`
1153
1358
  * embeds only a SHA-384 binding hash (keys are
1154
1359
  * fetched later via the `PrePair` round-trip).
1155
- * `HashedKeys` requires `ownTransportUri` to be
1156
- * ephemeral.
1360
+ * `HashedKeys` requires the advertised own
1361
+ * transports to be ephemeral.
1157
1362
  */
1158
1363
  /**
1159
1364
  * Single entry point for all three `ContactMode` variants.
@@ -1197,29 +1402,11 @@ export declare class DeRecProtocol {
1197
1402
  * contact peers — follow up with
1198
1403
  * <c>start(FlowKind.UpdateChannelInfo, ...)</c> to propagate.
1199
1404
  */
1200
- setCommunicationInfo(info: Record<string, string>): void;
1201
-
1202
- /**
1203
- * Replace this node's endpoint for one protocol, leaving the others
1204
- * alone. A node serves at most one endpoint per protocol, so the
1205
- * `(uri, protocol)` pair identifies the entry it replaces; an entry for
1206
- * a protocol not yet served is appended, and a replaced one keeps its
1207
- * position in the preference order.
1208
- *
1209
- * IMPORTANT: keep the old endpoint operational during the changeover
1210
- * (see the Rust docs on the matching setter for the discipline).
1211
- *
1212
- * @deprecated Use {@link setOwnTransports}, which takes the whole
1213
- * preference list and is the only way to change which protocols this
1214
- * node serves, or their order. Removed at 0.0.5.
1215
- */
1216
- setOwnTransport(uri: string, protocol: string): void;
1405
+ setCommunicationInfo(info: Record<string, string>): Promise<void>;
1217
1406
 
1218
1407
  /**
1219
1408
  * Replace every endpoint this node advertises, in preference order —
1220
- * the runtime counterpart to <c>withOwnTransports</c>, and the way to
1221
- * change the whole set (<c>setOwnTransport</c> replaces only the entry
1222
- * for the protocol its URI names).
1409
+ * the runtime counterpart to `withOwnTransports`.
1223
1410
  *
1224
1411
  * A node serves at most one endpoint per protocol, so this list is a
1225
1412
  * preference order over distinct protocols. Two entries of the same
@@ -1228,7 +1415,7 @@ export declare class DeRecProtocol {
1228
1415
  * Every entry is validated before any is stored, so a malformed URI
1229
1416
  * leaves the previous set intact. An empty array is rejected.
1230
1417
  */
1231
- setOwnTransports(transports: { uri: string; protocol: string }[]): void;
1418
+ setOwnTransports(transports: Endpoint[]): Promise<void>;
1232
1419
 
1233
1420
  process(message: Uint8Array): Promise<DeRecEvent[]>;
1234
1421
 
@@ -1242,14 +1429,14 @@ export declare class DeRecProtocol {
1242
1429
  * shorter than the configured timeout.
1243
1430
  *
1244
1431
  * Safe to call at any time; with nothing in flight it resolves to an empty
1245
- * array. It mutates the same round state an inbound response does, so it
1246
- * must be serialized against `process` for the same `secretId`.
1432
+ * array. Overlapping calls with `process` on the same instance queue
1433
+ * rather than collide.
1247
1434
  */
1248
1435
  tick(): Promise<DeRecEvent[]>;
1249
1436
 
1250
1437
  accept(actionBytes: Uint8Array): Promise<DeRecEvent[]>;
1251
1438
 
1252
- reject(actionBytes: Uint8Array, status: number, memo: string): Promise<void>;
1439
+ reject(actionBytes: Uint8Array, status: StatusEnum, memo: string): Promise<void>;
1253
1440
 
1254
1441
  /**
1255
1442
  * Derive the human-readable fingerprint for a paired channel. Both sides
@@ -1264,6 +1451,11 @@ export declare class DeRecProtocol {
1264
1451
  * Verify `fingerprint` against the channel's locally-derived one. On
1265
1452
  * match, the channel transitions from `Pending` to `Paired`. Returns
1266
1453
  * `true` on confirmation, `false` on mismatch.
1454
+ *
1455
+ * On a replica destination, confirming is also the decision to adopt the
1456
+ * group's vault: the source's publish is then installed as it arrives,
1457
+ * with no further prompt. Ask the user before calling this; to decline,
1458
+ * never confirm.
1267
1459
  */
1268
1460
  verifyFingerprint(channelId: bigint | number, fingerprint: string): Promise<boolean>;
1269
1461
  /**
@@ -1274,23 +1466,36 @@ export declare class DeRecProtocol {
1274
1466
  * threshold given even when that policy is disabled. The age comparison
1275
1467
  * is strict, so a channel created within the current second survives
1276
1468
  * even `0`.
1469
+ *
1470
+ * `olderThanSecs` is a `u64`: a `bigint`, a non-negative safe-integer
1471
+ * `number`, or a decimal string. Anything else throws `decode_error`.
1277
1472
  */
1278
- removeExpiredChannels(olderThanSecs: number): Promise<string[]>;
1473
+ removeExpiredChannels(olderThanSecs: bigint | number | string): Promise<string[]>;
1279
1474
 
1280
1475
  /**
1281
1476
  * Rebuild this protocol's `secret_id` namespace from a recovered
1282
1477
  * `Secret`. Mirrors the Rust `DeRecProtocol::restore` — pass the
1283
1478
  * typed `secret` carried by the `SecretRecovered` event verbatim.
1284
1479
  *
1285
- * Errors surface as structured objects with a `code` field:
1480
+ * Recovery and restore are separate steps on purpose: `SecretRecovered`
1481
+ * writes nothing. Show the user what was recovered, or ask them, before
1482
+ * calling `restore`, which commits it to this device.
1483
+ *
1484
+ * A helper or member whose `transports` is empty gets no channel: it is
1485
+ * reported as a `PeerNotRestored` event in the returned array and the rest
1486
+ * of the roster is restored.
1487
+ *
1488
+ * Errors surface as a `DeRecError` (`category`, `code`, `message`):
1286
1489
  *
1287
1490
  * | code | meaning |
1288
1491
  * |--------------------|------------------------------------------------------------------|
1289
- * | `ALREADY_RESTORED` | A user-secret snapshot already exists for this `secret_id`. |
1290
- * | `CONFLICT` | Channels live at canonical helper / replica ids. The error |
1492
+ * | `already_restored` | A user-secret snapshot already exists for this `secret_id`. |
1493
+ * | `restore_conflict` | Channels live at ids restore is about to write. The error |
1291
1494
  * | | carries `channel_ids: string[]` listing the collisions. |
1292
- * | `INVARIANT` | The recovered `Secret` is internally inconsistent. |
1293
- * | `STORAGE` | A store I/O call failed mid-restore. |
1495
+ * | `invariant` | The recovered `Secret` is internally inconsistent. |
1496
+ * | `invalid_recovered_secret` | `recoveredSecret` is malformed — e.g. a missing or |
1497
+ * | | non-decimal `channel_id` / `replica_id`. |
1498
+ * | `store_error` | A store call failed mid-restore; `category` names the store. |
1294
1499
  */
1295
1500
  restore(
1296
1501
  recoveredSecret: Extract<DeRecEvent, { type: "SecretRecovered" }>["secret"],
@@ -1397,6 +1602,22 @@ export interface CommunicationInfo {
1397
1602
  * protocol can raise. Matches the Rust `PendingActionKind` discriminants
1398
1603
  * one-for-one.
1399
1604
  */
1605
+ /** Why a `MessageIgnored` event dropped a message. Matches the Rust
1606
+ * `IgnoreReason` discriminants one-for-one. */
1607
+ export type IgnoreReason = "PendingVerification" | "Expired";
1608
+
1609
+ /** Why a `PeerNotRestored` event left a roster entry without a channel.
1610
+ * Matches the Rust `NotRestoredReason` discriminants one-for-one. */
1611
+ export type NotRestoredReason = "NoTransports";
1612
+
1613
+ /** Why a `RecoveryShareCorrupted` event set a helper's share aside.
1614
+ * Matches the Rust `CorruptionReason` discriminants one-for-one:
1615
+ * `Malformed` — no decodable share for the requested secret and version;
1616
+ * `InvalidProof` — the share fails its own Merkle proof;
1617
+ * `Inconsistent` — valid on its own, but its commitment root or ciphertext
1618
+ * disagrees with the shares the secret was rebuilt from. */
1619
+ export type CorruptionReason = "Malformed" | "InvalidProof" | "Inconsistent";
1620
+
1400
1621
  export type PendingActionKind =
1401
1622
  | "Pairing"
1402
1623
  | "PrePair"
@@ -1427,17 +1648,9 @@ export interface PairRequestMessage {
1427
1648
  nonce: bigint;
1428
1649
  communication_info?: CommunicationInfo;
1429
1650
  parameter_range?: ParameterRange;
1430
- /**
1431
- * @deprecated Reading this field directly is incorrect: its meaning narrowed
1432
- * to "one entry of a list, and possibly absent", so a peer advertising only
1433
- * `supported_transports` looks unreachable to a reader that was correct
1434
- * before 0.0.3. Call {@link advertisedEndpoints}, which resolves both
1435
- * spellings. Removed at 0.0.5.
1436
- */
1437
- transport_protocol?: TransportProtocol;
1438
1651
  timestamp?: Timestamp;
1439
1652
  /** Every transport endpoint the initiator can be reached on, in its own
1440
- * preference order. Empty means "only `transport_protocol` is offered". */
1653
+ * preference order. */
1441
1654
  supported_transports: TransportProtocol[];
1442
1655
  }
1443
1656
 
@@ -1459,21 +1672,12 @@ export interface PairResponseMessage {
1459
1672
 
1460
1673
  export interface PrePairRequestMessage {
1461
1674
  nonce: bigint;
1462
- /**
1463
- * @deprecated Reading this field directly is incorrect: its meaning narrowed
1464
- * to "one entry of a list, and possibly absent", so a peer advertising only
1465
- * `supported_transports` looks unreachable to a reader that was correct
1466
- * before 0.0.3. Call {@link advertisedEndpoints}, which resolves both
1467
- * spellings. Removed at 0.0.5.
1468
- */
1469
- transport_protocol?: TransportProtocol;
1470
1675
  timestamp?: Timestamp;
1471
1676
  /**
1472
1677
  * Every endpoint the sender can be reached on for the PrePair reply, in
1473
- * its own preference order. At least one of this and `transport_protocol`
1474
- * must be present.
1678
+ * its own preference order.
1475
1679
  */
1476
- supported_transports?: TransportProtocol[];
1680
+ supported_transports: TransportProtocol[];
1477
1681
  }
1478
1682
 
1479
1683
  export interface PrePairResponseMessage {
@@ -1659,6 +1863,15 @@ export interface ProducePrePairResult {
1659
1863
  envelope: Uint8Array;
1660
1864
  }
1661
1865
 
1866
+ export interface ProducePrePairNoKeysResult {
1867
+
1868
+ envelope: Uint8Array;
1869
+
1870
+ /** Secret key material generated for this pairing. The caller MUST persist
1871
+ * it: the `PairRequest` that follows is encrypted to it. */
1872
+ secret_key_material: Uint8Array;
1873
+ }
1874
+
1662
1875
  export interface PrePairRequestExtractResult {
1663
1876
 
1664
1877
  request: PrePairRequestMessage;
@@ -1711,6 +1924,7 @@ export type DeRecErrorCategory =
1711
1924
  | "secret_store"
1712
1925
  | "channel_store"
1713
1926
  | "share_store"
1927
+ | "state_store"
1714
1928
  | "input"
1715
1929
  | "protobuf"
1716
1930
  | "invariant"
@@ -1724,6 +1938,16 @@ export interface DeRecError {
1724
1938
  memo?: string;
1725
1939
  expected?: number;
1726
1940
  got?: number;
1941
+ /** The channel the failing inbound message arrived on, when `process()` could tell. */
1942
+ channel_id?: string;
1943
+ /**
1944
+ * On a failed `process()`: the events it produced before failing, such as
1945
+ * sharing-round and unpair timeouts. They are not reported again, so
1946
+ * handle them as you would a successful call's events.
1947
+ */
1948
+ events?: DeRecEvent[];
1949
+ /** On a `restore` `CONFLICT`: the pre-existing channels at canonical ids. */
1950
+ channel_ids?: string[];
1727
1951
  }
1728
1952
 
1729
1953
  export declare const primitives: {
@@ -1758,16 +1982,32 @@ export declare const primitives: {
1758
1982
  * commitment and the scanner must complete a
1759
1983
  * `PrePair` round-trip first.
1760
1984
  * @param transport_protocols Every endpoint this initiator serves, in
1761
- * preference order. The first also fills the
1762
- * legacy singular field. For `HashedKeys`
1763
- * mode they MUST be ephemeral.
1985
+ * preference order. For `HashedKeys` mode
1986
+ * they MUST be ephemeral.
1987
+ * @param nonce Correlation nonce embedded in the contact. Omitted or
1988
+ * `null`, the library draws a random `u64` — the right
1989
+ * choice for `InlineKeys` / `HashedKeys`, where the nonce
1990
+ * is a security parameter. Required for `ContactMode.NoKeys`,
1991
+ * where the caller typically picks a short human-typable
1992
+ * value for manual entry. Must fit in a `u64`.
1764
1993
  */
1765
1994
  create_contact(
1766
1995
  channel_id: bigint,
1767
1996
  contact_mode: ContactMode | number,
1768
1997
  transport_protocols: TransportProtocol[],
1998
+ nonce?: bigint | number | null,
1769
1999
  ): CreateContactResult;
2000
+ /**
2001
+ * Serializes a `ContactMessage` to the bytes delivered out of band.
2002
+ * Throws on a contact that violates the invariants of its contact mode
2003
+ * or advertises no endpoint.
2004
+ */
1770
2005
  encode_contact(contact_message: ContactMessage): Uint8Array;
2006
+ /**
2007
+ * Parses out-of-band contact bytes back into a `ContactMessage`. Throws
2008
+ * on bytes that are not a contact, or a contact that violates the
2009
+ * invariants of its contact mode.
2010
+ */
1771
2011
  decode_contact(bytes: Uint8Array): ContactMessage;
1772
2012
  produce(
1773
2013
  kind: SenderKind,
@@ -1777,7 +2017,17 @@ export declare const primitives: {
1777
2017
  parameter_range: ParameterRange | null,
1778
2018
  ): PairingRequestProduceResult;
1779
2019
 
1780
- extract(envelope_bytes: Uint8Array, secret_key: Uint8Array): { request: PairRequestMessage };
2020
+ /**
2021
+ * @param parameter_range The range this side accepts, or `null` for
2022
+ * none. A request advertising a range that does
2023
+ * not overlap it is refused with
2024
+ * `incompatible_parameter_range`.
2025
+ */
2026
+ extract(
2027
+ envelope_bytes: Uint8Array,
2028
+ secret_key: Uint8Array,
2029
+ parameter_range: ParameterRange | null,
2030
+ ): { request: PairRequestMessage };
1781
2031
 
1782
2032
  /**
1783
2033
  * Scanner-side: build a plaintext `PrePairRequest` envelope when the
@@ -1789,8 +2039,6 @@ export declare const primitives: {
1789
2039
  /**
1790
2040
  * @param own_transports Every endpoint this scanner serves for the
1791
2041
  * PrePair reply, in its own preference order.
1792
- * The first entry also fills the deprecated
1793
- * singular field for peers predating the list.
1794
2042
  */
1795
2043
  produce_pre_pair(
1796
2044
  own_transports: TransportProtocol[],
@@ -1805,6 +2053,10 @@ export declare const primitives: {
1805
2053
  };
1806
2054
  response: {
1807
2055
  /**
2056
+ * @param parameter_range The range this side accepts and advertises,
2057
+ * or `null` for none. A request advertising a
2058
+ * range that does not overlap it is refused
2059
+ * with `incompatible_parameter_range`.
1808
2060
  * @param unsafe_connection Accept plaintext peer endpoints
1809
2061
  * (`http://`, `grpc://`). Development only.
1810
2062
  */
@@ -1818,10 +2070,18 @@ export declare const primitives: {
1818
2070
  ): PairingResponseProduceResult;
1819
2071
 
1820
2072
  extract(envelope_bytes: Uint8Array, secret_key: Uint8Array): { response: PairResponseMessage };
2073
+ /**
2074
+ * @param parameter_range The range this side accepts, or `null` for
2075
+ * none. A response advertising a range that
2076
+ * does not overlap it is refused with
2077
+ * `incompatible_parameter_range` before any key
2078
+ * is derived.
2079
+ */
1821
2080
  process(
1822
2081
  contact_message: ContactMessage,
1823
2082
  response: PairResponseMessage,
1824
2083
  secret_key: Uint8Array,
2084
+ parameter_range: ParameterRange | null,
1825
2085
  ): PairingProcessResult;
1826
2086
 
1827
2087
  /**
@@ -1849,7 +2109,41 @@ export declare const primitives: {
1849
2109
  contact_message: ContactMessage,
1850
2110
  response: PrePairResponseMessage,
1851
2111
  ): ProcessPrePairResult;
2112
+
2113
+ /**
2114
+ * Contact-creator side of a `NoKeys` pairing: generate key material and
2115
+ * answer the `PrePairRequest` with its public half.
2116
+ *
2117
+ * The caller MUST first match the request's `nonce` against the contact
2118
+ * it issued — the only thing that authenticates a `NoKeys` request — and
2119
+ * MUST persist `secret_key_material`. The resulting channel MUST stay
2120
+ * unusable until both sides confirm `pairing.fingerprint` out of band.
2121
+ */
2122
+ produce_pre_pair_no_keys(
2123
+ channel_id: bigint,
2124
+ request: PrePairRequestMessage,
2125
+ ): ProducePrePairNoKeysResult;
2126
+
2127
+ /**
2128
+ * Scanner side of a `NoKeys` pairing: accept the contact creator's
2129
+ * public keys. There is no binding hash to check them against, so the
2130
+ * resulting channel MUST stay unusable until both sides confirm
2131
+ * `pairing.fingerprint` out of band. Throws on a non-`Ok` status, a
2132
+ * missing key or a `nonce` mismatch.
2133
+ */
2134
+ process_pre_pair_no_keys(
2135
+ contact_message: ContactMessage,
2136
+ response: PrePairResponseMessage,
2137
+ ): ProcessPrePairResult;
1852
2138
  };
2139
+
2140
+ /**
2141
+ * The human-readable fingerprint of a pairing's 32-byte shared key. Both
2142
+ * ends derive the same value; comparing it out of band confirms the
2143
+ * pairing. A `NoKeys` pairing MUST be confirmed this way before use.
2144
+ * Throws on a key that is not 32 bytes.
2145
+ */
2146
+ fingerprint(shared_key: Uint8Array): string;
1853
2147
  };
1854
2148
  recovery: {
1855
2149
  request: {