Class: BSV::Auth::Peer

Inherits:
Object
  • Object
show all
Defined in:
lib/bsv/auth/peer.rb

Overview

BRC-31/BRC-103 mutual authentication peer.

Manages the cryptographic handshake between two parties using a transport for bidirectional message delivery.

High-level API (preferred):

peer_a.to_peer(payload, peer_b_identity_key)    — send authenticated message
peer_a.get_authenticated_session(identity_key)  — obtain/create authenticated session
peer_a.request_certificates(certs, identity_key) — request certs post-handshake
peer_a.send_certificate_response(key, certs)    — send certs to a peer

The high-level API auto-initiates the handshake when no authenticated session exists. The low-level handle_incoming_message is called internally by the transport callback and is not part of the public contract.

Sessions are indexed by session_nonce (our nonce), which is the primary key in the SessionManager.

Examples:

Using Peer with a transport

transport_a, transport_b = PairedTransport.create_pair
peer_a = BSV::Auth::Peer.new(wallet: wallet_a, transport: transport_a)
peer_b = BSV::Auth::Peer.new(wallet: wallet_b, transport: transport_b)

peer_b.on_general_message { |key, payload| puts "received: #{payload}" }
peer_a.to_peer([72, 101, 108, 108, 111], peer_b.identity_key)

Constant Summary collapse

AUTH_PROTOCOL =
[2, 'auth message signature'].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(wallet:, transport:, session_manager: nil, certificates_to_request: nil, auto_persist_last_session: true, handshake_timeout: 30) ⇒ Peer

Returns a new instance of Peer.

Parameters:

  • wallet (BSV::Wallet::Interface)

    wallet providing crypto operations

  • transport (Transport)

    transport for message delivery (required)

  • session_manager (SessionManager, nil) (defaults to: nil)

    optional custom session store

  • certificates_to_request (Hash, nil) (defaults to: nil)

    certificate set to request from peers

  • auto_persist_last_session (Boolean) (defaults to: true)

    whether to track the last-interacted peer (default: true)

  • handshake_timeout (Integer) (defaults to: 30)

    seconds to wait for handshake response (default: 30)

Raises:

  • (ArgumentError)

    if transport is nil



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/bsv/auth/peer.rb', line 46

def initialize(wallet:, transport:, session_manager: nil, certificates_to_request: nil,
               auto_persist_last_session: true, handshake_timeout: 30)
  raise ArgumentError, 'transport is required' if transport.nil?

  @wallet                    = wallet
  @transport                 = transport
  @session_manager           = session_manager || SessionManager.new
  @certificates_to_request   = certificates_to_request || { certifiers: [], types: {} }
  @identity_public_key       = nil
  @callbacks                 = { general_message: {}, certificates_received: {}, certificate_request: {} }
  @callback_id_counter       = 0
  @callback_mutex            = Mutex.new
  @last_interacted_peer      = nil
  @auto_persist_last_session = auto_persist_last_session
  @handshake_queues          = {}
  @handshake_queues_mutex    = Mutex.new
  @handshake_timeout         = handshake_timeout

  @transport.on_data { |msg| handle_incoming_message(msg) }
end

Instance Attribute Details

#last_interacted_peerObject (readonly)

Returns the value of attribute last_interacted_peer.



37
38
39
# File 'lib/bsv/auth/peer.rb', line 37

def last_interacted_peer
  @last_interacted_peer
end

#session_managerObject (readonly)

Returns the value of attribute session_manager.



37
38
39
# File 'lib/bsv/auth/peer.rb', line 37

def session_manager
  @session_manager
end

#walletObject (readonly)

Returns the value of attribute wallet.



37
38
39
# File 'lib/bsv/auth/peer.rb', line 37

def wallet
  @wallet
end

Instance Method Details

#authenticated?(identifier) ⇒ Boolean

Checks whether we have an authenticated session with a peer.

Accepts either a session_nonce or peer_identity_key.

Parameters:

  • identifier (String)

Returns:

  • (Boolean)


192
193
194
195
# File 'lib/bsv/auth/peer.rb', line 192

def authenticated?(identifier)
  session = @session_manager.get_session(identifier)
  session&.authenticated? || false
end

#get_authenticated_session(identity_key = nil) ⇒ PeerSession

Returns an authenticated session for a peer, initiating a handshake if needed.

If identity_key is given and an authenticated session already exists, returns it immediately. Otherwise initiates a handshake and blocks until the response arrives or the timeout expires.

Parameters:

  • identity_key (String, nil) (defaults to: nil)

    peer identity key to look up or connect to

Returns:

Raises:

  • (AuthError)

    if the handshake fails or times out



98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/bsv/auth/peer.rb', line 98

def get_authenticated_session(identity_key = nil)
  if identity_key
    session = @session_manager.get_session(identity_key)
    return session if session&.authenticated?
  end

  session_nonce = initiate_handshake(identity_key)
  session       = @session_manager.get_session(session_nonce)
  raise AuthError, 'Unable to establish mutual authentication with peer!' unless session&.authenticated?

  session
end

#identity_keyString

Returns our identity key (cached after first call).

Returns:

  • (String)

    compressed public key hex



182
183
184
# File 'lib/bsv/auth/peer.rb', line 182

def identity_key
  @identity_key ||= @wallet.get_public_key({ identity_key: true })[:public_key]
end

#off_certificate_request(callback_id) ⇒ Object

Removes a certificate request callback.

Parameters:



175
176
177
# File 'lib/bsv/auth/peer.rb', line 175

def off_certificate_request(callback_id)
  unregister_callback(:certificate_request, callback_id)
end

#off_certificates_received(callback_id) ⇒ Object

Removes a certificates received callback.

Parameters:



160
161
162
# File 'lib/bsv/auth/peer.rb', line 160

def off_certificates_received(callback_id)
  unregister_callback(:certificates_received, callback_id)
end

#off_general_message(callback_id) ⇒ Object

Removes a general message callback.

Parameters:



145
146
147
# File 'lib/bsv/auth/peer.rb', line 145

def off_general_message(callback_id)
  unregister_callback(:general_message, callback_id)
end

#on_certificate_request {|sender_key, requested_certs| ... } ⇒ Integer

Registers a callback to be called when a certificate request is received from a peer.

Yields:

  • (sender_key, requested_certs)

    called with sender’s identity key and requested cert set

Returns:



168
169
170
# File 'lib/bsv/auth/peer.rb', line 168

def on_certificate_request(&block)
  register_callback(:certificate_request, block)
end

#on_certificates_received {|sender_key, certs| ... } ⇒ Integer

Registers a callback to be called when certificates are received from a peer.

Yields:

  • (sender_key, certs)

    called with sender’s identity key and certificate array

Returns:



153
154
155
# File 'lib/bsv/auth/peer.rb', line 153

def on_certificates_received(&block)
  register_callback(:certificates_received, block)
end

#on_general_message {|sender_key, payload| ... } ⇒ Integer

Registers a callback to be called when a general message is received.

Yields:

  • (sender_key, payload)

    called with the sender’s identity key and payload bytes

Returns:



138
139
140
# File 'lib/bsv/auth/peer.rb', line 138

def on_general_message(&block)
  register_callback(:general_message, block)
end

#request_certificates(certificates_to_request, identity_key = nil) ⇒ Object

Sends a certificate request to a peer post-handshake.

Parameters:

  • certificates_to_request (Hash)

    certifiers and types to request

  • identity_key (String, nil) (defaults to: nil)

    peer identity key; falls back to last-interacted peer



115
116
117
118
119
120
# File 'lib/bsv/auth/peer.rb', line 115

def request_certificates(certificates_to_request, identity_key = nil)
  identity_key = resolve_identity_key(identity_key)
  session      = get_authenticated_session(identity_key)
  message      = create_certificate_request(session.peer_identity_key, certificates_to_request)
  @transport.send(message)
end

#send_certificate_response(peer_identity_key, certificates) ⇒ Object

Sends a certificate response to a peer.

Parameters:

  • peer_identity_key (String)

    the peer’s identity key

  • certificates (Array<VerifiableCertificate, Hash>)

    certificates to send



126
127
128
129
130
# File 'lib/bsv/auth/peer.rb', line 126

def send_certificate_response(peer_identity_key, certificates)
  session = get_authenticated_session(peer_identity_key)
  message = create_certificate_response(session.peer_identity_key, certificates)
  @transport.send(message)
end

#to_peer(payload, identity_key = nil) ⇒ Object

Sends an authenticated general message to a peer, initiating a handshake automatically if no authenticated session exists.

Parameters:

  • payload (Array<Integer>)

    byte array payload

  • identity_key (String, nil) (defaults to: nil)

    peer identity key; falls back to last-interacted peer

Raises:

  • (AuthError)

    if certificates are required but not yet validated

  • (AuthError)

    if handshake times out



76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/bsv/auth/peer.rb', line 76

def to_peer(payload, identity_key = nil)
  identity_key = resolve_identity_key(identity_key)
  session      = get_authenticated_session(identity_key)

  if session.certificates_required && !session.certificates_validated
    raise AuthError, 'Cannot send general message before certificate validation is complete'
  end

  message = create_general_message(session.peer_identity_key, payload)
  @transport.send(message)
  @last_interacted_peer = session.peer_identity_key if @auto_persist_last_session
end