diff --git a/hnix-store-core.cabal b/hnix-store-core.cabal
--- a/hnix-store-core.cabal
+++ b/hnix-store-core.cabal
@@ -1,5 +1,5 @@
 name:                hnix-store-core
-version:             0.1.0.0
+version:             0.2.0.0
 synopsis:            Core effects for interacting with the Nix store.
 description:
         This package contains types and functions needed to describe
@@ -18,15 +18,15 @@
 
 library
   exposed-modules:     System.Nix.Base32
-                     , System.Nix.Build
-                     , System.Nix.Derivation
-                     , System.Nix.GC
                      , System.Nix.Hash
                      , System.Nix.Internal.Hash
+                     , System.Nix.Internal.Signature
+                     , System.Nix.Internal.StorePath
                      , System.Nix.Nar
-                     , System.Nix.Path
                      , System.Nix.ReadonlyStore
-                     , System.Nix.Store
+                     , System.Nix.Signature
+                     , System.Nix.StorePath
+                     , System.Nix.StorePathMetadata
                      , System.Nix.Util
   build-depends:       base >=4.10 && <5
                      , base16-bytestring
@@ -42,7 +42,9 @@
                      , hashable
                      , mtl
                      , regex-base
-                     , regex-tdfa-text
+                     , regex-tdfa >= 1.3.1.0
+                     , saltine
+                     , time
                      , text
                      , unix
                      , unordered-containers
diff --git a/src/System/Nix/Base32.hs b/src/System/Nix/Base32.hs
--- a/src/System/Nix/Base32.hs
+++ b/src/System/Nix/Base32.hs
@@ -9,23 +9,31 @@
 
 -- | Encode a 'BS.ByteString' in Nix's base32 encoding
 encode :: BS.ByteString -> T.Text
-encode c = T.pack $ concatMap char32 [nChar - 1, nChar - 2 .. 0]
+encode c = T.pack $ map char32 [nChar - 1, nChar - 2 .. 0]
   where
     digits32 = V.fromList "0123456789abcdfghijklmnpqrsvwxyz"
-    -- The base32 encoding is 8/5's as long as the base256 digest.  This `+ 1`
-    -- `- 1` business is a bit odd, but has always been used in C++ since the
-    -- base32 truncation was added in was first added in
-    -- d58a11e019813902b6c4547ca61a127938b2cc20.
+    -- Each base32 character gives us 5 bits of information, while
+    -- each byte gives is 8. Because 'div' rounds down, we need to add
+    -- one extra character to the result, and because of that extra 1
+    -- we need to subtract one from the number of bits in the
+    -- bytestring to cover for the case where the number of bits is
+    -- already a factor of 5. Thus, the + 1 outside of the 'div' and
+    -- the - 1 inside of it.
     nChar = fromIntegral $ ((BS.length c * 8 - 1) `div` 5) + 1
 
-    char32 :: Integer -> [Char]
-    char32 i = [digits32 V.! digitInd]
+    byte = BS.index c . fromIntegral
+
+    -- May need to switch to a more efficient calculation at some
+    -- point.
+    bAsInteger :: Integer
+    bAsInteger = sum [fromIntegral (byte j) * (256 ^ j)
+                     | j <- [0 .. BS.length c - 1]
+                     ]
+
+    char32 :: Integer -> Char
+    char32 i = digits32 V.! digitInd
       where
-        byte j   = BS.index c (fromIntegral j)
-        fromIntegral' :: Num b => Integer -> b
-        fromIntegral' = fromIntegral
-        digitInd = fromIntegral' $
-                   sum [fromIntegral (byte j) * (256^j)
-                       | j <- [0 .. BS.length c - 1]]
+        digitInd = fromIntegral $
+                   bAsInteger
                    `div` (32^i)
                    `mod` 32
diff --git a/src/System/Nix/Build.hs b/src/System/Nix/Build.hs
deleted file mode 100644
--- a/src/System/Nix/Build.hs
+++ /dev/null
@@ -1,50 +0,0 @@
-{-# LANGUAGE RecordWildCards #-}
-{-|
-Description : Build related types
-Maintainer  : srk <srk@48.io>
-|-}
-module System.Nix.Build (
-    BuildMode(..)
-  , BuildStatus(..)
-  , BuildResult(..)
-  , buildSuccess
-  ) where
-
-import           Data.Text                 (Text)
-import           Data.HashSet              (HashSet)
-import           System.Nix.Path           (Path)
-
-data BuildMode = Normal | Repair | Check
-  deriving (Eq, Ord, Enum, Show)
-
-data BuildStatus =
-    Built
-  | Substituted
-  | AlreadyValid
-  | PermanentFailure
-  | InputRejected
-  | OutputRejected
-  | TransientFailure -- possibly transient
-  | CachedFailure    -- no longer used
-  | TimedOut
-  | MiscFailure
-  | DependencyFailed
-  | LogLimitExceeded
-  | NotDeterministic
-  deriving (Eq, Ord, Enum, Show)
-
-
--- | Result of the build
-data BuildResult = BuildResult
-  { -- | build status, MiscFailure should be default
-    status             :: !BuildStatus
-  , -- | possible build error message
-    error              :: !(Maybe Text)
-  , -- | How many times this build was performed
-    timesBuilt         :: !Integer
-  , -- | If timesBuilt > 1, whether some builds did not produce the same result
-    isNonDeterministic :: !Bool
-    -- XXX: | startTime stopTime time_t
-  } deriving (Eq, Ord, Show)
-
-buildSuccess BuildResult{..} = status == Built || status == Substituted || status == AlreadyValid
diff --git a/src/System/Nix/Derivation.hs b/src/System/Nix/Derivation.hs
deleted file mode 100644
--- a/src/System/Nix/Derivation.hs
+++ /dev/null
@@ -1,30 +0,0 @@
-{-|
-Description : Derivation types
-Maintainer  : srk <srk@48.io>
-|-}
-
-module System.Nix.Derivation where
-
-
-import Data.Text (Text)
-import Data.HashMap.Strict (HashMap)
-import Data.HashSet (HashSet)
-import System.Nix.Path
-
-type OutputName = Text
-
-newtype DerivationInputs = DerivationInputs
-  { _unDerivationInputs :: HashMap Path (HashSet OutputName)
-  } deriving (Eq, Ord, Show)
-
-data Derivation = Derivation
-  { _derivationInputs    :: DerivationInputs
-  , _derivationOutputs   :: !(HashMap OutputName Path)
-    -- | Inputs that are sources
-  , _derivationInputSrcs :: !PathSet
-  , _derivationPlatform  :: !Text
-    -- | Path to builder
-  , _derivationBuilder   :: !Path
-  , _derivationArgs      :: ![Text]
-  , _derivationEnv       :: ![HashMap Text Text]
-  } deriving (Eq, Ord, Show)
diff --git a/src/System/Nix/GC.hs b/src/System/Nix/GC.hs
deleted file mode 100644
--- a/src/System/Nix/GC.hs
+++ /dev/null
@@ -1,47 +0,0 @@
-{-|
-Description : Garbage collection actions / options
-Maintainer  : srk <srk@48.io>
-|-}
-module System.Nix.GC (
-    Action(..)
-  , Options(..)
-  , Result(..)
-  ) where
-
-import           System.Nix.Path           (PathSet)
-
-{- Garbage collector operation:
-     - ReturnLive: return the set of paths reachable from
-       (i.e. in the closure of) the roots.
-     - ReturnDead: return the set of paths not reachable from
-       the roots.
-     - DeleteDead: actually delete the latter set.
-     - DeleteSpecific: delete the paths listed in
-        `pathsToDelete', insofar as they are not reachable.
--}
-
-data Action = ReturnLive | ReturnDead | DeleteDead | DeleteSpecific
-  deriving (Eq, Ord, Enum, Show)
-
--- | Garbage collector operation options
-data Options = Options
-  { -- | operation
-    operation      :: !Action
-    -- | If `ignoreLiveness' is set, then reachability from the roots is
-    -- ignored (dangerous!).  However, the paths must still be
-    -- unreferenced *within* the store (i.e., there can be no other
-    -- store paths that depend on them).
-  , ignoreLiveness :: !Bool
-    -- | For DeleteSpecific, the paths to delete
-  , pathsToDelete  :: !PathSet
-  , -- | Stop after at least `maxFreed` bytes have been freed
-    maxFreed       :: !Integer
-  } deriving (Eq, Ord, Show)
-
-data Result = Result
- { -- | Depending on the action, the GC roots, or the paths that would be or have been deleted
-   paths      :: !PathSet
- , -- |  For ReturnDead, DeleteDead and DeleteSpecific, the number of bytes that would be or was freed
-   bytesFreed :: !Integer
- } deriving (Eq, Ord, Show)
-
diff --git a/src/System/Nix/Hash.hs b/src/System/Nix/Hash.hs
--- a/src/System/Nix/Hash.hs
+++ b/src/System/Nix/Hash.hs
@@ -7,6 +7,7 @@
   , HNix.HashAlgorithm(..)
   , HNix.ValidAlgo(..)
   , HNix.NamedAlgo(..)
+  , HNix.SomeNamedDigest(..)
   , HNix.hash
   , HNix.hashLazy
 
diff --git a/src/System/Nix/Internal/Hash.hs b/src/System/Nix/Internal/Hash.hs
--- a/src/System/Nix/Internal/Hash.hs
+++ b/src/System/Nix/Internal/Hash.hs
@@ -9,6 +9,7 @@
 {-# LANGUAGE TypeApplications    #-}
 {-# LANGUAGE DataKinds           #-}
 {-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE ExistentialQuantification #-}
 
 module System.Nix.Internal.Hash where
 
@@ -71,6 +72,9 @@
 
 instance NamedAlgo 'SHA256 where
   algoName = "sha256"
+
+-- | A digest whose 'NamedAlgo' is not known at compile time.
+data SomeNamedDigest = forall a . NamedAlgo a => SomeDigest (Digest a)
 
 -- | Hash an entire (strict) 'BS.ByteString' as a single call.
 --
diff --git a/src/System/Nix/Internal/Signature.hs b/src/System/Nix/Internal/Signature.hs
new file mode 100644
--- /dev/null
+++ b/src/System/Nix/Internal/Signature.hs
@@ -0,0 +1,29 @@
+{-|
+Description : Nix-relevant interfaces to NaCl signatures.
+-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+module System.Nix.Internal.Signature where
+
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as BS
+import Data.Coerce (coerce)
+import Crypto.Saltine.Core.Sign (PublicKey)
+import Crypto.Saltine.Class (IsEncoding(..))
+import qualified Crypto.Saltine.Internal.ByteSizes as NaClSizes
+
+-- | A NaCl signature.
+newtype Signature = Signature ByteString deriving (Eq, Ord)
+
+instance IsEncoding Signature where
+  decode s
+    | BS.length s == NaClSizes.sign = Just (Signature s)
+    | otherwise = Nothing
+  encode = coerce
+
+-- | A detached NaCl signature attesting to a nix archive's validity.
+data NarSignature = NarSignature
+  { -- | The public key used to sign the archive.
+    publicKey :: PublicKey
+  , -- | The archive's signature.
+    sig :: Signature
+  } deriving (Eq, Ord)
diff --git a/src/System/Nix/Internal/StorePath.hs b/src/System/Nix/Internal/StorePath.hs
new file mode 100644
--- /dev/null
+++ b/src/System/Nix/Internal/StorePath.hs
@@ -0,0 +1,201 @@
+{-|
+Description : Representation of Nix store paths.
+-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE TypeInType #-} -- Needed for GHC 8.4.4 for some reason
+module System.Nix.Internal.StorePath where
+import System.Nix.Hash
+  ( HashAlgorithm(Truncated, SHA256)
+  , Digest
+  , encodeBase32
+  , SomeNamedDigest
+  )
+import Text.Regex.Base.RegexLike (makeRegex, matchTest)
+import Text.Regex.TDFA.Text (Regex)
+import Data.Text (Text)
+import Data.Text.Encoding (encodeUtf8)
+import GHC.TypeLits (Symbol, KnownSymbol, symbolVal)
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Char8 as BC
+import Data.Hashable (Hashable(..))
+import Data.HashSet (HashSet)
+import Data.Proxy (Proxy(..))
+
+-- | A path in a Nix store.
+--
+-- From the Nix thesis: A store path is the full path of a store
+-- object. It has the following anatomy: storeDir/hashPart-name.
+--
+-- @storeDir@: The root of the Nix store (e.g. \/nix\/store).
+--
+-- See the 'StoreDir' haddocks for details on why we represent this at
+-- the type level.
+data StorePath (storeDir :: StoreDir) = StorePath
+  { -- | The 160-bit hash digest reflecting the "address" of the name.
+    -- Currently, this is a truncated SHA256 hash.
+    storePathHash :: !(Digest StorePathHashAlgo)
+  , -- | The (typically human readable) name of the path. For packages
+    -- this is typically the package name and version (e.g.
+    -- hello-1.2.3).
+    storePathName :: !StorePathName
+  } deriving (Eq, Ord)
+
+instance Hashable (StorePath storeDir) where
+  hashWithSalt s (StorePath {..}) =
+    s `hashWithSalt` storePathHash `hashWithSalt` storePathName
+
+-- | The name portion of a Nix path.
+--
+-- 'unStorePathName' must only contain a-zA-Z0-9+._?=-, can't start
+-- with a -, and must have at least one character (i.e. it must match
+-- 'storePathNameRegex').
+newtype StorePathName = StorePathName
+  { -- | Extract the contents of the name.
+    unStorePathName :: Text
+  } deriving (Eq, Hashable, Ord)
+
+-- | The hash algorithm used for store path hashes.
+type StorePathHashAlgo = 'Truncated 20 'SHA256
+
+-- | A set of 'StorePath's.
+type StorePathSet storeDir = HashSet (StorePath storeDir)
+
+-- | An address for a content-addressable store path, i.e. one whose
+-- store path hash is purely a function of its contents (as opposed to
+-- paths that are derivation outputs, whose hashes are a function of
+-- the contents of the derivation file instead).
+--
+-- For backwards-compatibility reasons, the same information is
+-- encodable in multiple ways, depending on the method used to add the
+-- path to the store. These unfortunately result in separate store
+-- paths.
+data ContentAddressableAddress
+  = -- | The path is a plain file added via makeTextPath or
+    -- addTextToStore. It is addressed according to a sha256sum of the
+    -- file contents.
+    Text !(Digest 'SHA256)
+  | -- | The path was added to the store via makeFixedOutputPath or
+    -- addToStore. It is addressed according to some hash algorithm
+    -- applied to the nar serialization via some 'NarHashMode'.
+    Fixed !NarHashMode !SomeNamedDigest
+
+-- | Schemes for hashing a nix archive.
+--
+-- For backwards-compatibility reasons, there are two different modes
+-- here, even though 'Recursive' should be able to cover both.
+data NarHashMode
+  = -- | Require the nar to represent a non-executable regular file.
+    RegularFile
+  | -- | Hash an arbitrary nar, including a non-executable regular
+    -- file if so desired.
+    Recursive
+
+-- | A type-level representation of the root directory of a Nix store.
+--
+-- The extra complexity of type indices requires justification.
+-- Fundamentally, this boils down to the fact that there is little
+-- meaningful sense in which 'StorePath's rooted at different
+-- directories are of the same type, i.e. there are few if any
+-- non-trivial non-contrived functions or data types that could
+-- equally well accept 'StorePath's from different stores. In current
+-- practice, any real application dealing with Nix stores (including,
+-- in particular, the Nix expression language) only operates over one
+-- store root and only cares about 'StorePath's belonging to that
+-- root. One could imagine a use case that cares about multiple store
+-- roots at once (e.g. the normal \/nix\/store along with some private
+-- store at \/root\/nix\/store to contain secrets), but in that case
+-- distinguishing 'StorePath's that belong to one store or the other
+-- is even /more/ critical: Most operations will only be correct over
+-- one of the stores or another, and it would be an error to mix and
+-- match (e.g. a 'StorePath' in one store could not legitimately refer
+-- to one in another).
+--
+-- As of @5886bc5996537fbf00d1fcfbb29595b8ccc9743e@, the C++ Nix
+-- codebase contains 30 separate places where we assert that a given
+-- store dir is, in fact, in the store we care about; those run-time
+-- assertions could be completely removed if we had stronger types
+-- there. Moreover, there are dozens of other cases where input coming
+-- from the user, from serializations, etc. is parsed and then
+-- required to be in the appropriate store; this case is the
+-- equivalent of an existentially quantified version of 'StorePath'
+-- and, notably, requiring at runtime that the index matches the
+-- ambient store directory we're working in. In every case where a
+-- path is treated as a store path, there is exactly one legitimate
+-- candidate for the store directory it belongs to.
+--
+-- It may be instructive to consider the example of "chroot stores".
+-- Since Nix 2.0, it has been possible to have a store actually live
+-- at one directory (say, $HOME\/nix\/store) with a different
+-- effective store directory (say, \/nix\/store). Nix can build into
+-- a chroot store by running the builds in a mount namespace where the
+-- store is at the effective store directory, can download from a
+-- binary cache containing paths for the effective store directory,
+-- and can run programs in the store that expect to be living at the
+-- effective store directory (via nix run). When viewed as store paths
+-- (rather than random files in the filesystem), paths in a chroot
+-- store have nothing in common with paths in a non-chroot store that
+-- lives in the same directory, and a lot in common with paths in a
+-- non-chroot store that lives in the effective store directory of the
+-- store in question. Store paths in stores with the same effective
+-- store directory share the same hashing scheme, can be copied
+-- between each other, etc. Store paths in stores with different
+-- effective store directories have no relationship to each other that
+-- they don't have to arbitrary other files.
+type StoreDir = Symbol
+
+-- | Smart constructor for 'StorePathName' that ensures the underlying
+-- content invariant is met.
+makeStorePathName :: Text -> Maybe StorePathName
+makeStorePathName n = case matchTest storePathNameRegex n of
+  True  -> Just $ StorePathName n
+  False -> Nothing
+
+-- | Regular expression to match valid store path names.
+storePathNameRegex :: Regex
+storePathNameRegex = makeRegex r
+  where
+    r :: String
+    r = "[a-zA-Z0-9\\+\\-\\_\\?\\=][a-zA-Z0-9\\+\\-\\.\\_\\?\\=]*"
+
+-- | Copied from @RawFilePath@ in the @unix@ package, duplicated here
+-- to avoid the dependency.
+type RawFilePath = ByteString
+
+-- | Render a 'StorePath' as a 'RawFilePath'.
+storePathToRawFilePath
+  :: forall storeDir . (KnownStoreDir storeDir)
+  => StorePath storeDir
+  -> RawFilePath
+storePathToRawFilePath (StorePath {..}) = BS.concat
+    [ root
+    , "/"
+    , hashPart
+    , "-"
+    , name
+    ]
+  where
+    root = storeDirVal @storeDir
+    hashPart = encodeUtf8 $ encodeBase32 storePathHash
+    name = encodeUtf8 $ unStorePathName storePathName
+
+-- | Get a value-level representation of a 'KnownStoreDir'
+storeDirVal :: forall storeDir . (KnownStoreDir storeDir)
+            => ByteString
+storeDirVal = BC.pack $ symbolVal @storeDir Proxy
+
+-- | A 'StoreDir' whose value is known at compile time.
+--
+-- A valid instance of 'KnownStoreDir' should represent a valid path,
+-- i.e. all "characters" fit into bytes (as determined by the logic of
+-- 'BC.pack') and there are no 0 "characters". Currently this is not
+-- enforced, but it should be.
+type KnownStoreDir = KnownSymbol
diff --git a/src/System/Nix/Nar.hs b/src/System/Nix/Nar.hs
--- a/src/System/Nix/Nar.hs
+++ b/src/System/Nix/Nar.hs
@@ -18,6 +18,8 @@
   , localUnpackNar
   , narEffectsIO
   , putNar
+  , FilePathPart(..)
+  , filePathPart
   ) where
 
 import           Control.Applicative
@@ -42,7 +44,6 @@
 import           System.Posix.Files         (createSymbolicLink, fileSize, getFileStatus,
                                              isDirectory, readSymbolicLink)
 
-import           System.Nix.Path
 
 data NarEffects (m :: * -> *) = NarEffects {
     narReadFile   :: FilePath -> m BSL.ByteString
@@ -64,6 +65,17 @@
 
 data Nar = Nar { narFile :: FileSystemObject }
     deriving (Eq, Show)
+
+-- | A valid filename or directory name
+newtype FilePathPart = FilePathPart { unFilePathPart :: BSC.ByteString }
+  deriving (Eq, Ord, Show)
+
+-- | Construct FilePathPart from Text by checking that there
+--   are no '/' or '\\NUL' characters
+filePathPart :: BSC.ByteString -> Maybe FilePathPart
+filePathPart p = case BSC.any (`elem` ['/', '\NUL']) p of
+  False -> Just $ FilePathPart p
+  True  -> Nothing
 
 -- | A FileSystemObject (FSO) is an anonymous entity that can be NAR archived
 data FileSystemObject =
diff --git a/src/System/Nix/Path.hs b/src/System/Nix/Path.hs
deleted file mode 100644
--- a/src/System/Nix/Path.hs
+++ /dev/null
@@ -1,128 +0,0 @@
-{-|
-Description : Types and effects for interacting with the Nix store.
-Maintainer  : Shea Levy <shea@shealevy.com>
--}
-{-# LANGUAGE DataKinds                  #-}
-{-# LANGUAGE OverloadedStrings          #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-module System.Nix.Path
-  ( FilePathPart(..)
-  , PathHashAlgo
-  , Path(..)
-  , pathToText
-  , PathSet
-  , SubstitutablePathInfo(..)
-  , ValidPathInfo(..)
-  , PathName(..)
-  , filePathPart
-  , pathName
-  , Roots
-  ) where
-
-import           System.Nix.Hash           (Digest(..),
-                                            HashAlgorithm(Truncated, SHA256))
-import           System.Nix.Internal.Hash
-import qualified Data.ByteString           as BS
-import qualified Data.ByteString.Char8     as BSC
-import           Data.Hashable             (Hashable (..), hashPtrWithSalt)
-import           Data.HashMap.Strict       (HashMap)
-import           Data.HashSet              (HashSet)
-import           Data.Map.Strict           (Map)
-import           Data.Monoid
-import           Data.Text                 (Text)
-import qualified Data.Text                 as T
-import           System.IO.Unsafe          (unsafeDupablePerformIO)
-import           Text.Regex.Base.RegexLike (makeRegex, matchTest)
-import           Text.Regex.TDFA.Text      (Regex)
-
--- | The hash algorithm used for store path hashes.
-type PathHashAlgo = Truncated 20 SHA256
-
-
--- | The name portion of a Nix path.
---
--- Must be composed of a-z, A-Z, 0-9, +, -, ., _, ?, and =, can't
--- start with a ., and must have at least one character.
-newtype PathName = PathName
-  { pathNameContents :: Text -- ^ The contents of the path name
-  } deriving (Eq, Ord, Show, Hashable)
-
--- | A regular expression for matching a valid 'PathName'
-nameRegex :: Regex
-nameRegex =
-  makeRegex ("[a-zA-Z0-9\\+\\-\\_\\?\\=][a-zA-Z0-9\\+\\-\\.\\_\\?\\=]*" :: String)
-
--- | Construct a 'PathName', assuming the provided contents are valid.
-pathName :: Text -> Maybe PathName
-pathName n = case matchTest nameRegex n of
-  True  -> Just $ PathName n
-  False -> Nothing
-
--- | A path in a store.
-data Path = Path !(Digest PathHashAlgo) !PathName
-  deriving (Eq, Ord, Show)
-
-pathToText :: Text -> Path -> Text
-pathToText storeDir (Path h nm) = storeDir <> "/" <> encodeBase32 h <> "-" <> pathNameContents nm
-
-type PathSet = HashSet Path
-
--- | Information about substitutes for a 'Path'.
-data SubstitutablePathInfo = SubstitutablePathInfo
-  { -- | The .drv which led to this 'Path'.
-    deriver      :: !(Maybe Path)
-  , -- | The references of the 'Path'
-    references   :: !PathSet
-  , -- | The (likely compressed) size of the download of this 'Path'.
-    downloadSize :: !Integer
-  , -- | The size of the uncompressed NAR serialization of this
-    -- 'Path'.
-    narSize      :: !Integer
-  } deriving (Eq, Ord, Show)
-
--- | Information about @Path@
-data ValidPathInfo = ValidPathInfo
-  { -- | Path itself
-    path             :: !Path
-  , -- | The .drv which led to this 'Path'.
-    deriverVP        :: !(Maybe Path)
-  , -- | NAR hash
-    narHash          :: !Text
-  , -- | The references of the 'Path'
-    referencesVP     :: !PathSet
-  , -- | Registration time should be time_t
-    registrationTime :: !Integer
-  , -- | The size of the uncompressed NAR serialization of this
-    -- 'Path'.
-    narSizeVP        :: !Integer
-  , -- | Whether the path is ultimately trusted, that is, it's a
-    -- derivation output that was built locally.
-    ultimate         :: !Bool
-  , -- | Signatures
-    sigs             :: ![Text]
-  , -- | Content-addressed
-    -- Store path is computed from a cryptographic hash
-    -- of the contents of the path, plus some other bits of data like
-    -- the "name" part of the path.
-    --
-    -- ‘ca’ has one of the following forms:
-    -- * ‘text:sha256:<sha256 hash of file contents>’ (paths by makeTextPath() / addTextToStore())
-    -- * ‘fixed:<r?>:<ht>:<h>’ (paths by makeFixedOutputPath() / addToStore())
-    ca               :: !Text
-  } deriving (Eq, Ord, Show)
-
--- | A valid filename or directory name
-newtype FilePathPart = FilePathPart { unFilePathPart :: BSC.ByteString }
-  deriving (Eq, Ord, Show)
-
--- | Construct FilePathPart from Text by checking that there
---   are no '/' or '\\NUL' characters
-filePathPart :: BSC.ByteString -> Maybe FilePathPart
-filePathPart p = case BSC.any (`elem` ['/', '\NUL']) p of
-  False -> Just $ FilePathPart p
-  True  -> Nothing
-
-type Roots = Map Path Path
-
-instance Hashable Path where
-  hashWithSalt s (Path hash name) = s `hashWithSalt` hash `hashWithSalt` name
diff --git a/src/System/Nix/ReadonlyStore.hs b/src/System/Nix/ReadonlyStore.hs
--- a/src/System/Nix/ReadonlyStore.hs
+++ b/src/System/Nix/ReadonlyStore.hs
@@ -1,34 +1,32 @@
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeApplications #-}
-
+{-# LANGUAGE ScopedTypeVariables #-}
 module System.Nix.ReadonlyStore where
 
 import           Data.ByteString (ByteString)
-import           Data.ByteString.Base16 as Base16
+import qualified Data.ByteString as BS
 import qualified Data.HashSet as HS
-import           Data.Text (Text)
-import qualified Data.Text as T
 import           Data.Text.Encoding
 import           System.Nix.Hash
-import           System.Nix.Path
+import           System.Nix.StorePath
 
-makeStorePath :: Text -> Text -> Digest 'SHA256 -> Text -> Path
-makeStorePath storeDir ty h nm = Path storeHash (PathName nm)
+makeStorePath :: forall storeDir hashAlgo . (KnownStoreDir storeDir, NamedAlgo hashAlgo) => ByteString -> Digest hashAlgo -> StorePathName -> StorePath storeDir
+makeStorePath ty h nm = StorePath storeHash nm
   where
-    s = T.intercalate ":"
+    s = BS.intercalate ":"
       [ ty
-      , algoName @'SHA256
-      , encodeBase16 h
-      , storeDir
-      , nm
+      , encodeUtf8 $ algoName @hashAlgo
+      , encodeUtf8 $ encodeBase16 h
+      , storeDirVal @storeDir
+      , encodeUtf8 $ unStorePathName nm
       ]
-    storeHash = hash $ encodeUtf8 s
+    storeHash = hash s
 
-makeTextPath :: Text -> Text -> Digest 'SHA256 -> PathSet -> Path
-makeTextPath storeDir nm h refs = makeStorePath storeDir ty h nm
+makeTextPath :: (KnownStoreDir storeDir) => StorePathName -> Digest 'SHA256 -> StorePathSet storeDir -> StorePath storeDir
+makeTextPath nm h refs = makeStorePath ty h nm
   where
-    ty = T.intercalate ":" ("text" : map (pathToText storeDir) (HS.toList refs))
+    ty = BS.intercalate ":" ("text" : map storePathToRawFilePath (HS.toList refs))
 
-computeStorePathForText :: Text -> Text -> ByteString -> PathSet -> Path
-computeStorePathForText storeDir nm s refs = makeTextPath storeDir nm (hash s) refs
+computeStorePathForText :: (KnownStoreDir storeDir) => StorePathName -> ByteString -> StorePathSet storeDir -> StorePath storeDir
+computeStorePathForText nm s refs = makeTextPath nm (hash s) refs
diff --git a/src/System/Nix/Signature.hs b/src/System/Nix/Signature.hs
new file mode 100644
--- /dev/null
+++ b/src/System/Nix/Signature.hs
@@ -0,0 +1,9 @@
+{-|
+Description : Nix-relevant interfaces to NaCl signatures.
+-}
+module System.Nix.Signature
+  ( Signature
+  , NarSignature(..)
+  ) where
+
+import System.Nix.Internal.Signature
diff --git a/src/System/Nix/Store.hs b/src/System/Nix/Store.hs
deleted file mode 100644
--- a/src/System/Nix/Store.hs
+++ /dev/null
@@ -1,66 +0,0 @@
-{-|
-Description : Types and effects for interacting with the Nix store.
-Maintainer  : Shea Levy <shea@shealevy.com>
--}
-{-# LANGUAGE DataKinds #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-module System.Nix.Store
-  ( PathName, pathNameContents, pathName
-  , PathHashAlgo, Path(..)
-  , StoreEffects(..)
-  , SubstitutablePathInfo(..)
-  ) where
-
-import qualified Data.ByteString.Lazy as BS
-import Data.Text (Text)
-import Text.Regex.Base.RegexLike (makeRegex, matchTest)
-import Text.Regex.TDFA.Text (Regex)
-import Data.Hashable (Hashable(..), hashPtrWithSalt)
-import Data.HashSet (HashSet)
-import Data.HashMap.Strict (HashMap)
-import System.IO.Unsafe (unsafeDupablePerformIO)
-
-import System.Nix.Hash (Digest)
-import System.Nix.Path
-import System.Nix.Nar
-
-
--- | Interactions with the Nix store.
---
--- @rootedPath@: A path plus a witness to the fact that the path is
--- reachable from a root whose liftime is at least as long as the
--- @rootedPath@ reference itself, when the implementation supports
--- this.
---
--- @validPath@: A @rootedPath@ plus a witness to the fact that the
--- path is valid. On implementations that support temporary roots,
--- this implies that the path will remain valid so long as the
--- reference is held.
---
--- @m@: The monad the effects operate in.
-data StoreEffects rootedPath validPath m =
-  StoreEffects
-    { -- | Project out the underlying 'Path' from a 'rootedPath'
-      fromRootedPath :: !(rootedPath -> Path)
-    , -- | Project out the underlying 'rootedPath' from a 'validPath'
-      fromValidPath :: !(validPath -> rootedPath)
-    , -- | Which of the given paths are valid?
-      validPaths :: !(HashSet rootedPath -> m (HashSet validPath))
-    , -- | Get the paths that refer to a given path.
-      referrers :: !(validPath -> m (HashSet Path))
-    , -- | Get a root to the 'Path'.
-      rootedPath :: !(Path -> m rootedPath)
-    , -- | Get information about substituters of a set of 'Path's
-      substitutablePathInfos ::
-        !(HashSet Path -> m (HashMap Path SubstitutablePathInfo))
-    , -- | Get the currently valid derivers of a 'Path'.
-      validDerivers :: !(Path -> m (HashSet Path))
-    , -- | Get the outputs of the derivation at a 'Path'.
-      derivationOutputs :: !(validPath -> m (HashSet Path))
-    , -- | Get the output names of the derivation at a 'Path'.
-      derivationOutputNames :: !(validPath -> m (HashSet Text))
-    , -- | Get a full 'Path' corresponding to a given 'Digest'.
-      pathFromHashPart :: !(Digest PathHashAlgo -> m Path)
-    , -- | Add a non-nar file to the store
-      addFile :: !(BS.ByteString -> m validPath)
-    }
diff --git a/src/System/Nix/StorePath.hs b/src/System/Nix/StorePath.hs
new file mode 100644
--- /dev/null
+++ b/src/System/Nix/StorePath.hs
@@ -0,0 +1,23 @@
+{-|
+Description : Representation of Nix store paths.
+-}
+module System.Nix.StorePath
+  ( -- * Basic store path types
+    StorePath(..)
+  , StorePathName
+  , StorePathSet
+  , StorePathHashAlgo
+  , StoreDir
+  , ContentAddressableAddress(..)
+  , NarHashMode(..)
+  , -- * Manipulating 'StorePathName'
+    makeStorePathName
+  , unStorePathName
+  , storePathNameRegex
+  , -- * Rendering out 'StorePath's
+    storePathToRawFilePath
+  , storeDirVal
+  , KnownStoreDir
+  ) where
+
+import System.Nix.Internal.StorePath
diff --git a/src/System/Nix/StorePathMetadata.hs b/src/System/Nix/StorePathMetadata.hs
new file mode 100644
--- /dev/null
+++ b/src/System/Nix/StorePathMetadata.hs
@@ -0,0 +1,49 @@
+{-|
+Description : Metadata about Nix store paths.
+-}
+module System.Nix.StorePathMetadata where
+
+import System.Nix.StorePath (StorePath, StorePathSet, ContentAddressableAddress)
+import System.Nix.Hash (SomeNamedDigest)
+import Data.Set (Set)
+import Data.Time (UTCTime)
+import Data.Word (Word64)
+import System.Nix.Signature (NarSignature)
+
+-- | Metadata about a 'StorePath' in @storeDir@.
+data StorePathMetadata storeDir = StorePathMetadata
+  { -- | The path this metadata is about
+    path :: !(StorePath storeDir)
+  , -- | The path to the derivation file that built this path, if any
+    -- and known.
+    deriverPath :: !(Maybe (StorePath storeDir))
+  , -- TODO should this be optional?
+    -- | The hash of the nar serialization of the path.
+    narHash :: !SomeNamedDigest
+  , -- | The paths that this path directly references
+    references :: !(StorePathSet storeDir)
+  , -- | When was this path registered valid in the store?
+    registrationTime :: !UTCTime
+  , -- | The size of the nar serialization of the path, in bytes.
+    narBytes :: !(Maybe Word64)
+  , -- | How much we trust this path.
+    trust :: !StorePathTrust
+  , -- | A set of cryptographic attestations of this path's validity.
+    --
+    -- There is no guarantee from this type alone that these
+    -- signatures are valid.
+    sigs :: !(Set NarSignature)
+  , -- | Whether and how this store path is content-addressable.
+    --
+    -- There is no guarantee from this type alone that this address
+    -- is actually correct for this store path.
+    contentAddressableAddress :: !(Maybe ContentAddressableAddress)
+  }
+
+-- | How much do we trust the path, based on its provenance?
+data StorePathTrust
+  = -- | It was built locally and thus ultimately trusted
+    BuiltLocally
+  | -- | It was built elsewhere (and substituted or similar) and so
+    -- is less trusted
+    BuiltElsewhere
diff --git a/tests/Hash.hs b/tests/Hash.hs
--- a/tests/Hash.hs
+++ b/tests/Hash.hs
@@ -23,7 +23,7 @@
 import           Text.Read                   (readMaybe)
 
 import           System.Nix.Hash
-import           System.Nix.Path
+import           System.Nix.StorePath
 import           NarFormat -- TODO: Move the fixtures into a common module
 
 spec_hash :: Spec
@@ -45,5 +45,5 @@
       let exampleStr =
             "source:sha256:2bfef67de873c54551d884fdab3055d84d573e654efa79db3"
             <> "c0d7b98883f9ee3:/nix/store:myfile"
-      shouldBe (encodeBase32 @PathHashAlgo (hash exampleStr))
+      shouldBe (encodeBase32 @StorePathHashAlgo (hash exampleStr))
         "xv2iccirbrvklck36f1g7vldn5v58vck"
diff --git a/tests/NarFormat.hs b/tests/NarFormat.hs
--- a/tests/NarFormat.hs
+++ b/tests/NarFormat.hs
@@ -33,7 +33,6 @@
 import           Text.Read                   (readMaybe)
 
 import           System.Nix.Nar
-import           System.Nix.Path
 
 
 
