gb-nix-cache
Nix Binary Cache Protocol for Haskell
Pure-first implementation of the complete Nix binary cache protocol — base32, NAR, narinfo, Ed25519 signing, store paths — with an optional WAI server.
Quick Start · Modules · Server · API Reference

What is gb-nix-cache?
A focused, minimal library implementing the full Nix binary cache protocol:
- Base32 — Nix-specific encoding with the custom
0123456789abcdfghijklmnpqrsvwxyz alphabet, O(n) ST-based decode
- Hash — SHA-256 hashing with
sha256:<nix-base32> formatting, composition pipeline over crypton
- StorePath — Store path parsing, validation, and hash extraction with enforced invariants via newtypes
- NAR — Binary serialization and deserialization of the Nix ARchive format using
Builder monoid composition
- NarInfo — Parse and render the key-value narinfo text format
- Signing — Ed25519 fingerprint signing and verification for binary cache trust
- Compression — xz compress/decompress for NAR transport
- Store — Filesystem storage backend for narinfo and NAR files
- Server — Optional WAI/Warp HTTP server implementing the cache protocol (behind
server cabal flag)
Every module is pure by default. IO lives at the boundaries only.
Quick Start
Add to your .cabal file:
build-depends: gb-nix-cache
Hash a Store Path
import GBNix.Hash (hashBytes, formatNixHash)
import qualified Data.ByteString as BS
main :: IO ()
main = do
contents <- BS.readFile "/nix/store/abc123-hello/bin/hello"
let nixHash = hashBytes contents
putStrLn (show (formatNixHash nixHash))
-- "sha256:0m6g5r7c..."
Parse a NarInfo
import GBNix.NarInfo (parseNarInfo, niStorePath, niNarHash)
main :: IO ()
main = do
let raw = "StorePath: /nix/store/abc...-hello\n\
\URL: nar/abc....nar.xz\n\
\Compression: xz\n\
\FileHash: sha256:...\n\
\FileSize: 1234\n\
\NarHash: sha256:...\n\
\NarSize: 5678\n\
\References: \n"
case parseNarInfo raw of
Left err -> putStrLn ("Parse error: " ++ err)
Right ni -> do
putStrLn ("Store path: " <> show (niStorePath ni))
putStrLn ("NAR hash: " <> show (niNarHash ni))
Sign and Verify
import GBNix.Signing (parseSecretKey, parsePublicKey, sign, verify)
main :: IO ()
main = do
let Right sk = parseSecretKey "mykey:base64secretkey..."
Right pk = parsePublicKey "mykey:base64pubkey..."
case sign sk narinfo of
Left err -> putStrLn ("Sign error: " ++ err)
Right sig -> putStrLn ("Verified: " <> show (verify pk narinfo sig))
Modules
GBNix.Base32
Nix uses a non-standard base32 alphabet that omits e, o, u, t and encodes in reverse byte order. The encoder extracts 5-bit groups from descending positions; the decoder uses an O(n) ST-based bit scatter with mutable vectors.
import GBNix.Base32 (encode, decode)
encode "\xff" -- "8z"
decode "0z" -- Right "\x1e"
decode (encode bs) -- Right bs (roundtrip)
GBNix.Hash
SHA-256 via crypton, formatted as sha256:<nix-base32>:
import GBNix.Hash (hashBytes, hashFile, formatNixHash, parseNixHash)
formatNixHash (hashBytes "hello")
-- "sha256:0m6g5r7c..."
nixHash <- hashFile "/nix/store/abc123-hello"
GBNix.StorePath
Newtypes enforce invariants — store path hashes are always 32 characters, names are validated against the Nix character set:
import GBNix.StorePath
let Right sp = parseStorePath defaultStoreDir "/nix/store/abc123...-hello-1.0"
storePathHashString sp -- "abc123..."
storePathBaseName sp -- "abc123...-hello-1.0"
renderStorePath defaultStoreDir sp -- "/nix/store/abc123...-hello-1.0"
GBNix.NAR
The NAR tree ADT with Builder-based serialization:
import GBNix.NAR
let entry = NarRegular False "file contents"
let bytes = serialise entry
deserialise bytes -- Right entry (roundtrip)
-- Hash a NAR directly (serialise + SHA-256)
narHash entry -- NixHash
-- Build from filesystem
tree <- serialiseFromPath "/nix/store/abc123-hello"
GBNix.Signing
Ed25519 signing and verification of narinfo fingerprints. Keys are name:base64 pairs:
import GBNix.Signing
-- Fingerprint format: 1;<StorePath>;<NarHash>;<NarSize>;<refs>
fingerprint narinfo
-- "1;/nix/store/abc...;sha256:...;5678;"
Server
Enable the WAI server with the server cabal flag:
cabal build --flag server
cabal run gb-nix-cache-server -- --port 5000 --store ./nix-cache
Or via environment variables:
PORT=5000 NIX_CACHE_DIR=./nix-cache gb-nix-cache-server
Endpoints
| Method |
Path |
Description |
GET |
/nix-cache-info |
Cache metadata (StoreDir, WantMassQuery, Priority) |
GET |
/<hash>.narinfo |
Fetch narinfo by store path hash |
GET |
/nar/<file> |
Fetch compressed NAR file |
PUT |
/<hash>.narinfo |
Upload narinfo |
PUT |
/nar/<file> |
Upload NAR file |
Use as a Nix Substituter
# Push to your cache
nix copy --to http://cache.example.com /nix/store/abc123-hello
# Pull from your cache
nix build --substituters http://cache.example.com --trusted-public-keys "mykey:base64..."
Architecture
Pure Core (no IO)
┌──────────────────────────────────────────┐
│ │
│ Base32 ──→ Hash ──→ StorePath │
│ │ │
│ NarInfo ──→ Signing │
│ │ │
│ NAR │
│ │
└──────────────────────────────────────────┘
│
IO Boundary (thin)
┌──────────────────────────────────────────┐
│ Compression Store Server (flag) │
└──────────────────────────────────────────┘
- 8 modules, 6 pure + 2 at the IO boundary
- 48 tests, hand-rolled harness, no framework dependencies
- Zero partial functions — total by construction
- Strict by default — bang patterns on all data fields
API Reference
Base32
encode :: ByteString -> Text
decode :: Text -> Either String ByteString
Hash
hashBytes :: ByteString -> NixHash
hashFile :: FilePath -> IO NixHash
formatNixHash :: NixHash -> Text
parseNixHash :: Text -> Either String NixHash
StorePath
parseStorePath :: StoreDir -> Text -> Either String StorePath
renderStorePath :: StoreDir -> StorePath -> Text
storePathHashString :: StorePath -> Text
storePathBaseName :: StorePath -> Text
NAR
serialise :: NarEntry -> ByteString
deserialise :: ByteString -> Either String NarEntry
narHash :: NarEntry -> NixHash
serialiseFromPath :: FilePath -> IO NarEntry
NarInfo
parseNarInfo :: Text -> Either String NarInfo
renderNarInfo :: NarInfo -> Text
Signing
parseSecretKey :: Text -> Either String SecretKey
parsePublicKey :: Text -> Either String PublicKey
fingerprint :: NarInfo -> Text
sign :: SecretKey -> NarInfo -> Either String Text
verify :: PublicKey -> NarInfo -> Text -> Bool
Compression
compressXz :: ByteString -> ByteString
decompressXz :: ByteString -> ByteString
Store
newFileStore :: FilePath -> IO FileStore
readNarInfo :: FileStore -> Text -> IO (Maybe ByteString)
writeNarInfo :: FileStore -> Text -> ByteString -> IO ()
readNar :: FileStore -> Text -> IO (Maybe ByteString)
writeNar :: FileStore -> Text -> ByteString -> IO ()
getCacheInfo :: FileStore -> (Text, Bool, Int)
Full Haddock documentation is available on Hackage.
Build & Test
cabal build # Build library
cabal test # Run all tests (48 tests, 8 groups)
cabal build --ghc-options="-Werror" # Warnings as errors
cabal build --flag server # Build with WAI server
cabal haddock # Generate docs
MIT License · Gondola Bros Entertainment