about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorEelco Dolstra <edolstra@gmail.com>2017-07-04T12·47+0200
committerEelco Dolstra <edolstra@gmail.com>2017-07-04T13·07+0200
commitc0015e87af70f539f24d2aa2bc224a9d8b84276b (patch)
tree3cf099db686920376a0d69c890845767b6aae7a8 /src
parentfe97c6989841460efca37f0f3b9b470c98229283 (diff)
Support base-64 hashes
Also simplify the Hash API.

Fixes #1437.
Diffstat (limited to 'src')
-rw-r--r--src/libexpr/primops.cc8
-rw-r--r--src/libstore/binary-cache-store.cc2
-rw-r--r--src/libstore/build.cc2
-rw-r--r--src/libstore/derivations.cc6
-rw-r--r--src/libstore/download.cc2
-rw-r--r--src/libstore/export-import.cc2
-rw-r--r--src/libstore/gc.cc2
-rw-r--r--src/libstore/local-store.cc26
-rw-r--r--src/libstore/nar-info-disk-cache.cc4
-rw-r--r--src/libstore/nar-info.cc6
-rw-r--r--src/libstore/optimise-store.cc4
-rw-r--r--src/libstore/remote-store.cc4
-rw-r--r--src/libstore/store-api.cc17
-rw-r--r--src/libutil/hash.cc185
-rw-r--r--src/libutil/hash.hh51
-rw-r--r--src/nix-daemon/nix-daemon.cc6
-rw-r--r--src/nix-prefetch-url/nix-prefetch-url.cc2
-rw-r--r--src/nix-store/nix-store.cc8
-rw-r--r--src/nix/hash.cc40
-rw-r--r--src/nix/verify.cc2
20 files changed, 180 insertions, 199 deletions
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 99ffddaeb8..b753d84e2e 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -708,8 +708,8 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
         HashType ht = parseHashType(outputHashAlgo);
         if (ht == htUnknown)
             throw EvalError(format("unknown hash algorithm ‘%1%’, at %2%") % outputHashAlgo % posDrvName);
-        Hash h = parseHash16or32(ht, *outputHash);
-        outputHash = printHash(h);
+        Hash h(*outputHash, ht);
+        outputHash = h.to_string(Base16, false);
         if (outputHashRecursive) outputHashAlgo = "r:" + outputHashAlgo;
 
         Path outPath = state.store->makeFixedOutputPath(outputHashRecursive, h, drvName);
@@ -1701,7 +1701,7 @@ static void prim_hashString(EvalState & state, const Pos & pos, Value * * args,
     PathSet context; // discarded
     string s = state.forceString(*args[1], context, pos);
 
-    mkString(v, printHash(hashString(ht, s)), context);
+    mkString(v, hashString(ht, s).to_string(Base16, false), context);
 }
 
 
@@ -1852,7 +1852,7 @@ void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
             if (n == "url")
                 url = state.forceStringNoCtx(*attr.value, *attr.pos);
             else if (n == "sha256")
-                expectedHash = parseHash16or32(htSHA256, state.forceStringNoCtx(*attr.value, *attr.pos));
+                expectedHash = Hash(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256);
             else if (n == "name")
                 name = state.forceStringNoCtx(*attr.value, *attr.pos);
             else
diff --git a/src/libstore/binary-cache-store.cc b/src/libstore/binary-cache-store.cc
index 8ce5f5bbc7..8147345c2e 100644
--- a/src/libstore/binary-cache-store.cc
+++ b/src/libstore/binary-cache-store.cc
@@ -239,7 +239,7 @@ void BinaryCacheStore::addToStore(const ValidPathInfo & info, const ref<std::str
         % duration);
 
     /* Atomically write the NAR file. */
-    narInfo->url = "nar/" + printHash32(narInfo->fileHash) + ".nar"
+    narInfo->url = "nar/" + narInfo->fileHash.to_string(Base32, false) + ".nar"
         + (compression == "xz" ? ".xz" :
            compression == "bzip2" ? ".bz2" :
            compression == "br" ? ".br" :
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 6c740d99c5..f40a8c5498 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -3236,7 +3236,7 @@ PathSet DerivationGoal::checkPathValidity(bool returnValid, bool checkHash)
 Path DerivationGoal::addHashRewrite(const Path & path)
 {
     string h1 = string(path, worker.store.storeDir.size() + 1, 32);
-    string h2 = string(printHash32(hashString(htSHA256, "rewrite:" + drvPath + ":" + path)), 0, 32);
+    string h2 = string(hashString(htSHA256, "rewrite:" + drvPath + ":" + path).to_string(Base32, false), 0, 32);
     Path p = worker.store.storeDir + "/" + h2 + string(path, worker.store.storeDir.size() + 33);
     deletePath(p);
     assert(path.size() == p.size());
diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc
index bb7b8fe628..48c0837ffa 100644
--- a/src/libstore/derivations.cc
+++ b/src/libstore/derivations.cc
@@ -23,7 +23,7 @@ void DerivationOutput::parseHashInfo(bool & recursive, Hash & hash) const
     if (hashType == htUnknown)
         throw Error(format("unknown hash algorithm ‘%1%’") % algo);
 
-    hash = parseHash(hashType, this->hash);
+    hash = Hash(this->hash, hashType);
 }
 
 
@@ -354,7 +354,7 @@ Hash hashDerivationModulo(Store & store, Derivation drv)
             h = hashDerivationModulo(store, drv2);
             drvHashes[i.first] = h;
         }
-        inputs2[printHash(h)] = i.second;
+        inputs2[h.to_string(Base16, false)] = i.second;
     }
     drv.inputDrvs = inputs2;
 
@@ -437,7 +437,7 @@ Sink & operator << (Sink & out, const BasicDerivation & drv)
 std::string hashPlaceholder(const std::string & outputName)
 {
     // FIXME: memoize?
-    return "/" + printHash32(hashString(htSHA256, "nix-output:" + outputName));
+    return "/" + hashString(htSHA256, "nix-output:" + outputName).to_string(Base32, false);
 }
 
 
diff --git a/src/libstore/download.cc b/src/libstore/download.cc
index 4f3bf2d14f..15eb68c69e 100644
--- a/src/libstore/download.cc
+++ b/src/libstore/download.cc
@@ -581,7 +581,7 @@ Path Downloader::downloadCached(ref<Store> store, const string & url_, bool unpa
     Path cacheDir = getCacheDir() + "/nix/tarballs";
     createDirs(cacheDir);
 
-    string urlHash = printHash32(hashString(htSHA256, url));
+    string urlHash = hashString(htSHA256, url).to_string(Base32, false);
 
     Path dataFile = cacheDir + "/" + urlHash + ".info";
     Path fileLink = cacheDir + "/" + urlHash + "-file";
diff --git a/src/libstore/export-import.cc b/src/libstore/export-import.cc
index 1b3a43df32..2cbcedc6fb 100644
--- a/src/libstore/export-import.cc
+++ b/src/libstore/export-import.cc
@@ -56,7 +56,7 @@ void Store::exportPath(const Path & path, Sink & sink)
     Hash hash = hashAndWriteSink.currentHash();
     if (hash != info->narHash && info->narHash != Hash(info->narHash.type))
         throw Error(format("hash of path ‘%1%’ has changed from ‘%2%’ to ‘%3%’!") % path
-            % printHash(info->narHash) % printHash(hash));
+            % info->narHash.to_string() % hash.to_string());
 
     hashAndWriteSink << exportMagic << path << info->references << info->deriver << 0;
 }
diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc
index 3cdbb114a7..0cf9f87cac 100644
--- a/src/libstore/gc.cc
+++ b/src/libstore/gc.cc
@@ -76,7 +76,7 @@ void LocalStore::syncWithGC()
 
 void LocalStore::addIndirectRoot(const Path & path)
 {
-    string hash = printHash32(hashString(htSHA1, path));
+    string hash = hashString(htSHA1, path).to_string(Base32, false);
     Path realRoot = canonPath((format("%1%/%2%/auto/%3%")
         % stateDir % gcRootsDir % hash).str());
     makeSymlink(realRoot, path);
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index a7a94a8b9e..7c41dfca7f 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -572,7 +572,7 @@ uint64_t LocalStore::addValidPath(State & state,
 
     state.stmtRegisterValidPath.use()
         (info.path)
-        ("sha256:" + printHash(info.narHash))
+        (info.narHash.to_string(Base16))
         (info.registrationTime == 0 ? time(0) : info.registrationTime)
         (info.deriver, info.deriver != "")
         (info.narSize, info.narSize != 0)
@@ -614,20 +614,6 @@ uint64_t LocalStore::addValidPath(State & state,
 }
 
 
-Hash parseHashField(const Path & path, const string & s)
-{
-    string::size_type colon = s.find(':');
-    if (colon == string::npos)
-        throw Error(format("corrupt hash ‘%1%’ in valid-path entry for ‘%2%’")
-            % s % path);
-    HashType ht = parseHashType(string(s, 0, colon));
-    if (ht == htUnknown)
-        throw Error(format("unknown hash type ‘%1%’ in valid-path entry for ‘%2%’")
-            % string(s, 0, colon) % path);
-    return parseHash(ht, string(s, colon + 1));
-}
-
-
 void LocalStore::queryPathInfoUncached(const Path & path,
     std::function<void(std::shared_ptr<ValidPathInfo>)> success,
     std::function<void(std::exception_ptr exc)> failure)
@@ -650,7 +636,11 @@ void LocalStore::queryPathInfoUncached(const Path & path,
 
             info->id = useQueryPathInfo.getInt(0);
 
-            info->narHash = parseHashField(path, useQueryPathInfo.getStr(1));
+            try {
+                info->narHash = Hash(useQueryPathInfo.getStr(1));
+            } catch (BadHash & e) {
+                throw Error("in valid-path entry for ‘%s’: %s", path, e.what());
+            }
 
             info->registrationTime = useQueryPathInfo.getInt(2);
 
@@ -685,7 +675,7 @@ void LocalStore::updatePathInfo(State & state, const ValidPathInfo & info)
 {
     state.stmtUpdatePathInfo.use()
         (info.narSize, info.narSize != 0)
-        ("sha256:" + printHash(info.narHash))
+        (info.narHash.to_string(Base16))
         (info.ultimate ? 1 : 0, info.ultimate)
         (concatStringsSep(" ", info.sigs), !info.sigs.empty())
         (info.ca, !info.ca.empty())
@@ -1211,7 +1201,7 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
                 if (info->narHash != nullHash && info->narHash != current.first) {
                     printError(format("path ‘%1%’ was modified! "
                             "expected hash ‘%2%’, got ‘%3%’")
-                        % i % printHash(info->narHash) % printHash(current.first));
+                        % i % info->narHash.to_string() % current.first.to_string());
                     if (repair) repairPath(i); else errors = true;
                 } else {
 
diff --git a/src/libstore/nar-info-disk-cache.cc b/src/libstore/nar-info-disk-cache.cc
index 180a936edb..6e155e8778 100644
--- a/src/libstore/nar-info-disk-cache.cc
+++ b/src/libstore/nar-info-disk-cache.cc
@@ -203,9 +203,9 @@ public:
             narInfo->url = queryNAR.getStr(3);
             narInfo->compression = queryNAR.getStr(4);
             if (!queryNAR.isNull(5))
-                narInfo->fileHash = parseHash(queryNAR.getStr(5));
+                narInfo->fileHash = Hash(queryNAR.getStr(5));
             narInfo->fileSize = queryNAR.getInt(6);
-            narInfo->narHash = parseHash(queryNAR.getStr(7));
+            narInfo->narHash = Hash(queryNAR.getStr(7));
             narInfo->narSize = queryNAR.getInt(8);
             for (auto & r : tokenizeString<Strings>(queryNAR.getStr(9), " "))
                 narInfo->references.insert(cache.storeDir + "/" + r);
diff --git a/src/libstore/nar-info.cc b/src/libstore/nar-info.cc
index d1042c6de2..660f6a42a1 100644
--- a/src/libstore/nar-info.cc
+++ b/src/libstore/nar-info.cc
@@ -11,7 +11,7 @@ NarInfo::NarInfo(const Store & store, const std::string & s, const std::string &
 
     auto parseHashField = [&](const string & s) {
         try {
-            return parseHash(s);
+            return Hash(s);
         } catch (BadHash &) {
             corrupt();
             return Hash(); // never reached
@@ -90,10 +90,10 @@ std::string NarInfo::to_string() const
     assert(compression != "");
     res += "Compression: " + compression + "\n";
     assert(fileHash.type == htSHA256);
-    res += "FileHash: sha256:" + printHash32(fileHash) + "\n";
+    res += "FileHash: " + fileHash.to_string(Base32) + "\n";
     res += "FileSize: " + std::to_string(fileSize) + "\n";
     assert(narHash.type == htSHA256);
-    res += "NarHash: sha256:" + printHash32(narHash) + "\n";
+    res += "NarHash: " + narHash.to_string(Base32) + "\n";
     res += "NarSize: " + std::to_string(narSize) + "\n";
 
     res += "References: " + concatStringsSep(" ", shortRefs()) + "\n";
diff --git a/src/libstore/optimise-store.cc b/src/libstore/optimise-store.cc
index 56167c4dfa..adaf313131 100644
--- a/src/libstore/optimise-store.cc
+++ b/src/libstore/optimise-store.cc
@@ -135,10 +135,10 @@ void LocalStore::optimisePath_(OptimiseStats & stats, const Path & path, InodeHa
        contents of the symlink (i.e. the result of readlink()), not
        the contents of the target (which may not even exist). */
     Hash hash = hashPath(htSHA256, path).first;
-    debug(format("‘%1%’ has hash ‘%2%’") % path % printHash(hash));
+    debug(format("‘%1%’ has hash ‘%2%’") % path % hash.to_string());
 
     /* Check if this is a known hash. */
-    Path linkPath = linksDir + "/" + printHash32(hash);
+    Path linkPath = linksDir + "/" + hash.to_string(Base32, false);
 
  retry:
     if (!pathExists(linkPath)) {
diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index 7337e406d2..ab726e7953 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -294,7 +294,7 @@ void RemoteStore::queryPathInfoUncached(const Path & path,
         info->path = path;
         info->deriver = readString(conn->from);
         if (info->deriver != "") assertStorePath(info->deriver);
-        info->narHash = parseHash(htSHA256, readString(conn->from));
+        info->narHash = Hash(readString(conn->from), htSHA256);
         info->references = readStorePaths<PathSet>(*this, conn->from);
         conn->from >> info->registrationTime >> info->narSize;
         if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 16) {
@@ -387,7 +387,7 @@ void RemoteStore::addToStore(const ValidPathInfo & info, const ref<std::string>
 
     else {
         conn->to << wopAddToStoreNar
-                 << info.path << info.deriver << printHash(info.narHash)
+                 << info.path << info.deriver << info.narHash.to_string(Base16, false)
                  << info.references << info.registrationTime << info.narSize
                  << info.ultimate << info.sigs << info.ca
                  << repair << !checkSigs;
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index 39b9466162..d7b784cfbc 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -176,13 +176,12 @@ Path Store::makeStorePath(const string & type,
     const Hash & hash, const string & name) const
 {
     /* e.g., "source:sha256:1abc...:/nix/store:foo.tar.gz" */
-    string s = type + ":sha256:" + printHash(hash) + ":"
-        + storeDir + ":" + name;
+    string s = type + ":" + hash.to_string(Base16) + ":" + storeDir + ":" + name;
 
     checkStoreName(name);
 
     return storeDir + "/"
-        + printHash32(compressHash(hashString(htSHA256, s), 20))
+        + compressHash(hashString(htSHA256, s), 20).to_string(Base32, false)
         + "-" + name;
 }
 
@@ -202,7 +201,7 @@ Path Store::makeFixedOutputPath(bool recursive,
         ? makeStorePath("source", hash, name)
         : makeStorePath("output:out", hashString(htSHA256,
                 "fixed:out:" + (recursive ? (string) "r:" : "") +
-                printHashType(hash.type) + ":" + printHash(hash) + ":"),
+                hash.to_string(Base16) + ":"),
             name);
 }
 
@@ -438,7 +437,7 @@ string Store::makeValidityRegistration(const PathSet & paths,
         auto info = queryPathInfo(i);
 
         if (showHash) {
-            s += printHash(info->narHash) + "\n";
+            s += info->narHash.to_string(Base16, false) + "\n";
             s += (format("%1%\n") % info->narSize).str();
         }
 
@@ -613,7 +612,7 @@ ValidPathInfo decodeValidPathInfo(std::istream & str, bool hashGiven)
     if (hashGiven) {
         string s;
         getline(str, s);
-        info.narHash = parseHash(htSHA256, s);
+        info.narHash = Hash(s, htSHA256);
         getline(str, s);
         if (!string2Int(s, info.narSize)) throw Error("number expected");
     }
@@ -648,7 +647,7 @@ std::string ValidPathInfo::fingerprint() const
             % path);
     return
         "1;" + path + ";"
-        + printHashType(narHash.type) + ":" + printHash32(narHash) + ";"
+        + narHash.to_string(Base32) + ";"
         + std::to_string(narSize) + ";"
         + concatStringsSep(",", references);
 }
@@ -667,7 +666,7 @@ bool ValidPathInfo::isContentAddressed(const Store & store) const
     };
 
     if (hasPrefix(ca, "text:")) {
-        auto hash = parseHash(std::string(ca, 5));
+        Hash hash(std::string(ca, 5));
         if (store.makeTextPath(storePathToName(path), hash, references) == path)
             return true;
         else
@@ -676,7 +675,7 @@ bool ValidPathInfo::isContentAddressed(const Store & store) const
 
     else if (hasPrefix(ca, "fixed:")) {
         bool recursive = ca.compare(6, 2, "r:") == 0;
-        auto hash = parseHash(std::string(ca, recursive ? 8 : 6));
+        Hash hash(std::string(ca, recursive ? 8 : 6));
         if (store.makeFixedOutputPath(recursive, hash, storePathToName(path)) == path)
             return true;
         else
diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc
index fa1bb5d971..6b45ac859d 100644
--- a/src/libutil/hash.cc
+++ b/src/libutil/hash.cc
@@ -16,17 +16,8 @@
 namespace nix {
 
 
-Hash::Hash()
+void Hash::init()
 {
-    type = htUnknown;
-    hashSize = 0;
-    memset(hash, 0, maxHashSize);
-}
-
-
-Hash::Hash(HashType type)
-{
-    this->type = type;
     if (type == htMD5) hashSize = md5HashSize;
     else if (type == htSHA1) hashSize = sha1HashSize;
     else if (type == htSHA256) hashSize = sha256HashSize;
@@ -62,16 +53,10 @@ bool Hash::operator < (const Hash & h) const
 }
 
 
-std::string Hash::to_string(bool base32) const
-{
-    return printHashType(type) + ":" + (base32 ? printHash32(*this) : printHash(*this));
-}
-
-
 const string base16Chars = "0123456789abcdef";
 
 
-string printHash(const Hash & hash)
+static string printHash16(const Hash & hash)
 {
     char buf[hash.hashSize * 2];
     for (unsigned int i = 0; i < hash.hashSize; i++) {
@@ -82,42 +67,11 @@ string printHash(const Hash & hash)
 }
 
 
-Hash parseHash(const string & s)
-{
-    string::size_type colon = s.find(':');
-    if (colon == string::npos)
-        throw BadHash(format("invalid hash ‘%s’") % s);
-    string hts = string(s, 0, colon);
-    HashType ht = parseHashType(hts);
-    if (ht == htUnknown)
-        throw BadHash(format("unknown hash type ‘%s’") % hts);
-    return parseHash16or32(ht, string(s, colon + 1));
-}
-
-
-Hash parseHash(HashType ht, const string & s)
-{
-    Hash hash(ht);
-    if (s.length() != hash.hashSize * 2)
-        throw BadHash(format("invalid hash ‘%1%’") % s);
-    for (unsigned int i = 0; i < hash.hashSize; i++) {
-        string s2(s, i * 2, 2);
-        if (!isxdigit(s2[0]) || !isxdigit(s2[1]))
-            throw BadHash(format("invalid hash ‘%1%’") % s);
-        istringstream_nocopy str(s2);
-        int n;
-        str >> std::hex >> n;
-        hash.hash[i] = n;
-    }
-    return hash;
-}
-
-
 // omitted: E O U T
 const string base32Chars = "0123456789abcdfghijklmnpqrsvwxyz";
 
 
-string printHash32(const Hash & hash)
+static string printHash32(const Hash & hash)
 {
     assert(hash.hashSize);
     size_t len = hash.base32Len();
@@ -142,66 +96,103 @@ string printHash32(const Hash & hash)
 
 string printHash16or32(const Hash & hash)
 {
-    return hash.type == htMD5 ? printHash(hash) : printHash32(hash);
+    return hash.to_string(hash.type == htMD5 ? Base16 : Base32);
 }
 
 
-Hash parseHash32(HashType ht, const string & s)
+std::string Hash::to_string(Base base, bool includeType) const
 {
-    Hash hash(ht);
-    size_t len = hash.base32Len();
-    assert(s.size() == len);
-
-    for (unsigned int n = 0; n < len; ++n) {
-        char c = s[len - n - 1];
-        unsigned char digit;
-        for (digit = 0; digit < base32Chars.size(); ++digit) /* !!! slow */
-            if (base32Chars[digit] == c) break;
-        if (digit >= 32)
-            throw BadHash(format("invalid base-32 hash ‘%1%’") % s);
-        unsigned int b = n * 5;
-        unsigned int i = b / 8;
-        unsigned int j = b % 8;
-        hash.hash[i] |= digit << j;
-
-        if (i < hash.hashSize - 1) {
-            hash.hash[i + 1] |= digit >> (8 - j);
-        } else {
-            if (digit >> (8 - j))
-                throw BadHash(format("invalid base-32 hash ‘%1%’") % s);
-        }
+    std::string s;
+    if (includeType) {
+        s += printHashType(type);
+        s += ':';
     }
-
-    return hash;
+    switch (base) {
+    case Base16:
+        s += printHash16(*this);
+        break;
+    case Base32:
+        s += printHash32(*this);
+        break;
+    case Base64:
+        s += base64Encode(std::string((const char *) hash, hashSize));
+        break;
+    }
+    return s;
 }
 
 
-Hash parseHash16or32(HashType ht, const string & s)
+Hash::Hash(const std::string & s, HashType type)
+    : type(type)
 {
-    Hash hash(ht);
-    if (s.size() == hash.hashSize * 2)
-        /* hexadecimal representation */
-        hash = parseHash(ht, s);
-    else if (s.size() == hash.base32Len())
-        /* base-32 representation */
-        hash = parseHash32(ht, s);
-    else
-        throw BadHash(format("hash ‘%1%’ has wrong length for hash type ‘%2%’")
-            % s % printHashType(ht));
-    return hash;
-}
+    auto colon = s.find(':');
+
+    size_t pos = 0;
+
+    if (colon == string::npos) {
+        if (type == htUnknown)
+            throw BadHash("hash ‘%s’ does not include a type", s);
+    } else {
+        string hts = string(s, 0, colon);
+        this->type = parseHashType(hts);
+        if (this->type == htUnknown)
+            throw BadHash("unknown hash type ‘%s’", hts);
+        if (type != htUnknown && type != this->type)
+            throw BadHash("hash ‘%s’ should have type ‘%s’", s, printHashType(type));
+        pos = colon + 1;
+    }
 
+    init();
 
-bool isHash(const string & s)
-{
-    if (s.length() != 32) return false;
-    for (int i = 0; i < 32; i++) {
-        char c = s[i];
-        if (!((c >= '0' && c <= '9') ||
-              (c >= 'a' && c <= 'f')))
-            return false;
+    size_t size = s.size() - pos;
+
+    if (size == base16Len()) {
+
+        auto parseHexDigit = [&](char c) {
+            if (c >= '0' && c <= '9') return c - '0';
+            if (c >= 'A' && c <= 'F') return c - 'A' + 10;
+            if (c >= 'a' && c <= 'f') return c - 'a' + 10;
+            throw BadHash("invalid base-16 hash ‘%s’", s);
+        };
+
+        for (unsigned int i = 0; i < hashSize; i++) {
+            hash[i] =
+                parseHexDigit(s[pos + i * 2]) << 4
+                | parseHexDigit(s[pos + i * 2 + 1]);
+        }
     }
-    return true;
+
+    else if (size == base32Len()) {
+
+        for (unsigned int n = 0; n < size; ++n) {
+            char c = s[pos + size - n - 1];
+            unsigned char digit;
+            for (digit = 0; digit < base32Chars.size(); ++digit) /* !!! slow */
+                if (base32Chars[digit] == c) break;
+            if (digit >= 32)
+                throw BadHash("invalid base-32 hash ‘%s’", s);
+            unsigned int b = n * 5;
+            unsigned int i = b / 8;
+            unsigned int j = b % 8;
+            hash[i] |= digit << j;
+
+            if (i < hashSize - 1) {
+                hash[i + 1] |= digit >> (8 - j);
+            } else {
+                if (digit >> (8 - j))
+                    throw BadHash("invalid base-32 hash ‘%s’", s);
+            }
+        }
+    }
+
+    else if (size == base64Len()) {
+        auto d = base64Decode(std::string(s, pos));
+        assert(d.size() == hashSize);
+        memcpy(hash, d.data(), hashSize);
+    }
+
+    else
+        throw BadHash("hash ‘%s’ has wrong length for hash type ‘%s’", s, printHashType(type));
 }
 
 
diff --git a/src/libutil/hash.hh b/src/libutil/hash.hh
index 02e213fc7b..b8b432256c 100644
--- a/src/libutil/hash.hh
+++ b/src/libutil/hash.hh
@@ -20,20 +20,30 @@ const int sha512HashSize = 64;
 
 extern const string base32Chars;
 
+enum Base : int { Base64, Base32, Base16 };
+
 
 struct Hash
 {
     static const unsigned int maxHashSize = 64;
-    unsigned int hashSize;
-    unsigned char hash[maxHashSize];
+    unsigned int hashSize = 0;
+    unsigned char hash[maxHashSize] = {};
 
-    HashType type;
+    HashType type = htUnknown;
 
     /* Create an unset hash object. */
-    Hash();
+    Hash() { };
 
     /* Create a zero-filled hash object. */
-    Hash(HashType type);
+    Hash(HashType type) : type(type) { init(); };
+
+    /* Initialize the hash from a string representation, in the format
+       "[<type>:]<base16|base32|base64>". If the ‘type’ argument is
+       htUnknown, then the hash type must be specified in the
+       string. */
+    Hash(const std::string & s, HashType type = htUnknown);
+
+    void init();
 
     /* Check whether a hash is set. */
     operator bool () const { return type != htUnknown; }
@@ -59,33 +69,22 @@ struct Hash
         return (hashSize * 8 - 1) / 5 + 1;
     }
 
-    std::string to_string(bool base32 = true) const;
-};
-
-
-/* Convert a hash to a hexadecimal representation. */
-string printHash(const Hash & hash);
-
-Hash parseHash(const string & s);
+    /* Returns the length of a base-64 representation of this hash. */
+    size_t base64Len() const
+    {
+        return ((4 * hashSize / 3) + 3) & ~3;
+    }
 
-/* Parse a hexadecimal representation of a hash code. */
-Hash parseHash(HashType ht, const string & s);
+    /* Return a string representation of the hash, in base-16, base-32
+       or base-64. By default, this is prefixed by the hash type
+       (e.g. "sha256:"). */
+    std::string to_string(Base base = Base32, bool includeType = true) const;
+};
 
-/* Convert a hash to a base-32 representation. */
-string printHash32(const Hash & hash);
 
 /* Print a hash in base-16 if it's MD5, or base-32 otherwise. */
 string printHash16or32(const Hash & hash);
 
-/* Parse a base-32 representation of a hash code. */
-Hash parseHash32(HashType ht, const string & s);
-
-/* Parse a base-16 or base-32 representation of a hash code. */
-Hash parseHash16or32(HashType ht, const string & s);
-
-/* Verify that the given string is a valid hash code. */
-bool isHash(const string & s);
-
 /* Compute the hash of the given string. */
 Hash hashString(HashType ht, const string & s);
 
diff --git a/src/nix-daemon/nix-daemon.cc b/src/nix-daemon/nix-daemon.cc
index c9c1677663..b029b92db1 100644
--- a/src/nix-daemon/nix-daemon.cc
+++ b/src/nix-daemon/nix-daemon.cc
@@ -216,7 +216,7 @@ static void performOp(ref<LocalStore> store, bool trusted, unsigned int clientVe
         startWork();
         auto hash = store->queryPathInfo(path)->narHash;
         stopWork();
-        to << printHash(hash);
+        to << hash.to_string(Base16, false);
         break;
     }
 
@@ -550,7 +550,7 @@ static void performOp(ref<LocalStore> store, bool trusted, unsigned int clientVe
         if (info) {
             if (GET_PROTOCOL_MINOR(clientVersion) >= 17)
                 to << 1;
-            to << info->deriver << printHash(info->narHash) << info->references
+            to << info->deriver << info->narHash.to_string(Base16, false) << info->references
                << info->registrationTime << info->narSize;
             if (GET_PROTOCOL_MINOR(clientVersion) >= 16) {
                 to << info->ultimate
@@ -610,7 +610,7 @@ static void performOp(ref<LocalStore> store, bool trusted, unsigned int clientVe
         from >> info.deriver;
         if (!info.deriver.empty())
             store->assertStorePath(info.deriver);
-        info.narHash = parseHash(htSHA256, readString(from));
+        info.narHash = Hash(readString(from), htSHA256);
         info.references = readStorePaths<PathSet>(*store, from);
         from >> info.registrationTime >> info.narSize >> info.ultimate;
         info.sigs = readStrings<StringSet>(from);
diff --git a/src/nix-prefetch-url/nix-prefetch-url.cc b/src/nix-prefetch-url/nix-prefetch-url.cc
index b3b2fcac71..47e66eaa65 100644
--- a/src/nix-prefetch-url/nix-prefetch-url.cc
+++ b/src/nix-prefetch-url/nix-prefetch-url.cc
@@ -145,7 +145,7 @@ int main(int argc, char * * argv)
         Hash hash, expectedHash(ht);
         Path storePath;
         if (args.size() == 2) {
-            expectedHash = parseHash16or32(ht, args[1]);
+            expectedHash = Hash(args[1], ht);
             storePath = store->makeFixedOutputPath(unpack, expectedHash, name);
             if (store->isValidPath(storePath))
                 hash = expectedHash;
diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc
index 314c942390..6cea57a767 100644
--- a/src/nix-store/nix-store.cc
+++ b/src/nix-store/nix-store.cc
@@ -212,7 +212,7 @@ static void opPrintFixedPath(Strings opFlags, Strings opArgs)
     string name = *i++;
 
     cout << format("%1%\n") %
-        store->makeFixedOutputPath(recursive, parseHash16or32(hashAlgo, hash), name);
+        store->makeFixedOutputPath(recursive, Hash(hash, hashAlgo), name);
 }
 
 
@@ -380,9 +380,9 @@ static void opQuery(Strings opFlags, Strings opArgs)
                     auto info = store->queryPathInfo(j);
                     if (query == qHash) {
                         assert(info->narHash.type == htSHA256);
-                        cout << format("sha256:%1%\n") % printHash32(info->narHash);
+                        cout << fmt("%s\n", info->narHash.to_string(Base32));
                     } else if (query == qSize)
-                        cout << format("%1%\n") % info->narSize;
+                        cout << fmt("%d\n", info->narSize);
                 }
             }
             break;
@@ -734,7 +734,7 @@ static void opVerifyPath(Strings opFlags, Strings opArgs)
         if (current.first != info->narHash) {
             printError(
                 format("path ‘%1%’ was modified! expected hash ‘%2%’, got ‘%3%’")
-                % path % printHash(info->narHash) % printHash(current.first));
+                % path % info->narHash.to_string() % current.first.to_string());
             status = 1;
         }
     }
diff --git a/src/nix/hash.cc b/src/nix/hash.cc
index 5dd891e8ad..98de889711 100644
--- a/src/nix/hash.cc
+++ b/src/nix/hash.cc
@@ -9,15 +9,16 @@ struct CmdHash : Command
 {
     enum Mode { mFile, mPath };
     Mode mode;
-    bool base32 = false;
+    Base base = Base16;
     bool truncate = false;
     HashType ht = htSHA512;
     Strings paths;
 
     CmdHash(Mode mode) : mode(mode)
     {
-        mkFlag(0, "base32", "print hash in base-32", &base32);
-        mkFlag(0, "base16", "print hash in base-16", &base32, false);
+        mkFlag(0, "base64", "print hash in base-64", &base, Base64);
+        mkFlag(0, "base32", "print hash in base-32 (Nix-specific)", &base, Base32);
+        mkFlag(0, "base16", "print hash in base-16", &base, Base16);
         mkHashTypeFlag("type", &ht);
         expectArgs("paths", &paths);
     }
@@ -40,7 +41,7 @@ struct CmdHash : Command
             Hash h = mode == mFile ? hashFile(ht, path) : hashPath(ht, path).first;
             if (truncate && h.hashSize > 20) h = compressHash(h, 20);
             std::cout << format("%1%\n") %
-                (base32 ? printHash32(h) : printHash(h));
+                h.to_string(base, false);
         }
     }
 };
@@ -50,11 +51,11 @@ static RegisterCommand r2(make_ref<CmdHash>(CmdHash::mPath));
 
 struct CmdToBase : Command
 {
-    bool toBase32;
+    Base base;
     HashType ht = htSHA512;
     Strings args;
 
-    CmdToBase(bool toBase32) : toBase32(toBase32)
+    CmdToBase(Base base) : base(base)
     {
         mkHashTypeFlag("type", &ht);
         expectArgs("strings", &args);
@@ -62,28 +63,29 @@ struct CmdToBase : Command
 
     std::string name() override
     {
-        return toBase32 ? "to-base32" : "to-base16";
+        return
+            base == Base16 ? "to-base16" :
+            base == Base32 ? "to-base32" :
+            "to-base64";
     }
 
     std::string description() override
     {
-        return toBase32
-            ? "convert a hash to base-32 representation"
-            : "convert a hash to base-16 representation";
+        return fmt("convert a hash to base-%d representation",
+            base == Base16 ? 16 :
+            base == Base32 ? 32 : 64);
     }
 
     void run() override
     {
-        for (auto s : args) {
-            Hash h = parseHash16or32(ht, s);
-            std::cout << format("%1%\n") %
-                (toBase32 ? printHash32(h) : printHash(h));
-        }
+        for (auto s : args)
+            std::cout << fmt("%s\n", Hash(s, ht).to_string(base, false));
     }
 };
 
-static RegisterCommand r3(make_ref<CmdToBase>(false));
-static RegisterCommand r4(make_ref<CmdToBase>(true));
+static RegisterCommand r3(make_ref<CmdToBase>(Base16));
+static RegisterCommand r4(make_ref<CmdToBase>(Base32));
+static RegisterCommand r5(make_ref<CmdToBase>(Base64));
 
 /* Legacy nix-hash command. */
 static int compatNixHash(int argc, char * * argv)
@@ -121,14 +123,14 @@ static int compatNixHash(int argc, char * * argv)
     if (op == opHash) {
         CmdHash cmd(flat ? CmdHash::mFile : CmdHash::mPath);
         cmd.ht = ht;
-        cmd.base32 = base32;
+        cmd.base = base32 ? Base32 : Base16;
         cmd.truncate = truncate;
         cmd.paths = ss;
         cmd.run();
     }
 
     else {
-        CmdToBase cmd(op == opTo32);
+        CmdToBase cmd(op == opTo32 ? Base32 : Base16);
         cmd.args = ss;
         cmd.ht = ht;
         cmd.run();
diff --git a/src/nix/verify.cc b/src/nix/verify.cc
index 18533e6066..973f60a74f 100644
--- a/src/nix/verify.cc
+++ b/src/nix/verify.cc
@@ -94,7 +94,7 @@ struct CmdVerify : StorePathsCommand
                         corrupted = 1;
                         printError(
                             format("path ‘%s’ was modified! expected hash ‘%s’, got ‘%s’")
-                            % info->path % printHash(info->narHash) % printHash(hash.first));
+                            % info->path % info->narHash.to_string() % hash.first.to_string());
                     }
 
                 }