@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/README.md +394 -6
- package/android/src/main/jniLibs/arm64-v8a/libderec_library.a +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libderec_library.a +0 -0
- package/android/src/main/jniLibs/x86_64/libderec_library.a +0 -0
- package/cpp/Convert.cpp +9 -0
- package/cpp/Convert.h +8 -2
- package/cpp/Primitives.cpp +113 -25
- package/cpp/StoreCallbacks.cpp +119 -20
- package/cpp/StoreCallbacks.h +8 -1
- package/cpp/derec_ffi.h +83 -15
- package/ios/DeRecFFI.xcframework/ios-arm64/Headers/derec_ffi.h +83 -15
- package/ios/DeRecFFI.xcframework/ios-arm64/libderec_library.a +0 -0
- package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/Headers/derec_ffi.h +83 -15
- package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/libderec_library.a +2 -2
- package/lib/errors.d.ts +11 -1
- package/lib/errors.js +63 -1
- package/lib/primitives.d.ts +2 -2
- package/lib/primitives.js +3 -1
- package/lib/protocol.d.ts +102 -6
- package/lib/protocol.js +158 -7
- package/lib/types.d.ts +426 -28
- package/lib/types.js +19 -0
- package/package.json +1 -1
- package/src/errors.ts +66 -1
- package/src/primitives.ts +13 -2
- package/src/protocol.ts +166 -8
- package/src/types.ts +432 -29
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
|
|
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
|
-
|
|
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. `
|
|
1115
|
-
* the
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
|
1176
|
-
* `
|
|
1177
|
-
*
|
|
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
|
|
1202
|
-
*
|
|
1203
|
-
*
|
|
1204
|
-
*
|
|
1205
|
-
* `
|
|
1206
|
-
*
|
|
1207
|
-
*
|
|
1208
|
-
*
|
|
1209
|
-
*
|
|
1210
|
-
*
|
|
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
|
|
1317
|
-
* `AutoAccepted.action_kind` — one value per
|
|
1318
|
-
* protocol can raise
|
|
1319
|
-
*
|
|
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-
|
|
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>;
|