@bcts/gstp 1.0.0-alpha.14

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/src/error.ts ADDED
@@ -0,0 +1,145 @@
1
+ /**
2
+ * GSTP Error Types
3
+ *
4
+ * Error types returned when operating on GSTP messages.
5
+ * Ported from gstp-rust/src/error.rs
6
+ */
7
+
8
+ /**
9
+ * Error codes for GSTP operations.
10
+ */
11
+ export enum GstpErrorCode {
12
+ /** Sender must have an encryption key. */
13
+ SENDER_MISSING_ENCRYPTION_KEY = "SENDER_MISSING_ENCRYPTION_KEY",
14
+
15
+ /** Recipient must have an encryption key. */
16
+ RECIPIENT_MISSING_ENCRYPTION_KEY = "RECIPIENT_MISSING_ENCRYPTION_KEY",
17
+
18
+ /** Sender must have a verification key. */
19
+ SENDER_MISSING_VERIFICATION_KEY = "SENDER_MISSING_VERIFICATION_KEY",
20
+
21
+ /** Continuation has expired. */
22
+ CONTINUATION_EXPIRED = "CONTINUATION_EXPIRED",
23
+
24
+ /** Continuation ID is invalid. */
25
+ CONTINUATION_ID_INVALID = "CONTINUATION_ID_INVALID",
26
+
27
+ /** Peer continuation must be encrypted. */
28
+ PEER_CONTINUATION_NOT_ENCRYPTED = "PEER_CONTINUATION_NOT_ENCRYPTED",
29
+
30
+ /** Requests must contain a peer continuation. */
31
+ MISSING_PEER_CONTINUATION = "MISSING_PEER_CONTINUATION",
32
+
33
+ /** Error from envelope operations. */
34
+ ENVELOPE = "ENVELOPE",
35
+
36
+ /** Error from XID operations. */
37
+ XID = "XID",
38
+ }
39
+
40
+ /**
41
+ * Error class for GSTP operations.
42
+ *
43
+ * Provides specific error types that can occur during GSTP message
44
+ * creation, sealing, and parsing operations.
45
+ */
46
+ export class GstpError extends Error {
47
+ readonly code: GstpErrorCode;
48
+ declare readonly cause?: Error;
49
+
50
+ constructor(code: GstpErrorCode, message: string, cause?: Error) {
51
+ super(message);
52
+ this.name = "GstpError";
53
+ this.code = code;
54
+ if (cause !== undefined) {
55
+ this.cause = cause;
56
+ }
57
+
58
+ // Maintains proper stack trace for where our error was thrown (only available on V8)
59
+ if ("captureStackTrace" in Error) {
60
+ (
61
+ Error as {
62
+ captureStackTrace(target: object, constructor: typeof GstpError): void;
63
+ }
64
+ ).captureStackTrace(this, GstpError);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Returned when the sender is missing an encryption key.
70
+ */
71
+ static senderMissingEncryptionKey(): GstpError {
72
+ return new GstpError(
73
+ GstpErrorCode.SENDER_MISSING_ENCRYPTION_KEY,
74
+ "sender must have an encryption key",
75
+ );
76
+ }
77
+
78
+ /**
79
+ * Returned when the recipient is missing an encryption key.
80
+ */
81
+ static recipientMissingEncryptionKey(): GstpError {
82
+ return new GstpError(
83
+ GstpErrorCode.RECIPIENT_MISSING_ENCRYPTION_KEY,
84
+ "recipient must have an encryption key",
85
+ );
86
+ }
87
+
88
+ /**
89
+ * Returned when the sender is missing a verification key.
90
+ */
91
+ static senderMissingVerificationKey(): GstpError {
92
+ return new GstpError(
93
+ GstpErrorCode.SENDER_MISSING_VERIFICATION_KEY,
94
+ "sender must have a verification key",
95
+ );
96
+ }
97
+
98
+ /**
99
+ * Returned when the continuation has expired.
100
+ */
101
+ static continuationExpired(): GstpError {
102
+ return new GstpError(GstpErrorCode.CONTINUATION_EXPIRED, "continuation expired");
103
+ }
104
+
105
+ /**
106
+ * Returned when the continuation ID is invalid.
107
+ */
108
+ static continuationIdInvalid(): GstpError {
109
+ return new GstpError(GstpErrorCode.CONTINUATION_ID_INVALID, "continuation ID invalid");
110
+ }
111
+
112
+ /**
113
+ * Returned when the peer continuation is not encrypted.
114
+ */
115
+ static peerContinuationNotEncrypted(): GstpError {
116
+ return new GstpError(
117
+ GstpErrorCode.PEER_CONTINUATION_NOT_ENCRYPTED,
118
+ "peer continuation must be encrypted",
119
+ );
120
+ }
121
+
122
+ /**
123
+ * Returned when a request is missing the peer continuation.
124
+ */
125
+ static missingPeerContinuation(): GstpError {
126
+ return new GstpError(
127
+ GstpErrorCode.MISSING_PEER_CONTINUATION,
128
+ "requests must contain a peer continuation",
129
+ );
130
+ }
131
+
132
+ /**
133
+ * Envelope error wrapper.
134
+ */
135
+ static envelope(cause?: Error): GstpError {
136
+ return new GstpError(GstpErrorCode.ENVELOPE, "envelope error", cause);
137
+ }
138
+
139
+ /**
140
+ * XID error wrapper.
141
+ */
142
+ static xid(cause?: Error): GstpError {
143
+ return new GstpError(GstpErrorCode.XID, "XID error", cause);
144
+ }
145
+ }
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * GSTP - Gordian Sealed Transaction Protocol
3
+ *
4
+ * A secure, authenticated, transport-agnostic data exchange protocol with
5
+ * distributed state management via Encrypted State Continuations (ESC).
6
+ *
7
+ * This is a 1:1 port of the Rust gstp library, maintaining the same
8
+ * API structure and functionality.
9
+ *
10
+ * @module gstp
11
+ */
12
+
13
+ // Error types
14
+ export { GstpError, GstpErrorCode } from "./error";
15
+
16
+ // Core types
17
+ export { Continuation } from "./continuation";
18
+
19
+ // Sealed message types
20
+ export { SealedRequest, type SealedRequestBehavior } from "./sealed-request";
21
+
22
+ export { SealedResponse, type SealedResponseBehavior } from "./sealed-response";
23
+
24
+ export { SealedEvent, type SealedEventBehavior } from "./sealed-event";
25
+
26
+ // Prelude for convenient imports
27
+ export * as prelude from "./prelude";
28
+
29
+ // Version information (matches gstp-rust v0.13.0)
30
+ export const VERSION = "0.13.0";
package/src/prelude.ts ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * GSTP Prelude - Convenient re-exports for common usage
3
+ *
4
+ * Import from this module for a curated set of commonly used types:
5
+ *
6
+ * ```typescript
7
+ * import { SealedRequest, SealedResponse, Continuation } from '@bcts/gstp/prelude';
8
+ * ```
9
+ *
10
+ * Ported from gstp-rust/src/prelude.rs
11
+ */
12
+
13
+ // Error types
14
+ export { GstpError, GstpErrorCode } from "./error";
15
+
16
+ // Core types
17
+ export { Continuation } from "./continuation";
18
+
19
+ // Sealed message types
20
+ export { SealedRequest, type SealedRequestBehavior } from "./sealed-request";
21
+
22
+ export { SealedResponse, type SealedResponseBehavior } from "./sealed-response";
23
+
24
+ export { SealedEvent, type SealedEventBehavior } from "./sealed-event";
@@ -0,0 +1,479 @@
1
+ /**
2
+ * SealedEvent - Sealed event messages for GSTP
3
+ *
4
+ * A SealedEvent wraps an Event with sender information and state
5
+ * continuations for secure, authenticated event messages.
6
+ *
7
+ * Unlike SealedRequest/SealedResponse which form a pair, SealedEvent
8
+ * is a standalone message for broadcasting information, logging,
9
+ * or publishing notifications.
10
+ *
11
+ * Ported from gstp-rust/src/sealed_event.rs
12
+ */
13
+
14
+ import type { ARID, PrivateKeys, Signer } from "@bcts/components";
15
+ import { Envelope, Event, type EnvelopeEncodableValue } from "@bcts/envelope";
16
+ import { SENDER, SENDER_CONTINUATION, RECIPIENT_CONTINUATION } from "@bcts/known-values";
17
+ import { XIDDocument } from "@bcts/xid";
18
+ import { Continuation } from "./continuation";
19
+ import { GstpError } from "./error";
20
+
21
+ /**
22
+ * Interface that defines the behavior of a sealed event.
23
+ *
24
+ * Extends EventBehavior with additional methods for managing
25
+ * sender information and state continuations.
26
+ */
27
+ export interface SealedEventBehavior<T extends EnvelopeEncodableValue> {
28
+ /**
29
+ * Adds state to the event that the receiver must return in the response.
30
+ */
31
+ withState(state: EnvelopeEncodableValue): SealedEvent<T>;
32
+
33
+ /**
34
+ * Adds optional state to the event.
35
+ */
36
+ withOptionalState(state: EnvelopeEncodableValue | undefined): SealedEvent<T>;
37
+
38
+ /**
39
+ * Adds a continuation previously received from the recipient.
40
+ */
41
+ withPeerContinuation(peerContinuation: Envelope): SealedEvent<T>;
42
+
43
+ /**
44
+ * Adds an optional continuation previously received from the recipient.
45
+ */
46
+ withOptionalPeerContinuation(peerContinuation: Envelope | undefined): SealedEvent<T>;
47
+
48
+ /**
49
+ * Returns the underlying event.
50
+ */
51
+ event(): Event<T>;
52
+
53
+ /**
54
+ * Returns the sender of the event.
55
+ */
56
+ sender(): XIDDocument;
57
+
58
+ /**
59
+ * Returns the state to be sent to the recipient.
60
+ */
61
+ state(): Envelope | undefined;
62
+
63
+ /**
64
+ * Returns the continuation received from the recipient.
65
+ */
66
+ peerContinuation(): Envelope | undefined;
67
+ }
68
+
69
+ /**
70
+ * A sealed event that combines an Event with sender information and
71
+ * state continuations for secure communication.
72
+ *
73
+ * @typeParam T - The type of content this event carries
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * import { SealedEvent, ARID } from '@bcts/gstp';
78
+ * import { XIDDocument } from '@bcts/xid';
79
+ *
80
+ * // Create sender XID document
81
+ * const sender = XIDDocument.new();
82
+ * const eventId = ARID.new();
83
+ *
84
+ * // Create a sealed event
85
+ * const event = SealedEvent.new("System notification", eventId, sender)
86
+ * .withNote("Status update")
87
+ * .withDate(new Date());
88
+ *
89
+ * // Convert to sealed envelope
90
+ * const envelope = event.toEnvelope(
91
+ * new Date(Date.now() + 60000), // Valid for 60 seconds
92
+ * senderPrivateKey,
93
+ * recipientXIDDocument
94
+ * );
95
+ * ```
96
+ */
97
+ export class SealedEvent<T extends EnvelopeEncodableValue> implements SealedEventBehavior<T> {
98
+ private _event: Event<T>;
99
+ private readonly _sender: XIDDocument;
100
+ private _state: Envelope | undefined;
101
+ private _peerContinuation: Envelope | undefined;
102
+
103
+ private constructor(
104
+ event: Event<T>,
105
+ sender: XIDDocument,
106
+ state?: Envelope,
107
+ peerContinuation?: Envelope,
108
+ ) {
109
+ this._event = event;
110
+ this._sender = sender;
111
+ this._state = state;
112
+ this._peerContinuation = peerContinuation;
113
+ }
114
+
115
+ /**
116
+ * Creates a new sealed event with the given content, ID, and sender.
117
+ *
118
+ * @param content - The content of the event
119
+ * @param id - The event ID
120
+ * @param sender - The sender's XID document
121
+ */
122
+ static new<T extends EnvelopeEncodableValue>(
123
+ content: T,
124
+ id: ARID,
125
+ sender: XIDDocument,
126
+ ): SealedEvent<T> {
127
+ return new SealedEvent(Event.new(content, id), sender);
128
+ }
129
+
130
+ // ============================================================================
131
+ // EventBehavior implementation
132
+ // ============================================================================
133
+
134
+ /**
135
+ * Adds a note to the event.
136
+ */
137
+ withNote(note: string): SealedEvent<T> {
138
+ this._event = this._event.withNote(note);
139
+ return this;
140
+ }
141
+
142
+ /**
143
+ * Adds a date to the event.
144
+ */
145
+ withDate(date: Date): SealedEvent<T> {
146
+ this._event = this._event.withDate(date);
147
+ return this;
148
+ }
149
+
150
+ /**
151
+ * Returns the content of the event.
152
+ */
153
+ content(): T {
154
+ return this._event.content();
155
+ }
156
+
157
+ /**
158
+ * Returns the ID of the event.
159
+ */
160
+ id(): ARID {
161
+ return this._event.id();
162
+ }
163
+
164
+ /**
165
+ * Returns the note of the event.
166
+ */
167
+ note(): string {
168
+ return this._event.note();
169
+ }
170
+
171
+ /**
172
+ * Returns the date of the event.
173
+ */
174
+ date(): Date | undefined {
175
+ return this._event.date();
176
+ }
177
+
178
+ // ============================================================================
179
+ // SealedEventBehavior implementation
180
+ // ============================================================================
181
+
182
+ /**
183
+ * Adds state to the event that the receiver must return in the response.
184
+ */
185
+ withState(state: EnvelopeEncodableValue): SealedEvent<T> {
186
+ this._state = Envelope.new(state);
187
+ return this;
188
+ }
189
+
190
+ /**
191
+ * Adds optional state to the event.
192
+ */
193
+ withOptionalState(state: EnvelopeEncodableValue | undefined): SealedEvent<T> {
194
+ if (state !== undefined) {
195
+ return this.withState(state);
196
+ }
197
+ this._state = undefined;
198
+ return this;
199
+ }
200
+
201
+ /**
202
+ * Adds a continuation previously received from the recipient.
203
+ */
204
+ withPeerContinuation(peerContinuation: Envelope): SealedEvent<T> {
205
+ this._peerContinuation = peerContinuation;
206
+ return this;
207
+ }
208
+
209
+ /**
210
+ * Adds an optional continuation previously received from the recipient.
211
+ */
212
+ withOptionalPeerContinuation(peerContinuation: Envelope | undefined): SealedEvent<T> {
213
+ this._peerContinuation = peerContinuation;
214
+ return this;
215
+ }
216
+
217
+ /**
218
+ * Returns the underlying event.
219
+ */
220
+ event(): Event<T> {
221
+ return this._event;
222
+ }
223
+
224
+ /**
225
+ * Returns the sender of the event.
226
+ */
227
+ sender(): XIDDocument {
228
+ return this._sender;
229
+ }
230
+
231
+ /**
232
+ * Returns the state to be sent to the recipient.
233
+ */
234
+ state(): Envelope | undefined {
235
+ return this._state;
236
+ }
237
+
238
+ /**
239
+ * Returns the continuation received from the recipient.
240
+ */
241
+ peerContinuation(): Envelope | undefined {
242
+ return this._peerContinuation;
243
+ }
244
+
245
+ // ============================================================================
246
+ // Conversion methods
247
+ // ============================================================================
248
+
249
+ /**
250
+ * Converts the sealed event to an Event.
251
+ */
252
+ toEvent(): Event<T> {
253
+ return this._event;
254
+ }
255
+
256
+ // ============================================================================
257
+ // Envelope methods
258
+ // ============================================================================
259
+
260
+ /**
261
+ * Creates an envelope that can be decrypted by zero or one recipient.
262
+ *
263
+ * @param validUntil - Optional expiration date for the continuation
264
+ * @param signer - Optional signer for the envelope
265
+ * @param recipient - Optional recipient XID document for encryption
266
+ * @returns The sealed event as an envelope
267
+ */
268
+ toEnvelope(validUntil?: Date, signer?: Signer, recipient?: XIDDocument): Envelope {
269
+ const recipients: XIDDocument[] = recipient !== undefined ? [recipient] : [];
270
+ return this.toEnvelopeForRecipients(validUntil, signer, recipients);
271
+ }
272
+
273
+ /**
274
+ * Creates an envelope that can be decrypted by zero or more recipients.
275
+ *
276
+ * @param validUntil - Optional expiration date for the continuation
277
+ * @param signer - Optional signer for the envelope
278
+ * @param recipients - Array of recipient XID documents for encryption
279
+ * @returns The sealed event as an envelope
280
+ */
281
+ toEnvelopeForRecipients(
282
+ validUntil?: Date,
283
+ signer?: Signer,
284
+ recipients?: XIDDocument[],
285
+ ): Envelope {
286
+ // Get sender's encryption key (from inception key)
287
+ const senderInceptionKey = this._sender.inceptionKey();
288
+ const senderEncryptionKey = senderInceptionKey?.publicKeys()?.encapsulationPublicKey();
289
+ if (senderEncryptionKey === undefined) {
290
+ throw GstpError.senderMissingEncryptionKey();
291
+ }
292
+
293
+ // Build sender continuation
294
+ let senderContinuation: Envelope | undefined;
295
+ if (this._state !== undefined) {
296
+ // If state is present, create continuation with state and optional valid_until
297
+ const continuation = new Continuation(this._state, undefined, validUntil);
298
+ senderContinuation = continuation.toEnvelope(senderEncryptionKey);
299
+ } else if (validUntil !== undefined) {
300
+ // If only valid_until is present (no state), create continuation with null state
301
+ const continuation = new Continuation(Envelope.new(null), undefined, validUntil);
302
+ senderContinuation = continuation.toEnvelope(senderEncryptionKey);
303
+ }
304
+
305
+ // Build the event envelope
306
+ let result = this._event.toEnvelope();
307
+
308
+ // Add sender assertion
309
+ result = result.addAssertion(SENDER, this._sender.toEnvelope());
310
+
311
+ // Add sender continuation if present
312
+ if (senderContinuation !== undefined) {
313
+ result = result.addAssertion(SENDER_CONTINUATION, senderContinuation);
314
+ }
315
+
316
+ // Add peer continuation if present
317
+ if (this._peerContinuation !== undefined) {
318
+ result = result.addAssertion(RECIPIENT_CONTINUATION, this._peerContinuation);
319
+ }
320
+
321
+ // Sign if signer provided (sign() wraps first, then adds signature)
322
+ if (signer !== undefined) {
323
+ result = result.sign(signer);
324
+ }
325
+
326
+ // Encrypt to recipients if provided
327
+ if (recipients !== undefined && recipients.length > 0) {
328
+ const recipientKeys = recipients.map((recipient) => {
329
+ const key = recipient.encryptionKey();
330
+ if (key === undefined) {
331
+ throw GstpError.recipientMissingEncryptionKey();
332
+ }
333
+ return key;
334
+ });
335
+
336
+ result = result.wrap().encryptSubjectToRecipients(recipientKeys);
337
+ }
338
+
339
+ return result;
340
+ }
341
+
342
+ /**
343
+ * Parses a sealed event from an encrypted envelope.
344
+ *
345
+ * @param encryptedEnvelope - The encrypted envelope to parse
346
+ * @param expectedId - Optional expected event ID for validation
347
+ * @param now - Optional current time for continuation validation
348
+ * @param recipientPrivateKey - The recipient's private keys for decryption
349
+ * @param contentExtractor - Function to extract content from envelope
350
+ * @returns The parsed sealed event
351
+ */
352
+ static tryFromEnvelope<T extends EnvelopeEncodableValue>(
353
+ encryptedEnvelope: Envelope,
354
+ expectedId: ARID | undefined,
355
+ now: Date | undefined,
356
+ recipientPrivateKey: PrivateKeys,
357
+ contentExtractor?: (env: Envelope) => T,
358
+ ): SealedEvent<T> {
359
+ // Decrypt the envelope
360
+ let signedEnvelope: Envelope;
361
+ try {
362
+ signedEnvelope = encryptedEnvelope.decryptToRecipient(recipientPrivateKey);
363
+ } catch (e) {
364
+ throw GstpError.envelope(e instanceof Error ? e : new Error(String(e)));
365
+ }
366
+
367
+ // Extract sender from the unwrapped envelope
368
+ let sender: XIDDocument;
369
+ try {
370
+ const unwrapped = signedEnvelope.tryUnwrap();
371
+ const senderEnvelope = unwrapped.objectForPredicate(SENDER);
372
+ if (senderEnvelope === undefined) {
373
+ throw new Error("Missing sender");
374
+ }
375
+ sender = XIDDocument.fromEnvelope(senderEnvelope);
376
+ } catch (e) {
377
+ throw GstpError.xid(e instanceof Error ? e : new Error(String(e)));
378
+ }
379
+
380
+ // Get sender's verification key and verify signature (from inception key)
381
+ const senderInceptionKey = sender.inceptionKey();
382
+ const senderVerificationKey = senderInceptionKey?.publicKeys()?.signingPublicKey();
383
+ if (senderVerificationKey === undefined) {
384
+ throw GstpError.senderMissingVerificationKey();
385
+ }
386
+
387
+ let eventEnvelope: Envelope;
388
+ try {
389
+ // verify() both verifies the signature AND unwraps the envelope
390
+ eventEnvelope = signedEnvelope.verify(senderVerificationKey);
391
+ } catch (e) {
392
+ throw GstpError.envelope(e instanceof Error ? e : new Error(String(e)));
393
+ }
394
+
395
+ // Get peer continuation (sender_continuation from the event)
396
+ const peerContinuation = eventEnvelope.optionalObjectForPredicate(SENDER_CONTINUATION);
397
+ if (peerContinuation !== undefined) {
398
+ // Verify peer continuation is encrypted
399
+ if (!peerContinuation.subject().isEncrypted()) {
400
+ throw GstpError.peerContinuationNotEncrypted();
401
+ }
402
+ }
403
+
404
+ // Get and decrypt our continuation (recipient_continuation)
405
+ const encryptedContinuation = eventEnvelope.optionalObjectForPredicate(RECIPIENT_CONTINUATION);
406
+ let state: Envelope | undefined;
407
+ if (encryptedContinuation !== undefined) {
408
+ const continuation = Continuation.tryFromEnvelope(
409
+ encryptedContinuation,
410
+ expectedId,
411
+ now,
412
+ recipientPrivateKey,
413
+ );
414
+ state = continuation.state();
415
+ }
416
+
417
+ // Parse the event
418
+ let event: Event<T>;
419
+ try {
420
+ // Use the content extractor if provided, otherwise use default string extraction
421
+ if (contentExtractor !== undefined) {
422
+ event = Event.fromEnvelope(eventEnvelope, contentExtractor);
423
+ } else {
424
+ // Default to string extraction
425
+ event = Event.stringFromEnvelope(eventEnvelope) as unknown as Event<T>;
426
+ }
427
+ } catch (e) {
428
+ throw GstpError.envelope(e instanceof Error ? e : new Error(String(e)));
429
+ }
430
+
431
+ return new SealedEvent(event, sender, state, peerContinuation);
432
+ }
433
+
434
+ // ============================================================================
435
+ // Display methods
436
+ // ============================================================================
437
+
438
+ /**
439
+ * Returns a string representation of the sealed event.
440
+ */
441
+ toString(): string {
442
+ const stateStr = this._state !== undefined ? this._state.formatFlat() : "None";
443
+ const peerStr = this._peerContinuation !== undefined ? "Some" : "None";
444
+ return `SealedEvent(${this._event.summary()}, state: ${stateStr}, peer_continuation: ${peerStr})`;
445
+ }
446
+
447
+ /**
448
+ * Checks equality with another sealed event.
449
+ */
450
+ equals(other: SealedEvent<T>): boolean {
451
+ if (!this._event.equals(other._event)) {
452
+ return false;
453
+ }
454
+ if (!this._sender.xid().equals(other._sender.xid())) {
455
+ return false;
456
+ }
457
+ // Compare state
458
+ if (this._state === undefined && other._state === undefined) {
459
+ // Both undefined, equal
460
+ } else if (this._state !== undefined && other._state !== undefined) {
461
+ if (!this._state.digest().equals(other._state.digest())) {
462
+ return false;
463
+ }
464
+ } else {
465
+ return false;
466
+ }
467
+ // Compare peer continuation
468
+ if (this._peerContinuation === undefined && other._peerContinuation === undefined) {
469
+ // Both undefined, equal
470
+ } else if (this._peerContinuation !== undefined && other._peerContinuation !== undefined) {
471
+ if (!this._peerContinuation.digest().equals(other._peerContinuation.digest())) {
472
+ return false;
473
+ }
474
+ } else {
475
+ return false;
476
+ }
477
+ return true;
478
+ }
479
+ }