@derec-alliance/react-native 0.0.8 → 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/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
|
|
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. `
|
|
1047
|
-
* the
|
|
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
|
-
/**
|
|
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
|
|
1136
|
-
* `
|
|
1137
|
-
*
|
|
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
|
|
1176
|
-
*
|
|
1177
|
-
*
|
|
1178
|
-
*
|
|
1179
|
-
* `
|
|
1180
|
-
*
|
|
1181
|
-
*
|
|
1182
|
-
*
|
|
1183
|
-
*
|
|
1184
|
-
*
|
|
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
|
|
1271
|
-
* `AutoAccepted.action_kind` — one value per
|
|
1272
|
-
* protocol can raise
|
|
1273
|
-
*
|
|
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-
|
|
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>;
|