@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/lib/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { DeRecErrorCode } from './errors';
1
2
  export interface SecretStore {
2
3
  load(secretId: string, channelId: string, kind: 0 | 1 | 2): Promise<Uint8Array | null | undefined>;
3
4
  /**
@@ -128,9 +129,18 @@ export type ReplicaFilter = ChannelFilter<ReplicaRoleName>;
128
129
  * return new TextEncoder().encode(`[${inner.join(",")}]`);
129
130
  * ```
130
131
  *
131
- * Do not `JSON.parse` and re-serialise. Every id in these records is a `u64`,
132
+ * Do not re-serialise a parsed record. Every id in these records is a `u64`,
132
133
  * and `JSON.parse` silently rounds anything above 2^53 — the corruption only
133
134
  * appears once a real id happens to be large.
135
+ *
136
+ * A helper record's `weight` is the number of Shamir subshares that helper
137
+ * receives: the weight the last `start(ProtectSecret)` resolved for it, which
138
+ * every publish the library starts on its own reuses (1 for a new pairing and
139
+ * for a record written before the field existed). Parsing a stored helper
140
+ * record to read `Helper.weight` is safe and is the normal way to pre-fill
141
+ * the weights the user is shown: it is a small integer, and the channel id
142
+ * comes from the store call's `channelId` argument, never from the parsed
143
+ * object. Keep persisting the original bytes.
134
144
  */
135
145
  export interface ChannelStore {
136
146
  load(secretId: string, channelId: string, replicaId: string): Promise<Uint8Array | null | undefined>;
@@ -172,6 +182,24 @@ export interface Share {
172
182
  secretId: string;
173
183
  version: number;
174
184
  bytes: Uint8Array;
185
+ /**
186
+ * How many subshares the share carries, recorded on the owner side only:
187
+ * the library sets it on every share the owner saves (including a replica
188
+ * Destination's mirrored copies) and leaves it absent on a helper's share.
189
+ * Owner shares stored before the field existed read back absent; the
190
+ * library never backfills them. Persist it as given and return it
191
+ * unchanged. `null` and `undefined` both mean no count.
192
+ */
193
+ subshares?: number | null;
194
+ /**
195
+ * The threshold this version was split with, recorded on the owner side
196
+ * only, on the same shares as `subshares`: a later round may change the
197
+ * threshold, so this is the only record of the one an older version needs
198
+ * to be recoverable. Absent on a helper's share and on owner shares stored
199
+ * before the field existed. Persist it as given and return it unchanged.
200
+ * `null` and `undefined` both mean none.
201
+ */
202
+ threshold?: number | null;
175
203
  }
176
204
  /**
177
205
  * Share-record persistence.
@@ -188,6 +216,11 @@ export interface ShareStore {
188
216
  load(secretId: string, channelId: string, versions: number[]): Promise<Share[]>;
189
217
  loadMany(secretId: string, channelIds: string[], versions: number[]): Promise<Share[]>;
190
218
  loadAll(secretId: string, channelIds: string[]): Promise<Share[]>;
219
+ /**
220
+ * Reject, or throw, a {@link shareStoreRefusal} `Error` to refuse a share
221
+ * update on a helper channel; any other rejection is an ordinary backend
222
+ * failure.
223
+ */
191
224
  save(secretId: string, channelId: string, share: Share): Promise<void>;
192
225
  latestVersion(secretId: string): Promise<number | null>;
193
226
  removeChannel(secretId: string, channelId: string): Promise<void>;
@@ -220,6 +253,17 @@ export interface ShareStore {
220
253
  * unrecoverable.
221
254
  */
222
255
  keepList(secretId: string, version: number): Promise<number[] | null | undefined>;
256
+ /**
257
+ * Record a verification challenge the helper is about to answer for
258
+ * `(secretId, channelId, version)`. Called once per accepted challenge,
259
+ * after the challenged share is found and before the helper answers.
260
+ *
261
+ * Reject, or throw, a {@link shareStoreRefusal} `Error` to refuse the
262
+ * verification on a helper channel; the library answers the owner with
263
+ * the mapped status instead of the verification result. Any other
264
+ * rejection is an ordinary backend failure.
265
+ */
266
+ recordVerification(secretId: string, channelId: string, version: number): Promise<void>;
223
267
  }
224
268
  export interface UserSecretEntry {
225
269
  id: Uint8Array;
@@ -234,6 +278,14 @@ export interface UserSecrets {
234
278
  * when none is recorded. Store and return it unchanged: replica members
235
279
  * compare it to detect a conflicting copy of the same version. */
236
280
  author_replica_id?: string;
281
+ /** The threshold the next publish splits at unless
282
+ * `ProtectSecretParams.threshold` passes its own. Every publish writes the
283
+ * threshold it used, and `restore` and replica hydration write the
284
+ * recovered roster's. Absent on a snapshot written before the threshold
285
+ * was recorded, or restored from a roster that carries none; the
286
+ * builder's `withThreshold` then applies. Store and return it unchanged.
287
+ * `null` and `undefined` both mean none. */
288
+ threshold?: number | null;
237
289
  }
238
290
  /**
239
291
  * Persistence for the user-facing secret contents, keyed by `secretId`.
@@ -271,6 +323,17 @@ export interface StateStore {
271
323
  remove(secretId: string, keyJson: Uint8Array): Promise<boolean>;
272
324
  loadAll(secretId: string, kind: 0 | 1 | 2 | 3 | 4): Promise<Uint8Array[]>;
273
325
  }
326
+ /**
327
+ * The five stores `DeRecProtocolBuilder.build` requires, bundled for
328
+ * `DeRecProtocolBuilder.withStores`.
329
+ */
330
+ export interface StoresInit {
331
+ channelStore: ChannelStore;
332
+ shareStore: ShareStore;
333
+ secretStore: SecretStore;
334
+ userSecretStore: UserSecretStore;
335
+ stateStore: StateStore;
336
+ }
274
337
  /** A transport protocol, by name. */
275
338
  export type TransportProtocolName = "https" | "grpc";
276
339
  /**
@@ -357,6 +420,17 @@ export declare function protocol_version(): {
357
420
  * `DeRecProtocolBuilder.withReplicaId` on every init.
358
421
  */
359
422
  export declare function generate_replica_id(): bigint;
423
+ /** Reason a `ShareStore` gives for refusing a `save` or `recordVerification`
424
+ * call, carried on the `refusal` property of the `Error` {@link
425
+ * shareStoreRefusal} builds. The app picks the reason; the library alone
426
+ * decides the status and memo that reach the peer. */
427
+ export type ShareStoreRefusalReason = "too_frequent" | "size_limit_exceeded" | "rejected";
428
+ /**
429
+ * Builds the `Error` a `ShareStore`'s `save`/`recordVerification` throws, or
430
+ * rejects its promise with, to refuse the call: `reason` tagged onto the
431
+ * error's `refusal` property.
432
+ */
433
+ export declare function shareStoreRefusal(reason: ShareStoreRefusalReason): Error;
360
434
  /**
361
435
  * Builds a {@link Transport} that tries each endpoint in the order the peer
362
436
  * offered it and stops at the first success.
@@ -492,6 +566,60 @@ export interface DiscoveryParams {
492
566
  export interface ProtectSecretParams {
493
567
  secrets: UserSecret[];
494
568
  description?: string;
569
+ /**
570
+ * Channel ID to how many subshares that channel should hold. The map is the
571
+ * full assignment, not a patch: a paired helper absent from it holds one
572
+ * subshare. The resolved weights are stored on each helper's channel
573
+ * record. A key is a channel id as bigint or decimal string, e.g. the
574
+ * `channel_id` string from an event.
575
+ *
576
+ * Weights are the user's choice. Before `start`, show the user each paired
577
+ * helper's current weight, pre-filled from your `ChannelStore` (the stored
578
+ * `Helper.weight`, which is safe to read; see `ChannelStore`), and pass the
579
+ * user's final choice as the whole map. Reading the stored weights first is
580
+ * the normal flow. Every other `start(ProtectSecret)` the app makes, such
581
+ * as a republish after editing the secrets or the follow-up after
582
+ * `restore`, must pass the stored weights unchanged: a helper left out
583
+ * returns to 1, which silently undoes the user's choice. Only the
584
+ * publishes the library starts on its own (after pairing, after a
585
+ * fingerprint is confirmed) reuse the stored weights by themselves.
586
+ *
587
+ * A stored weight the round's threshold does not allow fails the
588
+ * publishes the library starts on its own with `invalid_weight`, before
589
+ * anything is sent: `process()` throws with the message's `events`
590
+ * (`PairingCompleted` included); `accept()` throws after the pairing has
591
+ * completed and its response was sent, without returning
592
+ * `PairingCompleted`; `verifyFingerprint()` throws after the promotion,
593
+ * which stands, and a retry resolves `true` and publishes nothing. Call
594
+ * `start` with `ProtectSecret` and valid weights to publish.
595
+ *
596
+ * When the paired helpers' resolved total is below the round's threshold
597
+ * (see `threshold`), `start` skips the split: no helper is sent anything, so no
598
+ * `ProtectSecretStarted` / `ProtectSecretFailed` comes back for one. A
599
+ * paired replica still gets its usual secret-only copy and events; with
600
+ * none paired, `start` resolves with no events at all — this is not an
601
+ * error. The version still advances and the secrets/description are
602
+ * saved as the latest snapshot, so the next publish that reaches
603
+ * threshold shares it. Sum the weights about to be assigned against the
604
+ * threshold beforehand, or count the returned helper events afterward,
605
+ * to detect the case.
606
+ */
607
+ weights?: Map<bigint | string, number>;
608
+ /**
609
+ * The threshold this round splits at: the number of distinct subshares
610
+ * that reconstruct this version. Absent keeps the stored threshold
611
+ * (`UserSecrets.threshold`), or the builder's `withThreshold` when none is
612
+ * stored; a value changes it, and the new threshold is stored with this
613
+ * round. Leave it absent on the follow-up after `restore`, which has
614
+ * already put the recovered threshold back. Publishes the library starts
615
+ * on its own always use the stored threshold.
616
+ *
617
+ * The resolved threshold must be at least 2 (`invalid_threshold`), above
618
+ * every resolved weight (`invalid_weight`), and no greater than a
619
+ * `withMaxSubshares` cap (`invalid_input`). Each check runs before
620
+ * anything is written or sent.
621
+ */
622
+ threshold?: number;
495
623
  }
496
624
  export interface VerifySharesParams {
497
625
  secretId: bigint | string;
@@ -605,6 +733,21 @@ export type DeRecEvent = {
605
733
  type: "ShareStored";
606
734
  channel_id: string;
607
735
  version: number;
736
+ }
737
+ /** The library refused an inbound share (helper side) instead of raising
738
+ * `ActionRequired`: nothing was stored, no stored version was removed, and
739
+ * the owner was answered with `status` and `memo`, which it observes as
740
+ * `ShareRejected`. `status` is FORMAT_ERROR (the share does not decode),
741
+ * VERIFICATION_FAILED (it does not verify against its commitment),
742
+ * SIZE_LIMIT_EXCEEDED (outside the helper's size range) or
743
+ * VERSION_CONFLICT (a different share is stored at `version`). A refusal
744
+ * from the application's own share store does not raise it. */
745
+ | {
746
+ type: "ShareRefused";
747
+ channel_id: string;
748
+ version: number;
749
+ status: StatusEnum;
750
+ memo: string;
608
751
  } | {
609
752
  type: "ShareConfirmed";
610
753
  channel_id: string;
@@ -700,10 +843,31 @@ export type DeRecEvent = {
700
843
  version: number;
701
844
  synced: string[];
702
845
  behind: string[];
703
- } | {
846
+ }
847
+ /** A helper's verification proof checked out (owner side). `subshares` is
848
+ * how many subshares this helper holds for `version`: the count on the
849
+ * owner's stored share for this channel and version, not the helper's
850
+ * current weight; absent when that share was stored before the count was
851
+ * recorded. `threshold` is the threshold `version` was split with, from
852
+ * the same owner share, not the current one; absent when that share was
853
+ * stored before the threshold was recorded. To judge whether a version is
854
+ * recoverable, sum `subshares` over its `ShareVerified` events and compare
855
+ * with their `threshold`. If either is absent on any of those events, the
856
+ * result is unknown: an absent count is not `0` or `1`. */
857
+ | {
704
858
  type: "ShareVerified";
705
859
  channel_id: string;
706
860
  version: number;
861
+ subshares?: number;
862
+ threshold?: number;
863
+ }
864
+ /** A helper answered a verification challenge, but its proof does not
865
+ * match the share this device stored. The challenge is spent; a new
866
+ * `VerifyShares` round challenges the helper again. */
867
+ | {
868
+ type: "ShareVerifyInvalid";
869
+ channel_id: string;
870
+ version: number;
707
871
  }
708
872
  /** A helper refused a verification challenge: its response carried a
709
873
  * non-OK `status` instead of a proof. The challenge is spent; a new
@@ -775,7 +939,12 @@ export type DeRecEvent = {
775
939
  /** Every endpoint this peer advertised, in the order it offered them. */
776
940
  transports: Endpoint[];
777
941
  shared_key: Uint8Array;
778
- communication_info?: Record<string, string>;
942
+ communication_info?: Record<string, string> | null;
943
+ /** Shamir subshares this helper received in the round that wrote the
944
+ * roster. `restore` writes it into the helper's channel record, so the
945
+ * next publish gives it the same count. Every event carries it;
946
+ * `restore` reads an absent value as 1. */
947
+ weight?: number;
779
948
  }>;
780
949
  secrets: Array<{
781
950
  id: Uint8Array;
@@ -797,10 +966,15 @@ export type DeRecEvent = {
797
966
  /** Every endpoint this peer advertised, in the order it offered them. */
798
967
  transports: Endpoint[];
799
968
  role: "Source" | "Destination";
800
- communication_info?: Record<string, string>;
969
+ communication_info?: Record<string, string> | null;
801
970
  }>;
802
971
  shared_key: Uint8Array;
803
972
  };
973
+ /** The threshold the secret was split with. Absent when the roster
974
+ * records none. `restore` writes it into the snapshot, so the next
975
+ * publish splits at the same threshold; to change it, pass
976
+ * `threshold` on `start(ProtectSecret)`. */
977
+ threshold?: number;
804
978
  };
805
979
  } | {
806
980
  type: "Unpaired";
@@ -820,6 +994,31 @@ export type DeRecEvent = {
820
994
  status: StatusEnum;
821
995
  memo: string;
822
996
  }
997
+ /** The peer answered a message from this device with an
998
+ * `ErrorResponseMessage`: it could not process it, typically because the
999
+ * body could not be decoded (`FORMAT_ERROR`). Never answered. */
1000
+ | {
1001
+ type: "ErrorResponseReceived";
1002
+ channel_id: string;
1003
+ status: StatusEnum;
1004
+ memo: string;
1005
+ }
1006
+ /** This device refused an inbound `PairRequest` under a protocol rule
1007
+ * (`INCOMPATIBLE_PARAMETER_RANGE` or `REPLICA_ID_CONFLICT`), whatever the
1008
+ * auto-accept or `auto_respond_on_failure` setting, and sent the requester a
1009
+ * `PairResponse` carrying `status` and `memo`. No channel was created.
1010
+ * Returned by `process()`; it does not throw. `memo` is what the requester
1011
+ * reads, written from its side. `error_code` (`"incompatible_parameter_range"`
1012
+ * or `"replica_id_conflict"`) and `message` carry the local reason, as on
1013
+ * `RequestFailed`; `message` is never sent to the peer. */
1014
+ | {
1015
+ type: "PairingRefused";
1016
+ channel_id: string;
1017
+ status: StatusEnum;
1018
+ memo: string;
1019
+ error_code: DeRecErrorCode;
1020
+ message: string;
1021
+ }
823
1022
  /** Fires alongside `PairingCompleted` on replica-mode pair handshakes.
824
1023
  * `peer_replica_id` is the peer's `u64` as a **decimal** string,
825
1024
  * matching the wire `derec.replica_id` representation and every other
@@ -858,6 +1057,11 @@ export type DeRecEvent = {
858
1057
  transports: Endpoint[];
859
1058
  shared_key: Uint8Array;
860
1059
  communication_info?: Record<string, string>;
1060
+ /** Shamir subshares this helper received in the round that wrote the
1061
+ * roster. `restore` writes it into the helper's channel record, so the
1062
+ * next publish gives it the same count. Every event carries it;
1063
+ * `restore` reads an absent value as 1. */
1064
+ weight?: number;
861
1065
  }>;
862
1066
  secrets: Array<{
863
1067
  id: Uint8Array;
@@ -881,6 +1085,11 @@ export type DeRecEvent = {
881
1085
  }>;
882
1086
  shared_key: Uint8Array;
883
1087
  };
1088
+ /** The threshold the secret was split with. Absent when the roster
1089
+ * records none. `restore` writes it into the snapshot, so the next
1090
+ * publish splits at the same threshold; to change it, pass
1091
+ * `threshold` on `start(ProtectSecret)`. */
1092
+ threshold?: number;
884
1093
  };
885
1094
  shares: Array<{
886
1095
  channel_id: string;
@@ -910,6 +1119,11 @@ export type DeRecEvent = {
910
1119
  transports: Endpoint[];
911
1120
  shared_key: Uint8Array;
912
1121
  communication_info?: Record<string, string>;
1122
+ /** Shamir subshares this helper received in the round that wrote the
1123
+ * roster. `restore` writes it into the helper's channel record, so the
1124
+ * next publish gives it the same count. Every event carries it;
1125
+ * `restore` reads an absent value as 1. */
1126
+ weight?: number;
913
1127
  }>;
914
1128
  secrets: Array<{
915
1129
  id: Uint8Array;
@@ -933,6 +1147,11 @@ export type DeRecEvent = {
933
1147
  }>;
934
1148
  shared_key: Uint8Array;
935
1149
  };
1150
+ /** The threshold the secret was split with. Absent when the roster
1151
+ * records none. `restore` writes it into the snapshot, so the next
1152
+ * publish splits at the same threshold; to change it, pass
1153
+ * `threshold` on `start(ProtectSecret)`. */
1154
+ threshold?: number;
936
1155
  };
937
1156
  shares: Array<{
938
1157
  channel_id: string;
@@ -968,6 +1187,11 @@ export type DeRecEvent = {
968
1187
  transports: Endpoint[];
969
1188
  shared_key: Uint8Array;
970
1189
  communication_info?: Record<string, string>;
1190
+ /** Shamir subshares this helper received in the round that wrote the
1191
+ * roster. `restore` writes it into the helper's channel record, so the
1192
+ * next publish gives it the same count. Every event carries it;
1193
+ * `restore` reads an absent value as 1. */
1194
+ weight?: number;
971
1195
  }>;
972
1196
  secrets: Array<{
973
1197
  id: Uint8Array;
@@ -990,8 +1214,28 @@ export type DeRecEvent = {
990
1214
  }>;
991
1215
  shared_key: Uint8Array;
992
1216
  };
1217
+ /** The threshold the secret was split with. Absent when the roster
1218
+ * records none. `restore` writes it into the snapshot, so the next
1219
+ * publish splits at the same threshold; to change it, pass
1220
+ * `threshold` on `start(ProtectSecret)`. */
1221
+ threshold?: number;
993
1222
  };
994
1223
  }
1224
+ /** This device's library refused a group member's copy of the secret:
1225
+ * `FORMAT_ERROR` (the payload does not decode or is not self-consistent)
1226
+ * or `VERIFICATION_FAILED` (a share map entry does not verify against its
1227
+ * commitment). Nothing was written. On a push the publisher was answered
1228
+ * with this `status` and `memo`, which it observes as
1229
+ * `ReplicaSyncRejected`; a copy served by a catch-up is not answered. */
1230
+ | {
1231
+ type: "ReplicaSecretRefused";
1232
+ channel_id: string;
1233
+ from_replica_id: string;
1234
+ secret_id: string;
1235
+ version: number;
1236
+ status: StatusEnum;
1237
+ memo: string;
1238
+ }
995
1239
  /** Peer's ack of a secret sync we sent. `status` is the `StatusEnum`
996
1240
  * integer (0 = Ok), `memo` is the peer's explanation. */
997
1241
  | {
@@ -1031,6 +1275,37 @@ export type DeRecEvent = {
1031
1275
  type: "AutoAccepted";
1032
1276
  channel_id: string;
1033
1277
  action_kind: PendingActionKind;
1278
+ }
1279
+ /** A request that arrived on an authenticated channel failed while it
1280
+ * was being handled. Every such request is owed exactly one response.
1281
+ * When the library sent it, `responded` is `true` and `action` is absent.
1282
+ * Otherwise `responded` is `false` and `action` is the same opaque token
1283
+ * `ActionRequired` carries: pass it to `reject` to send the response
1284
+ * (`accept` refuses it). `action` is also absent when `reject` could
1285
+ * never answer it: a request on a replica group channel that names no
1286
+ * member, which has no reply target, or, with `autoRespondOnFailure`
1287
+ * off, a replica copy that hands this device the group key and fails
1288
+ * before that key is stored. Resolve each token once: the library is
1289
+ * stateless, so rejecting the same token twice sends two responses.
1290
+ * `action_kind` is the type of the failed request, `"Failed"` when its
1291
+ * body could not be decoded or is a request the channel cannot carry.
1292
+ * `status` is the `StatusEnum` value that was sent, or would have been,
1293
+ * and `memo` the wire-safe memo sent with it, or that would have been:
1294
+ * `reject(action, status, memo)` sends exactly the response
1295
+ * `autoRespondOnFailure` would have sent. `message` is a local
1296
+ * description, never sent to the peer. Reported
1297
+ * instead of a thrown error from `process()` or `accept()`, so the call's
1298
+ * other events are kept. */
1299
+ | {
1300
+ type: "RequestFailed";
1301
+ channel_id: string;
1302
+ action_kind: PendingActionKind;
1303
+ responded: boolean;
1304
+ status: StatusEnum;
1305
+ memo: string;
1306
+ error_code: DeRecErrorCode;
1307
+ message: string;
1308
+ action?: Uint8Array;
1034
1309
  } | {
1035
1310
  type: "NoOp";
1036
1311
  }
@@ -1043,8 +1318,12 @@ export type DeRecEvent = {
1043
1318
  * Confirming does not replay it: after `verifyFingerprint` succeeds, a
1044
1319
  * replica destination calls `start(FlowKind.ReplicaDiscovery)` to pull
1045
1320
  * the copy itself. `"Expired"` means the message was older than the
1046
- * inbound timeout. `trace_id` matches the peer's `*Started` event for
1047
- * the same round (`"0"` when the sender set none). */
1321
+ * inbound timeout. `"TimestampMismatch"` means the envelope's timestamp
1322
+ * and the encrypted inner message's timestamp differ, or either is
1323
+ * absent. `"StaleResponse"` means a `VerifyShareResponse` answered an
1324
+ * earlier challenge that a newer one on the channel has replaced; the
1325
+ * outstanding challenge is kept. `trace_id` matches the peer's
1326
+ * `*Started` event for the same round (`"0"` when the sender set none). */
1048
1327
  | {
1049
1328
  type: "MessageIgnored";
1050
1329
  channel_id: string;
@@ -1090,12 +1369,16 @@ export type DeRecEvent = {
1090
1369
  error: string;
1091
1370
  }
1092
1371
  /** A share-storage request was dispatched to `channel_id`. Emitted per
1093
- * targeted peer by `start(ProtectSecret)`. */
1372
+ * targeted peer by `start(ProtectSecret)`. `weight` is the number of
1373
+ * subshares sent, which is also the weight now stored for this channel:
1374
+ * its `ProtectSecretParams.weights` entry, 1 when the map does not name
1375
+ * it, or the stored weight on a publish the library starts on its own. */
1094
1376
  | {
1095
1377
  type: "ProtectSecretStarted";
1096
1378
  channel_id: string;
1097
1379
  version: number;
1098
1380
  trace_id: string;
1381
+ weight: number;
1099
1382
  }
1100
1383
  /** A share-storage request could not be dispatched to `channel_id`. */
1101
1384
  | {
@@ -1103,6 +1386,9 @@ export type DeRecEvent = {
1103
1386
  channel_id: string;
1104
1387
  version: number;
1105
1388
  error: string;
1389
+ /** Number of subshares that were to be sent to this channel; see
1390
+ * `ProtectSecretStarted.weight`. */
1391
+ weight: number;
1106
1392
  }
1107
1393
  /** A verify-share challenge was dispatched to `channel_id`. */
1108
1394
  | {
@@ -1111,11 +1397,12 @@ export type DeRecEvent = {
1111
1397
  version: number;
1112
1398
  trace_id: string;
1113
1399
  }
1114
- /** A verify-share challenge could not be dispatched to `channel_id`. */
1400
+ /** Verification reached no verdict for `channel_id`. `reason` says why. */
1115
1401
  | {
1116
1402
  type: "VerifySharesFailed";
1117
1403
  channel_id: string;
1118
1404
  version: number;
1405
+ reason: VerifyFailureReason;
1119
1406
  error: string;
1120
1407
  }
1121
1408
  /** A recovery share request was dispatched to `channel_id`. */
@@ -1132,9 +1419,13 @@ export type DeRecEvent = {
1132
1419
  version: number;
1133
1420
  error: string;
1134
1421
  }
1135
- /** An unpair request was dispatched to `channel_id`. Followed by an
1136
- * `Unpaired` event once the peer acknowledges (or in the same event
1137
- * vec, under `UnpairAck.NotRequired`). */
1422
+ /** An unpair request was dispatched to `channel_id`, by
1423
+ * `start(Unpair)` or by `restore` for each recovery channel. Under
1424
+ * `UnpairAck.Required` the channel is now `"Unpairing"`, and an
1425
+ * `Unpaired` event follows once the peer acknowledges or the
1426
+ * acknowledgement window elapses, or `UnpairRejected` if the peer
1427
+ * refuses (the channel is `"Paired"` again). Under
1428
+ * `UnpairAck.NotRequired`, `Unpaired` follows in the same event vec. */
1138
1429
  | {
1139
1430
  type: "UnpairFailed";
1140
1431
  channel_id: string;
@@ -1172,16 +1463,20 @@ export type DeRecEvent = {
1172
1463
  * oracle. Anyone who knows a HashedKeys contact's nonce can elicit a
1173
1464
  * key-publish response. Keep off unless you control both ends of
1174
1465
  * the transport.
1175
- * - `storeShare` — the helper's only admission-control point for
1176
- * inbound shares. The protocol enforces no size, quota or rate limit
1177
- * of its own, and `maxShareSize` is checked for range overlap at
1178
- * pairing time only, never against an actual share. While this is
1179
- * `false`, `ActionRequired` carries `share_size`, so the application
1180
- * can compare it to its quota and call `reject()` with
1181
- * `StatusEnum.SizeLimitExceeded`. Setting it `true` removes that
1182
- * opportunity entirely: every share from every paired Owner is stored
1183
- * unconditionally, at whatever size it arrives. Keep off in any
1184
- * deployment with per-user storage limits.
1466
+ * - `storeShare` — the helper's admission-control point for inbound
1467
+ * shares beyond what the library already checks: this device's own
1468
+ * configured `minShareSize`/`maxShareSize` is enforced against every
1469
+ * inbound share's actual length regardless of this flag, before
1470
+ * `ActionRequired` is ever considered — an out-of-range share is
1471
+ * refused with `StatusEnum.SizeLimitExceeded` either way, and this
1472
+ * flag has no say in it. While this is `false`, `ActionRequired`
1473
+ * carries the decoded request, which is where a tighter or per-user
1474
+ * size limit, storage quota, or any other app-specific decline
1475
+ * belongs — call `reject()` for that. Setting it `true` removes that
1476
+ * opportunity: every in-range share from every paired Owner is stored
1477
+ * unconditionally, unless the share store refuses it. Keep off in any
1478
+ * deployment with per-user storage limits tighter than this device's
1479
+ * own configured range.
1185
1480
  * - `unpair` — destructive. Accepting deletes the local channel
1186
1481
  * record before any UI confirmation.
1187
1482
  * - `updateChannelInfo` — silently overwrites the channel record with
@@ -1255,10 +1550,14 @@ export interface CommunicationInfo {
1255
1550
  }
1256
1551
  /** Why a `MessageIgnored` event dropped a message. Matches the Rust
1257
1552
  * `IgnoreReason` discriminants one-for-one. */
1258
- export type IgnoreReason = "PendingVerification" | "Expired";
1553
+ export type IgnoreReason = "PendingVerification" | "Expired" | "TimestampMismatch" | "StaleResponse";
1259
1554
  /** Why a `PeerNotRestored` event left a roster entry without a channel.
1260
1555
  * Matches the Rust `NotRestoredReason` discriminants one-for-one. */
1261
1556
  export type NotRestoredReason = "NoTransports";
1557
+ /** Why a `VerifySharesFailed` event reached no verdict for a channel.
1558
+ * Matches the Rust `VerifyFailureReason` discriminants
1559
+ * one-for-one. */
1560
+ export type VerifyFailureReason = "NoSharedKey" | "NoProof" | "DispatchFailed";
1262
1561
  /** Why a `RecoveryShareCorrupted` event set a helper's share aside.
1263
1562
  * Matches the Rust `CorruptionReason` discriminants one-for-one:
1264
1563
  * `Malformed` — no decodable share for the requested secret and version;
@@ -1267,12 +1566,13 @@ export type NotRestoredReason = "NoTransports";
1267
1566
  * disagrees with the shares the secret was rebuilt from. */
1268
1567
  export type CorruptionReason = "Malformed" | "InvalidProof" | "Inconsistent";
1269
1568
  /**
1270
- * The label vocabulary for `ActionRequired.action_kind` and
1271
- * `AutoAccepted.action_kind` — one value per pending-action kind the
1272
- * protocol can raise. Matches the Rust `PendingActionKind` discriminants
1273
- * one-for-one.
1569
+ * The label vocabulary for `ActionRequired.action_kind`,
1570
+ * `AutoAccepted.action_kind` and `RequestFailed.action_kind` — one value per
1571
+ * pending-action kind the protocol can raise, plus `"Failed"` for a request
1572
+ * whose body could not be decoded or that the channel cannot carry. Matches the Rust `PendingActionKind`
1573
+ * discriminants one-for-one.
1274
1574
  */
1275
- export type PendingActionKind = "Pairing" | "PrePair" | "StoreShare" | "VerifyShare" | "Discovery" | "GetShare" | "Unpair" | "UpdateChannelInfo";
1575
+ export type PendingActionKind = "Pairing" | "PrePair" | "StoreShare" | "VerifyShare" | "Discovery" | "GetShare" | "Unpair" | "UpdateChannelInfo" | "Failed";
1276
1576
  export interface ParameterRange {
1277
1577
  min_share_size: bigint;
1278
1578
  max_share_size: bigint;
@@ -1285,6 +1585,97 @@ export interface ParameterRange {
1285
1585
  min_unresponsive_deactivation_timeout: bigint;
1286
1586
  max_unresponsive_deactivation_timeout: bigint;
1287
1587
  }
1588
+ /**
1589
+ * `parameter_range` bounds inside `DeRecProtocolConfig`. Each bound is a
1590
+ * JSON number or a decimal string — the string form carries a bound past
1591
+ * `Number.MAX_SAFE_INTEGER` through a config file or a JSON encoder whose
1592
+ * numbers are `f64`. An absent bound is `0` ("no bound"), the same as
1593
+ * `DeRecProtocolBuilder.withParameterRange`.
1594
+ */
1595
+ export interface DeRecParameterRangeConfig {
1596
+ min_share_size?: number | string;
1597
+ max_share_size?: number | string;
1598
+ min_time_between_verifications?: number | string;
1599
+ max_time_between_verifications?: number | string;
1600
+ min_time_between_share_updates?: number | string;
1601
+ max_time_between_share_updates?: number | string;
1602
+ min_unresponsive_deletion_timeout?: number | string;
1603
+ max_unresponsive_deletion_timeout?: number | string;
1604
+ min_unresponsive_deactivation_timeout?: number | string;
1605
+ max_unresponsive_deactivation_timeout?: number | string;
1606
+ }
1607
+ /**
1608
+ * `auto_accept` inside `DeRecProtocolConfig`: the same eight flows as
1609
+ * {@link AutoAcceptPolicy}, named as the canonical JSON spells them rather
1610
+ * than in `camelCase`. An absent flow is `false`. See {@link AutoAcceptPolicy}
1611
+ * for per-field caveats.
1612
+ */
1613
+ export interface DeRecAutoAcceptConfig {
1614
+ pairing?: boolean;
1615
+ pre_pair?: boolean;
1616
+ store_share?: boolean;
1617
+ verify_share?: boolean;
1618
+ discovery?: boolean;
1619
+ get_share?: boolean;
1620
+ unpair?: boolean;
1621
+ update_channel_info?: boolean;
1622
+ }
1623
+ /**
1624
+ * Every data setting of `DeRecProtocolBuilder` in one serializable value, in
1625
+ * the canonical `snake_case` JSON shape every SDK accepts — the format for a
1626
+ * config file or a user record. Passed to `DeRecProtocolBuilder.withConfig`.
1627
+ *
1628
+ * Every field is optional: an absent or `null` field leaves that setting
1629
+ * unset. Stores, the delivery transport and the constructor's `secretId` are
1630
+ * not settings and are not carried here.
1631
+ *
1632
+ * Configs layer: apply a general config, then a per-user one, then any
1633
+ * individual `with*` call, and the last write wins per field —
1634
+ * `timeouts` merges per field too, so a config that sets only
1635
+ * `inbound_message_secs` keeps whatever `sharing_round_secs` an earlier
1636
+ * config or call set. Every other nested object (`parameter_range`,
1637
+ * `auto_accept`, `own_transports`) replaces as a whole.
1638
+ *
1639
+ * Values set through a field here are validated by `build()` exactly as the
1640
+ * matching `with*` setter's value would be: there is one rule set.
1641
+ *
1642
+ * ```json
1643
+ * {
1644
+ * "threshold": 3,
1645
+ * "max_subshares": "12",
1646
+ * "timeouts": { "inbound_message_secs": 120, "sharing_round_secs": 30 },
1647
+ * "parameter_range": { "min_share_size": 1, "max_share_size": 4096 },
1648
+ * "auto_accept": { "pairing": true, "store_share": true },
1649
+ * "auto_respond_on_failure": false,
1650
+ * "auto_reply_to": false,
1651
+ * "unpair_ack": "required",
1652
+ * "unsafe_connection": false,
1653
+ * "communication_info": { "name": "alice" },
1654
+ * "own_transports": [{ "uri": "https://me.example.com/derec", "protocol": "https" }],
1655
+ * "replica_id": "9007199254740993"
1656
+ * }
1657
+ * ```
1658
+ */
1659
+ export interface DeRecProtocolConfig {
1660
+ threshold?: number;
1661
+ /** Decimal string. A `bigint` is rejected; call
1662
+ * `DeRecProtocolBuilder.withMaxSubshares` directly for a `bigint`. */
1663
+ max_subshares?: string;
1664
+ timeouts?: Timeouts;
1665
+ parameter_range?: DeRecParameterRangeConfig;
1666
+ auto_accept?: DeRecAutoAcceptConfig;
1667
+ auto_respond_on_failure?: boolean;
1668
+ auto_reply_to?: boolean;
1669
+ /** Its name, or the discriminant `0` (`"required"`) / `1`
1670
+ * (`"not_required"`). */
1671
+ unpair_ack?: UnpairAck | 0 | 1;
1672
+ unsafe_connection?: boolean;
1673
+ communication_info?: Record<string, string>;
1674
+ own_transports?: Endpoint[];
1675
+ /** Decimal string. A `bigint` is rejected; call
1676
+ * `DeRecProtocolBuilder.withReplicaId` directly for a `bigint`. */
1677
+ replica_id?: string;
1678
+ }
1288
1679
  export interface PairRequestMessage {
1289
1680
  sender_kind: number;
1290
1681
  mlkem_ciphertext: Uint8Array;
@@ -1488,7 +1879,7 @@ export interface PrePairResponseExtractResult {
1488
1879
  response: PrePairResponseMessage;
1489
1880
  }
1490
1881
  export interface ProcessPrePairResult {
1491
- /** Initiator's ML-KEM-768 encapsulation key, validated against the
1882
+ /** Initiator's ML-KEM-1024 encapsulation key, validated against the
1492
1883
  * contact's `contactBindingHash`. */
1493
1884
  mlkem_encapsulation_key: Uint8Array;
1494
1885
  /** Initiator's ECIES public key, validated against the contact's
@@ -1497,6 +1888,13 @@ export interface ProcessPrePairResult {
1497
1888
  /** Nonce echoed from the original `ContactMessage`. */
1498
1889
  nonce: bigint;
1499
1890
  }
1891
+ /**
1892
+ * A channel paired with how many subshares it holds. A weight of `1` is an
1893
+ * ordinary helper; every weight must satisfy `1 <= weight < threshold`.
1894
+ * `channelId` is a channel id as bigint or decimal string, e.g. the
1895
+ * `channel_id` string from an event.
1896
+ */
1897
+ export type WeightedChannel = [channelId: bigint | string, weight: number];
1500
1898
  export interface SplitResult {
1501
1899
  /** Map keyed by channel id (`bigint`). */
1502
1900
  shares: Map<bigint, CommittedDeRecShare>;