createSessionKey
  :: (Group group, HashAlgorithm hashAlgorithm)
  => Protocol group hashAlgorithm  -- ^ The protocol used for this exchange
  -> Element group  -- ^ The outbound message, generated by this, \(X^{\star}\), or either side if symmetric
  -> Element group  -- ^ The inbound message, generated by the other side, \(Y^{\star}\), or either side if symmetric
  -> Element group  -- ^ The calculated key material, \(K\)
  -> Password  -- ^ The shared secret password
  -> ByteString  -- ^ A session key to use for further communication
createSessionKey Protocol{group, hashAlgorithm, relation} outbound inbound k (Password password) =
  hashDigest transcript

  where
    -- The protocol expects that when we include the hash of various
    -- components (e.g. the password) as input for the session key hash,
    -- that we use the *byte* representation of these elements.
    hashDigest :: ByteArrayAccess input => input -> ByteString
    hashDigest thing = ByteArray.convert (hashWith hashAlgorithm thing)

    transcript =
      case relation of
        Asymmetric{sideA, sideB, us} ->
          let (x, y) = case us of
                         SideA -> (inbound, outbound)
                         SideB -> (outbound, inbound)
          in mconcat [ hashDigest password
                     , hashDigest (unSideID (sideID sideA))
                     , hashDigest (unSideID (sideID sideB))
                     , encodeElement group x
                     , encodeElement group y
                     , encodeElement group k
                     ]
        Symmetric{bothSides} ->
          mconcat [ hashDigest password
                  , hashDigest (unSideID (sideID bothSides))
                  , symmetricElements
                  , encodeElement group k
                  ]

    symmetricElements =
      let [ firstMessage, secondMessage ] = sort [ encodeElement group inbound, encodeElement group outbound ]
      in firstMessage <> secondMessage
createSessionKey
  :: (Group group, HashAlgorithm hashAlgorithm)
  => Protocol group hashAlgorithm  -- ^ The protocol used for this exchange
  -> Element group  -- ^ The outbound message, generated by this, \(X^{\star}\), or either side if symmetric
  -> Element group  -- ^ The inbound message, generated by the other side, \(Y^{\star}\), or either side if symmetric
  -> Element group  -- ^ The calculated key material, \(K\)
  -> Password  -- ^ The shared secret password
  -> ByteString
createSessionKey Protocol{group, hashAlgorithm, relation} outbound inbound k (Password password) =
  hashDigest transcript

  where
    -- The protocol expects that when we include the hash of various
    -- components (e.g. the password) as input for the session key hash,
    -- that we use the *byte* representation of these elements.
    hashDigest :: ByteArrayAccess input => input -> ByteString
    hashDigest thing = ByteArray.convert (hashWith hashAlgorithm thing)

    transcript =
      case relation of
        Asymmetric{sideA, sideB, us} ->
          let (x, y) = case us of
                         SideA -> (inbound, outbound)
                         SideB -> (outbound, inbound)
          in mconcat [ hashDigest password
                     , hashDigest (unSideID (sideID sideA))
                     , hashDigest (unSideID (sideID sideB))
                     , encodeElement group x
                     , encodeElement group y
                     , encodeElement group k
                     ]
        Symmetric{bothSides} ->
          mconcat [ hashDigest password
                  , hashDigest (unSideID (sideID bothSides))
                  , symmetricElements
                  , encodeElement group k
                  ]

    symmetricElements =
      let [ firstMessage, secondMessage ] = sort [ encodeElement group inbound, encodeElement group outbound ]
      in firstMessage <> secondMessage
createSessionKey Protocol{group, hashAlgorithm, relation} outbound inbound k (Password password) =
  hashDigest transcript

  where
    -- The protocol expects that when we include the hash of various
    -- components (e.g. the password) as input for the session key hash,
    -- that we use the *byte* representation of these elements.
    hashDigest :: ByteArrayAccess input => input -> ByteString
    hashDigest thing = ByteArray.convert (hashWith hashAlgorithm thing)

    transcript =
      case relation of
        Asymmetric{sideA, sideB, us} ->
          let (x, y) = case us of
                         SideA -> (inbound, outbound)
                         SideB -> (outbound, inbound)
          in mconcat [ hashDigest password
                     , hashDigest (unSideID (sideID sideA))
                     , hashDigest (unSideID (sideID sideB))
                     , encodeElement group x
                     , encodeElement group y
                     , encodeElement group k
                     ]
        Symmetric{bothSides} ->
          mconcat [ hashDigest password
                  , hashDigest (unSideID (sideID bothSides))
                  , symmetricElements
                  , encodeElement group k
                  ]

    symmetricElements =
      let [ firstMessage, secondMessage ] = sort [ encodeElement group inbound, encodeElement group outbound ]
      in firstMessage <> secondMessage