@derec-alliance/react-native 0.0.7 → 0.0.9

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/src/types.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (c) 2026 DeRec Alliance. All rights reserved.
3
3
 
4
+ import type { DeRecErrorCode } from './errors';
4
5
  import { getNative } from './native';
5
6
 
6
7
  export interface SecretStore {
@@ -177,9 +178,18 @@ export type ReplicaFilter = ChannelFilter<ReplicaRoleName>;
177
178
  * return new TextEncoder().encode(`[${inner.join(",")}]`);
178
179
  * ```
179
180
  *
180
- * Do not `JSON.parse` and re-serialise. Every id in these records is a `u64`,
181
+ * Do not re-serialise a parsed record. Every id in these records is a `u64`,
181
182
  * and `JSON.parse` silently rounds anything above 2^53 — the corruption only
182
183
  * appears once a real id happens to be large.
184
+ *
185
+ * A helper record's `weight` is the number of Shamir subshares that helper
186
+ * receives: the weight the last `start(ProtectSecret)` resolved for it, which
187
+ * every publish the library starts on its own reuses (1 for a new pairing and
188
+ * for a record written before the field existed). Parsing a stored helper
189
+ * record to read `Helper.weight` is safe and is the normal way to pre-fill
190
+ * the weights the user is shown: it is a small integer, and the channel id
191
+ * comes from the store call's `channelId` argument, never from the parsed
192
+ * object. Keep persisting the original bytes.
183
193
  */
184
194
  export interface ChannelStore {
185
195
  load(
@@ -237,6 +247,24 @@ export interface Share {
237
247
  secretId: string;
238
248
  version: number;
239
249
  bytes: Uint8Array;
250
+ /**
251
+ * How many subshares the share carries, recorded on the owner side only:
252
+ * the library sets it on every share the owner saves (including a replica
253
+ * Destination's mirrored copies) and leaves it absent on a helper's share.
254
+ * Owner shares stored before the field existed read back absent; the
255
+ * library never backfills them. Persist it as given and return it
256
+ * unchanged. `null` and `undefined` both mean no count.
257
+ */
258
+ subshares?: number | null;
259
+ /**
260
+ * The threshold this version was split with, recorded on the owner side
261
+ * only, on the same shares as `subshares`: a later round may change the
262
+ * threshold, so this is the only record of the one an older version needs
263
+ * to be recoverable. Absent on a helper's share and on owner shares stored
264
+ * before the field existed. Persist it as given and return it unchanged.
265
+ * `null` and `undefined` both mean none.
266
+ */
267
+ threshold?: number | null;
240
268
  }
241
269
 
242
270
  /**
@@ -258,6 +286,11 @@ export interface ShareStore {
258
286
  versions: number[],
259
287
  ): Promise<Share[]>;
260
288
  loadAll(secretId: string, channelIds: string[]): Promise<Share[]>;
289
+ /**
290
+ * Reject, or throw, a {@link shareStoreRefusal} `Error` to refuse a share
291
+ * update on a helper channel; any other rejection is an ordinary backend
292
+ * failure.
293
+ */
261
294
  save(secretId: string, channelId: string, share: Share): Promise<void>;
262
295
  latestVersion(secretId: string): Promise<number | null>;
263
296
  removeChannel(secretId: string, channelId: string): Promise<void>;
@@ -297,6 +330,17 @@ export interface ShareStore {
297
330
  secretId: string,
298
331
  version: number,
299
332
  ): Promise<number[] | null | undefined>;
333
+ /**
334
+ * Record a verification challenge the helper is about to answer for
335
+ * `(secretId, channelId, version)`. Called once per accepted challenge,
336
+ * after the challenged share is found and before the helper answers.
337
+ *
338
+ * Reject, or throw, a {@link shareStoreRefusal} `Error` to refuse the
339
+ * verification on a helper channel; the library answers the owner with
340
+ * the mapped status instead of the verification result. Any other
341
+ * rejection is an ordinary backend failure.
342
+ */
343
+ recordVerification(secretId: string, channelId: string, version: number): Promise<void>;
300
344
  }
301
345
 
302
346
  export interface UserSecretEntry {
@@ -313,6 +357,14 @@ export interface UserSecrets {
313
357
  * when none is recorded. Store and return it unchanged: replica members
314
358
  * compare it to detect a conflicting copy of the same version. */
315
359
  author_replica_id?: string;
360
+ /** The threshold the next publish splits at unless
361
+ * `ProtectSecretParams.threshold` passes its own. Every publish writes the
362
+ * threshold it used, and `restore` and replica hydration write the
363
+ * recovered roster's. Absent on a snapshot written before the threshold
364
+ * was recorded, or restored from a roster that carries none; the
365
+ * builder's `withThreshold` then applies. Store and return it unchanged.
366
+ * `null` and `undefined` both mean none. */
367
+ threshold?: number | null;
316
368
  }
317
369
 
318
370
  /**
@@ -356,6 +408,18 @@ export interface StateStore {
356
408
  loadAll(secretId: string, kind: 0 | 1 | 2 | 3 | 4): Promise<Uint8Array[]>;
357
409
  }
358
410
 
411
+ /**
412
+ * The five stores `DeRecProtocolBuilder.build` requires, bundled for
413
+ * `DeRecProtocolBuilder.withStores`.
414
+ */
415
+ export interface StoresInit {
416
+ channelStore: ChannelStore;
417
+ shareStore: ShareStore;
418
+ secretStore: SecretStore;
419
+ userSecretStore: UserSecretStore;
420
+ stateStore: StateStore;
421
+ }
422
+
359
423
  /** A transport protocol, by name. */
360
424
  export type TransportProtocolName = "https" | "grpc";
361
425
 
@@ -455,6 +519,34 @@ export function generate_replica_id(): bigint {
455
519
  return getNative().generate_replica_id();
456
520
  }
457
521
 
522
+ /** Reason a `ShareStore` gives for refusing a `save` or `recordVerification`
523
+ * call, carried on the `refusal` property of the `Error` {@link
524
+ * shareStoreRefusal} builds. The app picks the reason; the library alone
525
+ * decides the status and memo that reach the peer. */
526
+ export type ShareStoreRefusalReason = "too_frequent" | "size_limit_exceeded" | "rejected";
527
+
528
+ const SHARE_STORE_REFUSAL_REASONS: ShareStoreRefusalReason[] = [
529
+ "too_frequent",
530
+ "size_limit_exceeded",
531
+ "rejected",
532
+ ];
533
+
534
+ /**
535
+ * Builds the `Error` a `ShareStore`'s `save`/`recordVerification` throws, or
536
+ * rejects its promise with, to refuse the call: `reason` tagged onto the
537
+ * error's `refusal` property.
538
+ */
539
+ export function shareStoreRefusal(reason: ShareStoreRefusalReason): Error {
540
+ if (!SHARE_STORE_REFUSAL_REASONS.includes(reason)) {
541
+ throw new Error(
542
+ `unknown share store refusal reason ${JSON.stringify(reason)}; expected one of ${JSON.stringify(SHARE_STORE_REFUSAL_REASONS)}`,
543
+ );
544
+ }
545
+ const error = new Error(`share store refused: ${reason}`);
546
+ (error as Error & { refusal: ShareStoreRefusalReason }).refusal = reason;
547
+ return error;
548
+ }
549
+
458
550
  /**
459
551
  * Builds a {@link Transport} that tries each endpoint in the order the peer
460
552
  * offered it and stops at the first success.
@@ -629,6 +721,60 @@ export interface DiscoveryParams {
629
721
  export interface ProtectSecretParams {
630
722
  secrets: UserSecret[];
631
723
  description?: string;
724
+ /**
725
+ * Channel ID to how many subshares that channel should hold. The map is the
726
+ * full assignment, not a patch: a paired helper absent from it holds one
727
+ * subshare. The resolved weights are stored on each helper's channel
728
+ * record. A key is a channel id as bigint or decimal string, e.g. the
729
+ * `channel_id` string from an event.
730
+ *
731
+ * Weights are the user's choice. Before `start`, show the user each paired
732
+ * helper's current weight, pre-filled from your `ChannelStore` (the stored
733
+ * `Helper.weight`, which is safe to read; see `ChannelStore`), and pass the
734
+ * user's final choice as the whole map. Reading the stored weights first is
735
+ * the normal flow. Every other `start(ProtectSecret)` the app makes, such
736
+ * as a republish after editing the secrets or the follow-up after
737
+ * `restore`, must pass the stored weights unchanged: a helper left out
738
+ * returns to 1, which silently undoes the user's choice. Only the
739
+ * publishes the library starts on its own (after pairing, after a
740
+ * fingerprint is confirmed) reuse the stored weights by themselves.
741
+ *
742
+ * A stored weight the round's threshold does not allow fails the
743
+ * publishes the library starts on its own with `invalid_weight`, before
744
+ * anything is sent: `process()` throws with the message's `events`
745
+ * (`PairingCompleted` included); `accept()` throws after the pairing has
746
+ * completed and its response was sent, without returning
747
+ * `PairingCompleted`; `verifyFingerprint()` throws after the promotion,
748
+ * which stands, and a retry resolves `true` and publishes nothing. Call
749
+ * `start` with `ProtectSecret` and valid weights to publish.
750
+ *
751
+ * When the paired helpers' resolved total is below the round's threshold
752
+ * (see `threshold`), `start` skips the split: no helper is sent anything, so no
753
+ * `ProtectSecretStarted` / `ProtectSecretFailed` comes back for one. A
754
+ * paired replica still gets its usual secret-only copy and events; with
755
+ * none paired, `start` resolves with no events at all — this is not an
756
+ * error. The version still advances and the secrets/description are
757
+ * saved as the latest snapshot, so the next publish that reaches
758
+ * threshold shares it. Sum the weights about to be assigned against the
759
+ * threshold beforehand, or count the returned helper events afterward,
760
+ * to detect the case.
761
+ */
762
+ weights?: Map<bigint | string, number>;
763
+ /**
764
+ * The threshold this round splits at: the number of distinct subshares
765
+ * that reconstruct this version. Absent keeps the stored threshold
766
+ * (`UserSecrets.threshold`), or the builder's `withThreshold` when none is
767
+ * stored; a value changes it, and the new threshold is stored with this
768
+ * round. Leave it absent on the follow-up after `restore`, which has
769
+ * already put the recovered threshold back. Publishes the library starts
770
+ * on its own always use the stored threshold.
771
+ *
772
+ * The resolved threshold must be at least 2 (`invalid_threshold`), above
773
+ * every resolved weight (`invalid_weight`), and no greater than a
774
+ * `withMaxSubshares` cap (`invalid_input`). Each check runs before
775
+ * anything is written or sent.
776
+ */
777
+ threshold?: number;
632
778
  }
633
779
  export interface VerifySharesParams {
634
780
  secretId: bigint | string;
@@ -750,6 +896,15 @@ export type DeRecEvent =
750
896
  updated_transports?: Endpoint[];
751
897
  }
752
898
  | { type: "ShareStored"; channel_id: string; version: number }
899
+ /** The library refused an inbound share (helper side) instead of raising
900
+ * `ActionRequired`: nothing was stored, no stored version was removed, and
901
+ * the owner was answered with `status` and `memo`, which it observes as
902
+ * `ShareRejected`. `status` is FORMAT_ERROR (the share does not decode),
903
+ * VERIFICATION_FAILED (it does not verify against its commitment),
904
+ * SIZE_LIMIT_EXCEEDED (outside the helper's size range) or
905
+ * VERSION_CONFLICT (a different share is stored at `version`). A refusal
906
+ * from the application's own share store does not raise it. */
907
+ | { type: "ShareRefused"; channel_id: string; version: number; status: StatusEnum; memo: string }
753
908
  | { type: "ShareConfirmed"; channel_id: string; version: number }
754
909
  | { type: "ShareRejected"; channel_id: string; version: number; status: StatusEnum; memo: string }
755
910
  /** A publishing round finished — every targeted helper confirmed,
@@ -812,7 +967,21 @@ export type DeRecEvent =
812
967
  * not fail the round. `behind` is the application's retry list — the
813
968
  * library keeps no durable per-member sync state. */
814
969
  | { type: "ReplicaSyncComplete"; version: number; synced: string[]; behind: string[] }
815
- | { type: "ShareVerified"; channel_id: string; version: number }
970
+ /** A helper's verification proof checked out (owner side). `subshares` is
971
+ * how many subshares this helper holds for `version`: the count on the
972
+ * owner's stored share for this channel and version, not the helper's
973
+ * current weight; absent when that share was stored before the count was
974
+ * recorded. `threshold` is the threshold `version` was split with, from
975
+ * the same owner share, not the current one; absent when that share was
976
+ * stored before the threshold was recorded. To judge whether a version is
977
+ * recoverable, sum `subshares` over its `ShareVerified` events and compare
978
+ * with their `threshold`. If either is absent on any of those events, the
979
+ * result is unknown: an absent count is not `0` or `1`. */
980
+ | { type: "ShareVerified"; channel_id: string; version: number; subshares?: number; threshold?: number }
981
+ /** A helper answered a verification challenge, but its proof does not
982
+ * match the share this device stored. The challenge is spent; a new
983
+ * `VerifyShares` round challenges the helper again. */
984
+ | { type: "ShareVerifyInvalid"; channel_id: string; version: number }
816
985
  /** A helper refused a verification challenge: its response carried a
817
986
  * non-OK `status` instead of a proof. The challenge is spent; a new
818
987
  * `VerifyShares` round challenges the helper again. */
@@ -855,7 +1024,12 @@ export type DeRecEvent =
855
1024
  /** Every endpoint this peer advertised, in the order it offered them. */
856
1025
  transports: Endpoint[];
857
1026
  shared_key: Uint8Array;
858
- communication_info?: Record<string, string>;
1027
+ communication_info?: Record<string, string> | null;
1028
+ /** Shamir subshares this helper received in the round that wrote the
1029
+ * roster. `restore` writes it into the helper's channel record, so the
1030
+ * next publish gives it the same count. Every event carries it;
1031
+ * `restore` reads an absent value as 1. */
1032
+ weight?: number;
859
1033
  }>;
860
1034
  secrets: Array<{
861
1035
  id: Uint8Array;
@@ -877,10 +1051,15 @@ export type DeRecEvent =
877
1051
  /** Every endpoint this peer advertised, in the order it offered them. */
878
1052
  transports: Endpoint[];
879
1053
  role: "Source" | "Destination";
880
- communication_info?: Record<string, string>;
1054
+ communication_info?: Record<string, string> | null;
881
1055
  }>;
882
1056
  shared_key: Uint8Array;
883
1057
  };
1058
+ /** The threshold the secret was split with. Absent when the roster
1059
+ * records none. `restore` writes it into the snapshot, so the next
1060
+ * publish splits at the same threshold; to change it, pass
1061
+ * `threshold` on `start(ProtectSecret)`. */
1062
+ threshold?: number;
884
1063
  };
885
1064
  }
886
1065
 
@@ -892,6 +1071,26 @@ export type DeRecEvent =
892
1071
  * non-Ok status (HashedKeys flow). Distinct from a cryptographic
893
1072
  * hash mismatch, which surfaces as a thrown error from `process()`. */
894
1073
  | { type: "PrePairRejected"; channel_id: string; status: StatusEnum; memo: string }
1074
+ /** The peer answered a message from this device with an
1075
+ * `ErrorResponseMessage`: it could not process it, typically because the
1076
+ * body could not be decoded (`FORMAT_ERROR`). Never answered. */
1077
+ | { type: "ErrorResponseReceived"; channel_id: string; status: StatusEnum; memo: string }
1078
+ /** This device refused an inbound `PairRequest` under a protocol rule
1079
+ * (`INCOMPATIBLE_PARAMETER_RANGE` or `REPLICA_ID_CONFLICT`), whatever the
1080
+ * auto-accept or `auto_respond_on_failure` setting, and sent the requester a
1081
+ * `PairResponse` carrying `status` and `memo`. No channel was created.
1082
+ * Returned by `process()`; it does not throw. `memo` is what the requester
1083
+ * reads, written from its side. `error_code` (`"incompatible_parameter_range"`
1084
+ * or `"replica_id_conflict"`) and `message` carry the local reason, as on
1085
+ * `RequestFailed`; `message` is never sent to the peer. */
1086
+ | {
1087
+ type: "PairingRefused";
1088
+ channel_id: string;
1089
+ status: StatusEnum;
1090
+ memo: string;
1091
+ error_code: DeRecErrorCode;
1092
+ message: string;
1093
+ }
895
1094
 
896
1095
  /** Fires alongside `PairingCompleted` on replica-mode pair handshakes.
897
1096
  * `peer_replica_id` is the peer's `u64` as a **decimal** string,
@@ -931,6 +1130,11 @@ export type DeRecEvent =
931
1130
  transports: Endpoint[];
932
1131
  shared_key: Uint8Array;
933
1132
  communication_info?: Record<string, string>;
1133
+ /** Shamir subshares this helper received in the round that wrote the
1134
+ * roster. `restore` writes it into the helper's channel record, so the
1135
+ * next publish gives it the same count. Every event carries it;
1136
+ * `restore` reads an absent value as 1. */
1137
+ weight?: number;
934
1138
  }>;
935
1139
  secrets: Array<{
936
1140
  id: Uint8Array;
@@ -954,6 +1158,11 @@ export type DeRecEvent =
954
1158
  }>;
955
1159
  shared_key: Uint8Array;
956
1160
  };
1161
+ /** The threshold the secret was split with. Absent when the roster
1162
+ * records none. `restore` writes it into the snapshot, so the next
1163
+ * publish splits at the same threshold; to change it, pass
1164
+ * `threshold` on `start(ProtectSecret)`. */
1165
+ threshold?: number;
957
1166
  };
958
1167
  shares: Array<{
959
1168
  channel_id: string;
@@ -983,6 +1192,11 @@ export type DeRecEvent =
983
1192
  transports: Endpoint[];
984
1193
  shared_key: Uint8Array;
985
1194
  communication_info?: Record<string, string>;
1195
+ /** Shamir subshares this helper received in the round that wrote the
1196
+ * roster. `restore` writes it into the helper's channel record, so the
1197
+ * next publish gives it the same count. Every event carries it;
1198
+ * `restore` reads an absent value as 1. */
1199
+ weight?: number;
986
1200
  }>;
987
1201
  secrets: Array<{
988
1202
  id: Uint8Array;
@@ -1006,6 +1220,11 @@ export type DeRecEvent =
1006
1220
  }>;
1007
1221
  shared_key: Uint8Array;
1008
1222
  };
1223
+ /** The threshold the secret was split with. Absent when the roster
1224
+ * records none. `restore` writes it into the snapshot, so the next
1225
+ * publish splits at the same threshold; to change it, pass
1226
+ * `threshold` on `start(ProtectSecret)`. */
1227
+ threshold?: number;
1009
1228
  };
1010
1229
  shares: Array<{
1011
1230
  channel_id: string;
@@ -1041,6 +1260,11 @@ export type DeRecEvent =
1041
1260
  transports: Endpoint[];
1042
1261
  shared_key: Uint8Array;
1043
1262
  communication_info?: Record<string, string>;
1263
+ /** Shamir subshares this helper received in the round that wrote the
1264
+ * roster. `restore` writes it into the helper's channel record, so the
1265
+ * next publish gives it the same count. Every event carries it;
1266
+ * `restore` reads an absent value as 1. */
1267
+ weight?: number;
1044
1268
  }>;
1045
1269
  secrets: Array<{
1046
1270
  id: Uint8Array;
@@ -1063,8 +1287,28 @@ export type DeRecEvent =
1063
1287
  }>;
1064
1288
  shared_key: Uint8Array;
1065
1289
  };
1290
+ /** The threshold the secret was split with. Absent when the roster
1291
+ * records none. `restore` writes it into the snapshot, so the next
1292
+ * publish splits at the same threshold; to change it, pass
1293
+ * `threshold` on `start(ProtectSecret)`. */
1294
+ threshold?: number;
1066
1295
  };
1067
1296
  }
1297
+ /** This device's library refused a group member's copy of the secret:
1298
+ * `FORMAT_ERROR` (the payload does not decode or is not self-consistent)
1299
+ * or `VERIFICATION_FAILED` (a share map entry does not verify against its
1300
+ * commitment). Nothing was written. On a push the publisher was answered
1301
+ * with this `status` and `memo`, which it observes as
1302
+ * `ReplicaSyncRejected`; a copy served by a catch-up is not answered. */
1303
+ | {
1304
+ type: "ReplicaSecretRefused";
1305
+ channel_id: string;
1306
+ from_replica_id: string;
1307
+ secret_id: string;
1308
+ version: number;
1309
+ status: StatusEnum;
1310
+ memo: string;
1311
+ }
1068
1312
  /** Peer's ack of a secret sync we sent. `status` is the `StatusEnum`
1069
1313
  * integer (0 = Ok), `memo` is the peer's explanation. */
1070
1314
  | {
@@ -1101,6 +1345,37 @@ export type DeRecEvent =
1101
1345
  * is the same label vocabulary as `ActionRequired.action_kind`
1102
1346
  * (`"Pairing"`, `"StoreShare"`, …). */
1103
1347
  | { type: "AutoAccepted"; channel_id: string; action_kind: PendingActionKind }
1348
+ /** A request that arrived on an authenticated channel failed while it
1349
+ * was being handled. Every such request is owed exactly one response.
1350
+ * When the library sent it, `responded` is `true` and `action` is absent.
1351
+ * Otherwise `responded` is `false` and `action` is the same opaque token
1352
+ * `ActionRequired` carries: pass it to `reject` to send the response
1353
+ * (`accept` refuses it). `action` is also absent when `reject` could
1354
+ * never answer it: a request on a replica group channel that names no
1355
+ * member, which has no reply target, or, with `autoRespondOnFailure`
1356
+ * off, a replica copy that hands this device the group key and fails
1357
+ * before that key is stored. Resolve each token once: the library is
1358
+ * stateless, so rejecting the same token twice sends two responses.
1359
+ * `action_kind` is the type of the failed request, `"Failed"` when its
1360
+ * body could not be decoded or is a request the channel cannot carry.
1361
+ * `status` is the `StatusEnum` value that was sent, or would have been,
1362
+ * and `memo` the wire-safe memo sent with it, or that would have been:
1363
+ * `reject(action, status, memo)` sends exactly the response
1364
+ * `autoRespondOnFailure` would have sent. `message` is a local
1365
+ * description, never sent to the peer. Reported
1366
+ * instead of a thrown error from `process()` or `accept()`, so the call's
1367
+ * other events are kept. */
1368
+ | {
1369
+ type: "RequestFailed";
1370
+ channel_id: string;
1371
+ action_kind: PendingActionKind;
1372
+ responded: boolean;
1373
+ status: StatusEnum;
1374
+ memo: string;
1375
+ error_code: DeRecErrorCode;
1376
+ message: string;
1377
+ action?: Uint8Array;
1378
+ }
1104
1379
  | { type: "NoOp" }
1105
1380
  /** An inbound message was dropped untouched: no store was written and
1106
1381
  * nothing was sent back. `reason` says why.
@@ -1111,8 +1386,12 @@ export type DeRecEvent =
1111
1386
  * Confirming does not replay it: after `verifyFingerprint` succeeds, a
1112
1387
  * replica destination calls `start(FlowKind.ReplicaDiscovery)` to pull
1113
1388
  * the copy itself. `"Expired"` means the message was older than the
1114
- * inbound timeout. `trace_id` matches the peer's `*Started` event for
1115
- * the same round (`"0"` when the sender set none). */
1389
+ * inbound timeout. `"TimestampMismatch"` means the envelope's timestamp
1390
+ * and the encrypted inner message's timestamp differ, or either is
1391
+ * absent. `"StaleResponse"` means a `VerifyShareResponse` answered an
1392
+ * earlier challenge that a newer one on the channel has replaced; the
1393
+ * outstanding challenge is kept. `trace_id` matches the peer's
1394
+ * `*Started` event for the same round (`"0"` when the sender set none). */
1116
1395
  | {
1117
1396
  type: "MessageIgnored";
1118
1397
  channel_id: string;
@@ -1145,22 +1424,29 @@ export type DeRecEvent =
1145
1424
  * targeted channels are unaffected. */
1146
1425
  | { type: "DiscoveryFailed"; channel_id: string; error: string }
1147
1426
  /** A share-storage request was dispatched to `channel_id`. Emitted per
1148
- * targeted peer by `start(ProtectSecret)`. */
1149
- | { type: "ProtectSecretStarted"; channel_id: string; version: number; trace_id: string }
1427
+ * targeted peer by `start(ProtectSecret)`. `weight` is the number of
1428
+ * subshares sent, which is also the weight now stored for this channel:
1429
+ * its `ProtectSecretParams.weights` entry, 1 when the map does not name
1430
+ * it, or the stored weight on a publish the library starts on its own. */
1431
+ | { type: "ProtectSecretStarted"; channel_id: string; version: number; trace_id: string; weight: number }
1150
1432
  /** A share-storage request could not be dispatched to `channel_id`. */
1151
1433
  | {
1152
1434
  type: "ProtectSecretFailed";
1153
1435
  channel_id: string;
1154
1436
  version: number;
1155
1437
  error: string;
1438
+ /** Number of subshares that were to be sent to this channel; see
1439
+ * `ProtectSecretStarted.weight`. */
1440
+ weight: number;
1156
1441
  }
1157
1442
  /** A verify-share challenge was dispatched to `channel_id`. */
1158
1443
  | { type: "VerifySharesStarted"; channel_id: string; version: number; trace_id: string }
1159
- /** A verify-share challenge could not be dispatched to `channel_id`. */
1444
+ /** Verification reached no verdict for `channel_id`. `reason` says why. */
1160
1445
  | {
1161
1446
  type: "VerifySharesFailed";
1162
1447
  channel_id: string;
1163
1448
  version: number;
1449
+ reason: VerifyFailureReason;
1164
1450
  error: string;
1165
1451
  }
1166
1452
  /** A recovery share request was dispatched to `channel_id`. */
@@ -1172,9 +1458,13 @@ export type DeRecEvent =
1172
1458
  version: number;
1173
1459
  error: string;
1174
1460
  }
1175
- /** An unpair request was dispatched to `channel_id`. Followed by an
1176
- * `Unpaired` event once the peer acknowledges (or in the same event
1177
- * vec, under `UnpairAck.NotRequired`). */
1461
+ /** An unpair request was dispatched to `channel_id`, by
1462
+ * `start(Unpair)` or by `restore` for each recovery channel. Under
1463
+ * `UnpairAck.Required` the channel is now `"Unpairing"`, and an
1464
+ * `Unpaired` event follows once the peer acknowledges or the
1465
+ * acknowledgement window elapses, or `UnpairRejected` if the peer
1466
+ * refuses (the channel is `"Paired"` again). Under
1467
+ * `UnpairAck.NotRequired`, `Unpaired` follows in the same event vec. */
1178
1468
  | { type: "UnpairFailed"; channel_id: string; error: string }
1179
1469
  | { type: "UnpairStarted"; channel_id: string; trace_id: string }
1180
1470
  /** An update-channel-info request was dispatched to `channel_id`. */
@@ -1198,16 +1488,20 @@ export type DeRecEvent =
1198
1488
  * oracle. Anyone who knows a HashedKeys contact's nonce can elicit a
1199
1489
  * key-publish response. Keep off unless you control both ends of
1200
1490
  * the transport.
1201
- * - `storeShare` — the helper's only admission-control point for
1202
- * inbound shares. The protocol enforces no size, quota or rate limit
1203
- * of its own, and `maxShareSize` is checked for range overlap at
1204
- * pairing time only, never against an actual share. While this is
1205
- * `false`, `ActionRequired` carries `share_size`, so the application
1206
- * can compare it to its quota and call `reject()` with
1207
- * `StatusEnum.SizeLimitExceeded`. Setting it `true` removes that
1208
- * opportunity entirely: every share from every paired Owner is stored
1209
- * unconditionally, at whatever size it arrives. Keep off in any
1210
- * deployment with per-user storage limits.
1491
+ * - `storeShare` — the helper's admission-control point for inbound
1492
+ * shares beyond what the library already checks: this device's own
1493
+ * configured `minShareSize`/`maxShareSize` is enforced against every
1494
+ * inbound share's actual length regardless of this flag, before
1495
+ * `ActionRequired` is ever considered — an out-of-range share is
1496
+ * refused with `StatusEnum.SizeLimitExceeded` either way, and this
1497
+ * flag has no say in it. While this is `false`, `ActionRequired`
1498
+ * carries the decoded request, which is where a tighter or per-user
1499
+ * size limit, storage quota, or any other app-specific decline
1500
+ * belongs — call `reject()` for that. Setting it `true` removes that
1501
+ * opportunity: every in-range share from every paired Owner is stored
1502
+ * unconditionally, unless the share store refuses it. Keep off in any
1503
+ * deployment with per-user storage limits tighter than this device's
1504
+ * own configured range.
1211
1505
  * - `unpair` — destructive. Accepting deletes the local channel
1212
1506
  * record before any UI confirmation.
1213
1507
  * - `updateChannelInfo` — silently overwrites the channel record with
@@ -1298,12 +1592,17 @@ export interface CommunicationInfo {
1298
1592
 
1299
1593
  /** Why a `MessageIgnored` event dropped a message. Matches the Rust
1300
1594
  * `IgnoreReason` discriminants one-for-one. */
1301
- export type IgnoreReason = "PendingVerification" | "Expired";
1595
+ export type IgnoreReason = "PendingVerification" | "Expired" | "TimestampMismatch" | "StaleResponse";
1302
1596
 
1303
1597
  /** Why a `PeerNotRestored` event left a roster entry without a channel.
1304
1598
  * Matches the Rust `NotRestoredReason` discriminants one-for-one. */
1305
1599
  export type NotRestoredReason = "NoTransports";
1306
1600
 
1601
+ /** Why a `VerifySharesFailed` event reached no verdict for a channel.
1602
+ * Matches the Rust `VerifyFailureReason` discriminants
1603
+ * one-for-one. */
1604
+ export type VerifyFailureReason = "NoSharedKey" | "NoProof" | "DispatchFailed";
1605
+
1307
1606
  /** Why a `RecoveryShareCorrupted` event set a helper's share aside.
1308
1607
  * Matches the Rust `CorruptionReason` discriminants one-for-one:
1309
1608
  * `Malformed` — no decodable share for the requested secret and version;
@@ -1313,10 +1612,11 @@ export type NotRestoredReason = "NoTransports";
1313
1612
  export type CorruptionReason = "Malformed" | "InvalidProof" | "Inconsistent";
1314
1613
 
1315
1614
  /**
1316
- * The label vocabulary for `ActionRequired.action_kind` and
1317
- * `AutoAccepted.action_kind` — one value per pending-action kind the
1318
- * protocol can raise. Matches the Rust `PendingActionKind` discriminants
1319
- * one-for-one.
1615
+ * The label vocabulary for `ActionRequired.action_kind`,
1616
+ * `AutoAccepted.action_kind` and `RequestFailed.action_kind` — one value per
1617
+ * pending-action kind the protocol can raise, plus `"Failed"` for a request
1618
+ * whose body could not be decoded or that the channel cannot carry. Matches the Rust `PendingActionKind`
1619
+ * discriminants one-for-one.
1320
1620
  */
1321
1621
  export type PendingActionKind =
1322
1622
  | "Pairing"
@@ -1326,7 +1626,8 @@ export type PendingActionKind =
1326
1626
  | "Discovery"
1327
1627
  | "GetShare"
1328
1628
  | "Unpair"
1329
- | "UpdateChannelInfo";
1629
+ | "UpdateChannelInfo"
1630
+ | "Failed";
1330
1631
 
1331
1632
  export interface ParameterRange {
1332
1633
  min_share_size: bigint;
@@ -1341,6 +1642,100 @@ export interface ParameterRange {
1341
1642
  max_unresponsive_deactivation_timeout: bigint;
1342
1643
  }
1343
1644
 
1645
+ /**
1646
+ * `parameter_range` bounds inside `DeRecProtocolConfig`. Each bound is a
1647
+ * JSON number or a decimal string — the string form carries a bound past
1648
+ * `Number.MAX_SAFE_INTEGER` through a config file or a JSON encoder whose
1649
+ * numbers are `f64`. An absent bound is `0` ("no bound"), the same as
1650
+ * `DeRecProtocolBuilder.withParameterRange`.
1651
+ */
1652
+ export interface DeRecParameterRangeConfig {
1653
+ min_share_size?: number | string;
1654
+ max_share_size?: number | string;
1655
+ min_time_between_verifications?: number | string;
1656
+ max_time_between_verifications?: number | string;
1657
+ min_time_between_share_updates?: number | string;
1658
+ max_time_between_share_updates?: number | string;
1659
+ min_unresponsive_deletion_timeout?: number | string;
1660
+ max_unresponsive_deletion_timeout?: number | string;
1661
+ min_unresponsive_deactivation_timeout?: number | string;
1662
+ max_unresponsive_deactivation_timeout?: number | string;
1663
+ }
1664
+
1665
+ /**
1666
+ * `auto_accept` inside `DeRecProtocolConfig`: the same eight flows as
1667
+ * {@link AutoAcceptPolicy}, named as the canonical JSON spells them rather
1668
+ * than in `camelCase`. An absent flow is `false`. See {@link AutoAcceptPolicy}
1669
+ * for per-field caveats.
1670
+ */
1671
+ export interface DeRecAutoAcceptConfig {
1672
+ pairing?: boolean;
1673
+ pre_pair?: boolean;
1674
+ store_share?: boolean;
1675
+ verify_share?: boolean;
1676
+ discovery?: boolean;
1677
+ get_share?: boolean;
1678
+ unpair?: boolean;
1679
+ update_channel_info?: boolean;
1680
+ }
1681
+
1682
+ /**
1683
+ * Every data setting of `DeRecProtocolBuilder` in one serializable value, in
1684
+ * the canonical `snake_case` JSON shape every SDK accepts — the format for a
1685
+ * config file or a user record. Passed to `DeRecProtocolBuilder.withConfig`.
1686
+ *
1687
+ * Every field is optional: an absent or `null` field leaves that setting
1688
+ * unset. Stores, the delivery transport and the constructor's `secretId` are
1689
+ * not settings and are not carried here.
1690
+ *
1691
+ * Configs layer: apply a general config, then a per-user one, then any
1692
+ * individual `with*` call, and the last write wins per field —
1693
+ * `timeouts` merges per field too, so a config that sets only
1694
+ * `inbound_message_secs` keeps whatever `sharing_round_secs` an earlier
1695
+ * config or call set. Every other nested object (`parameter_range`,
1696
+ * `auto_accept`, `own_transports`) replaces as a whole.
1697
+ *
1698
+ * Values set through a field here are validated by `build()` exactly as the
1699
+ * matching `with*` setter's value would be: there is one rule set.
1700
+ *
1701
+ * ```json
1702
+ * {
1703
+ * "threshold": 3,
1704
+ * "max_subshares": "12",
1705
+ * "timeouts": { "inbound_message_secs": 120, "sharing_round_secs": 30 },
1706
+ * "parameter_range": { "min_share_size": 1, "max_share_size": 4096 },
1707
+ * "auto_accept": { "pairing": true, "store_share": true },
1708
+ * "auto_respond_on_failure": false,
1709
+ * "auto_reply_to": false,
1710
+ * "unpair_ack": "required",
1711
+ * "unsafe_connection": false,
1712
+ * "communication_info": { "name": "alice" },
1713
+ * "own_transports": [{ "uri": "https://me.example.com/derec", "protocol": "https" }],
1714
+ * "replica_id": "9007199254740993"
1715
+ * }
1716
+ * ```
1717
+ */
1718
+ export interface DeRecProtocolConfig {
1719
+ threshold?: number;
1720
+ /** Decimal string. A `bigint` is rejected; call
1721
+ * `DeRecProtocolBuilder.withMaxSubshares` directly for a `bigint`. */
1722
+ max_subshares?: string;
1723
+ timeouts?: Timeouts;
1724
+ parameter_range?: DeRecParameterRangeConfig;
1725
+ auto_accept?: DeRecAutoAcceptConfig;
1726
+ auto_respond_on_failure?: boolean;
1727
+ auto_reply_to?: boolean;
1728
+ /** Its name, or the discriminant `0` (`"required"`) / `1`
1729
+ * (`"not_required"`). */
1730
+ unpair_ack?: UnpairAck | 0 | 1;
1731
+ unsafe_connection?: boolean;
1732
+ communication_info?: Record<string, string>;
1733
+ own_transports?: Endpoint[];
1734
+ /** Decimal string. A `bigint` is rejected; call
1735
+ * `DeRecProtocolBuilder.withReplicaId` directly for a `bigint`. */
1736
+ replica_id?: string;
1737
+ }
1738
+
1344
1739
  export interface PairRequestMessage {
1345
1740
  sender_kind: number;
1346
1741
  mlkem_ciphertext: Uint8Array;
@@ -1584,7 +1979,7 @@ export interface PrePairResponseExtractResult {
1584
1979
 
1585
1980
  export interface ProcessPrePairResult {
1586
1981
 
1587
- /** Initiator's ML-KEM-768 encapsulation key, validated against the
1982
+ /** Initiator's ML-KEM-1024 encapsulation key, validated against the
1588
1983
  * contact's `contactBindingHash`. */
1589
1984
  mlkem_encapsulation_key: Uint8Array;
1590
1985
 
@@ -1596,6 +1991,14 @@ export interface ProcessPrePairResult {
1596
1991
  nonce: bigint;
1597
1992
  }
1598
1993
 
1994
+ /**
1995
+ * A channel paired with how many subshares it holds. A weight of `1` is an
1996
+ * ordinary helper; every weight must satisfy `1 <= weight < threshold`.
1997
+ * `channelId` is a channel id as bigint or decimal string, e.g. the
1998
+ * `channel_id` string from an event.
1999
+ */
2000
+ export type WeightedChannel = [channelId: bigint | string, weight: number];
2001
+
1599
2002
  export interface SplitResult {
1600
2003
  /** Map keyed by channel id (`bigint`). */
1601
2004
  shares: Map<bigint, CommittedDeRecShare>;