open_ :: Maybe Chain
      -> SecretKey Noise         -- ^ Receiver secret key
      -> Maybe (PublicKey Noise) -- ^ Sender public key (optional)
      -> ByteString
      -> Maybe (ByteString, Chain)
open_ chain recvSK sendPK encText =
     verifyMAC
  >> verifyDecKey sendPK
  >> verifyCT
  where
    ephPK       = PublicKey $ B.take 32 encText
    encSenderPK = B.take 32 $ B.drop 32 encText
    hdrMAC      = Poly1305.Auth $ B.take 16 $ B.drop 64 encText

    chainIn = maybe chain1 _chainBS chain
      where chain1 = B8.pack "Noise_Box_IV" <> B.replicate 32 0x0

    -- Key exchange
    dh1 = curve25519 recvSK ephPK
    dh2 = maybe dh1 (curve25519 recvSK) sendPK

    -- Key derivation, step one
    key1 = SecretKey $ nhash (txt <> dh1 <> chainIn)
      where txt = B8.pack "Noise_Box_KDF1"

    -- Block encryption, key derivation step two
    (chainTmp, macKey)
      = ( B.take 32 block
        , B.drop 32 block)
      where block = ncipher (B.replicate 64 0x0) key1

    -- MAC verification
    verifyMAC :: Maybe Bool
    verifyMAC =
      case Poly1305.verify (SecretKey macKey) hdrMAC (B.take 64 encText) of
        True  -> Just True
        False -> Nothing

    senderKey = PublicKey $ B.drop 64 (ncipher block key1)
      where block = chainTmp <> macKey <> encSenderPK

    -- Sender key verification
    verifyDecKey :: Maybe (PublicKey Noise) -> Maybe Bool
    verifyDecKey Nothing
      | equalPK senderKey ephPK = Just True
      | otherwise               = Nothing
    verifyDecKey (Just pk)
      | equalPK senderKey pk = Just True
      | otherwise            = Nothing

    -- Key derivation, step three
    key2 = Chain $ nhash (txt <> dh2 <> chainTmp)
      where txt = B8.pack "Noise_Box_KDF2"

    verifyCT :: Maybe (ByteString, Chain)
    verifyCT = openCiphertext key2 (Just $ B.take 80 encText) (B.drop 80 encText)

--------------------------------------------------------------------------------
-- Utils
open_ :: Maybe Chain
      -> SecretKey Noise         -- ^ Receiver secret key
      -> Maybe (PublicKey Noise) -- ^ Sender public key (optional)
      -> ByteString
      -> Maybe (ByteString, Chain)
open_ chain recvSK sendPK encText =
     verifyMAC
  >> verifyDecKey sendPK
  >> verifyCT
  where
    ephPK       = PublicKey $ B.take 32 encText
    encSenderPK = B.take 32 $ B.drop 32 encText
    hdrMAC      = Poly1305.Auth $ B.take 16 $ B.drop 64 encText

    chainIn = maybe chain1 _chainBS chain
      where chain1 = B8.pack "Noise_Box_IV" <> B.replicate 32 0x0

    -- Key exchange
    dh1 = curve25519 recvSK ephPK
    dh2 = maybe dh1 (curve25519 recvSK) sendPK

    -- Key derivation, step one
    key1 = SecretKey $ nhash (txt <> dh1 <> chainIn)
      where txt = B8.pack "Noise_Box_KDF1"

    -- Block encryption, key derivation step two
    (chainTmp, macKey)
      = ( B.take 32 block
        , B.drop 32 block)
      where block = ncipher (B.replicate 64 0x0) key1

    -- MAC verification
    verifyMAC :: Maybe Bool
    verifyMAC =
      case Poly1305.verify (SecretKey macKey) hdrMAC (B.take 64 encText) of
        True  -> Just True
        False -> Nothing

    senderKey = PublicKey $ B.drop 64 (ncipher block key1)
      where block = chainTmp <> macKey <> encSenderPK

    -- Sender key verification
    verifyDecKey :: Maybe (PublicKey Noise) -> Maybe Bool
    verifyDecKey Nothing
      | equalPK senderKey ephPK = Just True
      | otherwise               = Nothing
    verifyDecKey (Just pk)
      | equalPK senderKey pk = Just True
      | otherwise            = Nothing

    -- Key derivation, step three
    key2 = Chain $ nhash (txt <> dh2 <> chainTmp)
      where txt = B8.pack "Noise_Box_KDF2"

    verifyCT :: Maybe (ByteString, Chain)
    verifyCT = openCiphertext key2 (Just $ B.take 80 encText) (B.drop 80 encText)

--------------------------------------------------------------------------------
-- Utils
open_ chain recvSK sendPK encText =
     verifyMAC
  >> verifyDecKey sendPK
  >> verifyCT
  where
    ephPK       = PublicKey $ B.take 32 encText
    encSenderPK = B.take 32 $ B.drop 32 encText
    hdrMAC      = Poly1305.Auth $ B.take 16 $ B.drop 64 encText

    chainIn = maybe chain1 _chainBS chain
      where chain1 = B8.pack "Noise_Box_IV" <> B.replicate 32 0x0

    -- Key exchange
    dh1 = curve25519 recvSK ephPK
    dh2 = maybe dh1 (curve25519 recvSK) sendPK

    -- Key derivation, step one
    key1 = SecretKey $ nhash (txt <> dh1 <> chainIn)
      where txt = B8.pack "Noise_Box_KDF1"

    -- Block encryption, key derivation step two
    (chainTmp, macKey)
      = ( B.take 32 block
        , B.drop 32 block)
      where block = ncipher (B.replicate 64 0x0) key1

    -- MAC verification
    verifyMAC :: Maybe Bool
    verifyMAC =
      case Poly1305.verify (SecretKey macKey) hdrMAC (B.take 64 encText) of
        True  -> Just True
        False -> Nothing

    senderKey = PublicKey $ B.drop 64 (ncipher block key1)
      where block = chainTmp <> macKey <> encSenderPK

    -- Sender key verification
    verifyDecKey :: Maybe (PublicKey Noise) -> Maybe Bool
    verifyDecKey Nothing
      | equalPK senderKey ephPK = Just True
      | otherwise               = Nothing
    verifyDecKey (Just pk)
      | equalPK senderKey pk = Just True
      | otherwise            = Nothing

    -- Key derivation, step three
    key2 = Chain $ nhash (txt <> dh2 <> chainTmp)
      where txt = B8.pack "Noise_Box_KDF2"

    verifyCT :: Maybe (ByteString, Chain)
    verifyCT = openCiphertext key2 (Just $ B.take 80 encText) (B.drop 80 encText)

--------------------------------------------------------------------------------
-- Utils