@bsv/sdk 1.3.28 → 1.3.29

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/auth/Peer.ts CHANGED
@@ -20,6 +20,8 @@ const AUTH_VERSION = '0.1'
20
20
  * Represents a peer capable of performing mutual authentication.
21
21
  * Manages sessions, handles authentication handshakes, certificate requests and responses,
22
22
  * and sending and receiving general messages over a transport layer.
23
+ *
24
+ * This version supports multiple concurrent sessions per peer identityKey.
23
25
  */
24
26
  export class Peer {
25
27
  public sessionManager: SessionManager
@@ -55,7 +57,7 @@ export class Peer {
55
57
  // Whether to auto-persist the session with the last-interacted-with peer
56
58
  private readonly autoPersistLastSession: boolean = true
57
59
 
58
- // Last-interacted-with peer identity key
60
+ // Last-interacted-with peer identity key (if the user calls toPeer with no identityKey)
59
61
  private lastInteractedWithPeer: string | undefined
60
62
 
61
63
  /**
@@ -81,7 +83,8 @@ export class Peer {
81
83
  types: {}
82
84
  }
83
85
  this.transport.onData(this.handleIncomingMessage.bind(this)).catch(console.error)
84
- this.sessionManager = sessionManager != null ? sessionManager : new SessionManager()
86
+ this.sessionManager =
87
+ sessionManager != null ? sessionManager : new SessionManager()
85
88
  if (autoPersistLastSession === false) {
86
89
  this.autoPersistLastSession = false
87
90
  } else {
@@ -93,7 +96,8 @@ export class Peer {
93
96
  * Sends a general message to a peer, and initiates a handshake if necessary.
94
97
  *
95
98
  * @param {number[]} message - The message payload to send.
96
- * @param {string} [identityKey] - The identity public key of the peer. If not provided, a handshake will be initiated.
99
+ * @param {string} [identityKey] - The identity public key of the peer. If not provided, uses lastInteractedWithPeer (if any).
100
+ * @param {number} [maxWaitTime] - optional max wait time in ms
97
101
  * @returns {Promise<void>}
98
102
  * @throws Will throw an error if the message fails to send.
99
103
  */
@@ -104,16 +108,13 @@ export class Peer {
104
108
  ): Promise<void> {
105
109
  if (
106
110
  this.autoPersistLastSession &&
107
- this.lastInteractedWithPeer !== undefined &&
108
- this.lastInteractedWithPeer !== null &&
111
+ typeof this.lastInteractedWithPeer === 'string' &&
109
112
  typeof identityKey !== 'string'
110
113
  ) {
111
114
  identityKey = this.lastInteractedWithPeer
112
115
  }
113
- const peerSession = await this.getAuthenticatedSession(
114
- identityKey,
115
- maxWaitTime
116
- )
116
+
117
+ const peerSession = await this.getAuthenticatedSession(identityKey, maxWaitTime)
117
118
 
118
119
  // Prepare the general message
119
120
  const requestNonce = Utils.toBase64(Random(32))
@@ -135,11 +136,15 @@ export class Peer {
135
136
  signature
136
137
  }
137
138
 
139
+ peerSession.lastUpdate = Date.now()
140
+ this.sessionManager.updateSession(peerSession)
141
+
138
142
  try {
139
143
  await this.transport.send(generalMessage)
140
- } catch (error) {
144
+ } catch (error: any) {
141
145
  const e = new Error(
142
- `Failed to send message to peer ${peerSession.peerIdentityKey ?? 'unknown'}: ${String(error.message)}`
146
+ `Failed to send message to peer ${peerSession.peerIdentityKey ?? 'unknown'
147
+ }: ${String(error.message)}`
143
148
  )
144
149
  e.stack = error.stack
145
150
  throw e
@@ -152,7 +157,7 @@ export class Peer {
152
157
  * an initial handshake or message has been exchanged.
153
158
  *
154
159
  * @param {RequestedCertificateSet} certificatesToRequest - Specifies the certifiers and types of certificates required from the peer.
155
- * @param {string} [identityKey] - The identity public key of the peer. If not provided, the current session identity is used.
160
+ * @param {string} [identityKey] - The identity public key of the peer. If not provided, the current or last session identity is used.
156
161
  * @param {number} [maxWaitTime=10000] - Maximum time in milliseconds to wait for the peer session to be authenticated.
157
162
  * @returns {Promise<void>} Resolves if the certificate request message is successfully sent.
158
163
  * @throws Will throw an error if the peer session is not authenticated or if sending the request fails.
@@ -162,12 +167,20 @@ export class Peer {
162
167
  identityKey?: string,
163
168
  maxWaitTime = 10000
164
169
  ): Promise<void> {
170
+ if (
171
+ this.autoPersistLastSession &&
172
+ typeof this.lastInteractedWithPeer === 'string' &&
173
+ typeof identityKey !== 'string'
174
+ ) {
175
+ identityKey = this.lastInteractedWithPeer
176
+ }
177
+
165
178
  const peerSession = await this.getAuthenticatedSession(
166
179
  identityKey,
167
180
  maxWaitTime
168
181
  )
169
182
 
170
- // Prepare the general message
183
+ // Prepare the message
171
184
  const requestNonce = Utils.toBase64(Random(32))
172
185
  const { signature } = await this.wallet.createSignature({
173
186
  data: Utils.toArray(JSON.stringify(certificatesToRequest), 'utf8'),
@@ -188,11 +201,16 @@ export class Peer {
188
201
  signature
189
202
  }
190
203
 
204
+ // Update last-used timestamp
205
+ peerSession.lastUpdate = Date.now()
206
+ this.sessionManager.updateSession(peerSession)
207
+
191
208
  try {
192
209
  await this.transport.send(certRequestMessage)
193
210
  } catch (error: any) {
194
211
  throw new Error(
195
- `Failed to send certificate request message to peer ${peerSession.peerIdentityKey ?? 'unknown'}: ${String(error.message)}`
212
+ `Failed to send certificate request message to peer ${peerSession.peerIdentityKey ?? 'unknown'
213
+ }: ${String(error.message)}`
196
214
  )
197
215
  }
198
216
  }
@@ -201,12 +219,13 @@ export class Peer {
201
219
  * Retrieves an authenticated session for a given peer identity. If no session exists
202
220
  * or the session is not authenticated, initiates a handshake to create or authenticate the session.
203
221
  *
204
- * @param {string} [identityKey] - The identity public key of the peer. If provided, it attempts
205
- * to retrieve an existing session associated with this identity.
206
- * @param {number} [maxWaitTime] - The maximum time in milliseconds to wait for the handshake
207
- * to complete if a new session is required. Defaults to a pre-defined timeout if not specified.
222
+ * - If `identityKey` is provided, we look up any existing session for that identity key.
223
+ * - If none is found or not authenticated, we do a new handshake.
224
+ * - If `identityKey` is not provided, but we have a `lastInteractedWithPeer`, we try that key.
225
+ *
226
+ * @param {string} [identityKey] - The identity public key of the peer.
227
+ * @param {number} [maxWaitTime] - The maximum time in milliseconds to wait for the handshake.
208
228
  * @returns {Promise<PeerSession>} - A promise that resolves with an authenticated `PeerSession`.
209
- * @throws {Error} - Throws an error if the transport is not connected or if the handshake fails.
210
229
  */
211
230
  async getAuthenticatedSession (
212
231
  identityKey?: string,
@@ -216,16 +235,18 @@ export class Peer {
216
235
  throw new Error('Peer transport is not connected!')
217
236
  }
218
237
 
219
- let peerSession = identityKey !== undefined && identityKey !== ''
220
- ? this.sessionManager.getSession(identityKey)
221
- : undefined
222
- if (peerSession === undefined || !peerSession?.isAuthenticated) {
223
- const sessionNonce = await this.initiateHandshake(
224
- identityKey,
225
- maxWaitTime
226
- )
227
- peerSession = this.sessionManager.getSession(identityKey !== undefined && identityKey !== '' ? identityKey : sessionNonce)
228
- if (peerSession === undefined || !peerSession.isAuthenticated) {
238
+ let peerSession: PeerSession | undefined
239
+ if (typeof identityKey === 'string') {
240
+ peerSession = this.sessionManager.getSession(identityKey)
241
+ }
242
+
243
+ // If that session doesn't exist or isn't authenticated, initiate handshake
244
+ if ((peerSession == null) || !peerSession.isAuthenticated) {
245
+ // This will create a brand-new session
246
+ const sessionNonce = await this.initiateHandshake(identityKey, maxWaitTime)
247
+ // Now retrieve it by the sessionNonce
248
+ peerSession = this.sessionManager.getSession(sessionNonce)
249
+ if ((peerSession == null) || !peerSession.isAuthenticated) {
229
250
  throw new Error('Unable to establish mutual authentication with peer!')
230
251
  }
231
252
  }
@@ -259,7 +280,7 @@ export class Peer {
259
280
  /**
260
281
  * Registers a callback to listen for certificates received from peers.
261
282
  *
262
- * @param {(certs: VerifiableCertificate[]) => void} callback - The function to call when certificates are received.
283
+ * @param {(senderPublicKey: string, certs: VerifiableCertificate[]) => void} callback - The function to call when certificates are received.
263
284
  * @returns {number} The ID of the callback listener.
264
285
  */
265
286
  listenForCertificatesReceived (
@@ -310,6 +331,7 @@ export class Peer {
310
331
  *
311
332
  * @private
312
333
  * @param {string} [identityKey] - The identity public key of the peer.
334
+ * @param {number} [maxWaitTime=10000] - how long to wait for handshake
313
335
  * @returns {Promise<string>} A promise that resolves to the session nonce.
314
336
  */
315
337
  private async initiateHandshake (
@@ -317,10 +339,14 @@ export class Peer {
317
339
  maxWaitTime = 10000
318
340
  ): Promise<string> {
319
341
  const sessionNonce = await createNonce(this.wallet) // Initial request nonce
342
+
343
+ // Create the preliminary session (not yet authenticated)
344
+ const now = Date.now()
320
345
  this.sessionManager.addSession({
321
346
  isAuthenticated: false,
322
347
  sessionNonce,
323
- peerIdentityKey: identityKey
348
+ peerIdentityKey: identityKey,
349
+ lastUpdate: now
324
350
  })
325
351
 
326
352
  const initialRequest: AuthMessage = {
@@ -347,14 +373,11 @@ export class Peer {
347
373
  maxWaitTime = 10000
348
374
  ): Promise<string> {
349
375
  return await new Promise((resolve, reject) => {
350
- const callbackID = this.listenForInitialResponse(
351
- sessionNonce,
352
- sessionNonce => {
353
- clearTimeout(timeoutHandle)
354
- this.stopListeningForInitialResponses(callbackID)
355
- resolve(sessionNonce)
356
- }
357
- )
376
+ const callbackID = this.listenForInitialResponse(sessionNonce, nonce => {
377
+ clearTimeout(timeoutHandle)
378
+ this.stopListeningForInitialResponses(callbackID)
379
+ resolve(nonce)
380
+ })
358
381
 
359
382
  const timeoutHandle = setTimeout(() => {
360
383
  this.stopListeningForInitialResponses(callbackID)
@@ -400,9 +423,9 @@ export class Peer {
400
423
  * @returns {Promise<void>}
401
424
  */
402
425
  private async handleIncomingMessage (message: AuthMessage): Promise<void> {
403
- if (message.version === undefined || message.version === '' || message.version !== AUTH_VERSION) {
426
+ if (typeof message.version !== 'string' || message.version !== AUTH_VERSION) {
404
427
  console.error(
405
- `Invalid message auth version! Received: ${message.version}, expected: ${AUTH_VERSION}`
428
+ `Invalid or unsupported message auth version! Received: ${message.version}, expected: ${AUTH_VERSION}`
406
429
  )
407
430
  return
408
431
  }
@@ -425,7 +448,9 @@ export class Peer {
425
448
  break
426
449
  default:
427
450
  console.error(
428
- `Unknown message type of ${String(message.messageType)} from ${String(message.identityKey)}`
451
+ `Unknown message type of ${String(message.messageType)} from ${String(
452
+ message.identityKey
453
+ )}`
429
454
  )
430
455
  }
431
456
  }
@@ -434,32 +459,43 @@ export class Peer {
434
459
  * Processes an initial request message from a peer.
435
460
  *
436
461
  * @param {AuthMessage} message - The incoming initial request message.
437
- * @returns {Promise<void>}
438
462
  */
439
- async processInitialRequest (message: AuthMessage): Promise<void> {
440
- if (message.identityKey === undefined || message.identityKey === '' || message.initialNonce === undefined || message.initialNonce === '') {
441
- throw new Error('Missing required fields in initialResponse message.')
463
+ private async processInitialRequest (message: AuthMessage): Promise<void> {
464
+ if (
465
+ typeof message.identityKey !== 'string' ||
466
+ typeof message.initialNonce !== 'string' ||
467
+ message.initialNonce === ''
468
+ ) {
469
+ throw new Error('Missing required fields in initialRequest message.')
442
470
  }
443
471
 
444
- // Create an initial session nonce
472
+ // Create a new sessionNonce for our side
445
473
  const sessionNonce = await createNonce(this.wallet)
474
+ const now = Date.now()
475
+
476
+ // We'll treat this as fully authenticated from *our* perspective (the responding side).
446
477
  this.sessionManager.addSession({
447
478
  isAuthenticated: true,
448
479
  sessionNonce,
449
480
  peerNonce: message.initialNonce,
450
- peerIdentityKey: message.identityKey
481
+ peerIdentityKey: message.identityKey,
482
+ lastUpdate: now
451
483
  })
452
484
 
453
- // Handle initial certificate requests
454
- let certificatesToInclude
455
- if (message.requestedCertificates !== undefined && Array.isArray(message.requestedCertificates?.certifiers) && message.requestedCertificates?.certifiers?.length > 0) {
485
+ // Possibly handle the peer's requested certs
486
+ let certificatesToInclude: VerifiableCertificate[] | undefined
487
+ if (
488
+ (message.requestedCertificates != null) &&
489
+ Array.isArray(message.requestedCertificates.certifiers) &&
490
+ message.requestedCertificates.certifiers.length > 0
491
+ ) {
456
492
  if (this.onCertificateRequestReceivedCallbacks.size > 0) {
457
- // The application wants to handle certificate requests
458
- this.onCertificateRequestReceivedCallbacks.forEach(callback => {
459
- callback(message.identityKey, message.requestedCertificates as RequestedCertificateSet)
493
+ // Let the application handle it
494
+ this.onCertificateRequestReceivedCallbacks.forEach(cb => {
495
+ cb(message.identityKey, message.requestedCertificates as RequestedCertificateSet)
460
496
  })
461
497
  } else {
462
- // Attempt to find exact matching certificates to return automatically to save round trips
498
+ // Attempt to find automatically
463
499
  certificatesToInclude = await getVerifiableCertificates(
464
500
  this.wallet,
465
501
  message.requestedCertificates,
@@ -468,7 +504,7 @@ export class Peer {
468
504
  }
469
505
  }
470
506
 
471
- // Create the signature for the message
507
+ // Create signature
472
508
  const { signature } = await this.wallet.createSignature({
473
509
  data: Utils.toArray(message.initialNonce + sessionNonce, 'base64'),
474
510
  protocolID: [2, 'auth message signature'],
@@ -488,11 +524,12 @@ export class Peer {
488
524
  signature
489
525
  }
490
526
 
491
- // For security, only set the last-interacted-with peer here if this is the first peer we've interacted with.
492
- if (this.lastInteractedWithPeer === undefined || this.lastInteractedWithPeer === null) {
527
+ // If we haven't interacted with a peer yet, store this identity as "lastInteracted"
528
+ if (this.lastInteractedWithPeer === undefined) {
493
529
  this.lastInteractedWithPeer = message.identityKey
494
530
  }
495
531
 
532
+ // Send the response
496
533
  await this.transport.send(initialResponseMessage)
497
534
  }
498
535
 
@@ -501,8 +538,7 @@ export class Peer {
501
538
  *
502
539
  * @private
503
540
  * @param {AuthMessage} message - The incoming initial response message.
504
- * @returns {Promise<void>}
505
- * @throws Will throw an error if nonce verification or signature verification fails.
541
+ * @throws Will throw an error if nonce or signature verification fails.
506
542
  */
507
543
  private async processInitialResponse (message: AuthMessage): Promise<void> {
508
544
  const validNonce = await verifyNonce(message.yourNonce as string, this.wallet)
@@ -512,17 +548,19 @@ export class Peer {
512
548
  )
513
549
  }
514
550
 
551
+ // This is the session we previously created by calling initiateHandshake
515
552
  const peerSession = this.sessionManager.getSession(message.yourNonce as string)
516
- if (peerSession === null || peerSession === undefined) {
553
+ if (peerSession == null) {
517
554
  throw new Error(`Peer session not found for peer: ${message.identityKey}`)
518
555
  }
519
556
 
520
557
  // Validate message signature
558
+ const dataToVerify = Utils.toArray(
559
+ (peerSession.sessionNonce ?? '') + (message.initialNonce ?? ''),
560
+ 'base64'
561
+ )
521
562
  const { valid } = await this.wallet.verifySignature({
522
- data: Utils.toArray(
523
- (peerSession.sessionNonce ?? '') + (message.initialNonce ?? ''),
524
- 'base64'
525
- ),
563
+ data: dataToVerify,
526
564
  signature: message.signature as number[],
527
565
  protocolID: [2, 'auth message signature'],
528
566
  keyID: `${peerSession.sessionNonce ?? ''} ${message.initialNonce ?? ''}`,
@@ -534,51 +572,49 @@ export class Peer {
534
572
  )
535
573
  }
536
574
 
537
- // After signature and nonce verification is complete, the peer is considered authenticated
538
- // Save the peer's identity key and initial nonce
539
- // This allows future requests to be linked to the same session
575
+ // Now mark the session as authenticated
540
576
  peerSession.peerNonce = message.initialNonce
541
577
  peerSession.peerIdentityKey = message.identityKey
542
578
  peerSession.isAuthenticated = true
543
- // Run update to ensure lookup is available by both peerIdentityKey and sessionNonce
579
+ peerSession.lastUpdate = Date.now()
544
580
  this.sessionManager.updateSession(peerSession)
545
581
 
546
- // Process certificates received
582
+ // If the handshake had requested certificates, validate them
547
583
  if (
548
- (this.certificatesToRequest?.certifiers?.length ?? 0) > 0 &&
549
- (message.certificates !== null && message.certificates !== undefined && message.certificates.length > 0)
584
+ this.certificatesToRequest?.certifiers?.length > 0 &&
585
+ message.certificates?.length as number > 0
550
586
  ) {
551
- await validateCertificates(
552
- this.wallet,
553
- message,
554
- this.certificatesToRequest
555
- )
587
+ await validateCertificates(this.wallet, message, this.certificatesToRequest)
556
588
 
557
- const messageCertificates = message.certificates
558
- if (message.certificates !== undefined) {
559
- this.onCertificatesReceivedCallbacks.forEach(callback =>
560
- callback(message.identityKey, messageCertificates)
561
- )
562
- }
589
+ // Notify listeners
590
+ this.onCertificatesReceivedCallbacks.forEach(cb =>
591
+ cb(message.identityKey, message.certificates as VerifiableCertificate[])
592
+ )
563
593
  }
564
594
 
595
+ // Update lastInteractedWithPeer
565
596
  this.lastInteractedWithPeer = message.identityKey
566
597
 
598
+ // Let the handshake wait-latch know we got our response
567
599
  this.onInitialResponseReceivedCallbacks.forEach(entry => {
568
- if (entry?.sessionNonce === peerSession.sessionNonce) {
600
+ if (entry.sessionNonce === peerSession.sessionNonce) {
569
601
  entry.callback(peerSession.sessionNonce)
570
602
  }
571
603
  })
572
604
 
573
- // Check if the peer requested certificates from us
574
- if (message.requestedCertificates !== undefined && Array.isArray(message.requestedCertificates?.certifiers) && message.requestedCertificates?.certifiers?.length > 0) {
605
+ // The peer might also request certificates from us
606
+ if (
607
+ (message.requestedCertificates != null) &&
608
+ Array.isArray(message.requestedCertificates.certifiers) &&
609
+ message.requestedCertificates.certifiers.length > 0
610
+ ) {
575
611
  if (this.onCertificateRequestReceivedCallbacks.size > 0) {
576
- // Application wants to handle certificate requests
577
- this.onCertificateRequestReceivedCallbacks.forEach(callback => {
578
- callback(message.identityKey, message.requestedCertificates as RequestedCertificateSet)
612
+ // Let the application handle it
613
+ this.onCertificateRequestReceivedCallbacks.forEach(cb => {
614
+ cb(message.identityKey, message.requestedCertificates as RequestedCertificateSet)
579
615
  })
580
616
  } else {
581
- // Attempt to find exact matching certificates to respond automatically and save round trips
617
+ // Attempt auto
582
618
  const verifiableCertificates = await getVerifiableCertificates(
583
619
  this.wallet,
584
620
  message.requestedCertificates,
@@ -594,11 +630,10 @@ export class Peer {
594
630
 
595
631
  /**
596
632
  * Processes an incoming certificate request message from a peer.
597
- * Verifies the nonce and signature to ensure the authenticity of the request,
598
- * then initiates a response with any requested certificates that are available.
633
+ * Verifies nonce/signature and then possibly sends a certificateResponse.
599
634
  *
600
635
  * @param {AuthMessage} message - The certificate request message received from the peer.
601
- * @throws {Error} Throws an error if nonce verification fails, or the message signature is invalid.
636
+ * @throws {Error} if nonce or signature is invalid.
602
637
  */
603
638
  private async processCertificateRequest (message: AuthMessage): Promise<void> {
604
639
  const validNonce = await verifyNonce(message.yourNonce as string, this.wallet)
@@ -608,41 +643,45 @@ export class Peer {
608
643
  )
609
644
  }
610
645
  const peerSession = this.sessionManager.getSession(message.yourNonce as string)
646
+ if (peerSession == null) {
647
+ throw new Error(`Session not found for nonce: ${message.yourNonce as string}`)
648
+ }
611
649
 
612
650
  const { valid } = await this.wallet.verifySignature({
613
- data: Utils.toArray(
614
- JSON.stringify(message.requestedCertificates),
615
- 'utf8'
616
- ),
651
+ data: Utils.toArray(JSON.stringify(message.requestedCertificates), 'utf8'),
617
652
  signature: message.signature as number[],
618
653
  protocolID: [2, 'auth message signature'],
619
- keyID: `${message.nonce ?? ''} ${peerSession?.sessionNonce ?? ''}`,
620
- counterparty: peerSession?.peerIdentityKey
654
+ keyID: `${message.nonce ?? ''} ${peerSession.sessionNonce ?? ''}`,
655
+ counterparty: peerSession.peerIdentityKey
621
656
  })
622
-
623
657
  if (!valid) {
624
658
  throw new Error(
625
- `Invalid signature in certificate request message from ${peerSession?.peerIdentityKey ?? 'unknown'}`
659
+ `Invalid signature in certificate request message from ${peerSession.peerIdentityKey as string}`
626
660
  )
627
661
  }
628
662
 
629
- if (message.requestedCertificates !== undefined && Array.isArray(message.requestedCertificates?.certifiers) && message.requestedCertificates?.certifiers?.length > 0) {
663
+ // Update usage
664
+ peerSession.lastUpdate = Date.now()
665
+ this.sessionManager.updateSession(peerSession)
666
+
667
+ if (
668
+ (message.requestedCertificates != null) &&
669
+ Array.isArray(message.requestedCertificates.certifiers) &&
670
+ message.requestedCertificates.certifiers.length > 0
671
+ ) {
630
672
  if (this.onCertificateRequestReceivedCallbacks.size > 0) {
631
- // Application wants to handle certificate requests
632
- this.onCertificateRequestReceivedCallbacks.forEach(callback => {
633
- callback(message.identityKey, message.requestedCertificates as RequestedCertificateSet)
673
+ // Let the application handle it
674
+ this.onCertificateRequestReceivedCallbacks.forEach(cb => {
675
+ cb(message.identityKey, message.requestedCertificates as RequestedCertificateSet)
634
676
  })
635
677
  } else {
636
- // Attempt to find exact matching certificates to respond automatically and save round trips
678
+ // Attempt auto
637
679
  const verifiableCertificates = await getVerifiableCertificates(
638
680
  this.wallet,
639
681
  message.requestedCertificates,
640
682
  message.identityKey
641
683
  )
642
- await this.sendCertificateResponse(
643
- message.identityKey,
644
- verifiableCertificates
645
- )
684
+ await this.sendCertificateResponse(message.identityKey, verifiableCertificates)
646
685
  }
647
686
  }
648
687
  }
@@ -651,10 +690,8 @@ export class Peer {
651
690
  * Sends a certificate response message containing the specified certificates to a peer.
652
691
  *
653
692
  * @param {string} verifierIdentityKey - The identity key of the peer requesting the certificates.
654
- * @param {VerifiableCertificate[]} certificates - The list of certificates to be included in the response.
655
- * @returns {Promise<void>} - A promise that resolves once the certificate response has been sent successfully.
656
- *
657
- * @throws {Error} Throws an error if the peer session could not be authenticated or if message signing fails.
693
+ * @param {VerifiableCertificate[]} certificates - The list of certificates to include in the response.
694
+ * @throws Will throw an error if the transport fails to send the message.
658
695
  */
659
696
  async sendCertificateResponse (
660
697
  verifierIdentityKey: string,
@@ -681,12 +718,17 @@ export class Peer {
681
718
  signature
682
719
  }
683
720
 
721
+ // Update usage
722
+ peerSession.lastUpdate = Date.now()
723
+ this.sessionManager.updateSession(peerSession)
724
+
684
725
  try {
685
726
  await this.transport.send(certificateResponse)
686
727
  } catch (error: any) {
687
728
  const errorMessage = error instanceof Error ? error.message : String(error)
688
729
  throw new Error(
689
- `Failed to send certificate response message to peer ${peerSession.peerIdentityKey ?? 'unknown'}: ${errorMessage}`
730
+ `Failed to send certificate response message to peer ${peerSession.peerIdentityKey ?? 'unknown'
731
+ }: ${errorMessage}`
690
732
  )
691
733
  }
692
734
  }
@@ -696,43 +738,49 @@ export class Peer {
696
738
  *
697
739
  * @private
698
740
  * @param {AuthMessage} message - The incoming certificate response message.
699
- * @returns {Promise<void>}
700
741
  * @throws Will throw an error if nonce verification or signature verification fails.
701
742
  */
702
743
  private async processCertificateResponse (message: AuthMessage): Promise<void> {
703
744
  const validNonce = await verifyNonce(message.yourNonce as string, this.wallet)
704
745
  if (!validNonce) {
705
746
  throw new Error(
706
- `Unable to verify nonce for certificate response from: ${message.identityKey}!`
747
+ `Unable to verify nonce for certificate response from: ${message.identityKey}`
707
748
  )
708
749
  }
750
+
709
751
  const peerSession = this.sessionManager.getSession(message.yourNonce as string)
752
+ if (peerSession == null) {
753
+ throw new Error(`Session not found for nonce: ${message.yourNonce as string}`)
754
+ }
710
755
 
711
756
  // Validate message signature
712
757
  const { valid } = await this.wallet.verifySignature({
713
758
  data: Utils.toArray(JSON.stringify(message.certificates), 'utf8'),
714
759
  signature: message.signature as number[],
715
760
  protocolID: [2, 'auth message signature'],
716
- keyID: `${message.nonce ?? ''} ${peerSession?.sessionNonce ?? ''}`,
761
+ keyID: `${message.nonce ?? ''} ${peerSession.sessionNonce ?? ''}`,
717
762
  counterparty: message.identityKey
718
763
  })
719
-
720
764
  if (!valid) {
721
765
  throw new Error(
722
766
  `Unable to verify certificate response signature for peer: ${message.identityKey}`
723
767
  )
724
768
  }
725
769
 
726
- // Process and verify any certificates received
770
+ // We also handle optional validation if there's a requestedCertificates field
727
771
  await validateCertificates(
728
772
  this.wallet,
729
773
  message,
730
774
  message.requestedCertificates
731
775
  )
732
776
 
733
- this.onCertificatesReceivedCallbacks.forEach(callback => {
734
- callback(message.identityKey, message.certificates as VerifiableCertificate[])
777
+ // Notify any listeners
778
+ this.onCertificatesReceivedCallbacks.forEach(cb => {
779
+ cb(message.identityKey, message.certificates ?? [])
735
780
  })
781
+
782
+ peerSession.lastUpdate = Date.now()
783
+ this.sessionManager.updateSession(peerSession)
736
784
  }
737
785
 
738
786
  /**
@@ -740,8 +788,7 @@ export class Peer {
740
788
  *
741
789
  * @private
742
790
  * @param {AuthMessage} message - The incoming general message.
743
- * @returns {Promise<void>}
744
- * @throws Will throw an error if nonce verification or signature verification fails.
791
+ * @throws Will throw an error if nonce or signature verification fails.
745
792
  */
746
793
  private async processGeneralMessage (message: AuthMessage): Promise<void> {
747
794
  const validNonce = await verifyNonce(message.yourNonce as string, this.wallet)
@@ -750,26 +797,35 @@ export class Peer {
750
797
  `Unable to verify nonce for general message from: ${message.identityKey}`
751
798
  )
752
799
  }
800
+
753
801
  const peerSession = this.sessionManager.getSession(message.yourNonce as string)
802
+ if (peerSession == null) {
803
+ throw new Error(`Session not found for nonce: ${message.yourNonce as string}`)
804
+ }
754
805
 
755
806
  const { valid } = await this.wallet.verifySignature({
756
807
  data: message.payload,
757
808
  signature: message.signature as number[],
758
809
  protocolID: [2, 'auth message signature'],
759
- keyID: `${message.nonce ?? ''} ${peerSession?.sessionNonce ?? ''}`,
760
- counterparty: peerSession?.peerIdentityKey
810
+ keyID: `${message.nonce ?? ''} ${peerSession.sessionNonce ?? ''}`,
811
+ counterparty: peerSession.peerIdentityKey
761
812
  })
762
-
763
813
  if (!valid) {
764
814
  throw new Error(
765
- `Invalid signature in generalMessage from ${peerSession?.peerIdentityKey ?? 'unknown'}`
815
+ `Invalid signature in generalMessage from ${peerSession.peerIdentityKey as string}`
766
816
  )
767
817
  }
768
818
 
819
+ // Mark last usage
820
+ peerSession.lastUpdate = Date.now()
821
+ this.sessionManager.updateSession(peerSession)
822
+
823
+ // Update lastInteractedWithPeer
769
824
  this.lastInteractedWithPeer = message.identityKey
770
825
 
771
- this.onGeneralMessageReceivedCallbacks.forEach(callback => {
772
- callback(message.identityKey, message.payload as number[])
826
+ // Dispatch callbacks
827
+ this.onGeneralMessageReceivedCallbacks.forEach(cb => {
828
+ cb(message.identityKey, message.payload ?? [])
773
829
  })
774
830
  }
775
831
  }