@derec-alliance/react-native 0.0.5 → 0.0.7
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 +33 -15
- package/android/CMakeLists.txt +1 -1
- 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 +125 -0
- package/cpp/Convert.h +35 -0
- package/cpp/DeRecInstaller.cpp +0 -12
- package/cpp/Primitives.cpp +175 -26
- package/cpp/Primitives.h +33 -0
- package/cpp/ProtocolHost.cpp +38 -50
- package/cpp/StoreCallbacks.cpp +179 -301
- package/cpp/UserSecretsJson.cpp +109 -0
- package/cpp/UserSecretsJson.h +28 -0
- package/cpp/derec_ffi.h +298 -132
- package/ios/DeRecFFI.xcframework/Info.plist +5 -5
- package/ios/DeRecFFI.xcframework/ios-arm64/Headers/derec_ffi.h +298 -132
- package/ios/DeRecFFI.xcframework/ios-arm64/libderec_library.a +0 -0
- package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/Headers/derec_ffi.h +298 -132
- package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/libderec_library.a +2 -2
- package/lib/errors.d.ts +16 -0
- package/lib/native.d.ts +5 -1
- package/lib/primitives.d.ts +18 -21
- package/lib/primitives.js +48 -25
- package/lib/protocol.d.ts +65 -102
- package/lib/protocol.js +98 -171
- package/lib/types.d.ts +299 -110
- package/lib/types.js +43 -13
- package/package.json +1 -1
- package/src/errors.ts +17 -0
- package/src/native.ts +2 -1
- package/src/primitives.ts +82 -25
- package/src/protocol.ts +105 -189
- package/src/types.ts +304 -99
package/src/types.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (c) 2026 DeRec Alliance. All rights reserved.
|
|
3
3
|
|
|
4
|
+
import { getNative } from './native';
|
|
5
|
+
|
|
4
6
|
export interface SecretStore {
|
|
5
7
|
load(
|
|
6
8
|
secretId: string,
|
|
@@ -9,15 +11,15 @@ export interface SecretStore {
|
|
|
9
11
|
): Promise<Uint8Array | null | undefined>;
|
|
10
12
|
/**
|
|
11
13
|
* Load secrets of the same `kind` for several channels in one call,
|
|
12
|
-
* scoped to `secretId`. Must return an array with one entry per
|
|
13
|
-
* id, in the same order, using `null` (or `undefined`) for channels
|
|
14
|
-
* with no stored secret of `kind`.
|
|
14
|
+
* scoped to `secretId`. Must return an array with exactly one entry per
|
|
15
|
+
* input id, in the same order, using `null` (or `undefined`) for channels
|
|
16
|
+
* with no stored secret of `kind`. Whether a missing entry is an error is
|
|
17
|
+
* decided by the library.
|
|
15
18
|
*/
|
|
16
19
|
loadMany(
|
|
17
20
|
secretId: string,
|
|
18
21
|
channelIds: string[],
|
|
19
22
|
kind: 0 | 1 | 2,
|
|
20
|
-
missingPolicy: "skip" | "fail",
|
|
21
23
|
): Promise<Array<Uint8Array | null | undefined>>;
|
|
22
24
|
save(
|
|
23
25
|
secretId: string,
|
|
@@ -120,30 +122,15 @@ export function channelFilterMatches(
|
|
|
120
122
|
/**
|
|
121
123
|
* The endpoints a peer-supplied message advertises, in the peer's own order.
|
|
122
124
|
*
|
|
123
|
-
* Yields `supported_transports` when it is non-empty, and otherwise the
|
|
124
|
-
* singular `transport_protocol` — which is how every implementation predating
|
|
125
|
-
* the offer list advertises, and the reason this is a function rather than a
|
|
126
|
-
* field read. Reading `transport_protocol` directly is a bug: its meaning
|
|
127
|
-
* narrowed to "one entry of a list, and possibly absent", so a peer that has
|
|
128
|
-
* moved past it looks unreachable to a reader that was correct before 0.0.3.
|
|
129
|
-
*
|
|
130
125
|
* Reports what was advertised, not what is acceptable — nothing here is
|
|
131
126
|
* validated, and the protocol still applies its own transport policy to
|
|
132
127
|
* whatever it records.
|
|
133
128
|
*/
|
|
134
129
|
export function advertisedEndpoints(
|
|
135
|
-
message:
|
|
136
|
-
| {
|
|
137
|
-
transport_protocol?: TransportProtocol;
|
|
138
|
-
supported_transports?: TransportProtocol[];
|
|
139
|
-
}
|
|
140
|
-
| null
|
|
141
|
-
| undefined,
|
|
130
|
+
message: { supported_transports?: TransportProtocol[] } | null | undefined,
|
|
142
131
|
): TransportProtocol[] {
|
|
143
132
|
if (!message) return [];
|
|
144
|
-
|
|
145
|
-
if (offers.length > 0) return offers;
|
|
146
|
-
return message.transport_protocol ? [message.transport_protocol] : [];
|
|
133
|
+
return message.supported_transports ?? [];
|
|
147
134
|
}
|
|
148
135
|
|
|
149
136
|
/**
|
|
@@ -242,11 +229,7 @@ export interface ChannelStore {
|
|
|
242
229
|
secretId: string,
|
|
243
230
|
filter: ReplicaFilter,
|
|
244
231
|
): Promise<Uint8Array | null | undefined>;
|
|
245
|
-
linkChannel(
|
|
246
|
-
secretId: string,
|
|
247
|
-
channelId: string,
|
|
248
|
-
linkedChannelId: string,
|
|
249
|
-
): Promise<void>;
|
|
232
|
+
linkChannel(secretId: string, a: string, b: string): Promise<void>;
|
|
250
233
|
linkedChannels(secretId: string, channelId: string): Promise<string[]>;
|
|
251
234
|
}
|
|
252
235
|
|
|
@@ -278,6 +261,42 @@ export interface ShareStore {
|
|
|
278
261
|
save(secretId: string, channelId: string, share: Share): Promise<void>;
|
|
279
262
|
latestVersion(secretId: string): Promise<number | null>;
|
|
280
263
|
removeChannel(secretId: string, channelId: string): Promise<void>;
|
|
264
|
+
/**
|
|
265
|
+
* Drop the shares stored under `(secretId, channelId)` at each of
|
|
266
|
+
* `versions`. Idempotent: a version that is not stored is skipped, and
|
|
267
|
+
* an empty array is a no-op.
|
|
268
|
+
*
|
|
269
|
+
* A helper calls this to apply `StoreShareRequestMessage.keepList`, the
|
|
270
|
+
* complete set of versions the owner wants retained: every stored
|
|
271
|
+
* version outside it is removed once the incoming share is persisted.
|
|
272
|
+
* Shares under other channels or partitions must be left untouched.
|
|
273
|
+
*/
|
|
274
|
+
removeVersions(
|
|
275
|
+
secretId: string,
|
|
276
|
+
channelId: string,
|
|
277
|
+
versions: number[],
|
|
278
|
+
): Promise<void>;
|
|
279
|
+
/**
|
|
280
|
+
* Owner only: the versions every helper keeps after the owner distributes
|
|
281
|
+
* `version`. Asked once per sharing round, before anything is sent,
|
|
282
|
+
* including the rounds the library starts itself; the answer becomes
|
|
283
|
+
* `keepList` for every helper.
|
|
284
|
+
*
|
|
285
|
+
* Return `null` or `undefined` to send no `keepList` (it goes out empty):
|
|
286
|
+
* helpers then keep every version they hold. An app that wants to cap how
|
|
287
|
+
* many versions helpers retain returns that cap here. A returned list is
|
|
288
|
+
* used as is, plus `version`, which the library always adds. Helpers
|
|
289
|
+
* delete every version that is not listed, so list every version that
|
|
290
|
+
* could still become the latest: those that committed (for example, whose
|
|
291
|
+
* `SharingComplete` reported `threshold_met`) and those whose round is
|
|
292
|
+
* still open. Leave out only versions whose round failed or that the user
|
|
293
|
+
* rolled back; a list that leaves out too much can make the secret
|
|
294
|
+
* unrecoverable.
|
|
295
|
+
*/
|
|
296
|
+
keepList(
|
|
297
|
+
secretId: string,
|
|
298
|
+
version: number,
|
|
299
|
+
): Promise<number[] | null | undefined>;
|
|
281
300
|
}
|
|
282
301
|
|
|
283
302
|
export interface UserSecretEntry {
|
|
@@ -290,6 +309,10 @@ export interface UserSecrets {
|
|
|
290
309
|
version: number;
|
|
291
310
|
secrets: UserSecretEntry[];
|
|
292
311
|
description?: string;
|
|
312
|
+
/** Decimal `replica_id` of the member that published `version`. Absent
|
|
313
|
+
* when none is recorded. Store and return it unchanged: replica members
|
|
314
|
+
* compare it to detect a conflicting copy of the same version. */
|
|
315
|
+
author_replica_id?: string;
|
|
293
316
|
}
|
|
294
317
|
|
|
295
318
|
/**
|
|
@@ -333,6 +356,18 @@ export interface StateStore {
|
|
|
333
356
|
loadAll(secretId: string, kind: 0 | 1 | 2 | 3 | 4): Promise<Uint8Array[]>;
|
|
334
357
|
}
|
|
335
358
|
|
|
359
|
+
/** A transport protocol, by name. */
|
|
360
|
+
export type TransportProtocolName = "https" | "grpc";
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* One endpoint a node serves or a peer advertised, as every app-facing call
|
|
364
|
+
* and event carries it.
|
|
365
|
+
*/
|
|
366
|
+
export interface Endpoint {
|
|
367
|
+
uri: string;
|
|
368
|
+
protocol: TransportProtocolName;
|
|
369
|
+
}
|
|
370
|
+
|
|
336
371
|
/**
|
|
337
372
|
* Outbound message delivery.
|
|
338
373
|
*
|
|
@@ -378,7 +413,7 @@ export interface Transport {
|
|
|
378
413
|
* {@link singleEndpointTransport} rather than by indexing.
|
|
379
414
|
*/
|
|
380
415
|
send(
|
|
381
|
-
endpoints: ReadonlyArray<
|
|
416
|
+
endpoints: ReadonlyArray<Endpoint>,
|
|
382
417
|
message: Uint8Array,
|
|
383
418
|
): Promise<void>;
|
|
384
419
|
}
|
|
@@ -398,10 +433,28 @@ export interface Transport {
|
|
|
398
433
|
* a round trip; skipping one that would have worked costs the delivery.
|
|
399
434
|
*/
|
|
400
435
|
export type SendOne = (
|
|
401
|
-
endpoint:
|
|
436
|
+
endpoint: Endpoint,
|
|
402
437
|
message: Uint8Array,
|
|
403
438
|
) => Promise<void>;
|
|
404
439
|
|
|
440
|
+
/**
|
|
441
|
+
* The DeRec protocol version this build speaks — the `protocolVersionMajor` /
|
|
442
|
+
* `protocolVersionMinor` it writes into every envelope it produces. Not the
|
|
443
|
+
* package version.
|
|
444
|
+
*/
|
|
445
|
+
export function protocol_version(): { major: number; minor: number } {
|
|
446
|
+
return getNative().version();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* A fresh, random replica id — never `0`. Generate it once per device,
|
|
451
|
+
* persist it, and pass the same value to
|
|
452
|
+
* `DeRecProtocolBuilder.withReplicaId` on every init.
|
|
453
|
+
*/
|
|
454
|
+
export function generate_replica_id(): bigint {
|
|
455
|
+
return getNative().generate_replica_id();
|
|
456
|
+
}
|
|
457
|
+
|
|
405
458
|
/**
|
|
406
459
|
* Builds a {@link Transport} that tries each endpoint in the order the peer
|
|
407
460
|
* offered it and stops at the first success.
|
|
@@ -473,7 +526,7 @@ export enum SenderKind {
|
|
|
473
526
|
* the scanner must fetch the actual keys over the wire via the `PrePair`
|
|
474
527
|
* round-trip and verify them against the commitment before pairing.
|
|
475
528
|
* - `NoKeys`: no key material and no commitment. The contact carries only
|
|
476
|
-
* `channel_id`, `nonce`, and `
|
|
529
|
+
* `channel_id`, `nonce`, and `supported_transports` — small enough to be
|
|
477
530
|
* hand-typed or dictated. Keys are generated on the fly by the contact
|
|
478
531
|
* creator when the `PrePairRequest` arrives; the scanner accepts them
|
|
479
532
|
* without cryptographic verification. Trust rests entirely on the OOB
|
|
@@ -515,20 +568,33 @@ export enum FlowKind {
|
|
|
515
568
|
UnpairReplica = 8,
|
|
516
569
|
}
|
|
517
570
|
|
|
571
|
+
/** Result status carried in every protocol response (`result.proto`). Passed
|
|
572
|
+
* to `reject()` to say why an inbound request was refused. */
|
|
573
|
+
export enum StatusEnum {
|
|
574
|
+
Ok = 0,
|
|
575
|
+
Partial = 1,
|
|
576
|
+
Fail = 2,
|
|
577
|
+
SizeLimitExceeded = 3,
|
|
578
|
+
TooFrequent = 4,
|
|
579
|
+
UnknownSecretId = 5,
|
|
580
|
+
UnknownShareVersion = 6,
|
|
581
|
+
DecryptionFailed = 7,
|
|
582
|
+
VerificationFailed = 8,
|
|
583
|
+
FormatError = 9,
|
|
584
|
+
Rejected = 10,
|
|
585
|
+
IncompatibleParameterRange = 11,
|
|
586
|
+
UnsupportedTransportProtocol = 12,
|
|
587
|
+
VersionConflict = 13,
|
|
588
|
+
ReplicaIdConflict = 14,
|
|
589
|
+
RequestToClose = 99,
|
|
590
|
+
}
|
|
591
|
+
|
|
518
592
|
export type UnpairAck = "required" | "not_required";
|
|
519
593
|
|
|
520
594
|
export interface ContactMessage {
|
|
521
595
|
channel_id: bigint;
|
|
522
596
|
/** `ContactMode` numeric value (0 = INLINE_KEYS, 1 = HASHED_KEYS, 2 = NO_KEYS). */
|
|
523
597
|
contact_mode: number;
|
|
524
|
-
/**
|
|
525
|
-
* @deprecated Reading this field directly is incorrect: its meaning narrowed
|
|
526
|
-
* to "one entry of a list, and possibly absent", so a peer advertising only
|
|
527
|
-
* `supported_transports` looks unreachable to a reader that was correct
|
|
528
|
-
* before 0.0.3. Call {@link advertisedEndpoints}, which resolves both
|
|
529
|
-
* spellings. Removed at 0.0.5.
|
|
530
|
-
*/
|
|
531
|
-
transport_protocol?: TransportProtocol;
|
|
532
598
|
nonce: bigint;
|
|
533
599
|
/** Present only when `contact_mode === ContactMode.InlineKeys`. */
|
|
534
600
|
mlkem_encapsulation_key?: Uint8Array;
|
|
@@ -538,8 +604,7 @@ export interface ContactMessage {
|
|
|
538
604
|
contact_binding_hash?: Uint8Array;
|
|
539
605
|
timestamp?: Timestamp;
|
|
540
606
|
/** Every transport endpoint the creator of this contact can be reached
|
|
541
|
-
* on, in its own preference order.
|
|
542
|
-
* `transport_protocol` is offered". */
|
|
607
|
+
* on, in its own preference order. */
|
|
543
608
|
supported_transports: TransportProtocol[];
|
|
544
609
|
}
|
|
545
610
|
|
|
@@ -587,20 +652,11 @@ export interface UpdateChannelInfoParams {
|
|
|
587
652
|
* map untouched; pass an empty object to clear it. */
|
|
588
653
|
communication_info?: Record<string, string>;
|
|
589
654
|
|
|
590
|
-
/**
|
|
591
|
-
* New transport endpoint. Absent leaves it untouched.
|
|
592
|
-
*
|
|
593
|
-
* @deprecated Use `own_transports`, which carries every endpoint this node
|
|
594
|
-
* now serves; its first entry also fills this field for peers predating the
|
|
595
|
-
* list. Removed at 0.0.5.
|
|
596
|
-
*/
|
|
597
|
-
transport_protocol?: { uri: string; protocol: number };
|
|
598
655
|
/**
|
|
599
656
|
* Every endpoint this node now serves, in its own preference order.
|
|
600
|
-
* Omitted leaves the target(s)' stored set untouched.
|
|
601
|
-
* over `transport_protocol`, whose first entry it also fills.
|
|
657
|
+
* Omitted leaves the target(s)' stored set untouched.
|
|
602
658
|
*/
|
|
603
|
-
own_transports?:
|
|
659
|
+
own_transports?: Endpoint[];
|
|
604
660
|
}
|
|
605
661
|
|
|
606
662
|
/**
|
|
@@ -633,6 +689,15 @@ export interface Timeouts {
|
|
|
633
689
|
* are both read from the stores. The argument may be omitted entirely. */
|
|
634
690
|
export type ReplicaDiscoveryParams = Record<string, never>;
|
|
635
691
|
|
|
692
|
+
/** Any member may remove any member, the source included: a lost or stolen
|
|
693
|
+
* source must be removable by the devices that remain, and the library
|
|
694
|
+
* checks no role. Ask the user before starting this flow, above all when it
|
|
695
|
+
* names the source. Removing the source promotes the first remaining member
|
|
696
|
+
* in the order the channel store's `listReplicas` returns. The removed
|
|
697
|
+
* member is not asked and gets no event when told to leave: when a roster
|
|
698
|
+
* excluding it arrives it drops its whole `secret_id` partition and emits
|
|
699
|
+
* `SelfRemovedFromGroup`. The secret survives on the remaining members and
|
|
700
|
+
* the helpers. */
|
|
636
701
|
export interface UnpairReplicaParams {
|
|
637
702
|
/** The member to remove, as a **decimal** `u64` string — the same form
|
|
638
703
|
* `ReplicaPaired.peer_replica_id` hands back. A value naming no current
|
|
@@ -659,18 +724,34 @@ export type DeRecEvent =
|
|
|
659
724
|
action: Uint8Array;
|
|
660
725
|
|
|
661
726
|
action_kind: PendingActionKind;
|
|
727
|
+
/** Correlation token of the inbound request, decimal-encoded. */
|
|
728
|
+
trace_id: string;
|
|
729
|
+
/** Pairing only. */
|
|
662
730
|
peer_communication_info?: Record<string, string>;
|
|
663
|
-
|
|
731
|
+
/** `sender_kind` of the inbound pair request (Pairing only). */
|
|
664
732
|
sender_kind?: SenderKind;
|
|
665
|
-
|
|
733
|
+
/** Share version (StoreShare / VerifyShare / GetShare). */
|
|
666
734
|
version?: number;
|
|
735
|
+
/** Description of the secret version (StoreShare only). */
|
|
667
736
|
share_description?: string;
|
|
668
|
-
|
|
737
|
+
/** Secret identifier, decimal-encoded (StoreShare / VerifyShare /
|
|
738
|
+
* GetShare). On GetShare, with `version`, names the requested share. */
|
|
669
739
|
share_secret_id?: string;
|
|
740
|
+
/** Length in bytes of the share the helper would store (StoreShare
|
|
741
|
+
* only) — what a size or quota decision is made on. */
|
|
742
|
+
share_size?: number;
|
|
743
|
+
/** The peer's memo (Unpair only). */
|
|
744
|
+
unpair_memo?: string;
|
|
745
|
+
/** Communication info the peer replaces its stored map with
|
|
746
|
+
* (UpdateChannelInfo only). Absent: unchanged. Empty: cleared. */
|
|
747
|
+
updated_communication_info?: Record<string, string>;
|
|
748
|
+
/** Endpoints the peer is moving to (UpdateChannelInfo only). Absent:
|
|
749
|
+
* unchanged. */
|
|
750
|
+
updated_transports?: Endpoint[];
|
|
670
751
|
}
|
|
671
752
|
| { type: "ShareStored"; channel_id: string; version: number }
|
|
672
753
|
| { type: "ShareConfirmed"; channel_id: string; version: number }
|
|
673
|
-
| { type: "ShareRejected"; channel_id: string; version: number; status:
|
|
754
|
+
| { type: "ShareRejected"; channel_id: string; version: number; status: StatusEnum; memo: string }
|
|
674
755
|
/** A publishing round finished — every targeted helper confirmed,
|
|
675
756
|
* rejected, or timed out.
|
|
676
757
|
*
|
|
@@ -688,14 +769,17 @@ export type DeRecEvent =
|
|
|
688
769
|
| { type: "SharingComplete"; version: number; confirmed_count: number; failed_count: number; threshold_met: boolean }
|
|
689
770
|
/** A group member refused a secret sync. Keyed by `replica_id`, not
|
|
690
771
|
* `channel_id`: every member answers on the one group channel. A
|
|
691
|
-
* `VERSION_CONFLICT` status means
|
|
692
|
-
*
|
|
772
|
+
* `VERSION_CONFLICT` status means another member holds a different copy
|
|
773
|
+
* of this version: do not publish from this device again until the
|
|
774
|
+
* conflict is resolved. Run `start(FlowKind.ReplicaDiscovery)` to receive
|
|
775
|
+
* the group's copy as `ReplicaVersionConflict`, merge, and publish the
|
|
776
|
+
* result once with `start(FlowKind.ProtectSecret)`. */
|
|
693
777
|
| {
|
|
694
778
|
type: "ReplicaSyncRejected";
|
|
695
779
|
replica_id: string;
|
|
696
780
|
secret_id: string;
|
|
697
781
|
version: number;
|
|
698
|
-
status:
|
|
782
|
+
status: StatusEnum;
|
|
699
783
|
memo: string;
|
|
700
784
|
}
|
|
701
785
|
/** A secret sync could not be delivered to a member at all — distinct from
|
|
@@ -712,7 +796,8 @@ export type DeRecEvent =
|
|
|
712
796
|
/** This device left the group and dropped its whole `secret_id` partition —
|
|
713
797
|
* group channel, helper channels, shares, secrets and the snapshot. Fires
|
|
714
798
|
* only once it was told to leave *and* has since seen a roster excluding
|
|
715
|
-
* it; absence alone never destroys a copy of the secret.
|
|
799
|
+
* it; absence alone never destroys a copy of the secret. The teardown is
|
|
800
|
+
* automatic: this device is not asked first and gets no earlier event. */
|
|
716
801
|
| { type: "SelfRemovedFromGroup"; version: number }
|
|
717
802
|
/** A replica catch-up finished. `fetched_from` is absent when this device
|
|
718
803
|
* was already current, in which case no hydration event follows. */
|
|
@@ -728,6 +813,10 @@ export type DeRecEvent =
|
|
|
728
813
|
* library keeps no durable per-member sync state. */
|
|
729
814
|
| { type: "ReplicaSyncComplete"; version: number; synced: string[]; behind: string[] }
|
|
730
815
|
| { type: "ShareVerified"; channel_id: string; version: number }
|
|
816
|
+
/** A helper refused a verification challenge: its response carried a
|
|
817
|
+
* non-OK `status` instead of a proof. The challenge is spent; a new
|
|
818
|
+
* `VerifyShares` round challenges the helper again. */
|
|
819
|
+
| { type: "ShareVerifyRejected"; channel_id: string; version: number; status: StatusEnum; memo: string }
|
|
731
820
|
| {
|
|
732
821
|
type: "SecretsDiscovered";
|
|
733
822
|
channel_id: string;
|
|
@@ -736,6 +825,22 @@ export type DeRecEvent =
|
|
|
736
825
|
}
|
|
737
826
|
| { type: "RecoveryShareReceived"; channel_id: string; shares_received: number }
|
|
738
827
|
| { type: "RecoveryShareError"; channel_id: string; shares_received: number; error: string }
|
|
828
|
+
/** A helper refused a recovery share request: its response carried a
|
|
829
|
+
* non-OK `status` (e.g. `UNKNOWN_SHARE_VERSION`) instead of a share. The
|
|
830
|
+
* refusal is not collected — it does not count towards `shares_received`
|
|
831
|
+
* and the recovery stays open for the other helpers' shares — but it does
|
|
832
|
+
* answer that helper's `RecoverSecretStarted`. */
|
|
833
|
+
| { type: "RecoveryShareRefused"; channel_id: string; version: number; status: StatusEnum; memo: string }
|
|
834
|
+
/** A helper answered with a share that cannot be part of the secret;
|
|
835
|
+
* `reason` says how it failed. `Malformed` and `InvalidProof` are judged
|
|
836
|
+
* on arrival; `Inconsistent` (valid on its own but disagreeing with the
|
|
837
|
+
* shares the secret was rebuilt from) is reported alongside
|
|
838
|
+
* `SecretRecovered`, once per helper. The share is set aside — it does
|
|
839
|
+
* not count towards `shares_received` and never blocks the recovery. An
|
|
840
|
+
* honest helper never sends one, so the app may treat it as a sign of a
|
|
841
|
+
* damaged or compromised helper, e.g. offer to unpair it. It also
|
|
842
|
+
* answers that helper's `RecoverSecretStarted`. */
|
|
843
|
+
| { type: "RecoveryShareCorrupted"; channel_id: string; version: number; reason: CorruptionReason }
|
|
739
844
|
/** Recovery completed — the typed `Secret` snapshot the owner
|
|
740
845
|
* originally protected. Mirrors `ReplicaSecretReceived.secret`:
|
|
741
846
|
* `secrets` is the user-facing `Vec<UserSecret>` the application
|
|
@@ -748,9 +853,9 @@ export type DeRecEvent =
|
|
|
748
853
|
helpers: Array<{
|
|
749
854
|
channel_id: string;
|
|
750
855
|
/** Every endpoint this peer advertised, in the order it offered them. */
|
|
751
|
-
transports:
|
|
856
|
+
transports: Endpoint[];
|
|
752
857
|
shared_key: Uint8Array;
|
|
753
|
-
communication_info
|
|
858
|
+
communication_info?: Record<string, string>;
|
|
754
859
|
}>;
|
|
755
860
|
secrets: Array<{
|
|
756
861
|
id: Uint8Array;
|
|
@@ -770,9 +875,9 @@ export type DeRecEvent =
|
|
|
770
875
|
members: Array<{
|
|
771
876
|
replica_id: string;
|
|
772
877
|
/** Every endpoint this peer advertised, in the order it offered them. */
|
|
773
|
-
transports:
|
|
878
|
+
transports: Endpoint[];
|
|
774
879
|
role: "Source" | "Destination";
|
|
775
|
-
communication_info
|
|
880
|
+
communication_info?: Record<string, string>;
|
|
776
881
|
}>;
|
|
777
882
|
shared_key: Uint8Array;
|
|
778
883
|
};
|
|
@@ -781,12 +886,12 @@ export type DeRecEvent =
|
|
|
781
886
|
|
|
782
887
|
| { type: "Unpaired"; channel_id: string }
|
|
783
888
|
|
|
784
|
-
| { type: "UnpairRejected"; channel_id: string; status:
|
|
889
|
+
| { type: "UnpairRejected"; channel_id: string; status: StatusEnum; memo: string }
|
|
785
890
|
|
|
786
891
|
/** Contact creator answered the scanner's `PrePairRequest` with a
|
|
787
892
|
* non-Ok status (HashedKeys flow). Distinct from a cryptographic
|
|
788
893
|
* hash mismatch, which surfaces as a thrown error from `process()`. */
|
|
789
|
-
| { type: "PrePairRejected"; channel_id: string; status:
|
|
894
|
+
| { type: "PrePairRejected"; channel_id: string; status: StatusEnum; memo: string }
|
|
790
895
|
|
|
791
896
|
/** Fires alongside `PairingCompleted` on replica-mode pair handshakes.
|
|
792
897
|
* `peer_replica_id` is the peer's `u64` as a **decimal** string,
|
|
@@ -804,22 +909,28 @@ export type DeRecEvent =
|
|
|
804
909
|
/** A `ReplicaSource` peer pushed a secret sync on a
|
|
805
910
|
* `ReplicaDestination` channel. The library decoded the
|
|
806
911
|
* `ReplicaSecretPayload`; the app installs `secret.secrets` and
|
|
807
|
-
* optionally uses `shares` for recovery. `from_replica_id
|
|
808
|
-
* `replica_id` fields inside `secret` are
|
|
809
|
-
* strings.
|
|
912
|
+
* optionally uses `shares` for recovery. `from_replica_id`,
|
|
913
|
+
* `author_replica_id` and the `replica_id` fields inside `secret` are
|
|
914
|
+
* `u64` as **decimal** strings.
|
|
915
|
+
*
|
|
916
|
+
* `from_replica_id` is the member this copy came from: the publisher on
|
|
917
|
+
* a push, the serving member on a catch-up. `author_replica_id` is the
|
|
918
|
+
* member that published `version`, or `null` when the serving member's
|
|
919
|
+
* snapshot records none. */
|
|
810
920
|
| {
|
|
811
921
|
type: "ReplicaSecretReceived";
|
|
812
922
|
channel_id: string;
|
|
813
923
|
from_replica_id: string;
|
|
924
|
+
author_replica_id: string | null;
|
|
814
925
|
secret_id: string;
|
|
815
926
|
version: number;
|
|
816
927
|
secret: {
|
|
817
928
|
helpers: Array<{
|
|
818
929
|
channel_id: string;
|
|
819
930
|
/** Every endpoint this peer advertised, in the order it offered them. */
|
|
820
|
-
transports:
|
|
931
|
+
transports: Endpoint[];
|
|
821
932
|
shared_key: Uint8Array;
|
|
822
|
-
communication_info
|
|
933
|
+
communication_info?: Record<string, string>;
|
|
823
934
|
}>;
|
|
824
935
|
secrets: Array<{
|
|
825
936
|
id: Uint8Array;
|
|
@@ -837,9 +948,9 @@ export type DeRecEvent =
|
|
|
837
948
|
members: Array<{
|
|
838
949
|
replica_id: string;
|
|
839
950
|
/** Every endpoint this peer advertised, in the order it offered them. */
|
|
840
|
-
transports:
|
|
951
|
+
transports: Endpoint[];
|
|
841
952
|
role: "Source" | "Destination";
|
|
842
|
-
communication_info
|
|
953
|
+
communication_info?: Record<string, string>;
|
|
843
954
|
}>;
|
|
844
955
|
shared_key: Uint8Array;
|
|
845
956
|
};
|
|
@@ -862,15 +973,16 @@ export type DeRecEvent =
|
|
|
862
973
|
type: "ReplicaSecretInstalled";
|
|
863
974
|
channel_id: string;
|
|
864
975
|
from_replica_id: string;
|
|
976
|
+
author_replica_id: string | null;
|
|
865
977
|
secret_id: string;
|
|
866
978
|
version: number;
|
|
867
979
|
secret: {
|
|
868
980
|
helpers: Array<{
|
|
869
981
|
channel_id: string;
|
|
870
982
|
/** Every endpoint this peer advertised, in the order it offered them. */
|
|
871
|
-
transports:
|
|
983
|
+
transports: Endpoint[];
|
|
872
984
|
shared_key: Uint8Array;
|
|
873
|
-
communication_info
|
|
985
|
+
communication_info?: Record<string, string>;
|
|
874
986
|
}>;
|
|
875
987
|
secrets: Array<{
|
|
876
988
|
id: Uint8Array;
|
|
@@ -888,9 +1000,9 @@ export type DeRecEvent =
|
|
|
888
1000
|
members: Array<{
|
|
889
1001
|
replica_id: string;
|
|
890
1002
|
/** Every endpoint this peer advertised, in the order it offered them. */
|
|
891
|
-
transports:
|
|
1003
|
+
transports: Endpoint[];
|
|
892
1004
|
role: "Source" | "Destination";
|
|
893
|
-
communication_info
|
|
1005
|
+
communication_info?: Record<string, string>;
|
|
894
1006
|
}>;
|
|
895
1007
|
shared_key: Uint8Array;
|
|
896
1008
|
};
|
|
@@ -900,6 +1012,59 @@ export type DeRecEvent =
|
|
|
900
1012
|
committed_share: Uint8Array;
|
|
901
1013
|
}>;
|
|
902
1014
|
}
|
|
1015
|
+
/** A member offered a different copy of the version this device holds.
|
|
1016
|
+
* Nothing was written: this device keeps its own copy and refuses the
|
|
1017
|
+
* incoming one with `VERSION_CONFLICT`, so the publisher sees
|
|
1018
|
+
* `ReplicaSyncRejected`. Both copies are complete states — the held one
|
|
1019
|
+
* is in the local stores, the incoming one is `secret`. Resolve by
|
|
1020
|
+
* publishing the chosen state with `start(FlowKind.ProtectSecret)`; the
|
|
1021
|
+
* next version supersedes both on every member and helper. Until then,
|
|
1022
|
+
* do not publish from this device: any further `ProtectSecret` is a
|
|
1023
|
+
* higher version that every other member applies over its own copy,
|
|
1024
|
+
* losing the change it never merged.
|
|
1025
|
+
*
|
|
1026
|
+
* `held_author_replica_id` / `incoming_author_replica_id` are the
|
|
1027
|
+
* decimal `replica_id` of each copy's publisher, or `null` when that
|
|
1028
|
+
* copy records none. */
|
|
1029
|
+
| {
|
|
1030
|
+
type: "ReplicaVersionConflict";
|
|
1031
|
+
channel_id: string;
|
|
1032
|
+
from_replica_id: string;
|
|
1033
|
+
secret_id: string;
|
|
1034
|
+
version: number;
|
|
1035
|
+
held_author_replica_id: string | null;
|
|
1036
|
+
incoming_author_replica_id: string | null;
|
|
1037
|
+
secret: {
|
|
1038
|
+
helpers: Array<{
|
|
1039
|
+
channel_id: string;
|
|
1040
|
+
/** Every endpoint this peer advertised, in the order it offered them. */
|
|
1041
|
+
transports: Endpoint[];
|
|
1042
|
+
shared_key: Uint8Array;
|
|
1043
|
+
communication_info?: Record<string, string>;
|
|
1044
|
+
}>;
|
|
1045
|
+
secrets: Array<{
|
|
1046
|
+
id: Uint8Array;
|
|
1047
|
+
name: string;
|
|
1048
|
+
data: Uint8Array;
|
|
1049
|
+
}>;
|
|
1050
|
+
/** The same shape as `SecretRecovered.secret.replicas`. */
|
|
1051
|
+
replicas?: {
|
|
1052
|
+
/** The one channel every member is addressed on. */
|
|
1053
|
+
channel_id: string;
|
|
1054
|
+
/** Every member of the group, including the writer. Exactly one
|
|
1055
|
+
* carries `role: "Source"` — that member is where the secret
|
|
1056
|
+
* originated, which is why no separate owner field is needed. */
|
|
1057
|
+
members: Array<{
|
|
1058
|
+
replica_id: string;
|
|
1059
|
+
/** Every endpoint this peer advertised, in the order it offered them. */
|
|
1060
|
+
transports: Endpoint[];
|
|
1061
|
+
role: "Source" | "Destination";
|
|
1062
|
+
communication_info?: Record<string, string>;
|
|
1063
|
+
}>;
|
|
1064
|
+
shared_key: Uint8Array;
|
|
1065
|
+
};
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
903
1068
|
/** Peer's ack of a secret sync we sent. `status` is the `StatusEnum`
|
|
904
1069
|
* integer (0 = Ok), `memo` is the peer's explanation. */
|
|
905
1070
|
| {
|
|
@@ -908,7 +1073,7 @@ export type DeRecEvent =
|
|
|
908
1073
|
from_replica_id: string;
|
|
909
1074
|
secret_id: string;
|
|
910
1075
|
version: number;
|
|
911
|
-
status:
|
|
1076
|
+
status: StatusEnum;
|
|
912
1077
|
memo: string;
|
|
913
1078
|
}
|
|
914
1079
|
/** A peer announced an updated `communication_info` map and/or
|
|
@@ -925,7 +1090,7 @@ export type DeRecEvent =
|
|
|
925
1090
|
| {
|
|
926
1091
|
type: "ChannelInfoUpdateRejected";
|
|
927
1092
|
channel_id: string;
|
|
928
|
-
status:
|
|
1093
|
+
status: StatusEnum;
|
|
929
1094
|
memo: string;
|
|
930
1095
|
}
|
|
931
1096
|
/** Emitted by `process()` in place of `ActionRequired` when the
|
|
@@ -937,6 +1102,38 @@ export type DeRecEvent =
|
|
|
937
1102
|
* (`"Pairing"`, `"StoreShare"`, …). */
|
|
938
1103
|
| { type: "AutoAccepted"; channel_id: string; action_kind: PendingActionKind }
|
|
939
1104
|
| { type: "NoOp" }
|
|
1105
|
+
/** An inbound message was dropped untouched: no store was written and
|
|
1106
|
+
* nothing was sent back. `reason` says why.
|
|
1107
|
+
*
|
|
1108
|
+
* `"PendingVerification"` means the peer sent something before this
|
|
1109
|
+
* device confirmed the channel's fingerprint — typically a replica source
|
|
1110
|
+
* pushing its first copy while this destination still shows the code.
|
|
1111
|
+
* Confirming does not replay it: after `verifyFingerprint` succeeds, a
|
|
1112
|
+
* replica destination calls `start(FlowKind.ReplicaDiscovery)` to pull
|
|
1113
|
+
* the copy itself. `"Expired"` means the message was older than the
|
|
1114
|
+
* inbound timeout. `trace_id` matches the peer's `*Started` event for
|
|
1115
|
+
* the same round (`"0"` when the sender set none). */
|
|
1116
|
+
| {
|
|
1117
|
+
type: "MessageIgnored";
|
|
1118
|
+
channel_id: string;
|
|
1119
|
+
reason: IgnoreReason;
|
|
1120
|
+
trace_id: string;
|
|
1121
|
+
}
|
|
1122
|
+
/** Returned by `restore`: a roster entry got no channel, so this device
|
|
1123
|
+
* cannot reach that peer. Every other entry and the user-secret snapshot
|
|
1124
|
+
* were restored; `reason` says why this one was not. `"NoTransports"`
|
|
1125
|
+
* means the recovered roster names no endpoint for it.
|
|
1126
|
+
*
|
|
1127
|
+
* For a helper, `channel_id` is its channel and `replica_id` is absent.
|
|
1128
|
+
* For a replica group member, `channel_id` is the group's channel and
|
|
1129
|
+
* `replica_id` names the member. The peer itself is untouched — a helper
|
|
1130
|
+
* still holds its share — and pairing with it again makes it reachable. */
|
|
1131
|
+
| {
|
|
1132
|
+
type: "PeerNotRestored";
|
|
1133
|
+
channel_id: string;
|
|
1134
|
+
replica_id?: string;
|
|
1135
|
+
reason: NotRestoredReason;
|
|
1136
|
+
}
|
|
940
1137
|
/** A pairing handshake was dispatched successfully. `kind` is the
|
|
941
1138
|
* local party's role — same value the subsequent `PairingCompleted`
|
|
942
1139
|
* will carry. Emitted by `start(Pairing)`. */
|
|
@@ -1005,8 +1202,8 @@ export type DeRecEvent =
|
|
|
1005
1202
|
* inbound shares. The protocol enforces no size, quota or rate limit
|
|
1006
1203
|
* of its own, and `maxShareSize` is checked for range overlap at
|
|
1007
1204
|
* pairing time only, never against an actual share. While this is
|
|
1008
|
-
* `false`, `ActionRequired` carries
|
|
1009
|
-
*
|
|
1205
|
+
* `false`, `ActionRequired` carries `share_size`, so the application
|
|
1206
|
+
* can compare it to its quota and call `reject()` with
|
|
1010
1207
|
* `StatusEnum.SizeLimitExceeded`. Setting it `true` removes that
|
|
1011
1208
|
* opportunity entirely: every share from every paired Owner is stored
|
|
1012
1209
|
* unconditionally, at whatever size it arrives. Keep off in any
|
|
@@ -1099,6 +1296,22 @@ export interface CommunicationInfo {
|
|
|
1099
1296
|
// `ContactMessage` is defined once above and covers both `INLINE_KEYS` and
|
|
1100
1297
|
// `HASHED_KEYS` modes.
|
|
1101
1298
|
|
|
1299
|
+
/** Why a `MessageIgnored` event dropped a message. Matches the Rust
|
|
1300
|
+
* `IgnoreReason` discriminants one-for-one. */
|
|
1301
|
+
export type IgnoreReason = "PendingVerification" | "Expired";
|
|
1302
|
+
|
|
1303
|
+
/** Why a `PeerNotRestored` event left a roster entry without a channel.
|
|
1304
|
+
* Matches the Rust `NotRestoredReason` discriminants one-for-one. */
|
|
1305
|
+
export type NotRestoredReason = "NoTransports";
|
|
1306
|
+
|
|
1307
|
+
/** Why a `RecoveryShareCorrupted` event set a helper's share aside.
|
|
1308
|
+
* Matches the Rust `CorruptionReason` discriminants one-for-one:
|
|
1309
|
+
* `Malformed` — no decodable share for the requested secret and version;
|
|
1310
|
+
* `InvalidProof` — the share fails its own Merkle proof;
|
|
1311
|
+
* `Inconsistent` — valid on its own, but its commitment root or ciphertext
|
|
1312
|
+
* disagrees with the shares the secret was rebuilt from. */
|
|
1313
|
+
export type CorruptionReason = "Malformed" | "InvalidProof" | "Inconsistent";
|
|
1314
|
+
|
|
1102
1315
|
/**
|
|
1103
1316
|
* The label vocabulary for `ActionRequired.action_kind` and
|
|
1104
1317
|
* `AutoAccepted.action_kind` — one value per pending-action kind the
|
|
@@ -1135,17 +1348,9 @@ export interface PairRequestMessage {
|
|
|
1135
1348
|
nonce: bigint;
|
|
1136
1349
|
communication_info?: CommunicationInfo;
|
|
1137
1350
|
parameter_range?: ParameterRange;
|
|
1138
|
-
/**
|
|
1139
|
-
* @deprecated Reading this field directly is incorrect: its meaning narrowed
|
|
1140
|
-
* to "one entry of a list, and possibly absent", so a peer advertising only
|
|
1141
|
-
* `supported_transports` looks unreachable to a reader that was correct
|
|
1142
|
-
* before 0.0.3. Call {@link advertisedEndpoints}, which resolves both
|
|
1143
|
-
* spellings. Removed at 0.0.5.
|
|
1144
|
-
*/
|
|
1145
|
-
transport_protocol?: TransportProtocol;
|
|
1146
1351
|
timestamp?: Timestamp;
|
|
1147
1352
|
/** Every transport endpoint the initiator can be reached on, in its own
|
|
1148
|
-
* preference order.
|
|
1353
|
+
* preference order. */
|
|
1149
1354
|
supported_transports: TransportProtocol[];
|
|
1150
1355
|
}
|
|
1151
1356
|
|
|
@@ -1167,21 +1372,12 @@ export interface PairResponseMessage {
|
|
|
1167
1372
|
|
|
1168
1373
|
export interface PrePairRequestMessage {
|
|
1169
1374
|
nonce: bigint;
|
|
1170
|
-
/**
|
|
1171
|
-
* @deprecated Reading this field directly is incorrect: its meaning narrowed
|
|
1172
|
-
* to "one entry of a list, and possibly absent", so a peer advertising only
|
|
1173
|
-
* `supported_transports` looks unreachable to a reader that was correct
|
|
1174
|
-
* before 0.0.3. Call {@link advertisedEndpoints}, which resolves both
|
|
1175
|
-
* spellings. Removed at 0.0.5.
|
|
1176
|
-
*/
|
|
1177
|
-
transport_protocol?: TransportProtocol;
|
|
1178
1375
|
timestamp?: Timestamp;
|
|
1179
1376
|
/**
|
|
1180
1377
|
* Every endpoint the sender can be reached on for the PrePair reply, in
|
|
1181
|
-
* its own preference order.
|
|
1182
|
-
* must be present.
|
|
1378
|
+
* its own preference order.
|
|
1183
1379
|
*/
|
|
1184
|
-
supported_transports
|
|
1380
|
+
supported_transports: TransportProtocol[];
|
|
1185
1381
|
}
|
|
1186
1382
|
|
|
1187
1383
|
export interface PrePairResponseMessage {
|
|
@@ -1367,6 +1563,15 @@ export interface ProducePrePairResult {
|
|
|
1367
1563
|
envelope: Uint8Array;
|
|
1368
1564
|
}
|
|
1369
1565
|
|
|
1566
|
+
export interface ProducePrePairNoKeysResult {
|
|
1567
|
+
|
|
1568
|
+
envelope: Uint8Array;
|
|
1569
|
+
|
|
1570
|
+
/** Secret key material generated for this pairing. The caller MUST persist
|
|
1571
|
+
* it: the `PairRequest` that follows is encrypted to it. */
|
|
1572
|
+
secret_key_material: Uint8Array;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1370
1575
|
export interface PrePairRequestExtractResult {
|
|
1371
1576
|
|
|
1372
1577
|
request: PrePairRequestMessage;
|