@derec-alliance/react-native 0.0.5 → 0.0.6
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 +9 -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 +159 -26
- package/cpp/Primitives.h +16 -0
- package/cpp/ProtocolHost.cpp +22 -47
- package/cpp/StoreCallbacks.cpp +97 -304
- package/cpp/UserSecretsJson.cpp +109 -0
- package/cpp/UserSecretsJson.h +28 -0
- package/cpp/derec_ffi.h +256 -120
- package/ios/DeRecFFI.xcframework/ios-arm64/Headers/derec_ffi.h +256 -120
- package/ios/DeRecFFI.xcframework/ios-arm64/libderec_library.a +0 -0
- package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/Headers/derec_ffi.h +256 -120
- package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/libderec_library.a +2 -2
- package/lib/errors.d.ts +7 -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 +49 -97
- package/lib/protocol.js +71 -162
- package/lib/types.d.ts +207 -107
- package/lib/types.js +43 -13
- package/package.json +1 -1
- package/src/errors.ts +7 -0
- package/src/native.ts +2 -1
- package/src/primitives.ts +82 -25
- package/src/protocol.ts +79 -179
- package/src/types.ts +221 -96
package/lib/types.d.ts
CHANGED
|
@@ -2,11 +2,12 @@ export interface SecretStore {
|
|
|
2
2
|
load(secretId: string, channelId: string, kind: 0 | 1 | 2): Promise<Uint8Array | null | undefined>;
|
|
3
3
|
/**
|
|
4
4
|
* Load secrets of the same `kind` for several channels in one call,
|
|
5
|
-
* scoped to `secretId`. Must return an array with one entry per
|
|
6
|
-
* id, in the same order, using `null` (or `undefined`) for channels
|
|
7
|
-
* with no stored secret of `kind`.
|
|
5
|
+
* scoped to `secretId`. Must return an array with exactly one entry per
|
|
6
|
+
* input id, in the same order, using `null` (or `undefined`) for channels
|
|
7
|
+
* with no stored secret of `kind`. Whether a missing entry is an error is
|
|
8
|
+
* decided by the library.
|
|
8
9
|
*/
|
|
9
|
-
loadMany(secretId: string, channelIds: string[], kind: 0 | 1 | 2
|
|
10
|
+
loadMany(secretId: string, channelIds: string[], kind: 0 | 1 | 2): Promise<Array<Uint8Array | null | undefined>>;
|
|
10
11
|
save(secretId: string, channelId: string, kind: 0 | 1 | 2, value: Uint8Array): Promise<void>;
|
|
11
12
|
remove(secretId: string, channelId: string, kind: 0 | 1 | 2): Promise<void>;
|
|
12
13
|
}
|
|
@@ -78,19 +79,11 @@ export declare function channelFilterMatches(filter: HelperFilter | ReplicaFilte
|
|
|
78
79
|
/**
|
|
79
80
|
* The endpoints a peer-supplied message advertises, in the peer's own order.
|
|
80
81
|
*
|
|
81
|
-
* Yields `supported_transports` when it is non-empty, and otherwise the
|
|
82
|
-
* singular `transport_protocol` — which is how every implementation predating
|
|
83
|
-
* the offer list advertises, and the reason this is a function rather than a
|
|
84
|
-
* field read. Reading `transport_protocol` directly is a bug: its meaning
|
|
85
|
-
* narrowed to "one entry of a list, and possibly absent", so a peer that has
|
|
86
|
-
* moved past it looks unreachable to a reader that was correct before 0.0.3.
|
|
87
|
-
*
|
|
88
82
|
* Reports what was advertised, not what is acceptable — nothing here is
|
|
89
83
|
* validated, and the protocol still applies its own transport policy to
|
|
90
84
|
* whatever it records.
|
|
91
85
|
*/
|
|
92
86
|
export declare function advertisedEndpoints(message: {
|
|
93
|
-
transport_protocol?: TransportProtocol;
|
|
94
87
|
supported_transports?: TransportProtocol[];
|
|
95
88
|
} | null | undefined): TransportProtocol[];
|
|
96
89
|
/**
|
|
@@ -172,7 +165,7 @@ export interface ChannelStore {
|
|
|
172
165
|
* Order explicitly to make succession predictable.
|
|
173
166
|
*/
|
|
174
167
|
listReplicas(secretId: string, filter: ReplicaFilter): Promise<Uint8Array | null | undefined>;
|
|
175
|
-
linkChannel(secretId: string,
|
|
168
|
+
linkChannel(secretId: string, a: string, b: string): Promise<void>;
|
|
176
169
|
linkedChannels(secretId: string, channelId: string): Promise<string[]>;
|
|
177
170
|
}
|
|
178
171
|
export interface Share {
|
|
@@ -208,6 +201,10 @@ export interface UserSecrets {
|
|
|
208
201
|
version: number;
|
|
209
202
|
secrets: UserSecretEntry[];
|
|
210
203
|
description?: string;
|
|
204
|
+
/** Decimal `replica_id` of the member that published `version`. Absent
|
|
205
|
+
* when none is recorded. Store and return it unchanged: replica members
|
|
206
|
+
* compare it to detect a conflicting copy of the same version. */
|
|
207
|
+
author_replica_id?: string;
|
|
211
208
|
}
|
|
212
209
|
/**
|
|
213
210
|
* Persistence for the user-facing secret contents, keyed by `secretId`.
|
|
@@ -245,6 +242,16 @@ export interface StateStore {
|
|
|
245
242
|
remove(secretId: string, keyJson: Uint8Array): Promise<boolean>;
|
|
246
243
|
loadAll(secretId: string, kind: 0 | 1 | 2 | 3 | 4): Promise<Uint8Array[]>;
|
|
247
244
|
}
|
|
245
|
+
/** A transport protocol, by name. */
|
|
246
|
+
export type TransportProtocolName = "https" | "grpc";
|
|
247
|
+
/**
|
|
248
|
+
* One endpoint a node serves or a peer advertised, as every app-facing call
|
|
249
|
+
* and event carries it.
|
|
250
|
+
*/
|
|
251
|
+
export interface Endpoint {
|
|
252
|
+
uri: string;
|
|
253
|
+
protocol: TransportProtocolName;
|
|
254
|
+
}
|
|
248
255
|
/**
|
|
249
256
|
* Outbound message delivery.
|
|
250
257
|
*
|
|
@@ -289,10 +296,7 @@ export interface Transport {
|
|
|
289
296
|
* provide — if that is genuinely wanted, say so with
|
|
290
297
|
* {@link singleEndpointTransport} rather than by indexing.
|
|
291
298
|
*/
|
|
292
|
-
send(endpoints: ReadonlyArray<
|
|
293
|
-
protocol: string;
|
|
294
|
-
uri: string;
|
|
295
|
-
}>, message: Uint8Array): Promise<void>;
|
|
299
|
+
send(endpoints: ReadonlyArray<Endpoint>, message: Uint8Array): Promise<void>;
|
|
296
300
|
}
|
|
297
301
|
/**
|
|
298
302
|
* Delivers one message to one endpoint.
|
|
@@ -308,10 +312,22 @@ export interface Transport {
|
|
|
308
312
|
* which is the safe reading. Trying an endpoint that would have refused costs
|
|
309
313
|
* a round trip; skipping one that would have worked costs the delivery.
|
|
310
314
|
*/
|
|
311
|
-
export type SendOne = (endpoint:
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
+
export type SendOne = (endpoint: Endpoint, message: Uint8Array) => Promise<void>;
|
|
316
|
+
/**
|
|
317
|
+
* The DeRec protocol version this build speaks — the `protocolVersionMajor` /
|
|
318
|
+
* `protocolVersionMinor` it writes into every envelope it produces. Not the
|
|
319
|
+
* package version.
|
|
320
|
+
*/
|
|
321
|
+
export declare function protocol_version(): {
|
|
322
|
+
major: number;
|
|
323
|
+
minor: number;
|
|
324
|
+
};
|
|
325
|
+
/**
|
|
326
|
+
* A fresh, random replica id — never `0`. Generate it once per device,
|
|
327
|
+
* persist it, and pass the same value to
|
|
328
|
+
* `DeRecProtocolBuilder.withReplicaId` on every init.
|
|
329
|
+
*/
|
|
330
|
+
export declare function generate_replica_id(): bigint;
|
|
315
331
|
/**
|
|
316
332
|
* Builds a {@link Transport} that tries each endpoint in the order the peer
|
|
317
333
|
* offered it and stops at the first success.
|
|
@@ -353,7 +369,7 @@ export declare enum SenderKind {
|
|
|
353
369
|
* the scanner must fetch the actual keys over the wire via the `PrePair`
|
|
354
370
|
* round-trip and verify them against the commitment before pairing.
|
|
355
371
|
* - `NoKeys`: no key material and no commitment. The contact carries only
|
|
356
|
-
* `channel_id`, `nonce`, and `
|
|
372
|
+
* `channel_id`, `nonce`, and `supported_transports` — small enough to be
|
|
357
373
|
* hand-typed or dictated. Keys are generated on the fly by the contact
|
|
358
374
|
* creator when the `PrePairRequest` arrives; the scanner accepts them
|
|
359
375
|
* without cryptographic verification. Trust rests entirely on the OOB
|
|
@@ -393,19 +409,31 @@ export declare enum FlowKind {
|
|
|
393
409
|
* string so ids above 2^53 survive JS number handling. */
|
|
394
410
|
UnpairReplica = 8
|
|
395
411
|
}
|
|
412
|
+
/** Result status carried in every protocol response (`result.proto`). Passed
|
|
413
|
+
* to `reject()` to say why an inbound request was refused. */
|
|
414
|
+
export declare enum StatusEnum {
|
|
415
|
+
Ok = 0,
|
|
416
|
+
Partial = 1,
|
|
417
|
+
Fail = 2,
|
|
418
|
+
SizeLimitExceeded = 3,
|
|
419
|
+
TooFrequent = 4,
|
|
420
|
+
UnknownSecretId = 5,
|
|
421
|
+
UnknownShareVersion = 6,
|
|
422
|
+
DecryptionFailed = 7,
|
|
423
|
+
VerificationFailed = 8,
|
|
424
|
+
FormatError = 9,
|
|
425
|
+
Rejected = 10,
|
|
426
|
+
IncompatibleParameterRange = 11,
|
|
427
|
+
UnsupportedTransportProtocol = 12,
|
|
428
|
+
VersionConflict = 13,
|
|
429
|
+
ReplicaIdConflict = 14,
|
|
430
|
+
RequestToClose = 99
|
|
431
|
+
}
|
|
396
432
|
export type UnpairAck = "required" | "not_required";
|
|
397
433
|
export interface ContactMessage {
|
|
398
434
|
channel_id: bigint;
|
|
399
435
|
/** `ContactMode` numeric value (0 = INLINE_KEYS, 1 = HASHED_KEYS, 2 = NO_KEYS). */
|
|
400
436
|
contact_mode: number;
|
|
401
|
-
/**
|
|
402
|
-
* @deprecated Reading this field directly is incorrect: its meaning narrowed
|
|
403
|
-
* to "one entry of a list, and possibly absent", so a peer advertising only
|
|
404
|
-
* `supported_transports` looks unreachable to a reader that was correct
|
|
405
|
-
* before 0.0.3. Call {@link advertisedEndpoints}, which resolves both
|
|
406
|
-
* spellings. Removed at 0.0.5.
|
|
407
|
-
*/
|
|
408
|
-
transport_protocol?: TransportProtocol;
|
|
409
437
|
nonce: bigint;
|
|
410
438
|
/** Present only when `contact_mode === ContactMode.InlineKeys`. */
|
|
411
439
|
mlkem_encapsulation_key?: Uint8Array;
|
|
@@ -415,8 +443,7 @@ export interface ContactMessage {
|
|
|
415
443
|
contact_binding_hash?: Uint8Array;
|
|
416
444
|
timestamp?: Timestamp;
|
|
417
445
|
/** Every transport endpoint the creator of this contact can be reached
|
|
418
|
-
* on, in its own preference order.
|
|
419
|
-
* `transport_protocol` is offered". */
|
|
446
|
+
* on, in its own preference order. */
|
|
420
447
|
supported_transports: TransportProtocol[];
|
|
421
448
|
}
|
|
422
449
|
export interface UserSecret {
|
|
@@ -455,23 +482,11 @@ export interface UpdateChannelInfoParams {
|
|
|
455
482
|
/** New communication-info map. `null`/absent leaves the peer's stored
|
|
456
483
|
* map untouched; pass an empty object to clear it. */
|
|
457
484
|
communication_info?: Record<string, string>;
|
|
458
|
-
/**
|
|
459
|
-
* New transport endpoint. Absent leaves it untouched.
|
|
460
|
-
*
|
|
461
|
-
* @deprecated Use `own_transports`, which carries every endpoint this node
|
|
462
|
-
* now serves; its first entry also fills this field for peers predating the
|
|
463
|
-
* list. Removed at 0.0.5.
|
|
464
|
-
*/
|
|
465
|
-
transport_protocol?: {
|
|
466
|
-
uri: string;
|
|
467
|
-
protocol: number;
|
|
468
|
-
};
|
|
469
485
|
/**
|
|
470
486
|
* Every endpoint this node now serves, in its own preference order.
|
|
471
|
-
* Omitted leaves the target(s)' stored set untouched.
|
|
472
|
-
* over `transport_protocol`, whose first entry it also fills.
|
|
487
|
+
* Omitted leaves the target(s)' stored set untouched.
|
|
473
488
|
*/
|
|
474
|
-
own_transports?:
|
|
489
|
+
own_transports?: Endpoint[];
|
|
475
490
|
}
|
|
476
491
|
/**
|
|
477
492
|
* How long the protocol waits on each thing that can keep it waiting. Every
|
|
@@ -524,11 +539,30 @@ export type DeRecEvent = {
|
|
|
524
539
|
channel_id: string;
|
|
525
540
|
action: Uint8Array;
|
|
526
541
|
action_kind: PendingActionKind;
|
|
542
|
+
/** Correlation token of the inbound request, decimal-encoded. */
|
|
543
|
+
trace_id: string;
|
|
544
|
+
/** Pairing only. */
|
|
527
545
|
peer_communication_info?: Record<string, string>;
|
|
546
|
+
/** `sender_kind` of the inbound pair request (Pairing only). */
|
|
528
547
|
sender_kind?: SenderKind;
|
|
548
|
+
/** Share version (StoreShare / VerifyShare / GetShare). */
|
|
529
549
|
version?: number;
|
|
550
|
+
/** Description of the secret version (StoreShare only). */
|
|
530
551
|
share_description?: string;
|
|
552
|
+
/** Secret identifier, decimal-encoded (StoreShare / VerifyShare /
|
|
553
|
+
* GetShare). On GetShare, with `version`, names the requested share. */
|
|
531
554
|
share_secret_id?: string;
|
|
555
|
+
/** Length in bytes of the share the helper would store (StoreShare
|
|
556
|
+
* only) — what a size or quota decision is made on. */
|
|
557
|
+
share_size?: number;
|
|
558
|
+
/** The peer's memo (Unpair only). */
|
|
559
|
+
unpair_memo?: string;
|
|
560
|
+
/** Communication info the peer replaces its stored map with
|
|
561
|
+
* (UpdateChannelInfo only). Absent: unchanged. Empty: cleared. */
|
|
562
|
+
updated_communication_info?: Record<string, string>;
|
|
563
|
+
/** Endpoints the peer is moving to (UpdateChannelInfo only). Absent:
|
|
564
|
+
* unchanged. */
|
|
565
|
+
updated_transports?: Endpoint[];
|
|
532
566
|
} | {
|
|
533
567
|
type: "ShareStored";
|
|
534
568
|
channel_id: string;
|
|
@@ -541,7 +575,7 @@ export type DeRecEvent = {
|
|
|
541
575
|
type: "ShareRejected";
|
|
542
576
|
channel_id: string;
|
|
543
577
|
version: number;
|
|
544
|
-
status:
|
|
578
|
+
status: StatusEnum;
|
|
545
579
|
memo: string;
|
|
546
580
|
}
|
|
547
581
|
/** A publishing round finished — every targeted helper confirmed,
|
|
@@ -574,7 +608,7 @@ export type DeRecEvent = {
|
|
|
574
608
|
replica_id: string;
|
|
575
609
|
secret_id: string;
|
|
576
610
|
version: number;
|
|
577
|
-
status:
|
|
611
|
+
status: StatusEnum;
|
|
578
612
|
memo: string;
|
|
579
613
|
}
|
|
580
614
|
/** A secret sync could not be delivered to a member at all — distinct from
|
|
@@ -660,12 +694,9 @@ export type DeRecEvent = {
|
|
|
660
694
|
helpers: Array<{
|
|
661
695
|
channel_id: string;
|
|
662
696
|
/** Every endpoint this peer advertised, in the order it offered them. */
|
|
663
|
-
transports:
|
|
664
|
-
uri: string;
|
|
665
|
-
protocol: number;
|
|
666
|
-
}>;
|
|
697
|
+
transports: Endpoint[];
|
|
667
698
|
shared_key: Uint8Array;
|
|
668
|
-
communication_info
|
|
699
|
+
communication_info?: Record<string, string>;
|
|
669
700
|
}>;
|
|
670
701
|
secrets: Array<{
|
|
671
702
|
id: Uint8Array;
|
|
@@ -685,12 +716,9 @@ export type DeRecEvent = {
|
|
|
685
716
|
members: Array<{
|
|
686
717
|
replica_id: string;
|
|
687
718
|
/** Every endpoint this peer advertised, in the order it offered them. */
|
|
688
|
-
transports:
|
|
689
|
-
uri: string;
|
|
690
|
-
protocol: number;
|
|
691
|
-
}>;
|
|
719
|
+
transports: Endpoint[];
|
|
692
720
|
role: "Source" | "Destination";
|
|
693
|
-
communication_info
|
|
721
|
+
communication_info?: Record<string, string>;
|
|
694
722
|
}>;
|
|
695
723
|
shared_key: Uint8Array;
|
|
696
724
|
};
|
|
@@ -701,7 +729,7 @@ export type DeRecEvent = {
|
|
|
701
729
|
} | {
|
|
702
730
|
type: "UnpairRejected";
|
|
703
731
|
channel_id: string;
|
|
704
|
-
status:
|
|
732
|
+
status: StatusEnum;
|
|
705
733
|
memo: string;
|
|
706
734
|
}
|
|
707
735
|
/** Contact creator answered the scanner's `PrePairRequest` with a
|
|
@@ -710,7 +738,7 @@ export type DeRecEvent = {
|
|
|
710
738
|
| {
|
|
711
739
|
type: "PrePairRejected";
|
|
712
740
|
channel_id: string;
|
|
713
|
-
status:
|
|
741
|
+
status: StatusEnum;
|
|
714
742
|
memo: string;
|
|
715
743
|
}
|
|
716
744
|
/** Fires alongside `PairingCompleted` on replica-mode pair handshakes.
|
|
@@ -729,25 +757,28 @@ export type DeRecEvent = {
|
|
|
729
757
|
/** A `ReplicaSource` peer pushed a secret sync on a
|
|
730
758
|
* `ReplicaDestination` channel. The library decoded the
|
|
731
759
|
* `ReplicaSecretPayload`; the app installs `secret.secrets` and
|
|
732
|
-
* optionally uses `shares` for recovery. `from_replica_id
|
|
733
|
-
* `replica_id` fields inside `secret` are
|
|
734
|
-
* strings.
|
|
760
|
+
* optionally uses `shares` for recovery. `from_replica_id`,
|
|
761
|
+
* `author_replica_id` and the `replica_id` fields inside `secret` are
|
|
762
|
+
* `u64` as **decimal** strings.
|
|
763
|
+
*
|
|
764
|
+
* `from_replica_id` is the member this copy came from: the publisher on
|
|
765
|
+
* a push, the serving member on a catch-up. `author_replica_id` is the
|
|
766
|
+
* member that published `version`, or `null` when the serving member's
|
|
767
|
+
* snapshot records none. */
|
|
735
768
|
| {
|
|
736
769
|
type: "ReplicaSecretReceived";
|
|
737
770
|
channel_id: string;
|
|
738
771
|
from_replica_id: string;
|
|
772
|
+
author_replica_id: string | null;
|
|
739
773
|
secret_id: string;
|
|
740
774
|
version: number;
|
|
741
775
|
secret: {
|
|
742
776
|
helpers: Array<{
|
|
743
777
|
channel_id: string;
|
|
744
778
|
/** Every endpoint this peer advertised, in the order it offered them. */
|
|
745
|
-
transports:
|
|
746
|
-
uri: string;
|
|
747
|
-
protocol: number;
|
|
748
|
-
}>;
|
|
779
|
+
transports: Endpoint[];
|
|
749
780
|
shared_key: Uint8Array;
|
|
750
|
-
communication_info
|
|
781
|
+
communication_info?: Record<string, string>;
|
|
751
782
|
}>;
|
|
752
783
|
secrets: Array<{
|
|
753
784
|
id: Uint8Array;
|
|
@@ -765,12 +796,9 @@ export type DeRecEvent = {
|
|
|
765
796
|
members: Array<{
|
|
766
797
|
replica_id: string;
|
|
767
798
|
/** Every endpoint this peer advertised, in the order it offered them. */
|
|
768
|
-
transports:
|
|
769
|
-
uri: string;
|
|
770
|
-
protocol: number;
|
|
771
|
-
}>;
|
|
799
|
+
transports: Endpoint[];
|
|
772
800
|
role: "Source" | "Destination";
|
|
773
|
-
communication_info
|
|
801
|
+
communication_info?: Record<string, string>;
|
|
774
802
|
}>;
|
|
775
803
|
shared_key: Uint8Array;
|
|
776
804
|
};
|
|
@@ -793,18 +821,16 @@ export type DeRecEvent = {
|
|
|
793
821
|
type: "ReplicaSecretInstalled";
|
|
794
822
|
channel_id: string;
|
|
795
823
|
from_replica_id: string;
|
|
824
|
+
author_replica_id: string | null;
|
|
796
825
|
secret_id: string;
|
|
797
826
|
version: number;
|
|
798
827
|
secret: {
|
|
799
828
|
helpers: Array<{
|
|
800
829
|
channel_id: string;
|
|
801
830
|
/** Every endpoint this peer advertised, in the order it offered them. */
|
|
802
|
-
transports:
|
|
803
|
-
uri: string;
|
|
804
|
-
protocol: number;
|
|
805
|
-
}>;
|
|
831
|
+
transports: Endpoint[];
|
|
806
832
|
shared_key: Uint8Array;
|
|
807
|
-
communication_info
|
|
833
|
+
communication_info?: Record<string, string>;
|
|
808
834
|
}>;
|
|
809
835
|
secrets: Array<{
|
|
810
836
|
id: Uint8Array;
|
|
@@ -822,12 +848,9 @@ export type DeRecEvent = {
|
|
|
822
848
|
members: Array<{
|
|
823
849
|
replica_id: string;
|
|
824
850
|
/** Every endpoint this peer advertised, in the order it offered them. */
|
|
825
|
-
transports:
|
|
826
|
-
uri: string;
|
|
827
|
-
protocol: number;
|
|
828
|
-
}>;
|
|
851
|
+
transports: Endpoint[];
|
|
829
852
|
role: "Source" | "Destination";
|
|
830
|
-
communication_info
|
|
853
|
+
communication_info?: Record<string, string>;
|
|
831
854
|
}>;
|
|
832
855
|
shared_key: Uint8Array;
|
|
833
856
|
};
|
|
@@ -837,6 +860,56 @@ export type DeRecEvent = {
|
|
|
837
860
|
committed_share: Uint8Array;
|
|
838
861
|
}>;
|
|
839
862
|
}
|
|
863
|
+
/** A member offered a different copy of the version this device holds.
|
|
864
|
+
* Nothing was written: this device keeps its own copy and refuses the
|
|
865
|
+
* incoming one with `VERSION_CONFLICT`, so the publisher sees
|
|
866
|
+
* `ReplicaSyncRejected`. Both copies are complete states — the held one
|
|
867
|
+
* is in the local stores, the incoming one is `secret`. Resolve by
|
|
868
|
+
* publishing the chosen state with `start(FlowKind.ProtectSecret)`; the
|
|
869
|
+
* next version supersedes both on every member and helper.
|
|
870
|
+
*
|
|
871
|
+
* `held_author_replica_id` / `incoming_author_replica_id` are the
|
|
872
|
+
* decimal `replica_id` of each copy's publisher, or `null` when that
|
|
873
|
+
* copy records none. */
|
|
874
|
+
| {
|
|
875
|
+
type: "ReplicaVersionConflict";
|
|
876
|
+
channel_id: string;
|
|
877
|
+
from_replica_id: string;
|
|
878
|
+
secret_id: string;
|
|
879
|
+
version: number;
|
|
880
|
+
held_author_replica_id: string | null;
|
|
881
|
+
incoming_author_replica_id: string | null;
|
|
882
|
+
secret: {
|
|
883
|
+
helpers: Array<{
|
|
884
|
+
channel_id: string;
|
|
885
|
+
/** Every endpoint this peer advertised, in the order it offered them. */
|
|
886
|
+
transports: Endpoint[];
|
|
887
|
+
shared_key: Uint8Array;
|
|
888
|
+
communication_info?: Record<string, string>;
|
|
889
|
+
}>;
|
|
890
|
+
secrets: Array<{
|
|
891
|
+
id: Uint8Array;
|
|
892
|
+
name: string;
|
|
893
|
+
data: Uint8Array;
|
|
894
|
+
}>;
|
|
895
|
+
/** The same shape as `SecretRecovered.secret.replicas`. */
|
|
896
|
+
replicas?: {
|
|
897
|
+
/** The one channel every member is addressed on. */
|
|
898
|
+
channel_id: string;
|
|
899
|
+
/** Every member of the group, including the writer. Exactly one
|
|
900
|
+
* carries `role: "Source"` — that member is where the secret
|
|
901
|
+
* originated, which is why no separate owner field is needed. */
|
|
902
|
+
members: Array<{
|
|
903
|
+
replica_id: string;
|
|
904
|
+
/** Every endpoint this peer advertised, in the order it offered them. */
|
|
905
|
+
transports: Endpoint[];
|
|
906
|
+
role: "Source" | "Destination";
|
|
907
|
+
communication_info?: Record<string, string>;
|
|
908
|
+
}>;
|
|
909
|
+
shared_key: Uint8Array;
|
|
910
|
+
};
|
|
911
|
+
};
|
|
912
|
+
}
|
|
840
913
|
/** Peer's ack of a secret sync we sent. `status` is the `StatusEnum`
|
|
841
914
|
* integer (0 = Ok), `memo` is the peer's explanation. */
|
|
842
915
|
| {
|
|
@@ -845,7 +918,7 @@ export type DeRecEvent = {
|
|
|
845
918
|
from_replica_id: string;
|
|
846
919
|
secret_id: string;
|
|
847
920
|
version: number;
|
|
848
|
-
status:
|
|
921
|
+
status: StatusEnum;
|
|
849
922
|
memo: string;
|
|
850
923
|
}
|
|
851
924
|
/** A peer announced an updated `communication_info` map and/or
|
|
@@ -862,7 +935,7 @@ export type DeRecEvent = {
|
|
|
862
935
|
| {
|
|
863
936
|
type: "ChannelInfoUpdateRejected";
|
|
864
937
|
channel_id: string;
|
|
865
|
-
status:
|
|
938
|
+
status: StatusEnum;
|
|
866
939
|
memo: string;
|
|
867
940
|
}
|
|
868
941
|
/** Emitted by `process()` in place of `ActionRequired` when the
|
|
@@ -879,6 +952,38 @@ export type DeRecEvent = {
|
|
|
879
952
|
} | {
|
|
880
953
|
type: "NoOp";
|
|
881
954
|
}
|
|
955
|
+
/** An inbound message was dropped untouched: no store was written and
|
|
956
|
+
* nothing was sent back. `reason` says why.
|
|
957
|
+
*
|
|
958
|
+
* `"PendingVerification"` means the peer sent something before this
|
|
959
|
+
* device confirmed the channel's fingerprint — typically a replica source
|
|
960
|
+
* pushing its first copy while this destination still shows the code.
|
|
961
|
+
* Confirming does not replay it: after `verifyFingerprint` succeeds, a
|
|
962
|
+
* replica destination calls `start(FlowKind.ReplicaDiscovery)` to pull
|
|
963
|
+
* the copy itself. `"Expired"` means the message was older than the
|
|
964
|
+
* inbound timeout. `trace_id` matches the peer's `*Started` event for
|
|
965
|
+
* the same round (`"0"` when the sender set none). */
|
|
966
|
+
| {
|
|
967
|
+
type: "MessageIgnored";
|
|
968
|
+
channel_id: string;
|
|
969
|
+
reason: IgnoreReason;
|
|
970
|
+
trace_id: string;
|
|
971
|
+
}
|
|
972
|
+
/** Returned by `restore`: a roster entry got no channel, so this device
|
|
973
|
+
* cannot reach that peer. Every other entry and the user-secret snapshot
|
|
974
|
+
* were restored; `reason` says why this one was not. `"NoTransports"`
|
|
975
|
+
* means the recovered roster names no endpoint for it.
|
|
976
|
+
*
|
|
977
|
+
* For a helper, `channel_id` is its channel and `replica_id` is absent.
|
|
978
|
+
* For a replica group member, `channel_id` is the group's channel and
|
|
979
|
+
* `replica_id` names the member. The peer itself is untouched — a helper
|
|
980
|
+
* still holds its share — and pairing with it again makes it reachable. */
|
|
981
|
+
| {
|
|
982
|
+
type: "PeerNotRestored";
|
|
983
|
+
channel_id: string;
|
|
984
|
+
replica_id?: string;
|
|
985
|
+
reason: NotRestoredReason;
|
|
986
|
+
}
|
|
882
987
|
/** A pairing handshake was dispatched successfully. `kind` is the
|
|
883
988
|
* local party's role — same value the subsequent `PairingCompleted`
|
|
884
989
|
* will carry. Emitted by `start(Pairing)`. */
|
|
@@ -989,8 +1094,8 @@ export type DeRecEvent = {
|
|
|
989
1094
|
* inbound shares. The protocol enforces no size, quota or rate limit
|
|
990
1095
|
* of its own, and `maxShareSize` is checked for range overlap at
|
|
991
1096
|
* pairing time only, never against an actual share. While this is
|
|
992
|
-
* `false`, `ActionRequired` carries
|
|
993
|
-
*
|
|
1097
|
+
* `false`, `ActionRequired` carries `share_size`, so the application
|
|
1098
|
+
* can compare it to its quota and call `reject()` with
|
|
994
1099
|
* `StatusEnum.SizeLimitExceeded`. Setting it `true` removes that
|
|
995
1100
|
* opportunity entirely: every share from every paired Owner is stored
|
|
996
1101
|
* unconditionally, at whatever size it arrives. Keep off in any
|
|
@@ -1066,6 +1171,12 @@ export interface CommunicationInfoKeyValue {
|
|
|
1066
1171
|
export interface CommunicationInfo {
|
|
1067
1172
|
communication_info_entries: CommunicationInfoKeyValue[];
|
|
1068
1173
|
}
|
|
1174
|
+
/** Why a `MessageIgnored` event dropped a message. Matches the Rust
|
|
1175
|
+
* `IgnoreReason` discriminants one-for-one. */
|
|
1176
|
+
export type IgnoreReason = "PendingVerification" | "Expired";
|
|
1177
|
+
/** Why a `PeerNotRestored` event left a roster entry without a channel.
|
|
1178
|
+
* Matches the Rust `NotRestoredReason` discriminants one-for-one. */
|
|
1179
|
+
export type NotRestoredReason = "NoTransports";
|
|
1069
1180
|
/**
|
|
1070
1181
|
* The label vocabulary for `ActionRequired.action_kind` and
|
|
1071
1182
|
* `AutoAccepted.action_kind` — one value per pending-action kind the
|
|
@@ -1092,17 +1203,9 @@ export interface PairRequestMessage {
|
|
|
1092
1203
|
nonce: bigint;
|
|
1093
1204
|
communication_info?: CommunicationInfo;
|
|
1094
1205
|
parameter_range?: ParameterRange;
|
|
1095
|
-
/**
|
|
1096
|
-
* @deprecated Reading this field directly is incorrect: its meaning narrowed
|
|
1097
|
-
* to "one entry of a list, and possibly absent", so a peer advertising only
|
|
1098
|
-
* `supported_transports` looks unreachable to a reader that was correct
|
|
1099
|
-
* before 0.0.3. Call {@link advertisedEndpoints}, which resolves both
|
|
1100
|
-
* spellings. Removed at 0.0.5.
|
|
1101
|
-
*/
|
|
1102
|
-
transport_protocol?: TransportProtocol;
|
|
1103
1206
|
timestamp?: Timestamp;
|
|
1104
1207
|
/** Every transport endpoint the initiator can be reached on, in its own
|
|
1105
|
-
* preference order.
|
|
1208
|
+
* preference order. */
|
|
1106
1209
|
supported_transports: TransportProtocol[];
|
|
1107
1210
|
}
|
|
1108
1211
|
export interface PairResponseMessage {
|
|
@@ -1122,21 +1225,12 @@ export interface PairResponseMessage {
|
|
|
1122
1225
|
}
|
|
1123
1226
|
export interface PrePairRequestMessage {
|
|
1124
1227
|
nonce: bigint;
|
|
1125
|
-
/**
|
|
1126
|
-
* @deprecated Reading this field directly is incorrect: its meaning narrowed
|
|
1127
|
-
* to "one entry of a list, and possibly absent", so a peer advertising only
|
|
1128
|
-
* `supported_transports` looks unreachable to a reader that was correct
|
|
1129
|
-
* before 0.0.3. Call {@link advertisedEndpoints}, which resolves both
|
|
1130
|
-
* spellings. Removed at 0.0.5.
|
|
1131
|
-
*/
|
|
1132
|
-
transport_protocol?: TransportProtocol;
|
|
1133
1228
|
timestamp?: Timestamp;
|
|
1134
1229
|
/**
|
|
1135
1230
|
* Every endpoint the sender can be reached on for the PrePair reply, in
|
|
1136
|
-
* its own preference order.
|
|
1137
|
-
* must be present.
|
|
1231
|
+
* its own preference order.
|
|
1138
1232
|
*/
|
|
1139
|
-
supported_transports
|
|
1233
|
+
supported_transports: TransportProtocol[];
|
|
1140
1234
|
}
|
|
1141
1235
|
export interface PrePairResponseMessage {
|
|
1142
1236
|
result?: DeRecResult;
|
|
@@ -1292,6 +1386,12 @@ export interface PairingProcessResult {
|
|
|
1292
1386
|
export interface ProducePrePairResult {
|
|
1293
1387
|
envelope: Uint8Array;
|
|
1294
1388
|
}
|
|
1389
|
+
export interface ProducePrePairNoKeysResult {
|
|
1390
|
+
envelope: Uint8Array;
|
|
1391
|
+
/** Secret key material generated for this pairing. The caller MUST persist
|
|
1392
|
+
* it: the `PairRequest` that follows is encrypted to it. */
|
|
1393
|
+
secret_key_material: Uint8Array;
|
|
1394
|
+
}
|
|
1295
1395
|
export interface PrePairRequestExtractResult {
|
|
1296
1396
|
request: PrePairRequestMessage;
|
|
1297
1397
|
}
|
package/lib/types.js
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
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.FlowKind = exports.ContactMode = exports.SenderKind = void 0;
|
|
5
|
+
exports.StatusEnum = exports.FlowKind = exports.ContactMode = exports.SenderKind = void 0;
|
|
6
6
|
exports.channelFilterMatches = channelFilterMatches;
|
|
7
7
|
exports.advertisedEndpoints = advertisedEndpoints;
|
|
8
|
+
exports.protocol_version = protocol_version;
|
|
9
|
+
exports.generate_replica_id = generate_replica_id;
|
|
8
10
|
exports.sequentialFailover = sequentialFailover;
|
|
9
11
|
exports.singleEndpointTransport = singleEndpointTransport;
|
|
12
|
+
const native_1 = require("./native");
|
|
10
13
|
/**
|
|
11
14
|
* Whether a channel or member with these attributes survives `filter`.
|
|
12
15
|
*
|
|
@@ -37,13 +40,6 @@ function channelFilterMatches(filter, id, status, role) {
|
|
|
37
40
|
/**
|
|
38
41
|
* The endpoints a peer-supplied message advertises, in the peer's own order.
|
|
39
42
|
*
|
|
40
|
-
* Yields `supported_transports` when it is non-empty, and otherwise the
|
|
41
|
-
* singular `transport_protocol` — which is how every implementation predating
|
|
42
|
-
* the offer list advertises, and the reason this is a function rather than a
|
|
43
|
-
* field read. Reading `transport_protocol` directly is a bug: its meaning
|
|
44
|
-
* narrowed to "one entry of a list, and possibly absent", so a peer that has
|
|
45
|
-
* moved past it looks unreachable to a reader that was correct before 0.0.3.
|
|
46
|
-
*
|
|
47
43
|
* Reports what was advertised, not what is acceptable — nothing here is
|
|
48
44
|
* validated, and the protocol still applies its own transport policy to
|
|
49
45
|
* whatever it records.
|
|
@@ -51,10 +47,23 @@ function channelFilterMatches(filter, id, status, role) {
|
|
|
51
47
|
function advertisedEndpoints(message) {
|
|
52
48
|
if (!message)
|
|
53
49
|
return [];
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
return message.supported_transports ?? [];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The DeRec protocol version this build speaks — the `protocolVersionMajor` /
|
|
54
|
+
* `protocolVersionMinor` it writes into every envelope it produces. Not the
|
|
55
|
+
* package version.
|
|
56
|
+
*/
|
|
57
|
+
function protocol_version() {
|
|
58
|
+
return (0, native_1.getNative)().version();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A fresh, random replica id — never `0`. Generate it once per device,
|
|
62
|
+
* persist it, and pass the same value to
|
|
63
|
+
* `DeRecProtocolBuilder.withReplicaId` on every init.
|
|
64
|
+
*/
|
|
65
|
+
function generate_replica_id() {
|
|
66
|
+
return (0, native_1.getNative)().generate_replica_id();
|
|
58
67
|
}
|
|
59
68
|
/**
|
|
60
69
|
* Builds a {@link Transport} that tries each endpoint in the order the peer
|
|
@@ -126,7 +135,7 @@ var SenderKind;
|
|
|
126
135
|
* the scanner must fetch the actual keys over the wire via the `PrePair`
|
|
127
136
|
* round-trip and verify them against the commitment before pairing.
|
|
128
137
|
* - `NoKeys`: no key material and no commitment. The contact carries only
|
|
129
|
-
* `channel_id`, `nonce`, and `
|
|
138
|
+
* `channel_id`, `nonce`, and `supported_transports` — small enough to be
|
|
130
139
|
* hand-typed or dictated. Keys are generated on the fly by the contact
|
|
131
140
|
* creator when the `PrePairRequest` arrives; the scanner accepts them
|
|
132
141
|
* without cryptographic verification. Trust rests entirely on the OOB
|
|
@@ -168,3 +177,24 @@ var FlowKind;
|
|
|
168
177
|
* string so ids above 2^53 survive JS number handling. */
|
|
169
178
|
FlowKind[FlowKind["UnpairReplica"] = 8] = "UnpairReplica";
|
|
170
179
|
})(FlowKind || (exports.FlowKind = FlowKind = {}));
|
|
180
|
+
/** Result status carried in every protocol response (`result.proto`). Passed
|
|
181
|
+
* to `reject()` to say why an inbound request was refused. */
|
|
182
|
+
var StatusEnum;
|
|
183
|
+
(function (StatusEnum) {
|
|
184
|
+
StatusEnum[StatusEnum["Ok"] = 0] = "Ok";
|
|
185
|
+
StatusEnum[StatusEnum["Partial"] = 1] = "Partial";
|
|
186
|
+
StatusEnum[StatusEnum["Fail"] = 2] = "Fail";
|
|
187
|
+
StatusEnum[StatusEnum["SizeLimitExceeded"] = 3] = "SizeLimitExceeded";
|
|
188
|
+
StatusEnum[StatusEnum["TooFrequent"] = 4] = "TooFrequent";
|
|
189
|
+
StatusEnum[StatusEnum["UnknownSecretId"] = 5] = "UnknownSecretId";
|
|
190
|
+
StatusEnum[StatusEnum["UnknownShareVersion"] = 6] = "UnknownShareVersion";
|
|
191
|
+
StatusEnum[StatusEnum["DecryptionFailed"] = 7] = "DecryptionFailed";
|
|
192
|
+
StatusEnum[StatusEnum["VerificationFailed"] = 8] = "VerificationFailed";
|
|
193
|
+
StatusEnum[StatusEnum["FormatError"] = 9] = "FormatError";
|
|
194
|
+
StatusEnum[StatusEnum["Rejected"] = 10] = "Rejected";
|
|
195
|
+
StatusEnum[StatusEnum["IncompatibleParameterRange"] = 11] = "IncompatibleParameterRange";
|
|
196
|
+
StatusEnum[StatusEnum["UnsupportedTransportProtocol"] = 12] = "UnsupportedTransportProtocol";
|
|
197
|
+
StatusEnum[StatusEnum["VersionConflict"] = 13] = "VersionConflict";
|
|
198
|
+
StatusEnum[StatusEnum["ReplicaIdConflict"] = 14] = "ReplicaIdConflict";
|
|
199
|
+
StatusEnum[StatusEnum["RequestToClose"] = 99] = "RequestToClose";
|
|
200
|
+
})(StatusEnum || (exports.StatusEnum = StatusEnum = {}));
|
package/package.json
CHANGED
package/src/errors.ts
CHANGED
|
@@ -42,4 +42,11 @@ export interface DeRecError {
|
|
|
42
42
|
memo?: string;
|
|
43
43
|
expected?: number;
|
|
44
44
|
got?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Present only when `code` is `"restore_conflict"`: the channels already
|
|
47
|
+
* living at the recovered canonical ids, as decimal strings — the same
|
|
48
|
+
* field `@derec-alliance/nodejs` carries on its restore conflict error.
|
|
49
|
+
* Clear exactly these and retry `restore`.
|
|
50
|
+
*/
|
|
51
|
+
channel_ids?: string[];
|
|
45
52
|
}
|