@bsv/sdk
Version:
BSV Blockchain Software Development Kit
843 lines (763 loc) • 30.3 kB
text/typescript
import { SessionManager } from './SessionManager.js'
import {
createNonce,
verifyNonce,
getVerifiableCertificates,
validateCertificates
} from './utils/index.js'
import {
AuthMessage,
PeerSession,
RequestedCertificateSet,
Transport
} from './types.js'
import { VerifiableCertificate } from './certificates/VerifiableCertificate.js'
import Random from '../primitives/Random.js'
import * as Utils from '../primitives/utils.js'
import { OriginatorDomainNameStringUnder250Bytes, WalletInterface } from '../wallet/Wallet.interfaces.js'
const AUTH_VERSION = '0.1'
/**
* Represents a peer capable of performing mutual authentication.
* Manages sessions, handles authentication handshakes, certificate requests and responses,
* and sending and receiving general messages over a transport layer.
*
* This version supports multiple concurrent sessions per peer identityKey.
*/
export class Peer {
public sessionManager: SessionManager
private readonly transport: Transport
private readonly wallet: WalletInterface
certificatesToRequest: RequestedCertificateSet
private readonly onGeneralMessageReceivedCallbacks: Map<
number,
(senderPublicKey: string, payload: number[]) => void
> = new Map()
private readonly onCertificatesReceivedCallbacks: Map<
number,
(senderPublicKey: string, certs: VerifiableCertificate[]) => void
> = new Map()
private readonly onCertificateRequestReceivedCallbacks: Map<
number,
(
senderPublicKey: string,
requestedCertificates: RequestedCertificateSet
) => void
> = new Map()
private readonly onInitialResponseReceivedCallbacks: Map<
number,
{ callback: (sessionNonce: string) => void, sessionNonce: string }
> = new Map()
// Single shared counter for all callback types
private callbackIdCounter: number = 0
// Whether to auto-persist the session with the last-interacted-with peer
private readonly autoPersistLastSession: boolean = true
// Last-interacted-with peer identity key (if the user calls toPeer with no identityKey)
private lastInteractedWithPeer: string | undefined
private readonly originator?: OriginatorDomainNameStringUnder250Bytes
/**
* Creates a new Peer instance
*
* @param {WalletInterface} wallet - The wallet instance used for cryptographic operations.
* @param {Transport} transport - The transport mechanism used for sending and receiving messages.
* @param {RequestedCertificateSet} [certificatesToRequest] - Optional set of certificates to request from a peer during the initial handshake.
* @param {SessionManager} [sessionManager] - Optional SessionManager to be used for managing peer sessions.
* @param {boolean} [autoPersistLastSession] - Whether to auto-persist the session with the last-interacted-with peer. Defaults to true.
*/
constructor (
wallet: WalletInterface,
transport: Transport,
certificatesToRequest?: RequestedCertificateSet,
sessionManager?: SessionManager,
autoPersistLastSession?: boolean,
originator?: OriginatorDomainNameStringUnder250Bytes
) {
this.wallet = wallet
this.originator = originator
this.transport = transport
this.certificatesToRequest = certificatesToRequest ?? {
certifiers: [],
types: {}
}
this.transport.onData(this.handleIncomingMessage.bind(this)).catch(e => {
throw e
})
this.sessionManager =
sessionManager != null ? sessionManager : new SessionManager()
if (autoPersistLastSession === false) {
this.autoPersistLastSession = false
} else {
this.autoPersistLastSession = true
}
}
/**
* Sends a general message to a peer, and initiates a handshake if necessary.
*
* @param {number[]} message - The message payload to send.
* @param {string} [identityKey] - The identity public key of the peer. If not provided, uses lastInteractedWithPeer (if any).
* @param {number} [maxWaitTime] - optional max wait time in ms
* @returns {Promise<void>}
* @throws Will throw an error if the message fails to send.
*/
async toPeer (
message: number[],
identityKey?: string,
maxWaitTime?: number
): Promise<void> {
if (
this.autoPersistLastSession &&
typeof this.lastInteractedWithPeer === 'string' &&
typeof identityKey !== 'string'
) {
identityKey = this.lastInteractedWithPeer
}
const peerSession = await this.getAuthenticatedSession(identityKey, maxWaitTime)
// Prepare the general message
const requestNonce = Utils.toBase64(Random(32))
const { signature } = await this.wallet.createSignature({
data: message,
protocolID: [2, 'auth message signature'],
keyID: `${requestNonce} ${peerSession.peerNonce ?? ''}`,
counterparty: peerSession.peerIdentityKey
}, this.originator)
const generalMessage: AuthMessage = {
version: AUTH_VERSION,
messageType: 'general',
identityKey: (await this.wallet.getPublicKey({ identityKey: true }, this.originator))
.publicKey,
nonce: requestNonce,
yourNonce: peerSession.peerNonce,
payload: message,
signature
}
peerSession.lastUpdate = Date.now()
this.sessionManager.updateSession(peerSession)
try {
await this.transport.send(generalMessage)
} catch (error: any) {
const e = new Error(
`Failed to send message to peer ${peerSession.peerIdentityKey ?? 'unknown'
}: ${String(error.message)}`
)
e.stack = error.stack
throw e
}
}
/**
* Sends a request for certificates to a peer.
* This method allows a peer to dynamically request specific certificates after
* an initial handshake or message has been exchanged.
*
* @param {RequestedCertificateSet} certificatesToRequest - Specifies the certifiers and types of certificates required from the peer.
* @param {string} [identityKey] - The identity public key of the peer. If not provided, the current or last session identity is used.
* @param {number} [maxWaitTime=10000] - Maximum time in milliseconds to wait for the peer session to be authenticated.
* @returns {Promise<void>} Resolves if the certificate request message is successfully sent.
* @throws Will throw an error if the peer session is not authenticated or if sending the request fails.
*/
async requestCertificates (
certificatesToRequest: RequestedCertificateSet,
identityKey?: string,
maxWaitTime = 10000
): Promise<void> {
if (
this.autoPersistLastSession &&
typeof this.lastInteractedWithPeer === 'string' &&
typeof identityKey !== 'string'
) {
identityKey = this.lastInteractedWithPeer
}
const peerSession = await this.getAuthenticatedSession(
identityKey,
maxWaitTime
)
// Prepare the message
const requestNonce = Utils.toBase64(Random(32))
const { signature } = await this.wallet.createSignature({
data: Utils.toArray(JSON.stringify(certificatesToRequest), 'utf8'),
protocolID: [2, 'auth message signature'],
keyID: `${requestNonce} ${peerSession.peerNonce ?? ''}`,
counterparty: peerSession.peerIdentityKey
}, this.originator)
const certRequestMessage: AuthMessage = {
version: AUTH_VERSION,
messageType: 'certificateRequest',
identityKey: (await this.wallet.getPublicKey({ identityKey: true }, this.originator))
.publicKey,
nonce: requestNonce,
initialNonce: peerSession.sessionNonce,
yourNonce: peerSession.peerNonce,
requestedCertificates: certificatesToRequest,
signature
}
// Update last-used timestamp
peerSession.lastUpdate = Date.now()
this.sessionManager.updateSession(peerSession)
try {
await this.transport.send(certRequestMessage)
} catch (error: any) {
throw new Error(
`Failed to send certificate request message to peer ${peerSession.peerIdentityKey ?? 'unknown'
}: ${String(error.message)}`
)
}
}
/**
* Retrieves an authenticated session for a given peer identity. If no session exists
* or the session is not authenticated, initiates a handshake to create or authenticate the session.
*
* - If `identityKey` is provided, we look up any existing session for that identity key.
* - If none is found or not authenticated, we do a new handshake.
* - If `identityKey` is not provided, but we have a `lastInteractedWithPeer`, we try that key.
*
* @param {string} [identityKey] - The identity public key of the peer.
* @param {number} [maxWaitTime] - The maximum time in milliseconds to wait for the handshake.
* @returns {Promise<PeerSession>} - A promise that resolves with an authenticated `PeerSession`.
*/
async getAuthenticatedSession (
identityKey?: string,
maxWaitTime?: number
): Promise<PeerSession> {
if (this.transport === undefined) {
throw new Error('Peer transport is not connected!')
}
let peerSession: PeerSession | undefined
if (typeof identityKey === 'string') {
peerSession = this.sessionManager.getSession(identityKey)
}
// If that session doesn't exist or isn't authenticated, initiate handshake
if ((peerSession == null) || !peerSession.isAuthenticated) {
// This will create a brand-new session
const sessionNonce = await this.initiateHandshake(identityKey, maxWaitTime)
// Now retrieve it by the sessionNonce
peerSession = this.sessionManager.getSession(sessionNonce)
if ((peerSession == null) || !peerSession.isAuthenticated) {
throw new Error('Unable to establish mutual authentication with peer!')
}
}
return peerSession
}
/**
* Registers a callback to listen for general messages from peers.
*
* @param {(senderPublicKey: string, payload: number[]) => void} callback - The function to call when a general message is received.
* @returns {number} The ID of the callback listener.
*/
listenForGeneralMessages (
callback: (senderPublicKey: string, payload: number[]) => void
): number {
const callbackID = this.callbackIdCounter++
this.onGeneralMessageReceivedCallbacks.set(callbackID, callback)
return callbackID
}
/**
* Removes a general message listener.
*
* @param {number} callbackID - The ID of the callback to remove.
*/
stopListeningForGeneralMessages (callbackID: number): void {
this.onGeneralMessageReceivedCallbacks.delete(callbackID)
}
/**
* Registers a callback to listen for certificates received from peers.
*
* @param {(senderPublicKey: string, certs: VerifiableCertificate[]) => void} callback - The function to call when certificates are received.
* @returns {number} The ID of the callback listener.
*/
listenForCertificatesReceived (
callback: (senderPublicKey: string, certs: VerifiableCertificate[]) => void
): number {
const callbackID = this.callbackIdCounter++
this.onCertificatesReceivedCallbacks.set(callbackID, callback)
return callbackID
}
/**
* Cancels and unsubscribes a certificatesReceived listener.
*
* @param {number} callbackID - The ID of the certificates received callback to cancel.
*/
stopListeningForCertificatesReceived (callbackID: number): void {
this.onCertificatesReceivedCallbacks.delete(callbackID)
}
/**
* Registers a callback to listen for certificates requested from peers.
*
* @param {(requestedCertificates: RequestedCertificateSet) => void} callback - The function to call when a certificate request is received
* @returns {number} The ID of the callback listener.
*/
listenForCertificatesRequested (
callback: (
senderPublicKey: string,
requestedCertificates: RequestedCertificateSet
) => void
): number {
const callbackID = this.callbackIdCounter++
this.onCertificateRequestReceivedCallbacks.set(callbackID, callback)
return callbackID
}
/**
* Cancels and unsubscribes a certificatesRequested listener.
*
* @param {number} callbackID - The ID of the requested certificates callback to cancel.
*/
stopListeningForCertificatesRequested (callbackID: number): void {
this.onCertificateRequestReceivedCallbacks.delete(callbackID)
}
/**
* Initiates the mutual authentication handshake with a peer.
*
* @private
* @param {string} [identityKey] - The identity public key of the peer.
* @param {number} [maxWaitTime=10000] - how long to wait for handshake
* @returns {Promise<string>} A promise that resolves to the session nonce.
*/
private async initiateHandshake (
identityKey?: string,
maxWaitTime = 10000
): Promise<string> {
const sessionNonce = await createNonce(this.wallet, undefined, this.originator) // Initial request nonce
// Create the preliminary session (not yet authenticated)
const now = Date.now()
this.sessionManager.addSession({
isAuthenticated: false,
sessionNonce,
peerIdentityKey: identityKey,
lastUpdate: now
})
const initialRequest: AuthMessage = {
version: AUTH_VERSION,
messageType: 'initialRequest',
identityKey: (await this.wallet.getPublicKey({ identityKey: true }, this.originator))
.publicKey,
initialNonce: sessionNonce,
requestedCertificates: this.certificatesToRequest
}
await this.transport.send(initialRequest)
return await this.waitForInitialResponse(sessionNonce, maxWaitTime)
}
/**
* Waits for the initial response from the peer after sending an initial handshake request message.
*
* @param {string} sessionNonce - The session nonce created in the initial request.
* @returns {Promise<string>} A promise that resolves with the session nonce when the initial response is received.
*/
private async waitForInitialResponse (
sessionNonce: string,
maxWaitTime = 10000
): Promise<string> {
return await new Promise((resolve, reject) => {
const callbackID = this.listenForInitialResponse(sessionNonce, nonce => {
clearTimeout(timeoutHandle)
this.stopListeningForInitialResponses(callbackID)
resolve(nonce)
})
const timeoutHandle = setTimeout(() => {
this.stopListeningForInitialResponses(callbackID)
reject(new Error('Initial response timed out.'))
}, maxWaitTime)
})
}
/**
* Adds a listener for an initial response message matching a specific initial nonce.
*
* @private
* @param {string} sessionNonce - The session nonce to match.
* @param {(sessionNonce: string) => void} callback - The callback to invoke when the initial response is received.
* @returns {number} The ID of the callback listener.
*/
private listenForInitialResponse (
sessionNonce: string,
callback: (sessionNonce: string) => void
): number {
const callbackID = this.callbackIdCounter++
this.onInitialResponseReceivedCallbacks.set(callbackID, {
callback,
sessionNonce
})
return callbackID
}
/**
* Removes a listener for initial responses.
*
* @private
* @param {number} callbackID - The ID of the callback to remove.
*/
private stopListeningForInitialResponses (callbackID: number): void {
this.onInitialResponseReceivedCallbacks.delete(callbackID)
}
/**
* Handles incoming messages from the transport.
*
* @param {AuthMessage} message - The incoming message to process.
* @returns {Promise<void>}
*/
private async handleIncomingMessage (message: AuthMessage): Promise<void> {
if (typeof message.version !== 'string' || message.version !== AUTH_VERSION) {
throw new Error(
`Invalid or unsupported message auth version! Received: ${message.version}, expected: ${AUTH_VERSION}`
)
}
switch (message.messageType) {
case 'initialRequest':
await this.processInitialRequest(message)
break
case 'initialResponse':
await this.processInitialResponse(message)
break
case 'certificateRequest':
await this.processCertificateRequest(message)
break
case 'certificateResponse':
await this.processCertificateResponse(message)
break
case 'general':
await this.processGeneralMessage(message)
break
default:
throw new Error(
`Unknown message type of ${String(message.messageType)} from ${String(
message.identityKey
)}`
)
}
}
/**
* Processes an initial request message from a peer.
*
* @param {AuthMessage} message - The incoming initial request message.
*/
private async processInitialRequest (message: AuthMessage): Promise<void> {
if (
typeof message.identityKey !== 'string' ||
typeof message.initialNonce !== 'string' ||
message.initialNonce === ''
) {
throw new Error('Missing required fields in initialRequest message.')
}
// Create a new sessionNonce for our side
const sessionNonce = await createNonce(this.wallet, undefined, this.originator)
const now = Date.now()
// We'll treat this as fully authenticated from *our* perspective (the responding side).
this.sessionManager.addSession({
isAuthenticated: true,
sessionNonce,
peerNonce: message.initialNonce,
peerIdentityKey: message.identityKey,
lastUpdate: now
})
// Possibly handle the peer's requested certs
let certificatesToInclude: VerifiableCertificate[] | undefined
if (
(message.requestedCertificates != null) &&
Array.isArray(message.requestedCertificates.certifiers) &&
message.requestedCertificates.certifiers.length > 0
) {
if (this.onCertificateRequestReceivedCallbacks.size > 0) {
// Let the application handle it
this.onCertificateRequestReceivedCallbacks.forEach(cb => {
cb(message.identityKey, message.requestedCertificates as RequestedCertificateSet)
})
} else {
// Attempt to find automatically
certificatesToInclude = await getVerifiableCertificates(
this.wallet,
message.requestedCertificates,
message.identityKey,
this.originator
)
}
}
// Create signature
const { signature } = await this.wallet.createSignature({
data: Utils.toArray(message.initialNonce + sessionNonce, 'base64'),
protocolID: [2, 'auth message signature'],
keyID: `${message.initialNonce} ${sessionNonce}`,
counterparty: message.identityKey
}, this.originator)
const initialResponseMessage: AuthMessage = {
version: AUTH_VERSION,
messageType: 'initialResponse',
identityKey: (await this.wallet.getPublicKey({ identityKey: true }, this.originator))
.publicKey,
initialNonce: sessionNonce,
yourNonce: message.initialNonce,
certificates: certificatesToInclude,
requestedCertificates: this.certificatesToRequest,
signature
}
// If we haven't interacted with a peer yet, store this identity as "lastInteracted"
if (this.lastInteractedWithPeer === undefined) {
this.lastInteractedWithPeer = message.identityKey
}
// Send the response
await this.transport.send(initialResponseMessage)
}
/**
* Processes an initial response message from a peer.
*
* @private
* @param {AuthMessage} message - The incoming initial response message.
* @throws Will throw an error if nonce or signature verification fails.
*/
private async processInitialResponse (message: AuthMessage): Promise<void> {
const validNonce = await verifyNonce(message.yourNonce as string, this.wallet, undefined, this.originator)
if (!validNonce) {
throw new Error(
`Initial response nonce verification failed from peer: ${message.identityKey}`
)
}
// This is the session we previously created by calling initiateHandshake
const peerSession = this.sessionManager.getSession(message.yourNonce as string)
if (peerSession == null) {
throw new Error(`Peer session not found for peer: ${message.identityKey}`)
}
// Validate message signature
const dataToVerify = Utils.toArray(
(peerSession.sessionNonce ?? '') + (message.initialNonce ?? ''),
'base64'
)
const { valid } = await this.wallet.verifySignature({
data: dataToVerify,
signature: message.signature as number[],
protocolID: [2, 'auth message signature'],
keyID: `${peerSession.sessionNonce ?? ''} ${message.initialNonce ?? ''}`,
counterparty: message.identityKey
}, this.originator)
if (!valid) {
throw new Error(
`Unable to verify initial response signature for peer: ${message.identityKey}`
)
}
// Now mark the session as authenticated
peerSession.peerNonce = message.initialNonce
peerSession.peerIdentityKey = message.identityKey
peerSession.isAuthenticated = true
peerSession.lastUpdate = Date.now()
this.sessionManager.updateSession(peerSession)
// If the handshake had requested certificates, validate them
if (
this.certificatesToRequest?.certifiers?.length > 0 &&
message.certificates?.length as number > 0
) {
await validateCertificates(this.wallet, message, this.certificatesToRequest, this.originator)
// Notify listeners
this.onCertificatesReceivedCallbacks.forEach(cb =>
cb(message.identityKey, message.certificates as VerifiableCertificate[])
)
}
// Update lastInteractedWithPeer
this.lastInteractedWithPeer = message.identityKey
// Let the handshake wait-latch know we got our response
this.onInitialResponseReceivedCallbacks.forEach(entry => {
if (entry.sessionNonce === peerSession.sessionNonce) {
entry.callback(peerSession.sessionNonce)
}
})
// The peer might also request certificates from us
if (
(message.requestedCertificates != null) &&
Array.isArray(message.requestedCertificates.certifiers) &&
message.requestedCertificates.certifiers.length > 0
) {
if (this.onCertificateRequestReceivedCallbacks.size > 0) {
// Let the application handle it
this.onCertificateRequestReceivedCallbacks.forEach(cb => {
cb(message.identityKey, message.requestedCertificates as RequestedCertificateSet)
})
} else {
// Attempt auto
const verifiableCertificates = await getVerifiableCertificates(
this.wallet,
message.requestedCertificates,
message.identityKey,
this.originator
)
await this.sendCertificateResponse(
message.identityKey,
verifiableCertificates
)
}
}
}
/**
* Processes an incoming certificate request message from a peer.
* Verifies nonce/signature and then possibly sends a certificateResponse.
*
* @param {AuthMessage} message - The certificate request message received from the peer.
* @throws {Error} if nonce or signature is invalid.
*/
private async processCertificateRequest (message: AuthMessage): Promise<void> {
const validNonce = await verifyNonce(message.yourNonce as string, this.wallet, undefined, this.originator)
if (!validNonce) {
throw new Error(
`Unable to verify nonce for certificate request message from: ${message.identityKey}`
)
}
const peerSession = this.sessionManager.getSession(message.yourNonce as string)
if (peerSession == null) {
throw new Error(`Session not found for nonce: ${message.yourNonce as string}`)
}
const { valid } = await this.wallet.verifySignature({
data: Utils.toArray(JSON.stringify(message.requestedCertificates), 'utf8'),
signature: message.signature as number[],
protocolID: [2, 'auth message signature'],
keyID: `${message.nonce ?? ''} ${peerSession.sessionNonce ?? ''}`,
counterparty: peerSession.peerIdentityKey
}, this.originator)
if (!valid) {
throw new Error(
`Invalid signature in certificate request message from ${peerSession.peerIdentityKey as string}`
)
}
// Update usage
peerSession.lastUpdate = Date.now()
this.sessionManager.updateSession(peerSession)
if (
(message.requestedCertificates != null) &&
Array.isArray(message.requestedCertificates.certifiers) &&
message.requestedCertificates.certifiers.length > 0
) {
if (this.onCertificateRequestReceivedCallbacks.size > 0) {
// Let the application handle it
this.onCertificateRequestReceivedCallbacks.forEach(cb => {
cb(message.identityKey, message.requestedCertificates as RequestedCertificateSet)
})
} else {
// Attempt auto
const verifiableCertificates = await getVerifiableCertificates(
this.wallet,
message.requestedCertificates,
message.identityKey,
this.originator
)
await this.sendCertificateResponse(message.identityKey, verifiableCertificates)
}
}
}
/**
* Sends a certificate response message containing the specified certificates to a peer.
*
* @param {string} verifierIdentityKey - The identity key of the peer requesting the certificates.
* @param {VerifiableCertificate[]} certificates - The list of certificates to include in the response.
* @throws Will throw an error if the transport fails to send the message.
*/
async sendCertificateResponse (
verifierIdentityKey: string,
certificates: VerifiableCertificate[]
): Promise<void> {
const peerSession = await this.getAuthenticatedSession(verifierIdentityKey)
const requestNonce = Utils.toBase64(Random(32))
const { signature } = await this.wallet.createSignature({
data: Utils.toArray(JSON.stringify(certificates), 'utf8'),
protocolID: [2, 'auth message signature'],
keyID: `${requestNonce} ${peerSession.peerNonce ?? ''}`,
counterparty: peerSession.peerIdentityKey
}, this.originator)
const certificateResponse: AuthMessage = {
version: AUTH_VERSION,
messageType: 'certificateResponse',
identityKey: (await this.wallet.getPublicKey({ identityKey: true }, this.originator))
.publicKey,
nonce: requestNonce,
initialNonce: peerSession.sessionNonce,
yourNonce: peerSession.peerNonce,
certificates,
signature
}
// Update usage
peerSession.lastUpdate = Date.now()
this.sessionManager.updateSession(peerSession)
try {
await this.transport.send(certificateResponse)
} catch (error: any) {
const errorMessage = error instanceof Error ? error.message : String(error)
throw new Error(
`Failed to send certificate response message to peer ${peerSession.peerIdentityKey ?? 'unknown'
}: ${errorMessage}`
)
}
}
/**
* Processes a certificate response message from a peer.
*
* @private
* @param {AuthMessage} message - The incoming certificate response message.
* @throws Will throw an error if nonce verification or signature verification fails.
*/
private async processCertificateResponse (message: AuthMessage): Promise<void> {
const validNonce = await verifyNonce(message.yourNonce as string, this.wallet, undefined, this.originator)
if (!validNonce) {
throw new Error(
`Unable to verify nonce for certificate response from: ${message.identityKey}`
)
}
const peerSession = this.sessionManager.getSession(message.yourNonce as string)
if (peerSession == null) {
throw new Error(`Session not found for nonce: ${message.yourNonce as string}`)
}
// Validate message signature
const { valid } = await this.wallet.verifySignature({
data: Utils.toArray(JSON.stringify(message.certificates), 'utf8'),
signature: message.signature as number[],
protocolID: [2, 'auth message signature'],
keyID: `${message.nonce ?? ''} ${peerSession.sessionNonce ?? ''}`,
counterparty: message.identityKey
}, this.originator)
if (!valid) {
throw new Error(
`Unable to verify certificate response signature for peer: ${message.identityKey}`
)
}
// We also handle optional validation if there's a requestedCertificates field
await validateCertificates(
this.wallet,
message,
message.requestedCertificates,
this.originator
)
// Notify any listeners
this.onCertificatesReceivedCallbacks.forEach(cb => {
cb(message.identityKey, message.certificates ?? [])
})
peerSession.lastUpdate = Date.now()
this.sessionManager.updateSession(peerSession)
}
/**
* Processes a general message from a peer.
*
* @private
* @param {AuthMessage} message - The incoming general message.
* @throws Will throw an error if nonce or signature verification fails.
*/
private async processGeneralMessage (message: AuthMessage): Promise<void> {
const validNonce = await verifyNonce(message.yourNonce as string, this.wallet, undefined, this.originator)
if (!validNonce) {
throw new Error(
`Unable to verify nonce for general message from: ${message.identityKey}`
)
}
const peerSession = this.sessionManager.getSession(message.yourNonce as string)
if (peerSession == null) {
throw new Error(`Session not found for nonce: ${message.yourNonce as string}`)
}
const { valid } = await this.wallet.verifySignature({
data: message.payload,
signature: message.signature as number[],
protocolID: [2, 'auth message signature'],
keyID: `${message.nonce ?? ''} ${peerSession.sessionNonce ?? ''}`,
counterparty: peerSession.peerIdentityKey
}, this.originator)
if (!valid) {
throw new Error(
`Invalid signature in generalMessage from ${peerSession.peerIdentityKey as string}`
)
}
// Mark last usage
peerSession.lastUpdate = Date.now()
this.sessionManager.updateSession(peerSession)
// Update lastInteractedWithPeer
this.lastInteractedWithPeer = message.identityKey
// Dispatch callbacks
this.onGeneralMessageReceivedCallbacks.forEach(cb => {
cb(message.identityKey, message.payload ?? [])
})
}
}