@derec-alliance/nodejs 0.0.1-alpha.10
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/LICENSE +201 -0
- package/README.md +551 -0
- package/derec_library.d.ts +466 -0
- package/derec_library.js +2008 -0
- package/derec_library_bg.wasm +0 -0
- package/derec_library_bg.wasm.d.ts +92 -0
- package/index.d.ts +1547 -0
- package/index.js +106 -0
- package/package.json +34 -0
package/index.d.ts
ADDED
|
@@ -0,0 +1,1547 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) 2026 DeRec Alliance. All rights reserved.
|
|
3
|
+
|
|
4
|
+
export interface SecretStore {
|
|
5
|
+
load(
|
|
6
|
+
secretId: string,
|
|
7
|
+
channelId: string,
|
|
8
|
+
kind: 0 | 1 | 2,
|
|
9
|
+
): Promise<Uint8Array | null | undefined>;
|
|
10
|
+
/**
|
|
11
|
+
* Load secrets of the same `kind` for several channels in one call,
|
|
12
|
+
* scoped to `secretId`. Must return an array with one entry per input
|
|
13
|
+
* id, in the same order, using `null` (or `undefined`) for channels
|
|
14
|
+
* with no stored secret of `kind`.
|
|
15
|
+
*/
|
|
16
|
+
loadMany(
|
|
17
|
+
secretId: string,
|
|
18
|
+
channelIds: string[],
|
|
19
|
+
kind: 0 | 1 | 2,
|
|
20
|
+
missingPolicy: "skip" | "fail",
|
|
21
|
+
): Promise<Array<Uint8Array | null | undefined>>;
|
|
22
|
+
save(
|
|
23
|
+
secretId: string,
|
|
24
|
+
channelId: string,
|
|
25
|
+
kind: 0 | 1 | 2,
|
|
26
|
+
value: Uint8Array,
|
|
27
|
+
): Promise<void>;
|
|
28
|
+
remove(secretId: string, channelId: string, kind: 0 | 1 | 2): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Channel-record persistence.
|
|
33
|
+
*
|
|
34
|
+
* A record is addressed by `(channelId, replicaId)`. A `replicaId` of `"0"` —
|
|
35
|
+
* the value the protocol reserves as "absent" — addresses the helper channel
|
|
36
|
+
* at `channelId`.
|
|
37
|
+
*
|
|
38
|
+
* Any other value addresses that member of the replica group, and the member
|
|
39
|
+
* is keyed by **`replicaId` alone**. The accompanying `channelId` is context,
|
|
40
|
+
* not part of the key: a member moves between channels during an admission
|
|
41
|
+
* handover while remaining the same member, and a lookup that required both to
|
|
42
|
+
* match would miss it exactly when the move needs to be observed. Keep two
|
|
43
|
+
* maps — helpers by `channelId`, members by `replicaId` — not one keyed by the
|
|
44
|
+
* pair.
|
|
45
|
+
*
|
|
46
|
+
* `load`/`save` bytes are a JSON-encoded `ChannelRecord`: an externally
|
|
47
|
+
* tagged union carrying exactly one of `Helper` or `Replica`.
|
|
48
|
+
*
|
|
49
|
+
* `listHelpers` and `listReplicas` are **not** arrays of that union — they
|
|
50
|
+
* return a JSON array of the **inner** records with the tag stripped:
|
|
51
|
+
* `[{ channel_id, transport, ... }, ...]`, `HelperChannel` for the first and
|
|
52
|
+
* `ReplicaMember` for the second. Wrapping each element back in
|
|
53
|
+
* `{ "Helper": ... }` will not decode.
|
|
54
|
+
*
|
|
55
|
+
* Build that array by **splicing the stored bytes as text** — the payloads are
|
|
56
|
+
* opaque, so persist and re-emit them verbatim:
|
|
57
|
+
*
|
|
58
|
+
* ```js
|
|
59
|
+
* const inner = rows.map((r) => new TextDecoder().decode(r));
|
|
60
|
+
* return new TextEncoder().encode(`[${inner.join(",")}]`);
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* Do not `JSON.parse` and re-serialise. Every id in these records is a `u64`,
|
|
64
|
+
* and `JSON.parse` silently rounds anything above 2^53 — the corruption only
|
|
65
|
+
* appears once a real id happens to be large. `bindings/web` implements this.
|
|
66
|
+
*/
|
|
67
|
+
export interface ChannelStore {
|
|
68
|
+
load(
|
|
69
|
+
secretId: string,
|
|
70
|
+
channelId: string,
|
|
71
|
+
replicaId: string,
|
|
72
|
+
): Promise<Uint8Array | null | undefined>;
|
|
73
|
+
save(
|
|
74
|
+
secretId: string,
|
|
75
|
+
channelId: string,
|
|
76
|
+
replicaId: string,
|
|
77
|
+
bytes: Uint8Array,
|
|
78
|
+
): Promise<void>;
|
|
79
|
+
remove(secretId: string, channelId: string, replicaId: string): Promise<boolean>;
|
|
80
|
+
/** JSON array of the helper channels stored under `secretId`. */
|
|
81
|
+
listHelpers(secretId: string): Promise<Uint8Array | null | undefined>;
|
|
82
|
+
/**
|
|
83
|
+
* JSON array of the replica-group members stored under `secretId`,
|
|
84
|
+
* including this device's own row.
|
|
85
|
+
*
|
|
86
|
+
* The order is significant in exactly one situation. A group has one member
|
|
87
|
+
* holding the `Source` role; when it is removed, the protocol promotes the
|
|
88
|
+
* first element of this array that is neither the departing member nor
|
|
89
|
+
* itself leaving. Ordering this array is therefore how an application
|
|
90
|
+
* chooses its succession policy. The choice is read once, on the single
|
|
91
|
+
* device running the removal, and is then published in the roster, so
|
|
92
|
+
* implementations on different devices need not agree on order. Nothing else
|
|
93
|
+
* consults it.
|
|
94
|
+
*
|
|
95
|
+
* Returning an arbitrary order is correct and simply delegates the choice to
|
|
96
|
+
* the storage — note that a SQL `SELECT` without `ORDER BY` and `Map`
|
|
97
|
+
* insertion order after arbitrary edits are both effectively arbitrary.
|
|
98
|
+
* Order explicitly to make succession predictable.
|
|
99
|
+
*/
|
|
100
|
+
listReplicas(secretId: string): Promise<Uint8Array | null | undefined>;
|
|
101
|
+
linkChannel(
|
|
102
|
+
secretId: string,
|
|
103
|
+
channelId: string,
|
|
104
|
+
linkedChannelId: string,
|
|
105
|
+
): Promise<void>;
|
|
106
|
+
linkedChannels(secretId: string, channelId: string): Promise<string[]>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface Share {
|
|
110
|
+
secretId: string;
|
|
111
|
+
version: number;
|
|
112
|
+
bytes: Uint8Array;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface ShareStore {
|
|
116
|
+
load(secretId: string, channelId: string, versions: number[]): Promise<Share[]>;
|
|
117
|
+
loadMany(
|
|
118
|
+
secretId: string,
|
|
119
|
+
channelIds: string[],
|
|
120
|
+
versions: number[],
|
|
121
|
+
): Promise<Share[]>;
|
|
122
|
+
loadAll(secretId: string, channelIds: string[]): Promise<Share[]>;
|
|
123
|
+
save(secretId: string, channelId: string, share: Share): Promise<void>;
|
|
124
|
+
latestVersion(secretId: string): Promise<number | null>;
|
|
125
|
+
removeChannel(secretId: string, channelId: string): Promise<void>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface UserSecretEntry {
|
|
129
|
+
id: Uint8Array;
|
|
130
|
+
name: string;
|
|
131
|
+
data: Uint8Array;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface UserSecrets {
|
|
135
|
+
version: number;
|
|
136
|
+
secrets: UserSecretEntry[];
|
|
137
|
+
description?: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Persistence for the user-facing secret contents, keyed by `secretId`.
|
|
142
|
+
* One `secretId` maps to at most one stored snapshot — the most recent
|
|
143
|
+
* `start(ProtectSecret)` value. Read back by the pair-completion
|
|
144
|
+
* auto-publish hook so freshly-paired peers receive the current secret.
|
|
145
|
+
*/
|
|
146
|
+
export interface UserSecretStore {
|
|
147
|
+
loadLatest(secretId: string): Promise<UserSecrets | null | undefined>;
|
|
148
|
+
saveLatest(secretId: string, value: UserSecrets): Promise<void>;
|
|
149
|
+
remove(secretId: string): Promise<void>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* In-flight orchestrator state persistence. The library treats item
|
|
154
|
+
* payloads as opaque JSON blobs — `save` writes the blob, `load`
|
|
155
|
+
* returns the exact blob it received, `remove` drops the row, and
|
|
156
|
+
* `loadAll` returns every blob whose `kind` matches the requested
|
|
157
|
+
* category (`0` = PendingVerification, `1` = PendingRecovery,
|
|
158
|
+
* `2` = PendingUnpair, `3` = SharingRound, `4` = PendingSyncCheck).
|
|
159
|
+
*
|
|
160
|
+
* Rows are keyed by `(secretId, StateKey)` — the `keyJson` buffer is
|
|
161
|
+
* a JSON object `{ kind, channel_id?, version? }` matching the `kind`
|
|
162
|
+
* numbering above. The library will `save`/`load`/`remove` under the
|
|
163
|
+
* same key across a session, so implementations can hash the entire
|
|
164
|
+
* `keyJson` buffer or unpack its fields (`kind` + `channel_id` +
|
|
165
|
+
* `version`) as the composite key.
|
|
166
|
+
*
|
|
167
|
+
* Save is full-replacement upsert — accumulator-style state
|
|
168
|
+
* (PendingRecovery and SharingRound) grows via load-modify-save cycles
|
|
169
|
+
* from the library; no per-row append primitive is required.
|
|
170
|
+
*/
|
|
171
|
+
export interface StateStore {
|
|
172
|
+
save(secretId: string, itemJson: Uint8Array): Promise<void>;
|
|
173
|
+
load(
|
|
174
|
+
secretId: string,
|
|
175
|
+
keyJson: Uint8Array,
|
|
176
|
+
): Promise<Uint8Array | null | undefined>;
|
|
177
|
+
remove(secretId: string, keyJson: Uint8Array): Promise<boolean>;
|
|
178
|
+
loadAll(secretId: string, kind: 0 | 1 | 2 | 3 | 4): Promise<Uint8Array[]>;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Outbound message delivery.
|
|
183
|
+
*
|
|
184
|
+
* This is a mailbox, not a request/response channel: every peer has an
|
|
185
|
+
* address, and a reply is posted to that address rather than returned from
|
|
186
|
+
* `process`. Where both sides are reachable services, a one-way push is all
|
|
187
|
+
* that is needed.
|
|
188
|
+
*
|
|
189
|
+
* A peer that cannot be addressed — a phone, a browser, anything behind NAT —
|
|
190
|
+
* breaks that silently: the reply is handed to `send`, goes nowhere, and
|
|
191
|
+
* nothing reports an error. Such a service must answer on the connection the
|
|
192
|
+
* request arrived on, by building the protocol per request with a `Transport`
|
|
193
|
+
* that collects into a buffer instead of sending, then returning the collected
|
|
194
|
+
* message whose trace id matches the inbound envelope's
|
|
195
|
+
* (`envelope_read_trace_id`). One call can emit several messages, so the rest
|
|
196
|
+
* of the buffer is genuine fan-out and still has to be delivered. See "Serving
|
|
197
|
+
* DeRec over request/response transports" in the Rust SDK README.
|
|
198
|
+
*/
|
|
199
|
+
export interface Transport {
|
|
200
|
+
send(endpoint: { protocol: string; uri: string }, message: Uint8Array): Promise<void>;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export enum SenderKind {
|
|
204
|
+
Owner = 0,
|
|
205
|
+
Helper = 1,
|
|
206
|
+
ReplicaSource = 3,
|
|
207
|
+
ReplicaDestination = 4,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Selects how the initiator's public encryption material is delivered in a
|
|
212
|
+
* `ContactMessage`.
|
|
213
|
+
*
|
|
214
|
+
* - `InlineKeys` (default): keys are embedded in the contact itself.
|
|
215
|
+
* - `HashedKeys`: only a SHA-384 commitment to the keys is in the contact;
|
|
216
|
+
* the scanner must fetch the actual keys over the wire via the `PrePair`
|
|
217
|
+
* round-trip and verify them against the commitment before pairing.
|
|
218
|
+
* - `NoKeys`: no key material and no commitment. The contact carries only
|
|
219
|
+
* `channel_id`, `nonce`, and `transport_protocol` — small enough to be
|
|
220
|
+
* hand-typed or dictated. Keys are generated on the fly by the contact
|
|
221
|
+
* creator when the `PrePairRequest` arrives; the scanner accepts them
|
|
222
|
+
* without cryptographic verification. Trust rests entirely on the OOB
|
|
223
|
+
* delivery channel being fully trusted (e.g. a verified email from an
|
|
224
|
+
* already-KYC-authenticated institution). Applications MUST rate-limit
|
|
225
|
+
* inbound `PrePairRequest`s per channel and expire outstanding NoKeys
|
|
226
|
+
* contacts on a short timer.
|
|
227
|
+
*
|
|
228
|
+
* Because nothing binds the published keys to the contact, the channel is
|
|
229
|
+
* held `Pending` until `verifyFingerprint` succeeds on both sides: it is
|
|
230
|
+
* not a publish target, not a recovery source, and inbound messages on it
|
|
231
|
+
* are ignored. A man-in-the-middle on the plaintext `PrePair` leg leaves
|
|
232
|
+
* the two sides with different shared keys and so different fingerprints,
|
|
233
|
+
* which is what the comparison catches — the role `contact_binding_hash`
|
|
234
|
+
* plays for `HashedKeys`.
|
|
235
|
+
*/
|
|
236
|
+
export enum ContactMode {
|
|
237
|
+
InlineKeys = 0,
|
|
238
|
+
HashedKeys = 1,
|
|
239
|
+
NoKeys = 2,
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export enum FlowKind {
|
|
243
|
+
Pairing = 0,
|
|
244
|
+
Discovery = 1,
|
|
245
|
+
ProtectSecret = 2,
|
|
246
|
+
VerifyShares = 3,
|
|
247
|
+
RecoverSecret = 4,
|
|
248
|
+
Unpair = 5,
|
|
249
|
+
UpdateChannelInfo = 6,
|
|
250
|
+
/** Ask the replica group whether this device is behind, and catch up if it
|
|
251
|
+
* is. Replica-only, and takes no parameters — the group and this device's
|
|
252
|
+
* own version both come from the stores. */
|
|
253
|
+
SyncCheck = 7,
|
|
254
|
+
/** Remove a member from the replica group. Replica-only. Naming this device
|
|
255
|
+
* is a voluntary departure; naming another is an eviction. Params:
|
|
256
|
+
* `{ replica_id: string; memo?: string }` — `replica_id` is a decimal
|
|
257
|
+
* string so ids above 2^53 survive JS number handling. */
|
|
258
|
+
RemoveReplica = 8,
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export type UnpairAck = "required" | "not_required";
|
|
262
|
+
|
|
263
|
+
export interface ContactMessage {
|
|
264
|
+
channel_id: bigint;
|
|
265
|
+
/** `ContactMode` numeric value (0 = INLINE_KEYS, 1 = HASHED_KEYS, 2 = NO_KEYS). */
|
|
266
|
+
contact_mode: number;
|
|
267
|
+
transport_protocol?: TransportProtocol;
|
|
268
|
+
nonce: bigint;
|
|
269
|
+
/** Present only when `contact_mode === ContactMode.InlineKeys`. */
|
|
270
|
+
mlkem_encapsulation_key?: Uint8Array;
|
|
271
|
+
/** Present only when `contact_mode === ContactMode.InlineKeys`. */
|
|
272
|
+
ecies_public_key?: Uint8Array;
|
|
273
|
+
/** Present only when `contact_mode === ContactMode.HashedKeys`. SHA-384 digest (48 bytes). */
|
|
274
|
+
contact_binding_hash?: Uint8Array;
|
|
275
|
+
timestamp?: Timestamp;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export interface UserSecret {
|
|
279
|
+
|
|
280
|
+
id: Uint8Array;
|
|
281
|
+
name: string;
|
|
282
|
+
data: Uint8Array;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export type Target = bigint | bigint[] | null;
|
|
286
|
+
|
|
287
|
+
export interface PairingParams {
|
|
288
|
+
kind: SenderKind;
|
|
289
|
+
contact: ContactMessage;
|
|
290
|
+
|
|
291
|
+
peerCommunicationInfo?: Record<string, string>;
|
|
292
|
+
}
|
|
293
|
+
export interface DiscoveryParams {
|
|
294
|
+
target?: Target;
|
|
295
|
+
}
|
|
296
|
+
export interface ProtectSecretParams {
|
|
297
|
+
secrets: UserSecret[];
|
|
298
|
+
description?: string;
|
|
299
|
+
}
|
|
300
|
+
export interface VerifySharesParams {
|
|
301
|
+
secretId: bigint | string;
|
|
302
|
+
version: number;
|
|
303
|
+
target?: Target;
|
|
304
|
+
}
|
|
305
|
+
export interface RecoverSecretParams {
|
|
306
|
+
|
|
307
|
+
secretId: bigint | string;
|
|
308
|
+
version: number;
|
|
309
|
+
}
|
|
310
|
+
export interface UnpairParams {
|
|
311
|
+
channel_id: string;
|
|
312
|
+
|
|
313
|
+
memo?: string;
|
|
314
|
+
}
|
|
315
|
+
export interface UpdateChannelInfoParams {
|
|
316
|
+
target?: Target;
|
|
317
|
+
|
|
318
|
+
/** New communication-info map. `null`/absent leaves the peer's stored
|
|
319
|
+
* map untouched; pass an empty object to clear it. */
|
|
320
|
+
communication_info?: Record<string, string>;
|
|
321
|
+
|
|
322
|
+
/** New transport endpoint. Absent leaves it untouched. */
|
|
323
|
+
transport_protocol?: { uri: string; protocol: number };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* How long the protocol waits on each thing that can keep it waiting. Every
|
|
328
|
+
* field is optional; omit one to keep the library's default for it.
|
|
329
|
+
*/
|
|
330
|
+
export interface Timeouts {
|
|
331
|
+
/** Staleness boundary for inbound envelopes — the replay-defence window.
|
|
332
|
+
* Any message older than this is discarded on receipt, whatever the flow.
|
|
333
|
+
* Lowering it starts refusing legitimately old messages from slow
|
|
334
|
+
* transports or skewed clocks. Library default: 300. */
|
|
335
|
+
inbound_message_secs?: number;
|
|
336
|
+
/** How long a publishing round waits on a peer that has not answered.
|
|
337
|
+
* Bounds how long `SharingComplete` can be delayed by one unreachable
|
|
338
|
+
* peer. Library default: 60. */
|
|
339
|
+
sharing_round_secs?: number;
|
|
340
|
+
/** How long to wait for an unpair acknowledgement before dropping local
|
|
341
|
+
* channel state anyway. Library default: 60. */
|
|
342
|
+
unpair_ack_secs?: number;
|
|
343
|
+
/** Removal of channels still awaiting out-of-band fingerprint
|
|
344
|
+
* confirmation — every replica pairing, and every `NoKeys` pairing.
|
|
345
|
+
* Unlike the others this can be disabled, leaving the sweep to the
|
|
346
|
+
* application via `removeExpiredChannels`. The budget is a **human** one:
|
|
347
|
+
* someone comparing a fingerprint, possibly over the phone. Library
|
|
348
|
+
* default: `{ enabled: true, timeout_in_secs: 300 }`. */
|
|
349
|
+
expired_channels?: { enabled: boolean; timeout_in_secs: number };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** `SyncCheck` takes no parameters: the group and this device's own version
|
|
353
|
+
* are both read from the stores. The argument may be omitted entirely. */
|
|
354
|
+
export type SyncCheckParams = Record<string, never>;
|
|
355
|
+
|
|
356
|
+
export interface RemoveReplicaParams {
|
|
357
|
+
/** The member to remove, as a **decimal** `u64` string — the same form
|
|
358
|
+
* `ReplicaPaired.peer_replica_id` hands back. A value naming no current
|
|
359
|
+
* member is rejected; it is not silently ignored. */
|
|
360
|
+
replica_id: string;
|
|
361
|
+
|
|
362
|
+
memo?: string;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export type DeRecEvent =
|
|
366
|
+
| {
|
|
367
|
+
type: "PairingCompleted";
|
|
368
|
+
/** Long-term `channel_id` both peers atomically rotated to at handshake completion. */
|
|
369
|
+
channel_id: string;
|
|
370
|
+
/** Transient `channel_id` used only during pairing (the one that traveled on the ContactMessage). No longer resolves in library state. */
|
|
371
|
+
pairing_channel_id: string;
|
|
372
|
+
kind: SenderKind;
|
|
373
|
+
peer_communication_info?: Record<string, string>;
|
|
374
|
+
}
|
|
375
|
+
| {
|
|
376
|
+
type: "ActionRequired";
|
|
377
|
+
channel_id: string;
|
|
378
|
+
|
|
379
|
+
action: Uint8Array;
|
|
380
|
+
|
|
381
|
+
action_kind: string;
|
|
382
|
+
peer_communication_info?: Record<string, string>;
|
|
383
|
+
|
|
384
|
+
sender_kind?: SenderKind;
|
|
385
|
+
|
|
386
|
+
version?: number;
|
|
387
|
+
share_description?: string;
|
|
388
|
+
|
|
389
|
+
share_secret_id?: string;
|
|
390
|
+
}
|
|
391
|
+
| { type: "ShareStored"; channel_id: string; version: number }
|
|
392
|
+
| { type: "ShareConfirmed"; channel_id: string; version: number }
|
|
393
|
+
| { type: "ShareRejected"; channel_id: string; version: number; status: number; memo: string }
|
|
394
|
+
/** A publishing round finished — every targeted helper confirmed,
|
|
395
|
+
* rejected, or timed out.
|
|
396
|
+
*
|
|
397
|
+
* **A mixed round waits for the replica leg.** The counts here describe
|
|
398
|
+
* helpers only and are known the instant the helpers answer, but the
|
|
399
|
+
* event is withheld until every replica member has also acknowledged,
|
|
400
|
+
* refused, or timed out. One unreachable member therefore delays it by
|
|
401
|
+
* up to the configured timeout, which is easy to mistake for a hang.
|
|
402
|
+
* Nothing is lost — the round always terminates and a silent member is
|
|
403
|
+
* reported in `ReplicaSyncComplete.behind` rather than failing it.
|
|
404
|
+
*
|
|
405
|
+
* Drive per-helper progress from `ShareConfirmed` instead: those land as
|
|
406
|
+
* each helper answers, with no cross-population wait. A helpers-only
|
|
407
|
+
* round is unaffected. */
|
|
408
|
+
| { type: "SharingComplete"; version: number; confirmed_count: number; failed_count: number; threshold_met: boolean }
|
|
409
|
+
/** A group member refused a secret sync. Keyed by `replica_id`, not
|
|
410
|
+
* `channel_id`: every member answers on the one group channel. A
|
|
411
|
+
* `VERSION_CONFLICT` status means the round must be resolved and
|
|
412
|
+
* republished at a new version. */
|
|
413
|
+
| {
|
|
414
|
+
type: "ReplicaSyncRejected";
|
|
415
|
+
replica_id: string;
|
|
416
|
+
secret_id: string;
|
|
417
|
+
version: number;
|
|
418
|
+
status: number;
|
|
419
|
+
memo: string;
|
|
420
|
+
}
|
|
421
|
+
/** A secret sync could not be delivered to a member at all — distinct from
|
|
422
|
+
* `ReplicaSyncRejected`, which is the member answering "no". */
|
|
423
|
+
| { type: "ReplicaSyncFailed"; replica_id: string; version: number; reason: string }
|
|
424
|
+
/** A member left the group and its roster row was dropped. Fires on the
|
|
425
|
+
* members that remain. */
|
|
426
|
+
| { type: "ReplicaRemoved"; replica_id: string }
|
|
427
|
+
/** The group's source role moved to another member because the previous
|
|
428
|
+
* source is leaving. Fires on the device that chose the successor — which
|
|
429
|
+
* it does by the order its channel store returns members in — and on the
|
|
430
|
+
* successor itself when the roster promoting it arrives. */
|
|
431
|
+
| { type: "ReplicaSourceChanged"; replica_id: string }
|
|
432
|
+
/** This device left the group and dropped its whole `secret_id` partition —
|
|
433
|
+
* group channel, helper channels, shares, secrets and the snapshot. Fires
|
|
434
|
+
* only once it was told to leave *and* has since seen a roster excluding
|
|
435
|
+
* it; absence alone never destroys a copy of the secret. */
|
|
436
|
+
| { type: "SelfRemovedFromGroup"; version: number }
|
|
437
|
+
/** A replica catch-up finished. `fetched_from` is absent when this device
|
|
438
|
+
* was already current, in which case no hydration event follows. */
|
|
439
|
+
| {
|
|
440
|
+
type: "SyncCheckComplete";
|
|
441
|
+
local_version: number;
|
|
442
|
+
group_version: number;
|
|
443
|
+
fetched_from?: string;
|
|
444
|
+
}
|
|
445
|
+
/** The replica leg of a publishing round finished. Reported separately from
|
|
446
|
+
* `SharingComplete`: replicas are best-effort, so a member in `behind` does
|
|
447
|
+
* not fail the round. `behind` is the application's retry list — the
|
|
448
|
+
* library keeps no durable per-member sync state. */
|
|
449
|
+
| { type: "ReplicaSyncComplete"; version: number; synced: string[]; behind: string[] }
|
|
450
|
+
| { type: "ShareVerified"; channel_id: string; version: number }
|
|
451
|
+
| {
|
|
452
|
+
type: "SecretsDiscovered";
|
|
453
|
+
channel_id: string;
|
|
454
|
+
|
|
455
|
+
secrets: Array<{ secret_id: string; versions: Array<{ version: number; description: string }> }>;
|
|
456
|
+
}
|
|
457
|
+
| { type: "RecoveryShareReceived"; channel_id: string; shares_received: number }
|
|
458
|
+
| { type: "RecoveryShareError"; channel_id: string; shares_received: number; error: string }
|
|
459
|
+
/** Recovery completed — the typed `Secret` snapshot the owner
|
|
460
|
+
* originally protected. Mirrors `ReplicaSecretReceived.secret`:
|
|
461
|
+
* `secrets` is the user-facing `Vec<UserSecret>` the application
|
|
462
|
+
* fed to `start(FlowKind.ProtectSecret)`; `helpers` and `replicas`
|
|
463
|
+
* are the roster snapshot captured at distribution time. The library handles the two-stage
|
|
464
|
+
* `DeRecSecret` → `Secret` protobuf decode internally. */
|
|
465
|
+
| {
|
|
466
|
+
type: "SecretRecovered";
|
|
467
|
+
secret: {
|
|
468
|
+
helpers: Array<{
|
|
469
|
+
channel_id: string;
|
|
470
|
+
transport_uri: string;
|
|
471
|
+
shared_key: Uint8Array;
|
|
472
|
+
communication_info: Record<string, string>;
|
|
473
|
+
}>;
|
|
474
|
+
secrets: Array<{
|
|
475
|
+
id: Uint8Array;
|
|
476
|
+
name: string;
|
|
477
|
+
data: Uint8Array;
|
|
478
|
+
}>;
|
|
479
|
+
/** Replica composite. Absent when this `secret_id` has no
|
|
480
|
+
* replica setup. Carries the full member roster, the one channel
|
|
481
|
+
* they share, and the 32-byte group key. Required by `restore` to
|
|
482
|
+
* rebuild replica state without re-pairing. */
|
|
483
|
+
replicas?: {
|
|
484
|
+
/** The one channel every member is addressed on. */
|
|
485
|
+
channel_id: string;
|
|
486
|
+
/** Every member of the group, including the writer. Exactly one
|
|
487
|
+
* carries `role: "Source"` — that member is where the secret
|
|
488
|
+
* originated, which is why no separate owner field is needed. */
|
|
489
|
+
members: Array<{
|
|
490
|
+
replica_id: string;
|
|
491
|
+
transport_uri: string;
|
|
492
|
+
role: "Source" | "Destination";
|
|
493
|
+
communication_info: Record<string, string>;
|
|
494
|
+
}>;
|
|
495
|
+
shared_key: Uint8Array;
|
|
496
|
+
};
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
| { type: "Unpaired"; channel_id: string }
|
|
501
|
+
|
|
502
|
+
| { type: "UnpairRejected"; channel_id: string; status: number; memo: string }
|
|
503
|
+
|
|
504
|
+
/** Contact creator answered the scanner's `PrePairRequest` with a
|
|
505
|
+
* non-Ok status (HashedKeys flow). Distinct from a cryptographic
|
|
506
|
+
* hash mismatch, which surfaces as a thrown error from `process()`. */
|
|
507
|
+
| { type: "PrePairRejected"; channel_id: string; status: number; memo: string }
|
|
508
|
+
|
|
509
|
+
/** Fires alongside `PairingCompleted` on replica-mode pair handshakes.
|
|
510
|
+
* `peer_replica_id` is the peer's `u64` as a **decimal** string,
|
|
511
|
+
* matching the wire `derec.replica_id` representation and every other
|
|
512
|
+
* id across this boundary. Pass it back verbatim — `RemoveReplica`
|
|
513
|
+
* expects the same decimal form. The local side's role
|
|
514
|
+
* (`ReplicaSource` vs `ReplicaDestination`) is on the persisted
|
|
515
|
+
* channel record — replica pairings are unidirectional, so there is
|
|
516
|
+
* no separate "role in pair" field. */
|
|
517
|
+
| {
|
|
518
|
+
type: "ReplicaPaired";
|
|
519
|
+
channel_id: string;
|
|
520
|
+
peer_replica_id: string;
|
|
521
|
+
}
|
|
522
|
+
/** A `ReplicaSource` peer pushed a secret sync on a
|
|
523
|
+
* `ReplicaDestination` channel. The library decoded the
|
|
524
|
+
* `ReplicaSecretPayload`; the app installs `secret.secrets` and
|
|
525
|
+
* optionally uses `shares` for recovery. `from_replica_id` and the
|
|
526
|
+
* `replica_id` fields inside `secret` are `u64` as **decimal**
|
|
527
|
+
* strings. */
|
|
528
|
+
| {
|
|
529
|
+
type: "ReplicaSecretReceived";
|
|
530
|
+
channel_id: string;
|
|
531
|
+
from_replica_id: string;
|
|
532
|
+
secret_id: string;
|
|
533
|
+
version: number;
|
|
534
|
+
secret: {
|
|
535
|
+
helpers: Array<{
|
|
536
|
+
channel_id: string;
|
|
537
|
+
transport_uri: string;
|
|
538
|
+
shared_key: Uint8Array;
|
|
539
|
+
communication_info: Record<string, string>;
|
|
540
|
+
}>;
|
|
541
|
+
secrets: Array<{
|
|
542
|
+
id: Uint8Array;
|
|
543
|
+
name: string;
|
|
544
|
+
data: Uint8Array;
|
|
545
|
+
}>;
|
|
546
|
+
/** Replica composite. Absent when this `secret_id` has no
|
|
547
|
+
* replica setup. The same shape as `SecretRecovered.secret.replicas`. */
|
|
548
|
+
replicas?: {
|
|
549
|
+
/** The one channel every member is addressed on. */
|
|
550
|
+
channel_id: string;
|
|
551
|
+
/** Every member of the group, including the writer. Exactly one
|
|
552
|
+
* carries `role: "Source"` — that member is where the secret
|
|
553
|
+
* originated, which is why no separate owner field is needed. */
|
|
554
|
+
members: Array<{
|
|
555
|
+
replica_id: string;
|
|
556
|
+
transport_uri: string;
|
|
557
|
+
role: "Source" | "Destination";
|
|
558
|
+
communication_info: Record<string, string>;
|
|
559
|
+
}>;
|
|
560
|
+
shared_key: Uint8Array;
|
|
561
|
+
};
|
|
562
|
+
};
|
|
563
|
+
shares: Array<{
|
|
564
|
+
channel_id: string;
|
|
565
|
+
committed_share: Uint8Array;
|
|
566
|
+
}>;
|
|
567
|
+
}
|
|
568
|
+
/** The first sync for a `secret_id` this device had no snapshot for —
|
|
569
|
+
* the secret now exists here. Same payload as `ReplicaSecretReceived`,
|
|
570
|
+
* which reports a later version of a secret the device already held.
|
|
571
|
+
* Both are written to the stores by the library before the event is
|
|
572
|
+
* delivered; the distinct type is what tells an application the set of
|
|
573
|
+
* secrets on the device changed.
|
|
574
|
+
*
|
|
575
|
+
* This is not a recovery: recovery reconstructs a secret from helper
|
|
576
|
+
* shares and is driven by the application through `restore`. */
|
|
577
|
+
| {
|
|
578
|
+
type: "ReplicaSecretInstalled";
|
|
579
|
+
channel_id: string;
|
|
580
|
+
from_replica_id: string;
|
|
581
|
+
secret_id: string;
|
|
582
|
+
version: number;
|
|
583
|
+
secret: {
|
|
584
|
+
helpers: Array<{
|
|
585
|
+
channel_id: string;
|
|
586
|
+
transport_uri: string;
|
|
587
|
+
shared_key: Uint8Array;
|
|
588
|
+
communication_info: Record<string, string>;
|
|
589
|
+
}>;
|
|
590
|
+
secrets: Array<{
|
|
591
|
+
id: Uint8Array;
|
|
592
|
+
name: string;
|
|
593
|
+
data: Uint8Array;
|
|
594
|
+
}>;
|
|
595
|
+
/** Replica composite. Absent when this `secret_id` has no
|
|
596
|
+
* replica setup. The same shape as `SecretRecovered.secret.replicas`. */
|
|
597
|
+
replicas?: {
|
|
598
|
+
/** The one channel every member is addressed on. */
|
|
599
|
+
channel_id: string;
|
|
600
|
+
/** Every member of the group, including the writer. Exactly one
|
|
601
|
+
* carries `role: "Source"` — that member is where the secret
|
|
602
|
+
* originated, which is why no separate owner field is needed. */
|
|
603
|
+
members: Array<{
|
|
604
|
+
replica_id: string;
|
|
605
|
+
transport_uri: string;
|
|
606
|
+
role: "Source" | "Destination";
|
|
607
|
+
communication_info: Record<string, string>;
|
|
608
|
+
}>;
|
|
609
|
+
shared_key: Uint8Array;
|
|
610
|
+
};
|
|
611
|
+
};
|
|
612
|
+
shares: Array<{
|
|
613
|
+
channel_id: string;
|
|
614
|
+
committed_share: Uint8Array;
|
|
615
|
+
}>;
|
|
616
|
+
}
|
|
617
|
+
/** Peer's ack of a secret sync we sent. `status` is the `StatusEnum`
|
|
618
|
+
* integer (0 = Ok), `memo` is the peer's explanation. */
|
|
619
|
+
| {
|
|
620
|
+
type: "ReplicaSecretAcked";
|
|
621
|
+
channel_id: string;
|
|
622
|
+
from_replica_id: string;
|
|
623
|
+
secret_id: string;
|
|
624
|
+
version: number;
|
|
625
|
+
status: number;
|
|
626
|
+
memo: string;
|
|
627
|
+
}
|
|
628
|
+
/** A peer announced an updated `communication_info` map and/or
|
|
629
|
+
* transport endpoint via `start(FlowKind.UpdateChannelInfo)`.
|
|
630
|
+
* Surfaces on both sides — the initiator sees its own update echo
|
|
631
|
+
* back after the responder accepts. */
|
|
632
|
+
| {
|
|
633
|
+
type: "ChannelInfoUpdated";
|
|
634
|
+
channel_id: string;
|
|
635
|
+
}
|
|
636
|
+
/** The peer answered our outbound `UpdateChannelInfo` with a
|
|
637
|
+
* non-`Ok` status. Local state is not rolled back — the app decides
|
|
638
|
+
* whether to retry. */
|
|
639
|
+
| {
|
|
640
|
+
type: "ChannelInfoUpdateRejected";
|
|
641
|
+
channel_id: string;
|
|
642
|
+
status: number;
|
|
643
|
+
memo: string;
|
|
644
|
+
}
|
|
645
|
+
/** Emitted by `process()` in place of `ActionRequired` when the
|
|
646
|
+
* configured {@link AutoAcceptPolicy} opts in to the inbound
|
|
647
|
+
* action's flow. The same event vec carries the flow's completion
|
|
648
|
+
* events (e.g. `ShareStored`, `PairingCompleted`). Use this purely
|
|
649
|
+
* for observability — no further action is required. `action_kind`
|
|
650
|
+
* is the same label vocabulary as `ActionRequired.action_kind`
|
|
651
|
+
* (`"Pairing"`, `"StoreShare"`, …). */
|
|
652
|
+
| { type: "AutoAccepted"; channel_id: string; action_kind: string }
|
|
653
|
+
| { type: "NoOp" }
|
|
654
|
+
/** A pairing handshake was dispatched successfully. `kind` is the
|
|
655
|
+
* local party's role — same value the subsequent `PairingCompleted`
|
|
656
|
+
* will carry. Emitted by `start(Pairing)`. */
|
|
657
|
+
| { type: "PairingStarted"; channel_id: string; kind: SenderKind }
|
|
658
|
+
/** A discovery request was dispatched to `channel_id`. Emitted per
|
|
659
|
+
* targeted helper by `start(Discovery)`. */
|
|
660
|
+
| { type: "DiscoveryStarted"; channel_id: string }
|
|
661
|
+
/** A discovery request could not be dispatched to `channel_id`. Other
|
|
662
|
+
* targeted channels are unaffected. */
|
|
663
|
+
| { type: "DiscoveryFailed"; channel_id: string; error: string }
|
|
664
|
+
/** A share-storage request was dispatched to `channel_id`. Emitted per
|
|
665
|
+
* targeted peer by `start(ProtectSecret)`. */
|
|
666
|
+
| { type: "ProtectSecretStarted"; channel_id: string; version: number }
|
|
667
|
+
/** A share-storage request could not be dispatched to `channel_id`. */
|
|
668
|
+
| {
|
|
669
|
+
type: "ProtectSecretFailed";
|
|
670
|
+
channel_id: string;
|
|
671
|
+
version: number;
|
|
672
|
+
error: string;
|
|
673
|
+
}
|
|
674
|
+
/** A verify-share challenge was dispatched to `channel_id`. */
|
|
675
|
+
| { type: "VerifySharesStarted"; channel_id: string; version: number }
|
|
676
|
+
/** A verify-share challenge could not be dispatched to `channel_id`. */
|
|
677
|
+
| {
|
|
678
|
+
type: "VerifySharesFailed";
|
|
679
|
+
channel_id: string;
|
|
680
|
+
version: number;
|
|
681
|
+
error: string;
|
|
682
|
+
}
|
|
683
|
+
/** A recovery share request was dispatched to `channel_id`. */
|
|
684
|
+
| { type: "RecoverSecretStarted"; channel_id: string; version: number }
|
|
685
|
+
/** A recovery share request could not be dispatched to `channel_id`. */
|
|
686
|
+
| {
|
|
687
|
+
type: "RecoverSecretFailed";
|
|
688
|
+
channel_id: string;
|
|
689
|
+
version: number;
|
|
690
|
+
error: string;
|
|
691
|
+
}
|
|
692
|
+
/** An unpair request was dispatched to `channel_id`. Followed by an
|
|
693
|
+
* `Unpaired` event once the peer acknowledges (or in the same event
|
|
694
|
+
* vec, under `UnpairAck.NotRequired`). */
|
|
695
|
+
| { type: "UnpairFailed"; channel_id: string; error: string }
|
|
696
|
+
| { type: "UnpairStarted"; channel_id: string }
|
|
697
|
+
/** An update-channel-info request was dispatched to `channel_id`. */
|
|
698
|
+
| { type: "UpdateChannelInfoStarted"; channel_id: string }
|
|
699
|
+
/** An update-channel-info request could not be dispatched to
|
|
700
|
+
* `channel_id`. */
|
|
701
|
+
| { type: "UpdateChannelInfoFailed"; channel_id: string; error: string };
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Per-flow auto-accept policy. When a field is `true`, `process()`
|
|
705
|
+
* internally accepts the matching inbound request and emits
|
|
706
|
+
* `AutoAccepted` in place of `ActionRequired`. Every field defaults
|
|
707
|
+
* to `false`.
|
|
708
|
+
*
|
|
709
|
+
* Per-field caveats (read before enabling in production):
|
|
710
|
+
* - `pairing` — covers standard and replica pairing. Replica pairing
|
|
711
|
+
* remains `Pending` until both sides run `verifyFingerprint()`, so
|
|
712
|
+
* auto-accept is safe for replicas. Standard pairing transitions to
|
|
713
|
+
* `Paired` immediately.
|
|
714
|
+
* - `prePair` — turns the initiator into a request-amplification
|
|
715
|
+
* oracle. Anyone who knows a HashedKeys contact's nonce can elicit a
|
|
716
|
+
* key-publish response. Keep off unless you control both ends of
|
|
717
|
+
* the transport.
|
|
718
|
+
* - `storeShare` — the helper's only admission-control point for
|
|
719
|
+
* inbound shares. The protocol enforces no size, quota or rate limit
|
|
720
|
+
* of its own, and `maxShareSize` is checked for range overlap at
|
|
721
|
+
* pairing time only, never against an actual share. While this is
|
|
722
|
+
* `false`, `ActionRequired` carries the decoded request, so the
|
|
723
|
+
* application can inspect the share and call `reject()` with
|
|
724
|
+
* `StatusEnum.SizeLimitExceeded`. Setting it `true` removes that
|
|
725
|
+
* opportunity entirely: every share from every paired Owner is stored
|
|
726
|
+
* unconditionally, at whatever size it arrives. Keep off in any
|
|
727
|
+
* deployment with per-user storage limits.
|
|
728
|
+
* - `unpair` — destructive. Accepting deletes the local channel
|
|
729
|
+
* record before any UI confirmation.
|
|
730
|
+
* - `updateChannelInfo` — silently overwrites the channel record with
|
|
731
|
+
* the peer's announced transport / communication info.
|
|
732
|
+
*/
|
|
733
|
+
export interface AutoAcceptPolicy {
|
|
734
|
+
pairing?: boolean;
|
|
735
|
+
prePair?: boolean;
|
|
736
|
+
storeShare?: boolean;
|
|
737
|
+
verifyShare?: boolean;
|
|
738
|
+
discovery?: boolean;
|
|
739
|
+
getShare?: boolean;
|
|
740
|
+
unpair?: boolean;
|
|
741
|
+
updateChannelInfo?: boolean;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Fluent builder for {@link DeRecProtocol}. Mirrors the Rust
|
|
746
|
+
* `DeRecProtocolBuilder` and the dotnet `DeRecProtocolBuilder`
|
|
747
|
+
* method-for-method so a developer who already knows one SDK can move
|
|
748
|
+
* between them without reaching for reference docs.
|
|
749
|
+
*
|
|
750
|
+
* Required setters: `withChannelStore`, `withShareStore`,
|
|
751
|
+
* `withSecretStore`, `withTransport`, `withOwnTransport`. Calling
|
|
752
|
+
* `build()` without all five throws.
|
|
753
|
+
*/
|
|
754
|
+
export declare class DeRecProtocolBuilder {
|
|
755
|
+
/**
|
|
756
|
+
* Construct a builder bound to a specific secret. `secretId`
|
|
757
|
+
* identifies the single secret this protocol instance manages.
|
|
758
|
+
* Apps that juggle multiple secrets instantiate one
|
|
759
|
+
* {@link DeRecProtocol} per id.
|
|
760
|
+
*/
|
|
761
|
+
constructor(secretId: bigint | number);
|
|
762
|
+
|
|
763
|
+
withChannelStore(store: ChannelStore): DeRecProtocolBuilder;
|
|
764
|
+
withShareStore(store: ShareStore): DeRecProtocolBuilder;
|
|
765
|
+
withSecretStore(store: SecretStore): DeRecProtocolBuilder;
|
|
766
|
+
withUserSecretStore(store: UserSecretStore): DeRecProtocolBuilder;
|
|
767
|
+
withStateStore(store: StateStore): DeRecProtocolBuilder;
|
|
768
|
+
withTransport(transport: Transport): DeRecProtocolBuilder;
|
|
769
|
+
withOwnTransport(endpoint: { uri: string; protocol: string }): DeRecProtocolBuilder;
|
|
770
|
+
|
|
771
|
+
/** Default: 3. */
|
|
772
|
+
withThreshold(threshold: number): DeRecProtocolBuilder;
|
|
773
|
+
/** Default: 3. */
|
|
774
|
+
withKeepVersionsCount(count: number): DeRecProtocolBuilder;
|
|
775
|
+
/**
|
|
776
|
+
* Configure how long the protocol waits on each thing that can keep it
|
|
777
|
+
* waiting. Every field is optional and **absent means "keep the library
|
|
778
|
+
* default"**; not calling this at all leaves all four at their defaults.
|
|
779
|
+
*
|
|
780
|
+
* These were one setting until it became clear they answer different
|
|
781
|
+
* questions. `inbound_message_secs` is a **security** boundary — how stale
|
|
782
|
+
* a message may be and still be accepted — so it has to tolerate transport
|
|
783
|
+
* latency and clock skew. The other three are **liveness** budgets: how
|
|
784
|
+
* long to keep hoping a peer will answer.
|
|
785
|
+
*
|
|
786
|
+
* Values are forwarded verbatim; clamping, and the meaning of a disabled
|
|
787
|
+
* `expired_channels`, are library decisions rather than this binding's.
|
|
788
|
+
*/
|
|
789
|
+
withTimeouts(timeouts: Timeouts): DeRecProtocolBuilder;
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Accept plaintext `http://` transport endpoints. **Development only.**
|
|
793
|
+
* Default: `false`.
|
|
794
|
+
*
|
|
795
|
+
* With `false`, plaintext is accepted in exactly one situation: an endpoint
|
|
796
|
+
* this device configured for **itself** that names loopback (`localhost`,
|
|
797
|
+
* `127.0.0.1`, `::1`). A local dev server therefore needs no configuration
|
|
798
|
+
* at all.
|
|
799
|
+
*
|
|
800
|
+
* With `true`, plaintext is accepted for **any host on any path**,
|
|
801
|
+
* including endpoints a peer supplies. That is what makes the LAN case
|
|
802
|
+
* work — a phone talking to a laptop, where neither side is loopback — and
|
|
803
|
+
* why the name is blunt.
|
|
804
|
+
*
|
|
805
|
+
* This is a guardrail, not transport security. The SDK opens no sockets;
|
|
806
|
+
* delivery is your `Transport`. Nothing here stops an application sending
|
|
807
|
+
* plaintext — it governs which endpoints the protocol will record,
|
|
808
|
+
* propagate to peers, and reply to.
|
|
809
|
+
*/
|
|
810
|
+
withUnsafeHttp(allow: boolean): DeRecProtocolBuilder;
|
|
811
|
+
/** Default: empty. */
|
|
812
|
+
withCommunicationInfo(info: Record<string, string>): DeRecProtocolBuilder;
|
|
813
|
+
/** Default: false. */
|
|
814
|
+
withAutoRespondOnFailure(enabled: boolean): DeRecProtocolBuilder;
|
|
815
|
+
/** Default: "required". */
|
|
816
|
+
withUnpairAck(ack: UnpairAck): DeRecProtocolBuilder;
|
|
817
|
+
/**
|
|
818
|
+
* When `true`, every outbound channel-mode request stamps
|
|
819
|
+
* `request.replyTo = ownTransport` so the responder routes its reply
|
|
820
|
+
* back here even if the channel's stored peer endpoint points
|
|
821
|
+
* elsewhere. Default: false.
|
|
822
|
+
*/
|
|
823
|
+
withAutoReplyTo(enabled: boolean): DeRecProtocolBuilder;
|
|
824
|
+
/**
|
|
825
|
+
* Per-flow auto-accept policy. When a field is `true`, `process()`
|
|
826
|
+
* internally accepts the matching inbound request and emits
|
|
827
|
+
* `AutoAccepted` in place of `ActionRequired`. See
|
|
828
|
+
* {@link AutoAcceptPolicy} for per-field caveats.
|
|
829
|
+
*
|
|
830
|
+
* Default: empty policy (every flow off).
|
|
831
|
+
*/
|
|
832
|
+
withAutoAccept(policy: AutoAcceptPolicy): DeRecProtocolBuilder;
|
|
833
|
+
/**
|
|
834
|
+
* Stable per-device replica id. Required to participate in any
|
|
835
|
+
* `ReplicaSource` / `ReplicaDestination` pairing. The id must be
|
|
836
|
+
* stable across restarts. Default: unset.
|
|
837
|
+
*/
|
|
838
|
+
withReplicaId(id: bigint | number): DeRecProtocolBuilder;
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Finalize the configuration. Throws if any of the required setters
|
|
842
|
+
* was not called.
|
|
843
|
+
*/
|
|
844
|
+
build(): DeRecProtocol;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
export declare class DeRecProtocol {
|
|
848
|
+
/** Use {@link DeRecProtocolBuilder} to construct instances. */
|
|
849
|
+
private constructor();
|
|
850
|
+
|
|
851
|
+
/** The secret identifier this protocol instance is bound to. */
|
|
852
|
+
secretId(): bigint;
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* Generate an out-of-band contact message used to bootstrap pairing.
|
|
856
|
+
*
|
|
857
|
+
* @param channelId Optional channel identifier. Pass `null` /
|
|
858
|
+
* `undefined` to have the library generate one.
|
|
859
|
+
* @param contactMode `ContactMode.InlineKeys` embeds the public keys
|
|
860
|
+
* directly in the contact. `ContactMode.HashedKeys`
|
|
861
|
+
* embeds only a SHA-384 binding hash (keys are
|
|
862
|
+
* fetched later via the `PrePair` round-trip).
|
|
863
|
+
* `HashedKeys` requires `ownTransportUri` to be
|
|
864
|
+
* ephemeral.
|
|
865
|
+
*/
|
|
866
|
+
/**
|
|
867
|
+
* Single entry point for all three `ContactMode` variants.
|
|
868
|
+
*
|
|
869
|
+
* @param channelId `null`/`undefined` lets the library mint a random id.
|
|
870
|
+
* @param contactMode `InlineKeys` embeds keys directly; `HashedKeys`
|
|
871
|
+
* embeds only a SHA-384 commitment (keys fetched via `PrePair`);
|
|
872
|
+
* `NoKeys` carries no key material — the creator generates keys on the
|
|
873
|
+
* fly when the `PrePairRequest` arrives. Only appropriate for `NoKeys`
|
|
874
|
+
* when the OOB delivery channel is fully trusted.
|
|
875
|
+
* @param nonce `null`/`undefined` lets the library generate a fresh
|
|
876
|
+
* random `bigint`. Required for `NoKeys` where callers typically pick
|
|
877
|
+
* a small human-typable value.
|
|
878
|
+
*/
|
|
879
|
+
createContact(
|
|
880
|
+
channelId: bigint | null | undefined,
|
|
881
|
+
contactMode: ContactMode,
|
|
882
|
+
nonce?: bigint | null,
|
|
883
|
+
): Promise<ContactMessage>;
|
|
884
|
+
|
|
885
|
+
start(flowKind: FlowKind.Pairing, params: PairingParams): Promise<DeRecEvent[]>;
|
|
886
|
+
start(flowKind: FlowKind.Discovery, params: DiscoveryParams): Promise<DeRecEvent[]>;
|
|
887
|
+
start(flowKind: FlowKind.ProtectSecret, params: ProtectSecretParams): Promise<DeRecEvent[]>;
|
|
888
|
+
start(flowKind: FlowKind.VerifyShares, params: VerifySharesParams): Promise<DeRecEvent[]>;
|
|
889
|
+
start(flowKind: FlowKind.RecoverSecret, params: RecoverSecretParams): Promise<DeRecEvent[]>;
|
|
890
|
+
start(flowKind: FlowKind.Unpair, params: UnpairParams): Promise<DeRecEvent[]>;
|
|
891
|
+
start(flowKind: FlowKind.UpdateChannelInfo, params: UpdateChannelInfoParams): Promise<DeRecEvent[]>;
|
|
892
|
+
start(flowKind: FlowKind.SyncCheck, params?: SyncCheckParams): Promise<DeRecEvent[]>;
|
|
893
|
+
|
|
894
|
+
/** Announce a member's removal. This does **not** remove anything on its
|
|
895
|
+
* own and emits no `ReplicaRemoved`: it tells every member and flags the
|
|
896
|
+
* target locally. The removal completes only once the application
|
|
897
|
+
* publishes a roster omitting that member — an ordinary
|
|
898
|
+
* `start(FlowKind.ProtectSecret)` — at which point `ReplicaRemoved`
|
|
899
|
+
* fires. A group with no secret to publish therefore cannot complete a
|
|
900
|
+
* removal. */
|
|
901
|
+
start(flowKind: FlowKind.RemoveReplica, params: RemoveReplicaParams): Promise<DeRecEvent[]>;
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* Replace this node's local <c>communication_info</c> map. Does not
|
|
905
|
+
* contact peers — follow up with
|
|
906
|
+
* <c>start(FlowKind.UpdateChannelInfo, ...)</c> to propagate.
|
|
907
|
+
*/
|
|
908
|
+
setCommunicationInfo(info: Record<string, string>): void;
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Replace this node's local transport endpoint. IMPORTANT: keep the
|
|
912
|
+
* old endpoint operational during the changeover (see the Rust docs
|
|
913
|
+
* on the matching setter for the discipline).
|
|
914
|
+
*/
|
|
915
|
+
setOwnTransport(uri: string, protocol: string): void;
|
|
916
|
+
|
|
917
|
+
process(message: Uint8Array): Promise<DeRecEvent[]>;
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Advance time-driven state without an inbound message.
|
|
921
|
+
*
|
|
922
|
+
* Timeouts are otherwise only evaluated by `process`, so a publish whose
|
|
923
|
+
* helpers all go quiet has nothing left to close it: the round stays open
|
|
924
|
+
* and no `SharingComplete` is ever emitted. Call this from a timer —
|
|
925
|
+
* `setInterval`, a service-worker alarm, a job runner — at an interval
|
|
926
|
+
* shorter than the configured timeout.
|
|
927
|
+
*
|
|
928
|
+
* Safe to call at any time; with nothing in flight it resolves to an empty
|
|
929
|
+
* array. It mutates the same round state an inbound response does, so it
|
|
930
|
+
* must be serialized against `process` for the same `secretId`.
|
|
931
|
+
*/
|
|
932
|
+
tick(): Promise<DeRecEvent[]>;
|
|
933
|
+
|
|
934
|
+
accept(actionBytes: Uint8Array): Promise<DeRecEvent[]>;
|
|
935
|
+
|
|
936
|
+
reject(actionBytes: Uint8Array, status: number, memo: string): Promise<void>;
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Derive the human-readable fingerprint for a paired channel. Both sides
|
|
940
|
+
* compute the same value from the shared key — users compare them out of
|
|
941
|
+
* band before calling `verifyFingerprint`. Required for every replica
|
|
942
|
+
* pairing and every `NoKeys` pairing, which stay unusable until it
|
|
943
|
+
* succeeds.
|
|
944
|
+
*/
|
|
945
|
+
getFingerprint(channelId: bigint | number): Promise<string>;
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Verify `fingerprint` against the channel's locally-derived one. On
|
|
949
|
+
* match, the channel transitions from `Pending` to `Paired`. Returns
|
|
950
|
+
* `true` on confirmation, `false` on mismatch.
|
|
951
|
+
*/
|
|
952
|
+
verifyFingerprint(channelId: bigint | number, fingerprint: string): Promise<boolean>;
|
|
953
|
+
/**
|
|
954
|
+
* Remove `Pending` channels older than `olderThanSecs`, along with their
|
|
955
|
+
* pairing keys. Resolves to the removed channel ids as decimal strings.
|
|
956
|
+
*
|
|
957
|
+
* Independent of the configured cleanup policy — this sweeps at the
|
|
958
|
+
* threshold given even when that policy is disabled. The age comparison
|
|
959
|
+
* is strict, so a channel created within the current second survives
|
|
960
|
+
* even `0`.
|
|
961
|
+
*/
|
|
962
|
+
removeExpiredChannels(olderThanSecs: number): Promise<string[]>;
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Rebuild this protocol's `secret_id` namespace from a recovered
|
|
966
|
+
* `Secret`. Mirrors the Rust `DeRecProtocol::restore` — pass the
|
|
967
|
+
* typed `secret` carried by the `SecretRecovered` event verbatim.
|
|
968
|
+
*
|
|
969
|
+
* Errors surface as structured objects with a `code` field:
|
|
970
|
+
*
|
|
971
|
+
* | code | meaning |
|
|
972
|
+
* |--------------------|------------------------------------------------------------------|
|
|
973
|
+
* | `ALREADY_RESTORED` | A user-secret snapshot already exists for this `secret_id`. |
|
|
974
|
+
* | `CONFLICT` | Channels live at canonical helper / replica ids. The error |
|
|
975
|
+
* | | carries `channel_ids: string[]` listing the collisions. |
|
|
976
|
+
* | `INVARIANT` | The recovered `Secret` is internally inconsistent. |
|
|
977
|
+
* | `STORAGE` | A store I/O call failed mid-restore. |
|
|
978
|
+
*/
|
|
979
|
+
restore(
|
|
980
|
+
recoveredSecret: Extract<DeRecEvent, { type: "SecretRecovered" }>["secret"],
|
|
981
|
+
version: number,
|
|
982
|
+
): Promise<DeRecEvent[]>;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* Envelope-level helpers that operate on raw `DeRecMessage` bytes without
|
|
987
|
+
* touching the encrypted inner payload. Useful for primitive-only consumers
|
|
988
|
+
* that need to set or read the `traceId` correlation token themselves
|
|
989
|
+
* (`DeRecProtocol` handles trace_id end-to-end automatically).
|
|
990
|
+
*/
|
|
991
|
+
export declare const envelope: {
|
|
992
|
+
/**
|
|
993
|
+
* Re-stamp `traceId` on an outbound envelope. Returns the re-encoded
|
|
994
|
+
* bytes. The encrypted inner message is untouched.
|
|
995
|
+
*/
|
|
996
|
+
apply_trace_id(envelope_bytes: Uint8Array, trace_id: bigint): Uint8Array;
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* Read `traceId` off an inbound envelope. Returns `0n` when unset (the
|
|
1000
|
+
* protobuf default is indistinguishable from an explicit zero).
|
|
1001
|
+
*/
|
|
1002
|
+
read_trace_id(envelope_bytes: Uint8Array): bigint;
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
export interface Timestamp {
|
|
1006
|
+
|
|
1007
|
+
seconds: bigint;
|
|
1008
|
+
nanos: number;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
export interface DeRecResult {
|
|
1012
|
+
status: number;
|
|
1013
|
+
memo: string;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
export interface GetSecretIdsVersionsRequestMessage {
|
|
1017
|
+
timestamp?: Timestamp;
|
|
1018
|
+
/** Ephemeral endpoint where the requester wants the response routed.
|
|
1019
|
+
* Absent means "use the channel's stored peer endpoint". */
|
|
1020
|
+
reply_to?: TransportProtocol;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
export interface VersionList {
|
|
1024
|
+
secret_id: bigint;
|
|
1025
|
+
versions: VersionListEntry[];
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
export interface VersionListEntry {
|
|
1029
|
+
version: number;
|
|
1030
|
+
version_description: string;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
export interface GetSecretIdsVersionsResponseMessage {
|
|
1034
|
+
result?: DeRecResult;
|
|
1035
|
+
secret_list: VersionList[];
|
|
1036
|
+
timestamp?: Timestamp;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
export interface VersionEntry {
|
|
1040
|
+
version: number;
|
|
1041
|
+
description: string;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
export interface SecretVersionEntry {
|
|
1045
|
+
secret_id: bigint;
|
|
1046
|
+
versions: VersionEntry[];
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
export interface TransportProtocol {
|
|
1050
|
+
uri: string;
|
|
1051
|
+
|
|
1052
|
+
protocol: number;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
export interface CommunicationInfoKeyValue {
|
|
1056
|
+
key: string;
|
|
1057
|
+
string_value: string | null;
|
|
1058
|
+
bytes_value: Uint8Array | null;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
export interface CommunicationInfo {
|
|
1062
|
+
communication_info_entries: CommunicationInfoKeyValue[];
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// `ContactMessage` is defined once above (line ~87) and covers both
|
|
1066
|
+
// `INLINE_KEYS` and `HASHED_KEYS` modes.
|
|
1067
|
+
|
|
1068
|
+
|
|
1069
|
+
export interface ParameterRange {
|
|
1070
|
+
min_share_size: bigint;
|
|
1071
|
+
max_share_size: bigint;
|
|
1072
|
+
min_time_between_verifications: bigint;
|
|
1073
|
+
max_time_between_verifications: bigint;
|
|
1074
|
+
min_time_between_share_updates: bigint;
|
|
1075
|
+
max_time_between_share_updates: bigint;
|
|
1076
|
+
min_unresponsive_deletion_timeout: bigint;
|
|
1077
|
+
max_unresponsive_deletion_timeout: bigint;
|
|
1078
|
+
min_unresponsive_deactivation_timeout: bigint;
|
|
1079
|
+
max_unresponsive_deactivation_timeout: bigint;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
export interface PairRequestMessage {
|
|
1083
|
+
sender_kind: number;
|
|
1084
|
+
mlkem_ciphertext: Uint8Array;
|
|
1085
|
+
ecies_public_key: Uint8Array;
|
|
1086
|
+
nonce: bigint;
|
|
1087
|
+
communication_info?: CommunicationInfo;
|
|
1088
|
+
parameter_range?: ParameterRange;
|
|
1089
|
+
transport_protocol?: TransportProtocol;
|
|
1090
|
+
timestamp?: Timestamp;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
export interface PairResponseMessage {
|
|
1094
|
+
result?: DeRecResult;
|
|
1095
|
+
nonce: bigint;
|
|
1096
|
+
communication_info?: CommunicationInfo;
|
|
1097
|
+
parameter_range?: ParameterRange;
|
|
1098
|
+
timestamp?: Timestamp;
|
|
1099
|
+
/**
|
|
1100
|
+
* Post-handshake rekey channel id. Both sides switch their local channel
|
|
1101
|
+
* record to this value once the response is accepted. Derived by the
|
|
1102
|
+
* responder as `SHA-384(u64_be(originalChannelId) || sharedKey)[..8]`
|
|
1103
|
+
* interpreted as big-endian `u64`, and validated by the requester against
|
|
1104
|
+
* its own derivation. Zero on rejection (non-Ok `result.status`).
|
|
1105
|
+
*/
|
|
1106
|
+
channel_id: bigint;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
export interface PrePairRequestMessage {
|
|
1110
|
+
nonce: bigint;
|
|
1111
|
+
transport_protocol?: TransportProtocol;
|
|
1112
|
+
timestamp?: Timestamp;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
export interface PrePairResponseMessage {
|
|
1116
|
+
result?: DeRecResult;
|
|
1117
|
+
/** Present only when `result.status === Ok`. */
|
|
1118
|
+
mlkem_encapsulation_key?: Uint8Array;
|
|
1119
|
+
/** Present only when `result.status === Ok`. */
|
|
1120
|
+
ecies_public_key?: Uint8Array;
|
|
1121
|
+
nonce: bigint;
|
|
1122
|
+
timestamp?: Timestamp;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
export interface GetShareRequestMessage {
|
|
1126
|
+
secret_id: bigint;
|
|
1127
|
+
version: number;
|
|
1128
|
+
timestamp?: Timestamp;
|
|
1129
|
+
/** Ephemeral response endpoint; see `replyTo` semantics. */
|
|
1130
|
+
reply_to?: TransportProtocol;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
export interface GetShareResponseMessage {
|
|
1134
|
+
share_algorithm: number;
|
|
1135
|
+
|
|
1136
|
+
committed_de_rec_share: Uint8Array;
|
|
1137
|
+
result?: DeRecResult;
|
|
1138
|
+
timestamp?: Timestamp;
|
|
1139
|
+
/** Echoed from the request so the Owner can correlate responses across
|
|
1140
|
+
* concurrent recoveries without inspecting the share bytes. */
|
|
1141
|
+
secret_id: bigint;
|
|
1142
|
+
/** Echoed from the request for the same correlation reasons as `secret_id`. */
|
|
1143
|
+
version: number;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
export interface SiblingHash {
|
|
1147
|
+
is_left: boolean;
|
|
1148
|
+
hash: Uint8Array;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
export interface CommittedDeRecShare {
|
|
1152
|
+
|
|
1153
|
+
de_rec_share: Uint8Array;
|
|
1154
|
+
commitment: Uint8Array;
|
|
1155
|
+
merkle_path: SiblingHash[];
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
export interface StoreShareRequestMessage {
|
|
1159
|
+
|
|
1160
|
+
share: Uint8Array;
|
|
1161
|
+
share_algorithm: number;
|
|
1162
|
+
version: number;
|
|
1163
|
+
keep_list: number[];
|
|
1164
|
+
version_description: string;
|
|
1165
|
+
timestamp?: Timestamp;
|
|
1166
|
+
secret_id: bigint;
|
|
1167
|
+
/** Ephemeral response endpoint; see `replyTo` semantics. */
|
|
1168
|
+
reply_to?: TransportProtocol;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
export interface StoreShareResponseMessage {
|
|
1172
|
+
result?: DeRecResult;
|
|
1173
|
+
version: number;
|
|
1174
|
+
timestamp?: Timestamp;
|
|
1175
|
+
secret_id: bigint;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
export interface UnpairRequestMessage {
|
|
1179
|
+
memo: string;
|
|
1180
|
+
timestamp?: Timestamp;
|
|
1181
|
+
/** Ephemeral response endpoint; see `replyTo` semantics. */
|
|
1182
|
+
reply_to?: TransportProtocol;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
export interface UnpairResponseMessage {
|
|
1186
|
+
result?: DeRecResult;
|
|
1187
|
+
timestamp?: Timestamp;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
export interface VerifyShareRequestMessage {
|
|
1191
|
+
secret_id: bigint;
|
|
1192
|
+
version: number;
|
|
1193
|
+
nonce: bigint;
|
|
1194
|
+
timestamp?: Timestamp;
|
|
1195
|
+
/** Ephemeral response endpoint; see `replyTo` semantics. */
|
|
1196
|
+
reply_to?: TransportProtocol;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
export interface VerifyShareResponseMessage {
|
|
1200
|
+
result?: DeRecResult;
|
|
1201
|
+
secret_id: bigint;
|
|
1202
|
+
version: number;
|
|
1203
|
+
nonce: bigint;
|
|
1204
|
+
hash: Uint8Array;
|
|
1205
|
+
timestamp?: Timestamp;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
export interface ProduceResult {
|
|
1209
|
+
|
|
1210
|
+
envelope: Uint8Array;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
export interface SharingResponseProduceResult extends ProduceResult {
|
|
1214
|
+
committed_share: CommittedDeRecShare;
|
|
1215
|
+
secret_id: bigint;
|
|
1216
|
+
version: number;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
export interface CreateContactResult {
|
|
1220
|
+
contact_message: ContactMessage;
|
|
1221
|
+
|
|
1222
|
+
secret_key: Uint8Array;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
export interface PairingRequestProduceResult extends ProduceResult {
|
|
1226
|
+
initiator_contact_message: ContactMessage;
|
|
1227
|
+
|
|
1228
|
+
secret_key: Uint8Array;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
export interface PairingResponseProduceResult extends ProduceResult {
|
|
1232
|
+
peer_transport_protocol: TransportProtocol;
|
|
1233
|
+
|
|
1234
|
+
shared_key: Uint8Array;
|
|
1235
|
+
|
|
1236
|
+
/**
|
|
1237
|
+
* Post-handshake rekey channel id the responder is committing to.
|
|
1238
|
+
* Callers MUST atomically rename their local channel record from the
|
|
1239
|
+
* pre-rekey id (the one passed to `pairing.response.produce`) to this
|
|
1240
|
+
* value as part of accepting the response.
|
|
1241
|
+
*/
|
|
1242
|
+
channel_id: bigint;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
export interface PairingProcessResult {
|
|
1246
|
+
|
|
1247
|
+
shared_key: Uint8Array;
|
|
1248
|
+
|
|
1249
|
+
/**
|
|
1250
|
+
* Post-handshake rekey channel id — already validated against the
|
|
1251
|
+
* caller's own derivation. Callers MUST atomically rename their local
|
|
1252
|
+
* channel record from the pre-rekey id (the one in the contact) to this
|
|
1253
|
+
* value.
|
|
1254
|
+
*/
|
|
1255
|
+
channel_id: bigint;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
export interface ProducePrePairResult {
|
|
1259
|
+
|
|
1260
|
+
envelope: Uint8Array;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
export interface PrePairRequestExtractResult {
|
|
1264
|
+
|
|
1265
|
+
request: PrePairRequestMessage;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
export interface PrePairResponseExtractResult {
|
|
1269
|
+
|
|
1270
|
+
response: PrePairResponseMessage;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
export interface ProcessPrePairResult {
|
|
1274
|
+
|
|
1275
|
+
/** Initiator's ML-KEM-768 encapsulation key, validated against the
|
|
1276
|
+
* contact's `contactBindingHash`. */
|
|
1277
|
+
mlkem_encapsulation_key: Uint8Array;
|
|
1278
|
+
|
|
1279
|
+
/** Initiator's ECIES public key, validated against the contact's
|
|
1280
|
+
* `contactBindingHash`. */
|
|
1281
|
+
ecies_public_key: Uint8Array;
|
|
1282
|
+
|
|
1283
|
+
/** Nonce echoed from the original `ContactMessage`. */
|
|
1284
|
+
nonce: bigint;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
export interface SplitResult {
|
|
1288
|
+
/** Map keyed by channel id (`bigint`). */
|
|
1289
|
+
shares: Map<bigint, CommittedDeRecShare>;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
export interface RecoverResult {
|
|
1293
|
+
secret_data: Uint8Array;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
export interface UnpairingProcessResult {
|
|
1297
|
+
acknowledged: boolean;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
export interface DiscoveryProcessResult {
|
|
1301
|
+
secret_list: SecretVersionEntry[];
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
export type DeRecErrorCategory =
|
|
1305
|
+
| "pairing"
|
|
1306
|
+
| "recovery"
|
|
1307
|
+
| "discovery"
|
|
1308
|
+
| "sharing"
|
|
1309
|
+
| "verification"
|
|
1310
|
+
| "unpairing"
|
|
1311
|
+
| "derec_message"
|
|
1312
|
+
| "secret_store"
|
|
1313
|
+
| "channel_store"
|
|
1314
|
+
| "share_store"
|
|
1315
|
+
| "input"
|
|
1316
|
+
| "protobuf"
|
|
1317
|
+
| "invariant"
|
|
1318
|
+
| "wasm";
|
|
1319
|
+
|
|
1320
|
+
export interface DeRecError {
|
|
1321
|
+
category: DeRecErrorCategory;
|
|
1322
|
+
code: string;
|
|
1323
|
+
message: string;
|
|
1324
|
+
status?: number;
|
|
1325
|
+
memo?: string;
|
|
1326
|
+
expected?: number;
|
|
1327
|
+
got?: number;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
export declare const primitives: {
|
|
1331
|
+
discovery: {
|
|
1332
|
+
request: {
|
|
1333
|
+
/**
|
|
1334
|
+
* @param reply_to Optional ephemeral response endpoint. `null` /
|
|
1335
|
+
* `undefined` means "no override" (the responder
|
|
1336
|
+
* routes to the channel's stored peer endpoint).
|
|
1337
|
+
*/
|
|
1338
|
+
produce(
|
|
1339
|
+
channel_id: bigint,
|
|
1340
|
+
shared_key: Uint8Array,
|
|
1341
|
+
reply_to?: TransportProtocol | null,
|
|
1342
|
+
): ProduceResult;
|
|
1343
|
+
extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { request: GetSecretIdsVersionsRequestMessage };
|
|
1344
|
+
};
|
|
1345
|
+
response: {
|
|
1346
|
+
produce(channel_id: bigint, secret_list: SecretVersionEntry[], shared_key: Uint8Array): ProduceResult;
|
|
1347
|
+
extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { response: GetSecretIdsVersionsResponseMessage };
|
|
1348
|
+
process(response: GetSecretIdsVersionsResponseMessage): DiscoveryProcessResult;
|
|
1349
|
+
};
|
|
1350
|
+
};
|
|
1351
|
+
pairing: {
|
|
1352
|
+
request: {
|
|
1353
|
+
/**
|
|
1354
|
+
* Creates an out-of-band `ContactMessage` to bootstrap pairing.
|
|
1355
|
+
*
|
|
1356
|
+
* @param channel_id Identifier for the local pairing session.
|
|
1357
|
+
* @param contact_mode `ContactMode.InlineKeys` embeds the keys directly;
|
|
1358
|
+
* `ContactMode.HashedKeys` embeds only a SHA-384
|
|
1359
|
+
* commitment and the scanner must complete a
|
|
1360
|
+
* `PrePair` round-trip first.
|
|
1361
|
+
* @param transport_protocol Endpoint the scanner uses to talk back. For
|
|
1362
|
+
* `HashedKeys` mode it MUST be ephemeral.
|
|
1363
|
+
*/
|
|
1364
|
+
create_contact(
|
|
1365
|
+
channel_id: bigint,
|
|
1366
|
+
contact_mode: ContactMode | number,
|
|
1367
|
+
transport_protocol: TransportProtocol,
|
|
1368
|
+
): CreateContactResult;
|
|
1369
|
+
encode_contact(contact_message: ContactMessage): Uint8Array;
|
|
1370
|
+
decode_contact(bytes: Uint8Array): ContactMessage;
|
|
1371
|
+
produce(
|
|
1372
|
+
kind: SenderKind,
|
|
1373
|
+
transport_protocol: TransportProtocol,
|
|
1374
|
+
contact_message: ContactMessage,
|
|
1375
|
+
communication_info: CommunicationInfo | null,
|
|
1376
|
+
parameter_range: ParameterRange | null,
|
|
1377
|
+
): PairingRequestProduceResult;
|
|
1378
|
+
|
|
1379
|
+
extract(envelope_bytes: Uint8Array, secret_key: Uint8Array): { request: PairRequestMessage };
|
|
1380
|
+
|
|
1381
|
+
/**
|
|
1382
|
+
* Scanner-side: build a plaintext `PrePairRequest` envelope when the
|
|
1383
|
+
* contact was sent in `HashedKeys` mode. The keys obtained via the
|
|
1384
|
+
* matching `PrePairResponse` MUST be checked against the contact's
|
|
1385
|
+
* binding hash with `pairing.response.process_pre_pair` before
|
|
1386
|
+
* proceeding to a normal `produce`.
|
|
1387
|
+
*/
|
|
1388
|
+
produce_pre_pair(
|
|
1389
|
+
transport_protocol: TransportProtocol,
|
|
1390
|
+
contact_message: ContactMessage,
|
|
1391
|
+
): ProducePrePairResult;
|
|
1392
|
+
|
|
1393
|
+
/**
|
|
1394
|
+
* Initiator-side: decode an inbound plaintext `PrePairRequest`
|
|
1395
|
+
* envelope.
|
|
1396
|
+
*/
|
|
1397
|
+
extract_pre_pair(envelope_bytes: Uint8Array): PrePairRequestExtractResult;
|
|
1398
|
+
};
|
|
1399
|
+
response: {
|
|
1400
|
+
produce(
|
|
1401
|
+
channel_id: bigint,
|
|
1402
|
+
request: PairRequestMessage,
|
|
1403
|
+
secret_key: Uint8Array,
|
|
1404
|
+
communication_info: CommunicationInfo | null,
|
|
1405
|
+
parameter_range: ParameterRange | null,
|
|
1406
|
+
): PairingResponseProduceResult;
|
|
1407
|
+
|
|
1408
|
+
extract(envelope_bytes: Uint8Array, secret_key: Uint8Array): { response: PairResponseMessage };
|
|
1409
|
+
process(
|
|
1410
|
+
contact_message: ContactMessage,
|
|
1411
|
+
response: PairResponseMessage,
|
|
1412
|
+
secret_key: Uint8Array,
|
|
1413
|
+
): PairingProcessResult;
|
|
1414
|
+
|
|
1415
|
+
/**
|
|
1416
|
+
* Contact-creator side: publish the actual public keys back to the
|
|
1417
|
+
* scanner in response to a `PrePairRequest`.
|
|
1418
|
+
*/
|
|
1419
|
+
produce_pre_pair(
|
|
1420
|
+
channel_id: bigint,
|
|
1421
|
+
request: PrePairRequestMessage,
|
|
1422
|
+
secret_key: Uint8Array,
|
|
1423
|
+
): ProducePrePairResult;
|
|
1424
|
+
|
|
1425
|
+
/**
|
|
1426
|
+
* Scanner-side: decode an inbound plaintext `PrePairResponse`
|
|
1427
|
+
* envelope.
|
|
1428
|
+
*/
|
|
1429
|
+
extract_pre_pair(envelope_bytes: Uint8Array): PrePairResponseExtractResult;
|
|
1430
|
+
|
|
1431
|
+
/**
|
|
1432
|
+
* Scanner-side: validate the `PrePairResponse` against the contact's
|
|
1433
|
+
* SHA-384 binding hash. Returns the validated public keys + echoed
|
|
1434
|
+
* nonce on match; throws on mismatch.
|
|
1435
|
+
*/
|
|
1436
|
+
process_pre_pair(
|
|
1437
|
+
contact_message: ContactMessage,
|
|
1438
|
+
response: PrePairResponseMessage,
|
|
1439
|
+
): ProcessPrePairResult;
|
|
1440
|
+
};
|
|
1441
|
+
};
|
|
1442
|
+
recovery: {
|
|
1443
|
+
request: {
|
|
1444
|
+
produce(
|
|
1445
|
+
channel_id: bigint,
|
|
1446
|
+
secret_id: bigint,
|
|
1447
|
+
version: number,
|
|
1448
|
+
shared_key: Uint8Array,
|
|
1449
|
+
/** See `discovery.request.produce.reply_to`. */
|
|
1450
|
+
reply_to?: TransportProtocol | null,
|
|
1451
|
+
): ProduceResult;
|
|
1452
|
+
extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { request: GetShareRequestMessage };
|
|
1453
|
+
};
|
|
1454
|
+
response: {
|
|
1455
|
+
produce(
|
|
1456
|
+
channel_id: bigint,
|
|
1457
|
+
request: GetShareRequestMessage,
|
|
1458
|
+
stored_share_request: StoreShareRequestMessage,
|
|
1459
|
+
shared_key: Uint8Array,
|
|
1460
|
+
): ProduceResult;
|
|
1461
|
+
extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { response: GetShareResponseMessage };
|
|
1462
|
+
recover(secret_id: bigint, version: number, responses: GetShareResponseMessage[]): RecoverResult;
|
|
1463
|
+
};
|
|
1464
|
+
};
|
|
1465
|
+
sharing: {
|
|
1466
|
+
request: {
|
|
1467
|
+
split(
|
|
1468
|
+
channels: bigint[],
|
|
1469
|
+
secret_id: bigint,
|
|
1470
|
+
version: number,
|
|
1471
|
+
secret_data: Uint8Array,
|
|
1472
|
+
threshold: number,
|
|
1473
|
+
): SplitResult;
|
|
1474
|
+
produce(
|
|
1475
|
+
channel_id: bigint,
|
|
1476
|
+
version: number,
|
|
1477
|
+
secret_id: bigint,
|
|
1478
|
+
committed_share: CommittedDeRecShare,
|
|
1479
|
+
keep_list: number[],
|
|
1480
|
+
description: string,
|
|
1481
|
+
shared_key: Uint8Array,
|
|
1482
|
+
/** See `discovery.request.produce.reply_to`. */
|
|
1483
|
+
reply_to?: TransportProtocol | null,
|
|
1484
|
+
): ProduceResult;
|
|
1485
|
+
extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { request: StoreShareRequestMessage };
|
|
1486
|
+
};
|
|
1487
|
+
response: {
|
|
1488
|
+
produce(
|
|
1489
|
+
channel_id: bigint,
|
|
1490
|
+
request: StoreShareRequestMessage,
|
|
1491
|
+
shared_key: Uint8Array,
|
|
1492
|
+
): SharingResponseProduceResult;
|
|
1493
|
+
extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { response: StoreShareResponseMessage };
|
|
1494
|
+
process(version: number, response: StoreShareResponseMessage): void;
|
|
1495
|
+
};
|
|
1496
|
+
};
|
|
1497
|
+
unpairing: {
|
|
1498
|
+
request: {
|
|
1499
|
+
produce(
|
|
1500
|
+
channel_id: bigint,
|
|
1501
|
+
memo: string,
|
|
1502
|
+
shared_key: Uint8Array,
|
|
1503
|
+
/** See `discovery.request.produce.reply_to`. */
|
|
1504
|
+
reply_to?: TransportProtocol | null,
|
|
1505
|
+
): ProduceResult;
|
|
1506
|
+
extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { request: UnpairRequestMessage };
|
|
1507
|
+
};
|
|
1508
|
+
response: {
|
|
1509
|
+
produce(channel_id: bigint, shared_key: Uint8Array): ProduceResult;
|
|
1510
|
+
extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { response: UnpairResponseMessage };
|
|
1511
|
+
process(response: UnpairResponseMessage): UnpairingProcessResult;
|
|
1512
|
+
};
|
|
1513
|
+
};
|
|
1514
|
+
verification: {
|
|
1515
|
+
request: {
|
|
1516
|
+
produce(
|
|
1517
|
+
channel_id: bigint,
|
|
1518
|
+
secret_id: bigint,
|
|
1519
|
+
version: number,
|
|
1520
|
+
shared_key: Uint8Array,
|
|
1521
|
+
/** See `discovery.request.produce.reply_to`. */
|
|
1522
|
+
reply_to?: TransportProtocol | null,
|
|
1523
|
+
): ProduceResult;
|
|
1524
|
+
extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { request: VerifyShareRequestMessage };
|
|
1525
|
+
};
|
|
1526
|
+
response: {
|
|
1527
|
+
produce(
|
|
1528
|
+
channel_id: bigint,
|
|
1529
|
+
request: VerifyShareRequestMessage,
|
|
1530
|
+
shared_key: Uint8Array,
|
|
1531
|
+
share_content: Uint8Array,
|
|
1532
|
+
): ProduceResult;
|
|
1533
|
+
extract(envelope_bytes: Uint8Array, shared_key: Uint8Array): { response: VerifyShareResponseMessage };
|
|
1534
|
+
|
|
1535
|
+
/** `request` must be the request the owner previously produced
|
|
1536
|
+
* for this challenge (kept by the caller in a per-channel
|
|
1537
|
+
* pending-verification map). Responses whose
|
|
1538
|
+
* `(nonce, secret_id, version)` triple doesn't match are
|
|
1539
|
+
* rejected — that's the anti-replay gate. */
|
|
1540
|
+
process(
|
|
1541
|
+
request: VerifyShareRequestMessage,
|
|
1542
|
+
response: VerifyShareResponseMessage,
|
|
1543
|
+
share_content: Uint8Array,
|
|
1544
|
+
): boolean;
|
|
1545
|
+
};
|
|
1546
|
+
};
|
|
1547
|
+
};
|