]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
feat: Use base16 encoding for all hash digest keys
authorJoel Rosdahl <joel@rosdahl.net>
Sat, 25 Oct 2025 17:21:09 +0000 (19:21 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Sun, 25 Jan 2026 08:41:33 +0000 (09:41 +0100)
This reverts the mixed encoding scheme introduced in commit 47cb00f7 ("Encode
hash digests as 4 base16 digits + 29 base32hex digits") back to pure base16
encoding.

Background: The mixed base16/base32hex encoding was introduced to reduce
filename lengths slightly, thereby reducing the number of system calls when
scanning local cache directories. At the time, "the effect is very small but
there is no real downside".

However, with the introduction of remote storage helpers implemented outside
ccache's code base, a downside has emerged: key encoding inconsistency. Remote
storage helpers must now either:

1. reimplement ccache's custom encoding algorithm to maintain key consistency
   between the remote storage and ccache's local storage (and logs), or
2. use standard base16 encoding, resulting in different key representations
   between ccache and the remote storage.

By standardizing on base16 encoding throughout, we ensure that:

- remote storage helpers can use simple, standard base16 encoding
- keys are represented identically in ccache's local storage and remote storage
- external implementations don't need to replicate custom encoding logic
- debugging and key correlation across systems is straightforward

The minor filesystem performance benefit of shorter filenames is outweighed by
the ecosystem benefits of a simple, standard encoding scheme.

Local storage will for now be backward compatible: to avoid invalidating
existing cache entries, ccache will write in the new format but will try
both the new and the legacy format on cache lookup. The legacy format is
also still used for hashes that are part of the input hash. When some
other input hash material is changed (which invalidates cache entries
anyway) in the future, we'll drop the legacy format completely.

15 files changed:
src/ccache/ccache.cpp
src/ccache/core/mainoptions.cpp
src/ccache/core/manifest.cpp
src/ccache/hashutil.cpp
src/ccache/storage/local/localstorage.cpp
src/ccache/storage/remote/filestorage.cpp
src/ccache/storage/remote/httpstorage.cpp
src/ccache/storage/remote/redisstorage.cpp
src/ccache/storage/storage.cpp
src/ccache/util/string.cpp
src/ccache/util/string.hpp
test/run
test/suites/base.bash
test/suites/direct.bash
unittest/test_hash.cpp

index 0bb3fa33662836ec2aa19f3cbc60360a42b5487d..efcd95b5a56f2cd952e6986f328b89d65615780a 100644 (file)
@@ -457,7 +457,7 @@ remember_include_file(Context& ctx,
       return tl::unexpected(Statistic::bad_input_file);
     }
     cpp_hash.hash_delimiter(using_pch_sum ? "pch_sum_hash" : "pch_hash");
-    cpp_hash.hash(util::format_digest(file_digest));
+    cpp_hash.hash(util::format_legacy_digest(file_digest));
   }
 
   if (ctx.config.direct_mode()) {
@@ -474,7 +474,7 @@ remember_include_file(Context& ctx,
 
     if (depend_mode_hash) {
       depend_mode_hash->hash_delimiter("include");
-      depend_mode_hash->hash(util::format_digest(file_digest));
+      depend_mode_hash->hash(util::format_legacy_digest(file_digest));
     }
   }
 
@@ -917,14 +917,14 @@ update_manifest(Context& ctx,
       };
     });
   if (added) {
-    LOG("Added result key to manifest {}", util::format_digest(manifest_key));
+    LOG("Added result key to manifest {}", util::format_base16(manifest_key));
     core::CacheEntry::Header header(ctx.config, core::CacheEntryType::manifest);
     ctx.storage.put(manifest_key,
                     core::CacheEntryType::manifest,
                     core::CacheEntry::serialize(header, ctx.manifest));
   } else {
     LOG("Did not add result key to manifest {}",
-        util::format_digest(manifest_key));
+        util::format_base16(manifest_key));
   }
 }
 
@@ -1279,7 +1279,7 @@ to_cache(Context& ctx,
       ASSERT(false);
     }
     LOG_RAW("Got result key from dependency file");
-    LOG("Result key: {}", util::format_digest(*result_key));
+    LOG("Result key: {}", util::format_base16(*result_key));
   }
 
   ASSERT(result_key);
@@ -2326,7 +2326,7 @@ get_manifest_key(Context& ctx, Hash& hash)
     ctx.config.set_direct_mode(false);
     return {};
   }
-  hash.hash(util::format_digest(input_file_digest));
+  hash.hash(util::format_legacy_digest(input_file_digest));
   return hash.digest();
 }
 
@@ -2436,7 +2436,7 @@ get_result_key_from_manifest(Context& ctx, const Hash::Digest& manifest_key)
     });
   if (read_manifests > 1 && !ctx.config.remote_only()) {
     LOG("Storing merged manifest {} locally",
-        util::format_digest(manifest_key));
+        util::format_base16(manifest_key));
     core::CacheEntry::Header header(ctx.config, core::CacheEntryType::manifest);
     ctx.storage.local.put(manifest_key,
                           core::CacheEntryType::manifest,
@@ -2509,7 +2509,7 @@ calculate_result_and_manifest_key(Context& ctx,
   if (direct_mode) {
     TRY_ASSIGN(manifest_key, get_manifest_key(ctx, hash));
     if (manifest_key && !ctx.config.recache()) {
-      LOG("Manifest key: {}", util::format_digest(*manifest_key));
+      LOG("Manifest key: {}", util::format_base16(*manifest_key));
       result_key = get_result_key_from_manifest(ctx, *manifest_key);
     }
   } else if (ctx.args_info.arch_args.empty()) {
@@ -2542,7 +2542,7 @@ calculate_result_and_manifest_key(Context& ctx,
   }
 
   if (result_key) {
-    LOG("Result key: {}", util::format_digest(*result_key));
+    LOG("Result key: {}", util::format_base16(*result_key));
   }
   return std::make_pair(result_key, manifest_key);
 }
@@ -2593,12 +2593,12 @@ from_cache(Context& ctx, FromCacheCallMode mode, const Hash::Digest& result_key)
     deserializer.visit(result_retriever);
   } catch (core::ResultRetriever::WriteError& e) {
     LOG("Write error when retrieving result from {}: {}",
-        util::format_digest(result_key),
+        util::format_base16(result_key),
         e.what());
     return tl::unexpected(Statistic::bad_output_file);
   } catch (core::Error& e) {
     LOG("Failed to get result from {}: {}",
-        util::format_digest(result_key),
+        util::format_base16(result_key),
         e.what());
     return false;
   }
index 4e0d34e07b14a9c0e8374d6e03f9562cfa236e8b..65b30893663945805541aa0742655a102060785e 100644 (file)
@@ -665,7 +665,7 @@ process_main_options(int argc, const char* const* argv)
       const auto result =
         arg == "-" ? hash.hash_fd(STDIN_FILENO) : hash.hash_file(arg);
       if (result) {
-        PRINT(stdout, "{}\n", util::format_digest(hash.digest()));
+        PRINT(stdout, "{}\n", util::format_base16(hash.digest()));
       } else {
         PRINT(stderr, "Error: Failed to hash {}: {}\n", arg, result.error());
         return EXIT_FAILURE;
index 12c7bf10c03ec998e424f1d2e16ae479ee2f577a..d2b84bef6e60606186c030439165d251b278915a 100644 (file)
@@ -166,7 +166,7 @@ Manifest::look_up_result_digest(const Context& ctx) const
     const auto& result = m_results[i - 1];
     LOG("Considering result entry {} ({})",
         i - 1,
-        util::format_digest(result.key));
+        util::format_base16(result.key));
     if (result_matches(ctx, result, stated_files, hashed_files)) {
       LOG("Result entry {} matched in manifest", i - 1);
       return result.key;
@@ -438,8 +438,8 @@ Manifest::result_matches(
     if (hashed_files_iter->second != fi.digest) {
       LOG("Mismatch for {}: hash {} != {}",
           path,
-          util::format_digest(hashed_files_iter->second),
-          util::format_digest(fi.digest));
+          util::format_base16(hashed_files_iter->second),
+          util::format_base16(fi.digest));
       return false;
     }
   }
@@ -462,7 +462,7 @@ Manifest::inspect(FILE* const stream) const
     PRINT(stream, "  {}:\n", i);
     PRINT(stream, "    Path index: {}\n", m_file_infos[i].index);
     PRINT(
-      stream, "    Hash: {}\n", util::format_digest(m_file_infos[i].digest));
+      stream, "    Hash: {}\n", util::format_base16(m_file_infos[i].digest));
     PRINT(stream, "    File size: {}\n", m_file_infos[i].fsize);
     if (m_file_infos[i].mtime == util::TimePoint()) {
       PRINT_RAW(stream, "    Mtime: -\n");
@@ -490,7 +490,7 @@ Manifest::inspect(FILE* const stream) const
       PRINT(stream, " {}", file_info_index);
     }
     PRINT_RAW(stream, "\n");
-    PRINT(stream, "    Key: {}\n", util::format_digest(m_results[i].key));
+    PRINT(stream, "    Key: {}\n", util::format_base16(m_results[i].key));
   }
 }
 
index bb76ed9f0c70836accd56035b406644b99a0344c..ead716b2a16d1d6236aab9058c07a56da08526e4 100644 (file)
@@ -256,7 +256,7 @@ hash_source_code_file(const Context& ctx,
   // macro expansions.
 
   Hash hash;
-  hash.hash(util::format_digest(digest));
+  hash.hash(util::format_legacy_digest(digest));
 
   if (result.contains(HashSourceCode::found_date)) {
     LOG("Found __DATE__ in {}", path);
@@ -320,7 +320,7 @@ hash_binary_file(const Context& ctx, Hash& hash, const fs::path& path)
   Hash::Digest digest;
   const bool success = hash_binary_file(ctx, digest, path);
   if (success) {
-    hash.hash(util::format_digest(digest));
+    hash.hash(util::format_legacy_digest(digest));
   }
   return success;
 }
index 9af0cf194b8383e4b0cb14c91756e099a7ef9b89..778e785b78ea5754e81b7f146fccf375553d34a2 100644 (file)
@@ -502,7 +502,7 @@ LocalStorage::get(const Hash::Digest& key, const core::CacheEntryType type)
     const auto value = util::read_file<util::Bytes>(cache_file.path);
     if (value) {
       LOG("Retrieved {} from local storage ({})",
-          util::format_digest(key),
+          util::format_base16(key),
           cache_file.path);
 
       // Update modification timestamp to save file from LRU cleanup.
@@ -513,7 +513,7 @@ LocalStorage::get(const Hash::Digest& key, const core::CacheEntryType type)
       LOG("Failed to read {}: {}", cache_file.path, value.error());
     }
   } else {
-    LOG("No {} in local storage", util::format_digest(key));
+    LOG("No {} in local storage", util::format_base16(key));
   }
 
   increment_statistic(return_value ? Statistic::local_storage_read_hit
@@ -555,7 +555,7 @@ LocalStorage::put(const Hash::Digest& key,
   }
 
   LOG("Stored {} in local storage ({})",
-      util::format_digest(key),
+      util::format_base16(key),
       cache_file.path);
   m_stored_data = true;
 
@@ -588,7 +588,7 @@ LocalStorage::put(const Hash::Digest& key,
   // be done almost anywhere, but we might as well do it near the end as we save
   // the stat call if we exit early.
   util::create_cachedir_tag(
-    FMT("{}/{}", m_config.cache_dir(), util::format_digest(key)[0]));
+    FMT("{}/{}", m_config.cache_dir(), util::format_base16(key)[0]));
 }
 
 void
@@ -596,7 +596,7 @@ LocalStorage::remove(const Hash::Digest& key, const core::CacheEntryType type)
 {
   const auto cache_file = look_up_cache_file(key, type);
   if (!cache_file.dir_entry) {
-    LOG("No {} to remove from local storage", util::format_digest(key));
+    LOG("No {} to remove from local storage", util::format_base16(key));
     return;
   }
 
@@ -611,7 +611,7 @@ LocalStorage::remove(const Hash::Digest& key, const core::CacheEntryType type)
   }
 
   LOG("Removed {} from local storage ({})",
-      util::format_digest(key),
+      util::format_base16(key),
       cache_file.path);
   increment_files_and_size_counters(
     key, -1, -static_cast<int64_t>(cache_file.dir_entry.size_on_disk() / 1024));
@@ -1075,7 +1075,7 @@ LocalStorage::look_up_cache_file(const Hash::Digest& key,
                                  const core::CacheEntryType type) const
 {
   const auto key_string =
-    FMT("{}{}", util::format_digest(key), suffix_from_type(type));
+    FMT("{}{}", util::format_base16(key), suffix_from_type(type));
 
   for (uint8_t level = k_min_cache_levels; level <= k_max_cache_levels;
        ++level) {
@@ -1113,7 +1113,7 @@ LocalStorage::move_to_wanted_cache_level(const StatisticsCounters& counters,
   const auto wanted_level =
     calculate_wanted_cache_level(counters.get(Statistic::files_in_cache));
   const auto wanted_path = get_path_in_cache(
-    wanted_level, util::format_digest(key) + suffix_from_type(type));
+    wanted_level, util::format_base16(key) + suffix_from_type(type));
   if (cache_file_path != wanted_path) {
     core::ensure_dir_exists(wanted_path.parent_path());
 
index 574b2da71dbcb6fe6ed8345bfc404cef93ecb224..1f34b6e440d3ac0cba86821ab64a3bd0f5b3721e 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2025 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2026 Joel Rosdahl and other contributors
 //
 // See doc/authors.adoc for a complete list of contributors.
 //
@@ -189,10 +189,10 @@ FileStorageBackend::get_entry_path(const Hash::Digest& key) const
 {
   switch (m_layout) {
   case Layout::flat:
-    return FMT("{}/{}", m_dir, util::format_digest(key));
+    return FMT("{}/{}", m_dir, util::format_base16(key));
 
   case Layout::subdirs: {
-    const auto key_str = util::format_digest(key);
+    const auto key_str = util::format_base16(key);
     const uint8_t digits = 2;
     ASSERT(key_str.length() > digits);
     return FMT("{}/{:.{}}/{}", m_dir, key_str, digits, &key_str[digits]);
index be4d7afea83b4fd618ce70dcd1ecbab94d5a9d16..5f7d8719a283753e2ae028e19be048536502f5a9 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2025 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2026 Joel Rosdahl and other contributors
 //
 // See doc/authors.adoc for a complete list of contributors.
 //
@@ -282,19 +282,18 @@ HttpStorageBackend::get_entry_path(const Hash::Digest& key) const
     const auto sha256_hex_size = 64;
     static_assert(std::tuple_size<Hash::Digest>() == 20,
                   "Update below if digest size changes");
-    std::string hex_digits = util::format_base16(key);
-    hex_digits.append(hex_digits.data(), sha256_hex_size - hex_digits.size());
-    LOG("Translated key {} to Bazel layout ac/{}",
-        util::format_digest(key),
-        hex_digits);
-    return FMT("{}ac/{}", m_url_path, hex_digits);
+    std::string hex_key = util::format_base16(key);
+    std::string bazel_key =
+      FMT("ac/{}{:.{}}", hex_key, hex_key, sha256_hex_size - hex_key.size());
+    LOG("Translated key {} to Bazel layout {}", hex_key, bazel_key);
+    return FMT("{}{}", m_url_path, bazel_key);
   }
 
   case Layout::flat:
-    return m_url_path + util::format_digest(key);
+    return m_url_path + util::format_base16(key);
 
   case Layout::subdirs: {
-    const auto key_str = util::format_digest(key);
+    const auto key_str = util::format_base16(key);
     const uint8_t digits = 2;
     ASSERT(key_str.length() > digits);
     return FMT("{}{:.{}}/{}", m_url_path, key_str, digits, &key_str[digits]);
index bd163e7dcbd97c8fd9ea39b1b0f920daca254af1..f7515a5bb9fe9d5f3963706430571d446040e480 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2025 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2026 Joel Rosdahl and other contributors
 //
 // See doc/authors.adoc for a complete list of contributors.
 //
@@ -344,7 +344,7 @@ RedisStorageBackend::redis_command(const char* format, ...)
 std::string
 RedisStorageBackend::get_key_string(const Hash::Digest& digest) const
 {
-  return FMT("{}:{}", m_prefix, util::format_digest(digest));
+  return FMT("{}:{}", m_prefix, util::format_base16(digest));
 }
 
 } // namespace
index c9ce6c357d2ecdb74afdbcfaf8004d7a3763b695..6020b99cb6bf75282d971697e3484619a670c0e8 100644 (file)
@@ -479,7 +479,7 @@ Storage::get_from_remote_storage(const Hash::Digest& key,
     auto& value = *result;
     if (value) {
       LOG("Retrieved {} from {} ({:.2f} ms)",
-          util::format_digest(key),
+          util::format_base16(key),
           backend->url_for_logging,
           ms);
       local.increment_statistic(core::Statistic::remote_storage_read_hit);
@@ -491,7 +491,7 @@ Storage::get_from_remote_storage(const Hash::Digest& key,
       }
     } else {
       LOG("No {} in {} ({:.2f} ms)",
-          util::format_digest(key),
+          util::format_base16(key),
           backend->url_for_logging,
           ms);
       local.increment_statistic(core::Statistic::remote_storage_read_miss);
@@ -506,7 +506,7 @@ Storage::put_in_remote_storage(const Hash::Digest& key,
 {
   if (!core::CacheEntry::Header(value).self_contained) {
     LOG("Not putting {} in remote storage since it's not self-contained",
-        util::format_digest(key));
+        util::format_base16(key));
     return;
   }
 
@@ -528,7 +528,7 @@ Storage::put_in_remote_storage(const Hash::Digest& key,
     const bool stored = *result;
     LOG("{} {} in {} ({:.2f} ms)",
         stored ? "Stored" : "Did not have to store",
-        util::format_digest(key),
+        util::format_base16(key),
         backend->url_for_logging,
         ms);
     local.increment_statistic(core::Statistic::remote_storage_write);
@@ -555,12 +555,12 @@ Storage::remove_from_remote_storage(const Hash::Digest& key)
     const bool removed = *result;
     if (removed) {
       LOG("Removed {} from {} ({:.2f} ms)",
-          util::format_digest(key),
+          util::format_base16(key),
           backend->url_for_logging,
           ms);
     } else {
       LOG("No {} to remove from {} ({:.2f} ms)",
-          util::format_digest(key),
+          util::format_base16(key),
           backend->url_for_logging,
           ms);
     }
index 86b783c8a485d2df2dae3e8180e3401919059e03..9026642258e43708ebdca5f542849fb7d4acdff0 100644 (file)
@@ -164,7 +164,7 @@ format_base32hex(nonstd::span<const uint8_t> data)
 }
 
 std::string
-format_digest(nonstd::span<const uint8_t> data)
+format_legacy_digest(nonstd::span<const uint8_t> data)
 {
   const size_t base16_bytes = 2;
   ASSERT(data.size() >= base16_bytes);
index e8c4989d2724c970a9242c57a04444e89c0ff737..a5ee5bcd1605dba96a33324f51864ae005ace42c 100644 (file)
@@ -70,14 +70,6 @@ std::string format_base16(nonstd::span<const uint8_t> data);
 // characters will be added.
 std::string format_base32hex(nonstd::span<const uint8_t> data);
 
-// Format a hash digest representing `data`.
-//
-// The first two bytes are encoded as four lowercase base16 digits to maintain
-// compatibility with the cleanup algorithm in older ccache versions and to
-// allow for up to four uniform cache levels. The rest are encoded as lowercase
-// base32hex digits without padding characters.
-std::string format_digest(nonstd::span<const uint8_t> data);
-
 // Format `ms` as a duration string.
 std::string format_duration(std::chrono::milliseconds ms);
 
@@ -85,6 +77,14 @@ std::string format_duration(std::chrono::milliseconds ms);
 std::string format_human_readable_diff(int64_t diff,
                                        SizeUnitPrefixType prefix_type);
 
+// Format a hash digest representing `data`.
+//
+// The first two bytes are encoded as four lowercase base16 digits to maintain
+// compatibility with the cleanup algorithm in older ccache versions and to
+// allow for up to four uniform cache levels. The rest are encoded as lowercase
+// base32hex digits without padding characters.
+std::string format_legacy_digest(nonstd::span<const uint8_t> data);
+
 // Format `size` as a human-readable string.
 std::string format_human_readable_size(uint64_t size,
                                        SizeUnitPrefixType prefix_type);
index 092b78f2d54c5ecf553cd001f9b837670c94fc2d..1b8a781b1c4cf17cf311642fba141901fa6def49 100755 (executable)
--- a/test/run
+++ b/test/run
@@ -208,6 +208,12 @@ expect_missing() {
     fi
 }
 
+expect_equal() {
+    if [ "$1" != "$2" ]; then
+        test_failed_internal "$1 != $2"
+    fi
+}
+
 expect_equal_content() {
     if [ ! -e "$1" ]; then
         test_failed_internal "expect_equal_content: $1 missing"
index 76650831b6260b972bd0b05cf206542cb79092a9..b3ce9e66b5c475163e14d13dd5f4a0a7814484e9 100644 (file)
@@ -1907,18 +1907,8 @@ fi
     # -------------------------------------------------------------------------
     TEST "--hash-file"
 
-    $CCACHE --hash-file /dev/null > hash.out
-    printf "a" | $CCACHE --hash-file - >> hash.out
-
-    hash_0='af1396svbud1kqg40jfa6reciicrpcisi'
-    hash_1='17765vetiqd4ae95qpbhfb1ut8gj42r6m'
-
-    if grep "$hash_0" hash.out >/dev/null 2>&1 && \
-       grep "$hash_1" hash.out >/dev/null 2>&1; then
-        : OK
-    else
-        test_failed "Unexpected output of --hash-file"
-    fi
+    expect_equal $($CCACHE --hash-file /dev/null) af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9
+    expect_equal $(printf a | $CCACHE --hash-file -) 17762fddd969a453925d65717ac3eea21320b66b
 }
 
 # =============================================================================
index 45fac9c0a4ee1d4b7a5b93cf39a4338fc775114f..d6de96691b124f2ffe30e81b7dd4820f20da3fb9 100644 (file)
@@ -1245,9 +1245,9 @@ EOF
     manifest=`find $CCACHE_DIR -name '*M'`
     $CCACHE --inspect $manifest >manifest.dump
 
-    checksum_test1_h='b7273h0ksdehi0o4pitg5jeehal3i54ns'
-    checksum_test2_h='24f1315jch5tcndjbm6uejtu8q3lf9100'
-    checksum_test3_h='56a6dkffffv485aepk44seaq3i6lbepq2'
+    checksum_test1_h='b7271c414e35d190304ccbb02cdce8aaa391497e'
+    checksum_test2_h='24f1184b3644bd65db35d8de74fbe468757a4200'
+    checksum_test3_h='56a66d1ef7bfe44154ecd084e395a1c8d55bb3a1'
 
     if grep "Hash: $checksum_test1_h" manifest.dump >/dev/null 2>&1 && \
        grep "Hash: $checksum_test2_h" manifest.dump >/dev/null 2>&1 && \
index ec3cfce12d42eb88b9e4779c6f4ffe85ca9c30ce..337c17b6c8b4fff05dc92548c9b38d14ccf7fc68 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2010-2024 Joel Rosdahl and other contributors
+// Copyright (C) 2010-2025 Joel Rosdahl and other contributors
 //
 // See doc/authors.adoc for a complete list of contributors.
 //
@@ -27,26 +27,26 @@ TEST_CASE("known strings")
 {
   SUBCASE("initial state")
   {
-    CHECK(util::format_digest(Hash().digest())
-          == "af1396svbud1kqg40jfa6reciicrpcisi");
+    CHECK(util::format_base16(Hash().digest())
+          == "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9");
   }
 
   SUBCASE("empty string")
   {
-    CHECK(util::format_digest(Hash().hash("").digest())
-          == "af1396svbud1kqg40jfa6reciicrpcisi");
+    CHECK(util::format_base16(Hash().hash("").digest())
+          == "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9");
   }
 
   SUBCASE("a")
   {
-    CHECK(util::format_digest(Hash().hash("a").digest())
-          == "17765vetiqd4ae95qpbhfb1ut8gj42r6m");
+    CHECK(util::format_base16(Hash().hash("a").digest())
+          == "17762fddd969a453925d65717ac3eea21320b66b");
   }
 
   SUBCASE("message digest")
   {
-    CHECK(util::format_digest(Hash().hash("message digest").digest())
-          == "7bc2kbnbinerv6ruptldpdrb8ko93hcdo");
+    CHECK(util::format_base16(Hash().hash("message digest").digest())
+          == "7bc2a2eeb95ddbf9b7ecf6adcb76b453091c58dc");
   }
 
   SUBCASE("long string")
@@ -54,8 +54,8 @@ TEST_CASE("known strings")
     const char long_string[] =
       "123456789012345678901234567890123456789012345678901234567890"
       "12345678901234567890";
-    CHECK(util::format_digest(Hash().hash(long_string).digest())
-          == "f263ljqhc8co1ee8rpeq98bt654o9o2qm");
+    CHECK(util::format_base16(Hash().hash(long_string).digest())
+          == "f263acf51621980b9c8de5da4a17d314984e05ab");
   }
 }
 
@@ -65,14 +65,17 @@ TEST_CASE("Hash::digest should not alter state")
   h.hash("message");
   h.digest();
   h.hash(" digest");
-  CHECK(util::format_digest(h.digest()) == "7bc2kbnbinerv6ruptldpdrb8ko93hcdo");
+  CHECK(util::format_base16(h.digest())
+        == "7bc2a2eeb95ddbf9b7ecf6adcb76b453091c58dc");
 }
 
 TEST_CASE("Hash::digest should be idempotent")
 {
   Hash h;
-  CHECK(util::format_digest(h.digest()) == "af1396svbud1kqg40jfa6reciicrpcisi");
-  CHECK(util::format_digest(h.digest()) == "af1396svbud1kqg40jfa6reciicrpcisi");
+  CHECK(util::format_base16(h.digest())
+        == "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9");
+  CHECK(util::format_base16(h.digest())
+        == "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9");
 }
 
 TEST_SUITE_END();