@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/lib/errors.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
// Copyright (c) 2026 DeRec Alliance. All rights reserved.
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
exports.DEREC_ERROR_CATEGORIES = void 0;
|
|
5
|
+
exports.DEREC_ERROR_CODES = exports.DEREC_ERROR_CATEGORIES = void 0;
|
|
6
6
|
/**
|
|
7
7
|
* Every category name `derec_error_category_name` can return, and no others.
|
|
8
8
|
*
|
|
@@ -33,3 +33,65 @@ exports.DEREC_ERROR_CATEGORIES = [
|
|
|
33
33
|
"invariant",
|
|
34
34
|
"state_store",
|
|
35
35
|
];
|
|
36
|
+
/**
|
|
37
|
+
* Every code `derec_error_code_name` can return, and no others.
|
|
38
|
+
*
|
|
39
|
+
* Held to the shared enum fixture by a test, like {@link DEREC_ERROR_CATEGORIES}.
|
|
40
|
+
* Unlike the WASM bindings, this SDK raises no marshalling-level codes of its
|
|
41
|
+
* own before a value reaches the library, so this list is exactly the Rust
|
|
42
|
+
* `Code` table in `library/src/interop/error_codes.rs`.
|
|
43
|
+
*/
|
|
44
|
+
exports.DEREC_ERROR_CODES = [
|
|
45
|
+
"ok",
|
|
46
|
+
"non_ok_status",
|
|
47
|
+
"version_mismatch",
|
|
48
|
+
"invariant",
|
|
49
|
+
"invalid_input",
|
|
50
|
+
"protobuf_decode",
|
|
51
|
+
"protobuf_encode",
|
|
52
|
+
"protocol_violation",
|
|
53
|
+
"store_error",
|
|
54
|
+
"builder_error",
|
|
55
|
+
"missing_shared_key",
|
|
56
|
+
"role_mismatch",
|
|
57
|
+
"replica_id_not_configured",
|
|
58
|
+
"channel_already_paired",
|
|
59
|
+
"already_restored",
|
|
60
|
+
"restore_conflict",
|
|
61
|
+
"replica_id_conflict",
|
|
62
|
+
"encryption",
|
|
63
|
+
"keygen",
|
|
64
|
+
"finish_pairing_initiator",
|
|
65
|
+
"finish_pairing_responder",
|
|
66
|
+
"empty_transport_uri",
|
|
67
|
+
"invalid_contact_message",
|
|
68
|
+
"invalid_pair_request_message",
|
|
69
|
+
"invalid_pair_response_message",
|
|
70
|
+
"prepair_hash_mismatch",
|
|
71
|
+
"missing_replica_id",
|
|
72
|
+
"unexpected_replica_id",
|
|
73
|
+
"incompatible_parameter_range",
|
|
74
|
+
"empty_channels",
|
|
75
|
+
"duplicate_channel_id",
|
|
76
|
+
"invalid_threshold",
|
|
77
|
+
"empty_secret_data",
|
|
78
|
+
"vss_share_failed",
|
|
79
|
+
"invalid_weight",
|
|
80
|
+
"too_many_subshares",
|
|
81
|
+
"empty_responses",
|
|
82
|
+
"empty_committed_derec_share",
|
|
83
|
+
"decode_committed_derec_share",
|
|
84
|
+
"decode_derec_share",
|
|
85
|
+
"secret_id_mismatch",
|
|
86
|
+
"reconstruction_failed",
|
|
87
|
+
"malformed_recovered_secret",
|
|
88
|
+
"ffi_null_ptr",
|
|
89
|
+
"ffi_bad_length",
|
|
90
|
+
"ffi_bad_utf8",
|
|
91
|
+
"ffi_bad_proto",
|
|
92
|
+
"ffi_invalid_enum",
|
|
93
|
+
"ffi_bad_shared_key",
|
|
94
|
+
"ffi_nul_in_string",
|
|
95
|
+
"transport_invalid",
|
|
96
|
+
"no_usable_endpoint",
|
|
97
|
+
];
|
package/lib/primitives.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CommittedDeRecShare, CommunicationInfo, ContactMessage, ContactMode, CreateContactResult, DiscoveryProcessResult, GetSecretIdsVersionsRequestMessage, GetSecretIdsVersionsResponseMessage, GetShareRequestMessage, GetShareResponseMessage, PairRequestMessage, PairResponseMessage, PairingProcessResult, PairingRequestProduceResult, PairingResponseProduceResult, ParameterRange, PrePairRequestExtractResult, PrePairRequestMessage, PrePairResponseExtractResult, PrePairResponseMessage, ProcessPrePairResult, ProducePrePairNoKeysResult, ProducePrePairResult, ProduceResult, RecoverResult, SecretVersionEntry, SenderKind, SharingResponseProduceResult, SplitResult, StoreShareRequestMessage, StoreShareResponseMessage, TransportProtocol, UnpairRequestMessage, UnpairResponseMessage, UnpairingProcessResult, VerifyShareRequestMessage, VerifyShareResponseMessage } from './types';
|
|
1
|
+
import type { CommittedDeRecShare, CommunicationInfo, ContactMessage, ContactMode, CreateContactResult, DiscoveryProcessResult, GetSecretIdsVersionsRequestMessage, GetSecretIdsVersionsResponseMessage, GetShareRequestMessage, GetShareResponseMessage, PairRequestMessage, PairResponseMessage, PairingProcessResult, PairingRequestProduceResult, PairingResponseProduceResult, ParameterRange, PrePairRequestExtractResult, PrePairRequestMessage, PrePairResponseExtractResult, PrePairResponseMessage, ProcessPrePairResult, ProducePrePairNoKeysResult, ProducePrePairResult, ProduceResult, RecoverResult, SecretVersionEntry, SenderKind, SharingResponseProduceResult, SplitResult, StoreShareRequestMessage, StoreShareResponseMessage, TransportProtocol, UnpairRequestMessage, UnpairResponseMessage, UnpairingProcessResult, VerifyShareRequestMessage, VerifyShareResponseMessage, WeightedChannel } from './types';
|
|
2
2
|
export declare const primitives: {
|
|
3
3
|
discovery: {
|
|
4
4
|
request: {
|
|
@@ -67,7 +67,7 @@ export declare const primitives: {
|
|
|
67
67
|
};
|
|
68
68
|
sharing: {
|
|
69
69
|
request: {
|
|
70
|
-
split(channels:
|
|
70
|
+
split(channels: WeightedChannel[], secret_id: bigint, version: number, secret_data: Uint8Array, threshold: number): SplitResult;
|
|
71
71
|
produce(channel_id: bigint, version: number, secret_id: bigint, committed_share: CommittedDeRecShare, keep_list: number[], description: string, shared_key: Uint8Array, reply_to?: TransportProtocol[]): ProduceResult;
|
|
72
72
|
extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): {
|
|
73
73
|
request: StoreShareRequestMessage;
|
package/lib/primitives.js
CHANGED
|
@@ -270,7 +270,9 @@ const recovery = {
|
|
|
270
270
|
const sharing = {
|
|
271
271
|
request: {
|
|
272
272
|
split(channels, secret_id, version, secret_data, threshold) {
|
|
273
|
-
const
|
|
273
|
+
const ids = channels.map(([channelId]) => BigInt(channelId));
|
|
274
|
+
const weights = channels.map(([, weight]) => weight);
|
|
275
|
+
const packed = call('protect_secret', secret_id, secret_data, ids, weights, threshold, version);
|
|
274
276
|
const shares = new Map();
|
|
275
277
|
for (const entry of (0, binary_1.decodeCommittedShareEntries)(packed)) {
|
|
276
278
|
shares.set(entry.channelId, (0, messages_1.decodeMessage)(messages_1.MessageKind.CommittedDeRecShare, entry.shareProto));
|
package/lib/protocol.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FlowKind } from './types';
|
|
2
|
-
import type { AutoAcceptPolicy, ChannelStore, ContactMessage, ContactMode, DeRecEvent, Endpoint, DiscoveryParams, PairingParams, ParameterRange, ProtectSecretParams, RecoverSecretParams, UnpairReplicaParams, SecretStore, ShareStore, StateStore, StatusEnum, ReplicaDiscoveryParams, Timeouts, Transport, UnpairAck, UnpairParams, UpdateChannelInfoParams, UserSecretStore, VerifySharesParams } from './types';
|
|
2
|
+
import type { AutoAcceptPolicy, ChannelStore, ContactMessage, ContactMode, DeRecEvent, DeRecProtocolConfig, Endpoint, DiscoveryParams, PairingParams, ParameterRange, ProtectSecretParams, RecoverSecretParams, UnpairReplicaParams, SecretStore, ShareStore, StateStore, StatusEnum, ReplicaDiscoveryParams, Timeouts, Transport, UnpairAck, UnpairParams, UpdateChannelInfoParams, UserSecretStore, VerifySharesParams, StoresInit } from './types';
|
|
3
3
|
/** Shape of the `__DeRec` host object as seen from this file: every member is
|
|
4
4
|
* a plain callable, since the JSI layer exposes host functions this way and
|
|
5
5
|
* TypeScript has no static knowledge of them (see `primitives.ts`'s `call`
|
|
@@ -20,9 +20,12 @@ export declare function buildProtectSecretParams(params: ProtectSecretParams): R
|
|
|
20
20
|
* `SecretWire`'s own shape: `channel_id`/`replica_id` are the decimal `u64`
|
|
21
21
|
* strings the `SecretRecovered` event already carries, forwarded verbatim;
|
|
22
22
|
* `communication_info` is omitted per-entry (not sent as `{}`) whenever a
|
|
23
|
-
* helper or replica member has none
|
|
24
|
-
* the
|
|
25
|
-
*
|
|
23
|
+
* helper or replica member has none — `undefined`, `null`, or an empty map
|
|
24
|
+
* are all treated the same — matching the Go SDK's `omitempty` and the
|
|
25
|
+
* shared golden fixture byte-for-byte; `weight` is forwarded as given
|
|
26
|
+
* whenever a helper carries one; `replicas` is omitted entirely when the
|
|
27
|
+
* recovered secret has no replica group, and `threshold` when the roster
|
|
28
|
+
* records none.
|
|
26
29
|
*
|
|
27
30
|
* Exported so it can be golden-tested against the shared wire fixture
|
|
28
31
|
* without a native module.
|
|
@@ -55,6 +58,13 @@ export declare class DeRecProtocolBuilder {
|
|
|
55
58
|
withSecretStore(store: SecretStore): this;
|
|
56
59
|
withUserSecretStore(store: UserSecretStore): this;
|
|
57
60
|
withStateStore(store: StateStore): this;
|
|
61
|
+
/**
|
|
62
|
+
* Set every store in one call. Forwards to {@link withChannelStore},
|
|
63
|
+
* {@link withShareStore}, {@link withSecretStore},
|
|
64
|
+
* {@link withUserSecretStore} and {@link withStateStore} in turn, so
|
|
65
|
+
* calling one of them again afterwards replaces just that store.
|
|
66
|
+
*/
|
|
67
|
+
withStores(stores: StoresInit): this;
|
|
58
68
|
withTransport(transport: Transport): this;
|
|
59
69
|
/**
|
|
60
70
|
* Set every transport endpoint this application serves, in preference
|
|
@@ -72,7 +82,22 @@ export declare class DeRecProtocolBuilder {
|
|
|
72
82
|
* since a node with no endpoint cannot be reached by any peer.
|
|
73
83
|
*/
|
|
74
84
|
withOwnTransports(transports: Endpoint[]): this;
|
|
75
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* The starting threshold: the minimum number of subshares required to
|
|
87
|
+
* reconstruct the secret, for a secret with no threshold stored. Every
|
|
88
|
+
* publish stores the threshold it used (`UserSecrets.threshold`), the
|
|
89
|
+
* roster carries it, and `restore` and replica hydration put it back. A
|
|
90
|
+
* round splits at `ProtectSecretParams.threshold`, otherwise at the stored
|
|
91
|
+
* threshold, and only otherwise at this value, so changing it later does
|
|
92
|
+
* not change a stored threshold.
|
|
93
|
+
*
|
|
94
|
+
* A Helper holds one subshare unless `ProtectSecretParams.weights` has
|
|
95
|
+
* given it a weight, so with no weights the threshold is a number of
|
|
96
|
+
* Helpers, as before. Every weight must be below the round's threshold, so
|
|
97
|
+
* no single Helper can recover alone. When this value applies and is
|
|
98
|
+
* lowered to or below a stored weight, every publish fails with
|
|
99
|
+
* `invalid_weight` until `ProtectSecret` assigns valid weights. Default: 3.
|
|
100
|
+
*/
|
|
76
101
|
withThreshold(threshold: number): this;
|
|
77
102
|
/**
|
|
78
103
|
* Every field is optional; not calling this at all leaves all four at the
|
|
@@ -92,7 +117,12 @@ export declare class DeRecProtocolBuilder {
|
|
|
92
117
|
* map is part of the protocol from construction.
|
|
93
118
|
*/
|
|
94
119
|
withCommunicationInfo(info: Record<string, string>): this;
|
|
95
|
-
/**
|
|
120
|
+
/**
|
|
121
|
+
* Who answers an inbound request whose handling failed. `true`: the
|
|
122
|
+
* library sends the response itself. `false`: nothing is sent, and the
|
|
123
|
+
* `RequestFailed` event carries a token to pass to `reject`. Default:
|
|
124
|
+
* false.
|
|
125
|
+
*/
|
|
96
126
|
withAutoRespondOnFailure(enabled: boolean): this;
|
|
97
127
|
/** Default: "required". */
|
|
98
128
|
withUnpairAck(ack: UnpairAck): this;
|
|
@@ -117,8 +147,53 @@ export declare class DeRecProtocolBuilder {
|
|
|
117
147
|
* applies its default.
|
|
118
148
|
*
|
|
119
149
|
* Default: unset — no constraints advertised, every peer range accepted.
|
|
150
|
+
*
|
|
151
|
+
* Share size is enforced by the helper; frequency limits are enforced by
|
|
152
|
+
* the app's share store (refuse from `save`/`recordVerification`); the
|
|
153
|
+
* deletion and deactivation timeouts are checked at pairing only.
|
|
120
154
|
*/
|
|
121
155
|
withParameterRange(range: Partial<ParameterRange>): this;
|
|
156
|
+
/**
|
|
157
|
+
* Cap the total number of subshares one sharing round may split the
|
|
158
|
+
* secret into — the sum of every helper's weight. Crosses to the library
|
|
159
|
+
* as a decimal string. Default: unset, no cap.
|
|
160
|
+
*
|
|
161
|
+
* Checked only by `start(ProtectSecret)`, including the publishes the
|
|
162
|
+
* library starts itself: a round over the cap fails with
|
|
163
|
+
* `too_many_subshares` before anything is sent. Recovery applies no cap.
|
|
164
|
+
* Recovering costs time that grows quadratically with the total number of
|
|
165
|
+
* subshares, so the cap bounds what a later recovery must do. A cap below
|
|
166
|
+
* the threshold makes `build()` throw.
|
|
167
|
+
*/
|
|
168
|
+
withMaxSubshares(max: bigint | number): this;
|
|
169
|
+
/**
|
|
170
|
+
* Layer a {@link DeRecProtocolConfig} onto the builder: a field the config
|
|
171
|
+
* sets (present and not `null`) replaces what the builder currently
|
|
172
|
+
* holds, a field it leaves unset is untouched, and `timeouts` merges per
|
|
173
|
+
* field against the builder's current timeouts rather than resetting the
|
|
174
|
+
* other three. Every other nested object replaces as a whole. Apply a
|
|
175
|
+
* general config, then a per-user one, then any individual `with*` call —
|
|
176
|
+
* the last write wins per field, same as two `withConfig` calls in a row.
|
|
177
|
+
*
|
|
178
|
+
* Every key is assigned verbatim into the same `snake_case` config JSON
|
|
179
|
+
* `build()` sends to `protocol_new`, whatever it is spelled as: nothing
|
|
180
|
+
* here validates, defaults, or recognizes field names. Rust does both —
|
|
181
|
+
* `deny_unknown_fields` is what turns a misspelled key into an error.
|
|
182
|
+
*
|
|
183
|
+
* `secret_id` is not a config field — it is the constructor argument —
|
|
184
|
+
* and `build()` always sends the constructor's value last, so a config
|
|
185
|
+
* (e.g. loaded from a per-user record) can never redirect which secret
|
|
186
|
+
* this instance manages even if it carries a `secret_id` key.
|
|
187
|
+
*
|
|
188
|
+
* A config parsed from JSON can carry an own `__proto__` key. Assigning
|
|
189
|
+
* it through plain bracket notation would hit the accessor
|
|
190
|
+
* `Object.prototype` defines for that name and replace this builder's
|
|
191
|
+
* config object's prototype instead of setting a field on it — the value
|
|
192
|
+
* would then vanish from the JSON payload instead of reaching Rust to be
|
|
193
|
+
* rejected as an unknown key. `Object.defineProperty` makes it a genuine
|
|
194
|
+
* own property, the same as every other key here.
|
|
195
|
+
*/
|
|
196
|
+
withConfig(config: DeRecProtocolConfig): this;
|
|
122
197
|
build(): DeRecProtocol;
|
|
123
198
|
}
|
|
124
199
|
/**
|
|
@@ -186,6 +261,17 @@ export declare class DeRecProtocol {
|
|
|
186
261
|
start(flowKind: FlowKind.UnpairReplica, params: UnpairReplicaParams): Promise<DeRecEvent[]>;
|
|
187
262
|
process(message: Uint8Array): Promise<DeRecEvent[]>;
|
|
188
263
|
tick(): Promise<DeRecEvent[]>;
|
|
264
|
+
/**
|
|
265
|
+
* Accept a pending action from an `ActionRequired` event, returning the
|
|
266
|
+
* events it produces.
|
|
267
|
+
*
|
|
268
|
+
* When the action completes a pairing, the publish that follows reuses the
|
|
269
|
+
* stored helper weights. If one is no longer valid under the round's
|
|
270
|
+
* threshold, that publish fails with `invalid_weight` before anything is
|
|
271
|
+
* sent and this throws: the pairing has completed and its response was
|
|
272
|
+
* sent, but the `PairingCompleted` event is not returned. Call `start`
|
|
273
|
+
* with `ProtectSecret` and valid weights to publish.
|
|
274
|
+
*/
|
|
189
275
|
accept(actionBytes: Uint8Array): Promise<DeRecEvent[]>;
|
|
190
276
|
reject(actionBytes: Uint8Array, status: StatusEnum, memo: string): Promise<void>;
|
|
191
277
|
getFingerprint(channelId: bigint | number): Promise<string>;
|
|
@@ -198,6 +284,13 @@ export declare class DeRecProtocol {
|
|
|
198
284
|
* group's vault: the source's publish is then installed as it arrives,
|
|
199
285
|
* with no further prompt. Ask the user before calling this; to decline,
|
|
200
286
|
* never confirm.
|
|
287
|
+
*
|
|
288
|
+
* On a promotion the current snapshot is published to the promoted peer,
|
|
289
|
+
* reusing the stored helper weights. If one is no longer valid under the
|
|
290
|
+
* current threshold, that publish fails with `invalid_weight` before
|
|
291
|
+
* anything is sent and this throws. The promotion stands: calling this
|
|
292
|
+
* again resolves `true` and publishes nothing, so call `start` with
|
|
293
|
+
* `ProtectSecret` and valid weights to reach the promoted peer.
|
|
201
294
|
*/
|
|
202
295
|
verifyFingerprint(channelId: bigint | number, fingerprint: string): Promise<boolean>;
|
|
203
296
|
removeExpiredChannels(olderThanSecs: number): Promise<string[]>;
|
|
@@ -211,6 +304,9 @@ export declare class DeRecProtocol {
|
|
|
211
304
|
* reported as a `PeerNotRestored` event in the returned array and the rest
|
|
212
305
|
* of the roster is restored.
|
|
213
306
|
*
|
|
307
|
+
* A helper's or member's `communication_info` may be absent or `null`;
|
|
308
|
+
* both mean the same thing — no entries.
|
|
309
|
+
*
|
|
214
310
|
* Rejects with a `DeRecError`; among its codes:
|
|
215
311
|
*
|
|
216
312
|
* | code | meaning |
|
package/lib/protocol.js
CHANGED
|
@@ -78,19 +78,32 @@ function buildProtectSecretParams(params) {
|
|
|
78
78
|
if (params.description !== undefined) {
|
|
79
79
|
out.description = params.description;
|
|
80
80
|
}
|
|
81
|
+
if (params.weights != null && params.weights.size > 0) {
|
|
82
|
+
const weights = {};
|
|
83
|
+
for (const [id, weight] of params.weights) {
|
|
84
|
+
weights[id.toString()] = weight;
|
|
85
|
+
}
|
|
86
|
+
out.weights = weights;
|
|
87
|
+
}
|
|
88
|
+
if (params.threshold != null) {
|
|
89
|
+
out.threshold = params.threshold;
|
|
90
|
+
}
|
|
81
91
|
return out;
|
|
82
92
|
}
|
|
83
93
|
function communicationInfoOrOmit(info) {
|
|
84
|
-
return info
|
|
94
|
+
return info != null && Object.keys(info).length > 0 ? info : undefined;
|
|
85
95
|
}
|
|
86
96
|
/**
|
|
87
97
|
* `RestoreParamsJson` (`library/src/interop/ffi/protocol/handle/flow.rs`). Mirrors
|
|
88
98
|
* `SecretWire`'s own shape: `channel_id`/`replica_id` are the decimal `u64`
|
|
89
99
|
* strings the `SecretRecovered` event already carries, forwarded verbatim;
|
|
90
100
|
* `communication_info` is omitted per-entry (not sent as `{}`) whenever a
|
|
91
|
-
* helper or replica member has none
|
|
92
|
-
* the
|
|
93
|
-
*
|
|
101
|
+
* helper or replica member has none — `undefined`, `null`, or an empty map
|
|
102
|
+
* are all treated the same — matching the Go SDK's `omitempty` and the
|
|
103
|
+
* shared golden fixture byte-for-byte; `weight` is forwarded as given
|
|
104
|
+
* whenever a helper carries one; `replicas` is omitted entirely when the
|
|
105
|
+
* recovered secret has no replica group, and `threshold` when the roster
|
|
106
|
+
* records none.
|
|
94
107
|
*
|
|
95
108
|
* Exported so it can be golden-tested against the shared wire fixture
|
|
96
109
|
* without a native module.
|
|
@@ -107,6 +120,9 @@ function buildRestoreParams(recoveredSecret, version) {
|
|
|
107
120
|
if (info !== undefined) {
|
|
108
121
|
out.communication_info = info;
|
|
109
122
|
}
|
|
123
|
+
if (helper.weight !== undefined) {
|
|
124
|
+
out.weight = helper.weight;
|
|
125
|
+
}
|
|
110
126
|
return out;
|
|
111
127
|
}),
|
|
112
128
|
secrets: recoveredSecret.secrets.map((secret) => ({
|
|
@@ -133,6 +149,9 @@ function buildRestoreParams(recoveredSecret, version) {
|
|
|
133
149
|
shared_key: Array.from(recoveredSecret.replicas.shared_key),
|
|
134
150
|
};
|
|
135
151
|
}
|
|
152
|
+
if (recoveredSecret.threshold != null) {
|
|
153
|
+
recovered.threshold = recoveredSecret.threshold;
|
|
154
|
+
}
|
|
136
155
|
return { version, recovered_secret: recovered };
|
|
137
156
|
}
|
|
138
157
|
/**
|
|
@@ -267,6 +286,19 @@ class DeRecProtocolBuilder {
|
|
|
267
286
|
this.stores.stateStore = store;
|
|
268
287
|
return this;
|
|
269
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* Set every store in one call. Forwards to {@link withChannelStore},
|
|
291
|
+
* {@link withShareStore}, {@link withSecretStore},
|
|
292
|
+
* {@link withUserSecretStore} and {@link withStateStore} in turn, so
|
|
293
|
+
* calling one of them again afterwards replaces just that store.
|
|
294
|
+
*/
|
|
295
|
+
withStores(stores) {
|
|
296
|
+
return this.withChannelStore(stores.channelStore)
|
|
297
|
+
.withShareStore(stores.shareStore)
|
|
298
|
+
.withSecretStore(stores.secretStore)
|
|
299
|
+
.withUserSecretStore(stores.userSecretStore)
|
|
300
|
+
.withStateStore(stores.stateStore);
|
|
301
|
+
}
|
|
270
302
|
withTransport(transport) {
|
|
271
303
|
this.stores.transport = transport;
|
|
272
304
|
return this;
|
|
@@ -290,7 +322,22 @@ class DeRecProtocolBuilder {
|
|
|
290
322
|
this.config.own_transports = transports.map(({ uri, protocol }) => ({ uri, protocol }));
|
|
291
323
|
return this;
|
|
292
324
|
}
|
|
293
|
-
/**
|
|
325
|
+
/**
|
|
326
|
+
* The starting threshold: the minimum number of subshares required to
|
|
327
|
+
* reconstruct the secret, for a secret with no threshold stored. Every
|
|
328
|
+
* publish stores the threshold it used (`UserSecrets.threshold`), the
|
|
329
|
+
* roster carries it, and `restore` and replica hydration put it back. A
|
|
330
|
+
* round splits at `ProtectSecretParams.threshold`, otherwise at the stored
|
|
331
|
+
* threshold, and only otherwise at this value, so changing it later does
|
|
332
|
+
* not change a stored threshold.
|
|
333
|
+
*
|
|
334
|
+
* A Helper holds one subshare unless `ProtectSecretParams.weights` has
|
|
335
|
+
* given it a weight, so with no weights the threshold is a number of
|
|
336
|
+
* Helpers, as before. Every weight must be below the round's threshold, so
|
|
337
|
+
* no single Helper can recover alone. When this value applies and is
|
|
338
|
+
* lowered to or below a stored weight, every publish fails with
|
|
339
|
+
* `invalid_weight` until `ProtectSecret` assigns valid weights. Default: 3.
|
|
340
|
+
*/
|
|
294
341
|
withThreshold(threshold) {
|
|
295
342
|
this.config.threshold = threshold;
|
|
296
343
|
return this;
|
|
@@ -322,7 +369,12 @@ class DeRecProtocolBuilder {
|
|
|
322
369
|
this.config.communication_info = info;
|
|
323
370
|
return this;
|
|
324
371
|
}
|
|
325
|
-
/**
|
|
372
|
+
/**
|
|
373
|
+
* Who answers an inbound request whose handling failed. `true`: the
|
|
374
|
+
* library sends the response itself. `false`: nothing is sent, and the
|
|
375
|
+
* `RequestFailed` event carries a token to pass to `reject`. Default:
|
|
376
|
+
* false.
|
|
377
|
+
*/
|
|
326
378
|
withAutoRespondOnFailure(enabled) {
|
|
327
379
|
this.config.auto_respond_on_failure = enabled;
|
|
328
380
|
return this;
|
|
@@ -371,6 +423,10 @@ class DeRecProtocolBuilder {
|
|
|
371
423
|
* applies its default.
|
|
372
424
|
*
|
|
373
425
|
* Default: unset — no constraints advertised, every peer range accepted.
|
|
426
|
+
*
|
|
427
|
+
* Share size is enforced by the helper; frequency limits are enforced by
|
|
428
|
+
* the app's share store (refuse from `save`/`recordVerification`); the
|
|
429
|
+
* deletion and deactivation timeouts are checked at pairing only.
|
|
374
430
|
*/
|
|
375
431
|
withParameterRange(range) {
|
|
376
432
|
this.config.parameter_range = {
|
|
@@ -387,11 +443,85 @@ class DeRecProtocolBuilder {
|
|
|
387
443
|
};
|
|
388
444
|
return this;
|
|
389
445
|
}
|
|
446
|
+
/**
|
|
447
|
+
* Cap the total number of subshares one sharing round may split the
|
|
448
|
+
* secret into — the sum of every helper's weight. Crosses to the library
|
|
449
|
+
* as a decimal string. Default: unset, no cap.
|
|
450
|
+
*
|
|
451
|
+
* Checked only by `start(ProtectSecret)`, including the publishes the
|
|
452
|
+
* library starts itself: a round over the cap fails with
|
|
453
|
+
* `too_many_subshares` before anything is sent. Recovery applies no cap.
|
|
454
|
+
* Recovering costs time that grows quadratically with the total number of
|
|
455
|
+
* subshares, so the cap bounds what a later recovery must do. A cap below
|
|
456
|
+
* the threshold makes `build()` throw.
|
|
457
|
+
*/
|
|
458
|
+
withMaxSubshares(max) {
|
|
459
|
+
this.config.max_subshares = BigInt(max).toString();
|
|
460
|
+
return this;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Layer a {@link DeRecProtocolConfig} onto the builder: a field the config
|
|
464
|
+
* sets (present and not `null`) replaces what the builder currently
|
|
465
|
+
* holds, a field it leaves unset is untouched, and `timeouts` merges per
|
|
466
|
+
* field against the builder's current timeouts rather than resetting the
|
|
467
|
+
* other three. Every other nested object replaces as a whole. Apply a
|
|
468
|
+
* general config, then a per-user one, then any individual `with*` call —
|
|
469
|
+
* the last write wins per field, same as two `withConfig` calls in a row.
|
|
470
|
+
*
|
|
471
|
+
* Every key is assigned verbatim into the same `snake_case` config JSON
|
|
472
|
+
* `build()` sends to `protocol_new`, whatever it is spelled as: nothing
|
|
473
|
+
* here validates, defaults, or recognizes field names. Rust does both —
|
|
474
|
+
* `deny_unknown_fields` is what turns a misspelled key into an error.
|
|
475
|
+
*
|
|
476
|
+
* `secret_id` is not a config field — it is the constructor argument —
|
|
477
|
+
* and `build()` always sends the constructor's value last, so a config
|
|
478
|
+
* (e.g. loaded from a per-user record) can never redirect which secret
|
|
479
|
+
* this instance manages even if it carries a `secret_id` key.
|
|
480
|
+
*
|
|
481
|
+
* A config parsed from JSON can carry an own `__proto__` key. Assigning
|
|
482
|
+
* it through plain bracket notation would hit the accessor
|
|
483
|
+
* `Object.prototype` defines for that name and replace this builder's
|
|
484
|
+
* config object's prototype instead of setting a field on it — the value
|
|
485
|
+
* would then vanish from the JSON payload instead of reaching Rust to be
|
|
486
|
+
* rejected as an unknown key. `Object.defineProperty` makes it a genuine
|
|
487
|
+
* own property, the same as every other key here.
|
|
488
|
+
*/
|
|
489
|
+
withConfig(config) {
|
|
490
|
+
for (const [key, value] of Object.entries(config)) {
|
|
491
|
+
if (value === undefined || value === null) {
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (key === 'timeouts') {
|
|
495
|
+
const held = this.config.timeouts ?? {};
|
|
496
|
+
const merged = { ...held };
|
|
497
|
+
for (const [timeoutKey, timeoutValue] of Object.entries(value)) {
|
|
498
|
+
if (timeoutValue !== undefined && timeoutValue !== null) {
|
|
499
|
+
merged[timeoutKey] = timeoutValue;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
this.config.timeouts = merged;
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
if (key === '__proto__') {
|
|
506
|
+
Object.defineProperty(this.config, key, {
|
|
507
|
+
value,
|
|
508
|
+
writable: true,
|
|
509
|
+
enumerable: true,
|
|
510
|
+
configurable: true,
|
|
511
|
+
});
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
this.config[key] = value;
|
|
515
|
+
}
|
|
516
|
+
return this;
|
|
517
|
+
}
|
|
390
518
|
build() {
|
|
391
519
|
const config = {
|
|
392
|
-
secret_id: this.secretIdValue.toString(),
|
|
393
520
|
replica_id: null,
|
|
394
521
|
...this.config,
|
|
522
|
+
// Last, so a config's own `secret_id` key (it is not a config field;
|
|
523
|
+
// see `withConfig`) can never override the constructor's.
|
|
524
|
+
secret_id: this.secretIdValue.toString(),
|
|
395
525
|
};
|
|
396
526
|
const host = (0, native_1.getNative)().protocol_new(JSON.stringify(config), new Uint8Array(0), this.stores);
|
|
397
527
|
return DeRecProtocol.fromHost(host);
|
|
@@ -490,6 +620,17 @@ class DeRecProtocol {
|
|
|
490
620
|
const buffer = (await this.host.tick());
|
|
491
621
|
return decodeEvents(buffer);
|
|
492
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* Accept a pending action from an `ActionRequired` event, returning the
|
|
625
|
+
* events it produces.
|
|
626
|
+
*
|
|
627
|
+
* When the action completes a pairing, the publish that follows reuses the
|
|
628
|
+
* stored helper weights. If one is no longer valid under the round's
|
|
629
|
+
* threshold, that publish fails with `invalid_weight` before anything is
|
|
630
|
+
* sent and this throws: the pairing has completed and its response was
|
|
631
|
+
* sent, but the `PairingCompleted` event is not returned. Call `start`
|
|
632
|
+
* with `ProtectSecret` and valid weights to publish.
|
|
633
|
+
*/
|
|
493
634
|
async accept(actionBytes) {
|
|
494
635
|
const buffer = (await this.host.accept(actionBytes));
|
|
495
636
|
return decodeEvents(buffer);
|
|
@@ -509,6 +650,13 @@ class DeRecProtocol {
|
|
|
509
650
|
* group's vault: the source's publish is then installed as it arrives,
|
|
510
651
|
* with no further prompt. Ask the user before calling this; to decline,
|
|
511
652
|
* never confirm.
|
|
653
|
+
*
|
|
654
|
+
* On a promotion the current snapshot is published to the promoted peer,
|
|
655
|
+
* reusing the stored helper weights. If one is no longer valid under the
|
|
656
|
+
* current threshold, that publish fails with `invalid_weight` before
|
|
657
|
+
* anything is sent and this throws. The promotion stands: calling this
|
|
658
|
+
* again resolves `true` and publishes nothing, so call `start` with
|
|
659
|
+
* `ProtectSecret` and valid weights to reach the promoted peer.
|
|
512
660
|
*/
|
|
513
661
|
async verifyFingerprint(channelId, fingerprint) {
|
|
514
662
|
return (await this.host.verifyFingerprint(toBigInt(channelId), fingerprint));
|
|
@@ -527,6 +675,9 @@ class DeRecProtocol {
|
|
|
527
675
|
* reported as a `PeerNotRestored` event in the returned array and the rest
|
|
528
676
|
* of the roster is restored.
|
|
529
677
|
*
|
|
678
|
+
* A helper's or member's `communication_info` may be absent or `null`;
|
|
679
|
+
* both mean the same thing — no entries.
|
|
680
|
+
*
|
|
530
681
|
* Rejects with a `DeRecError`; among its codes:
|
|
531
682
|
*
|
|
532
683
|
* | code | meaning |
|