diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,13 @@
 ### Added
 ### Changed
 
+## [0.2.2.0] - 2025-02-22
+### Added
+- SHA256 support, thanks [@jmazon](https://github.com/jmazon)
+- Ed25519 to Curve25519 conversion support, thanks [@jmazon](https://github.com/jmazon)
+### Changed
+- Version bounds relaxed
+
 ## [0.2.1.0] - 2023-02-17
 ### Changed
 - Fix Show instances formatting, and add instances for Keypairs, thanks [@NicolasT](https://github.com/NicolasT)
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Saltine 0.2.1.0 [![Hackage version](https://img.shields.io/hackage/v/saltine.svg?colorB=4FB900)](https://hackage.haskell.org/package/saltine)
+# Saltine 0.2.2.0 [![Hackage version](https://img.shields.io/hackage/v/saltine.svg?colorB=4FB900)](https://hackage.haskell.org/package/saltine)
 
 A Haskell binding for @jedisct1's portable binding for djb's
 NaCl. **This is an early release.** Please try it out, but don't just
@@ -43,22 +43,22 @@
 provide even more simplicity and safety to the usage of cryptography.
 
 Note that it's still possible to shoot yourself in the foot pretty
-easily using Saltine. Nonces must always be unique which must be managed 
+easily using Saltine. Nonces must always be unique which must be managed
 by the library user.
 [`Crypto.Saltine.Core.Stream`](https://github.com/tel/saltine/blob/master/src/Crypto/Saltine/Core/Stream.hs)
-produces messages which can beundetectably tampered with in-flight. 
-Keys are insecurely read from disk—they may be copied and then paged 
+produces messages which can beundetectably tampered with in-flight.
+Keys are insecurely read from disk—they may be copied and then paged
 back to disk.
 
-When uncertain, use [`Crypto.Saltine.Core.SecretBox`](https://github.com/tel/saltine/blob/master/src/Crypto/Saltine/Core/SecretBox.hs) 
+When uncertain, use [`Crypto.Saltine.Core.SecretBox`](https://github.com/tel/saltine/blob/master/src/Crypto/Saltine/Core/SecretBox.hs)
 and [`Crypto.Saltine.Core.Box`](https://github.com/tel/saltine/blob/master/src/Crypto/Saltine/Core/Box.hs).
-If you can think of ways to use Haskell's type system to enforce 
+If you can think of ways to use Haskell's type system to enforce
 security invariants, please suggest them.
 
-To use it on Windows systems, download 
-[a prebuild libsodium-\*-stable-mingw.tar.gz file](https://download.libsodium.org/libsodium/releases/) 
-and copy the files in `libsodium-win64`  into the equivalent places 
-in `C:\Program Files\Haskell Platform\*\mingw`. Then just add saltine 
+To use it on Windows systems, download
+[a prebuild libsodium-\*-stable-mingw.tar.gz file](https://download.libsodium.org/libsodium/releases/)
+and copy the files in `libsodium-win64`  into the equivalent places
+in `C:\Program Files\Haskell Platform\*\mingw`. Then just add saltine
 to your cabal file and watch it go.
 
 Tested with [`libsodium-1.0.18`](https://download.libsodium.org/libsodium/releases/).
diff --git a/saltine.cabal b/saltine.cabal
--- a/saltine.cabal
+++ b/saltine.cabal
@@ -1,7 +1,7 @@
 cabal-version: 2.0
 
 name:                saltine
-version:             0.2.1.0
+version:             0.2.2.0
 synopsis:            Cryptography that's easy to digest (NaCl/libsodium bindings).
 description:
 
@@ -85,11 +85,11 @@
   default-language:   Haskell2010
   build-depends:
                 base        >= 4.5    && < 5
-              , bytestring  >= 0.10.8 && < 0.12
-              , deepseq    ^>= 1.4
+              , bytestring  >= 0.10.8 && < 0.13
+              , deepseq     >= 1.4    && < 1.6
               , profunctors >= 5.3    && < 5.7
               , hashable
-              , text       ^>= 1.2 || ^>= 2.0
+              , text       ^>= 1.2 || >= 2.0 && < 2.2
 
 test-suite tests
   type:    exitcode-stdio-1.0
diff --git a/src/Crypto/Saltine/Core/Hash.hs b/src/Crypto/Saltine/Core/Hash.hs
--- a/src/Crypto/Saltine/Core/Hash.hs
+++ b/src/Crypto/Saltine/Core/Hash.hs
@@ -41,18 +41,28 @@
 -- This is version 2010.08.30 of the hash.html web page. Information
 -- about SipHash has been added.
 module Crypto.Saltine.Core.Hash (
+  -- * Documentation
   ShorthashKey,
   hash,
   shorthash, newShorthashKey,
   GenerichashKey,
   newGenerichashKey,
   GenerichashOutLen,
-  generichashOutLen, generichash
+  generichashOutLen, generichash,
+
+  -- * Advanced: SHA-2
+  --
+  -- | The SHA-256 function is provided for interoperability with
+  -- other applications. If you are looking for a generic hash
+  -- function and not specifically SHA-2, using 'generichash'
+  -- (BLAKE2b) might be a better choice.
+  sha256
   ) where
 
 import Crypto.Saltine.Internal.Hash
             ( c_hash
             , c_generichash
+            , c_hash_sha256
             , shorthash
             , ShorthashKey(..)
             , GenerichashKey(..)
@@ -101,3 +111,8 @@
 generichash (GhK k) m (GhOL outLen) = snd . buildUnsafeByteString outLen $ \ph ->
   constByteStrings [k, m] $ \[(pk, _), (pm, _)] ->
     c_generichash ph (fromIntegral outLen) pm (fromIntegral $ S.length m) pk (fromIntegral $ S.length k)
+
+-- | Computes a SHA256 hash.
+sha256 :: ByteString -> ByteString
+sha256 m = snd . buildUnsafeByteString Bytes.hash_sha256_bytes $ \ph ->
+  constByteStrings [m] $ \[(pm,_)] -> c_hash_sha256 ph pm (fromIntegral $ S.length m)
diff --git a/src/Crypto/Saltine/Core/Sign.hs b/src/Crypto/Saltine/Core/Sign.hs
--- a/src/Crypto/Saltine/Core/Sign.hs
+++ b/src/Crypto/Saltine/Core/Sign.hs
@@ -27,10 +27,19 @@
 -- This is current information as of 2013 June 6.
 
 module Crypto.Saltine.Core.Sign (
+  -- * Documentation
   SecretKey, PublicKey, Keypair(..), Signature,
   newKeypair,
   sign, signOpen,
-  signDetached, signVerifyDetached
+  signDetached, signVerifyDetached,
+
+  -- * Advanced: Ed25519 to Curve25519
+  --
+  -- | Ed25519 keys can be converted to X25519 keys, so that the same
+  -- key pair can be used both for authenticated encryption
+  -- ("Crypto.Saltine.Core.Box") and for signatures
+  -- ("Crypto.Saltine.Core.Sign").
+  signPublicKeyToScalarMult, signSecretKeyToScalarMult
   ) where
 
 import Crypto.Saltine.Internal.Sign
@@ -39,6 +48,8 @@
             , c_sign_open
             , c_sign_detached
             , c_sign_verify_detached
+            , c_sign_ed25519_pk_to_curve25519
+            , c_sign_ed25519_sk_to_curve25519
             , SecretKey(..)
             , PublicKey(..)
             , Keypair(..)
@@ -51,6 +62,7 @@
 import System.IO.Unsafe
 
 import qualified Crypto.Saltine.Internal.Sign as Bytes
+import qualified Crypto.Saltine.Internal.ScalarMult as SM
 import qualified Data.ByteString              as S
 
 -- | Creates a random key of the correct size for 'sign' and
@@ -128,4 +140,22 @@
         return (res == 0)
   where len = S.length sm
 
+-- | Converts an Ed25519 public key to an X25519 public key.
+signPublicKeyToScalarMult :: PublicKey -> Maybe SM.GroupElement
+signPublicKeyToScalarMult (PK pk) = unsafePerformIO $ do
+  (err,x) <- buildUnsafeByteString' SM.scalarmult_bytes $ \xbuf ->
+    constByteStrings [pk] $ \[(edbuf,_)] ->
+      c_sign_ed25519_pk_to_curve25519 xbuf edbuf
+  case err of
+    0 -> return $ Just $ SM.GE x
+    _ -> return   Nothing
 
+-- | Converts an Ed25519 secret key to an X25519 secret key.
+signSecretKeyToScalarMult :: SecretKey -> Maybe SM.Scalar
+signSecretKeyToScalarMult (SK sk) = unsafePerformIO $ do
+  (err,x) <- buildUnsafeByteString' SM.scalarmult_bytes $ \xbuf ->
+    constByteStrings [sk] $ \[(edbuf,_)] ->
+      c_sign_ed25519_sk_to_curve25519 xbuf edbuf
+  case err of
+    0 -> return $ Just $ SM.Sc x
+    _ -> return   Nothing
diff --git a/src/Crypto/Saltine/Internal/Hash.hs b/src/Crypto/Saltine/Internal/Hash.hs
--- a/src/Crypto/Saltine/Internal/Hash.hs
+++ b/src/Crypto/Saltine/Internal/Hash.hs
@@ -14,9 +14,11 @@
   , shorthash_keybytes
   , generichash_bytes_max
   , generichash_keybytes_max
+  , hash_sha256_bytes
   , c_hash
   , c_shorthash
   , c_generichash
+  , c_hash_sha256
   , nullShKey
   , shorthash
   , ShorthashKey(..)
@@ -85,7 +87,7 @@
 
 newtype GenerichashOutLen = GhOL { unGhOL :: Int } deriving (Eq, Ord, Hashable, Data, Typeable, Generic, NFData)
 
-hash_bytes, shorthash_bytes, shorthash_keybytes, generichash_bytes_max, generichash_keybytes_max :: Int
+hash_bytes, shorthash_bytes, shorthash_keybytes, generichash_bytes_max, generichash_keybytes_max, hash_sha256_bytes :: Int
 
 -- Hashes
 -- | The size of a hash resulting from
@@ -103,6 +105,9 @@
 -- | The maximum key size of the generic hash function
 -- 'Crypto.Saltine.Core.Hash.generichash'
 generichash_keybytes_max = fromIntegral c_crypto_generichash_keybytes_max
+-- | The size of a hash resulting from
+-- 'Crypto.Saltine.Core.Hash.sha256'.
+hash_sha256_bytes = fromIntegral c_crypto_hash_sha256_bytes
 
 -- src/libsodium/crypto_generichash/crypto_generichash.c
 foreign import ccall "crypto_generichash_bytes_max"
@@ -122,7 +127,12 @@
 foreign import ccall "crypto_shorthash_keybytes"
   c_crypto_shorthash_keybytes :: CSize
 
+-- src/libsodium/crypto_hash/sha256/hash_sha256.c
+-- src/libsodium/include/sodium/crypto_hash_sha256.h
+foreign import ccall "crypto_hash_sha256_bytes"
+  c_crypto_hash_sha256_bytes :: CSize
 
+
 foreign import ccall "crypto_hash"
   c_hash :: Ptr CChar
          -- ^ Output hash buffer
@@ -158,5 +168,15 @@
                 -- ^ Constant Key buffer
                 -> CULLong
                 -- ^ Key buffer length
+                -> IO CInt
+                -- ^ Always 0
+
+foreign import ccall "crypto_hash_sha256"
+  c_hash_sha256 :: Ptr CChar
+                -- ^ Output hash buffer
+                -> Ptr CChar
+                -- ^ Constant message buffer
+                -> CULLong
+                -- ^ Message buffer length
                 -> IO CInt
                 -- ^ Always 0
diff --git a/src/Crypto/Saltine/Internal/Sign.hs b/src/Crypto/Saltine/Internal/Sign.hs
--- a/src/Crypto/Saltine/Internal/Sign.hs
+++ b/src/Crypto/Saltine/Internal/Sign.hs
@@ -17,6 +17,8 @@
   , c_sign_open
   , c_sign_detached
   , c_sign_verify_detached
+  , c_sign_ed25519_pk_to_curve25519
+  , c_sign_ed25519_sk_to_curve25519
   , SecretKey(..)
   , PublicKey(..)
   , Keypair(..)
@@ -180,3 +182,17 @@
                            -> Ptr CChar
                            -- ^ Public key buffer
                            -> IO CInt
+
+foreign import ccall "crypto_sign_ed25519_pk_to_curve25519"
+    c_sign_ed25519_pk_to_curve25519 :: Ptr CChar
+                                    -- ^ X25519 public key output buffer
+                                    -> Ptr CChar
+                                    -- ^ Ed25519 public key buffer
+                                    -> IO CInt
+
+foreign import ccall "crypto_sign_ed25519_sk_to_curve25519"
+    c_sign_ed25519_sk_to_curve25519 :: Ptr CChar
+                                    -- ^ X25519 secret key output buffer
+                                    -> Ptr CChar
+                                    -- ^ Ed25519 secret key buffer
+                                    -> IO CInt
diff --git a/tests/HashProperties.hs b/tests/HashProperties.hs
--- a/tests/HashProperties.hs
+++ b/tests/HashProperties.hs
@@ -28,6 +28,9 @@
     testProperty "Hash of empty ByteString is correct"
     $ \(Message bs) -> (bs == S.empty) ==> hash bs == (read hashEmptyBS :: S.ByteString),
 
+    testProperty "SHA256 of empty ByteString is correct"
+    $ \(Message bs) -> (bs == S.empty) ==> sha256 bs == (read sha256EmptyBS :: S.ByteString),
+
     testProperty "No two shorthashes are alike"
     $ \(Message bs1, Message bs2) -> bs1 /= bs2 ==> shorthash shKey bs1 /= shorthash shKey bs2,
 
@@ -37,10 +40,14 @@
     testProperty "No two generic hashes are alike"
     $ \(Message bs1, Message bs2) -> bs1 /= bs2 ==> generichash ghKey bs1 ghOutLen /= generichash ghKey bs2 ghOutLen,
 
+    testProperty "No two SHA256 hashes are alike"
+    $ \(Message bs1, Message bs2) -> bs1 /= bs2 ==> sha256 bs1 /= sha256 bs2,
+
     testProperty "Different keys produce different generichashes"
     $ \(Message bs) -> generichash ghKey bs ghOutLen /= generichash ghKey2 bs ghOutLen
 
     ]
 
   where
-    hashEmptyBS = "\"\207\131\225\&5~\239\184\189\241T(P\214m\128\a\214 \228\ENQ\vW\NAK\220\131\244\169!\211l\233\206G\208\209<]\133\242\176\255\131\CAN\210\135~\236/c\185\&1\189GAz\129\165\&82z\249'\218>\""
+    hashEmptyBS   = "\"\207\131\225\&5~\239\184\189\241T(P\214m\128\a\214 \228\ENQ\vW\NAK\220\131\244\169!\211l\233\206G\208\209<]\133\242\176\255\131\CAN\210\135~\236/c\185\&1\189GAz\129\165\&82z\249'\218>\""
+    sha256EmptyBS = "\"\227\176\196B\152\252\FS\DC4\154\251\244\200\153o\185$'\174A\228d\155\147L\164\149\153\ESCxR\184U\""
diff --git a/tests/SignProperties.hs b/tests/SignProperties.hs
--- a/tests/SignProperties.hs
+++ b/tests/SignProperties.hs
@@ -5,10 +5,15 @@
   ) where
 
 import           Util
+import           Crypto.Saltine.Class
+
 import           Crypto.Saltine.Core.Sign
 import           Crypto.Saltine.Internal.Sign
-
+import qualified Crypto.Saltine.Core.Box              as Box
+import           Crypto.Saltine.Core.ScalarMult       ()
+import           Crypto.Saltine.Internal.ScalarMult
 import qualified Data.ByteString                      as S
+import           Data.Maybe                           (fromJust)
 import           Test.Framework.Providers.QuickCheck2
 import           Test.Framework
 import           Test.QuickCheck
@@ -19,7 +24,9 @@
   let sk1 = secretKey kp1
   let pk1 = publicKey kp1
   kp2 <- newKeypair
+  let sk2 = secretKey kp2
   let pk2 = publicKey kp2
+  n <- Box.newNonce
 
   return $ testGroup "...Internal.Sign" [
 
@@ -38,6 +45,15 @@
 
     testProperty "Rejects message with mismatched key w/ detached signature"
     $ \(Message bs) -> not (S.null bs) ==>
-                         not (signVerifyDetached pk2 (signDetached sk1 bs) bs)
+                         not (signVerifyDetached pk2 (signDetached sk1 bs) bs),
 
+    testProperty "Ed25515 -> Curve25519 conversion produces valid keypairs"
+    $ \(Message bs) ->
+        let p1 = fromJust . decode . unGE . fromJust $ signPublicKeyToScalarMult pk1
+            s1 = fromJust . decode . unSc . fromJust $ signSecretKeyToScalarMult sk1
+
+            p2 = fromJust . decode . unGE . fromJust $ signPublicKeyToScalarMult pk2
+            s2 = fromJust . decode . unSc . fromJust $ signSecretKeyToScalarMult sk2
+
+        in  (Box.boxOpen p2 s1 n (Box.box p1 s2 n bs)) == Just bs
     ]
diff --git a/tests/StreamProperties.hs b/tests/StreamProperties.hs
--- a/tests/StreamProperties.hs
+++ b/tests/StreamProperties.hs
@@ -23,8 +23,8 @@
               ==> S.length (stream k n len) == len,
 
     testProperty "xor munges input"
-    $ \(Message bs) -> not (S.null bs)
-                       ==> xor k n bs /= bs,
+    $ \(ByteString32 bs) ->     -- ByteString32 so we don't fail the test by pure chance, e.g. in case of 1-byte bytestrings
+        xor k n bs /= bs,
 
     testProperty "xor is involutive"
     $ \(Message bs) -> xor k n (xor k n bs) == bs
