spake2Exchange
  :: (AbelianGroup group, HashAlgorithm hashAlgorithm)
  => Protocol group hashAlgorithm
  -- ^ A 'Protocol' with all the parameters for the exchange. These parameters
  -- must be shared by both sides. Construct with 'makeAsymmetricProtocol' or
  -- 'makeSymmetricProtocol'.
  -> Password
  -- ^ The password shared between both sides. Construct with 'makePassword'.
  -> (ByteString -> IO ())
  -- ^ An action to send a message. The 'ByteString' parameter is this side's
  -- SPAKE2 element, encoded using the group encoding, prefixed according to
  -- the parameters in the 'Protocol'.
  -> IO (Either error ByteString)
  -- ^ An action to receive a message. The 'ByteString' generated ought to be
  -- the protocol-prefixed, group-encoded version of the other side's SPAKE2
  -- element.
  -> IO (Either (MessageError error) ByteString)
  -- ^ Either the shared session key or an error indicating we couldn't parse
  -- the other side's message.
spake2Exchange protocol password send receive = do
  exchange <- startSpake2 protocol password
  let outboundElement = Math.computeOutboundMessage exchange
  let outboundMessage = elementToMessage protocol outboundElement
  (_, inboundMessage) <- concurrently (send outboundMessage) receive
  pure $ do
    inboundMessage' <- first UnknownError inboundMessage
    inboundElement <- extractElement protocol inboundMessage'
    let keyMaterial = Math.generateKeyMaterial exchange inboundElement
    pure (createSessionKey protocol inboundElement outboundElement keyMaterial password)

-- | Commence a SPAKE2 exchange.
spake2Exchange
  :: (AbelianGroup group, HashAlgorithm hashAlgorithm)
  => Protocol group hashAlgorithm
  -- ^ A 'Protocol' with all the parameters for the exchange. These parameters
  -- must be shared by both sides. Construct with 'makeAsymmetricProtocol' or
  -- 'makeSymmetricProtocol'.
  -> Password
  -- ^ The password shared between both sides. Construct with 'makePassword'.
  -> (ByteString -> IO ())
  -- ^ An action to send a message. The 'ByteString' parameter is this side's
  -- SPAKE2 element, encoded using the group encoding, prefixed according to
  -- the parameters in the 'Protocol'.
  -> IO (Either error ByteString)
  -- ^ An action to receive a message. The 'ByteString' generated ought to be
  -- the protocol-prefixed, group-encoded version of the other side's SPAKE2
  -- element.
  -> IO (Either (MessageError error) ByteString)
spake2Exchange protocol password send receive = do
  exchange <- startSpake2 protocol password
  let outboundElement = Math.computeOutboundMessage exchange
  let outboundMessage = elementToMessage protocol outboundElement
  (_, inboundMessage) <- concurrently (send outboundMessage) receive
  pure $ do
    inboundMessage' <- first UnknownError inboundMessage
    inboundElement <- extractElement protocol inboundMessage'
    let keyMaterial = Math.generateKeyMaterial exchange inboundElement
    pure (createSessionKey protocol inboundElement outboundElement keyMaterial password)

-- | Commence a SPAKE2 exchange.
spake2Exchange protocol password send receive = do
  exchange <- startSpake2 protocol password
  let outboundElement = Math.computeOutboundMessage exchange
  let outboundMessage = elementToMessage protocol outboundElement
  (_, inboundMessage) <- concurrently (send outboundMessage) receive
  pure $ do
    inboundMessage' <- first UnknownError inboundMessage
    inboundElement <- extractElement protocol inboundMessage'
    let keyMaterial = Math.generateKeyMaterial exchange inboundElement
    pure (createSessionKey protocol inboundElement outboundElement keyMaterial password)

-- | Commence a SPAKE2 exchange.