Skip to main content

max / makenotwork

synckit-client: upgrade sha2 to 0.11 sha2 0.11's digest output no longer implements LowerHex, so the format!("{:x}", ..) sites become hex::encode. hash_row_id already built its hex by hand and is untouched; its test now also pins the wire value against an independent SHA-256. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-22 00:30 UTC
Commit: fb66fe9b888eeb5e33ce5aed5cd010408e12a483
Parent: c97ca66
7 files changed, +93 insertions, -32 deletions
@@ -8,7 +8,7 @@ version = "0.5.2"
8 8 source = "registry+https://github.com/rust-lang/crates.io-index"
9 9 checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
10 10 dependencies = [
11 - "crypto-common",
11 + "crypto-common 0.1.7",
12 12 "generic-array",
13 13 ]
14 14
@@ -247,7 +247,7 @@ version = "0.10.6"
247 247 source = "registry+https://github.com/rust-lang/crates.io-index"
248 248 checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
249 249 dependencies = [
250 - "digest",
250 + "digest 0.10.7",
251 251 ]
252 252
253 253 [[package]]
@@ -260,6 +260,15 @@ dependencies = [
260 260 ]
261 261
262 262 [[package]]
263 + name = "block-buffer"
264 + version = "0.12.1"
265 + source = "registry+https://github.com/rust-lang/crates.io-index"
266 + checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
267 + dependencies = [
268 + "hybrid-array",
269 + ]
270 +
271 + [[package]]
263 272 name = "block-padding"
264 273 version = "0.3.3"
265 274 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -379,7 +388,7 @@ version = "0.4.4"
379 388 source = "registry+https://github.com/rust-lang/crates.io-index"
380 389 checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
381 390 dependencies = [
382 - "crypto-common",
391 + "crypto-common 0.1.7",
383 392 "inout",
384 393 "zeroize",
385 394 ]
@@ -394,6 +403,12 @@ dependencies = [
394 403 ]
395 404
396 405 [[package]]
406 + name = "const-oid"
407 + version = "0.10.2"
408 + source = "registry+https://github.com/rust-lang/crates.io-index"
409 + checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
410 +
411 + [[package]]
397 412 name = "core-foundation"
398 413 version = "0.9.4"
399 414 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -455,6 +470,15 @@ dependencies = [
455 470 ]
456 471
457 472 [[package]]
473 + name = "crypto-common"
474 + version = "0.2.2"
475 + source = "registry+https://github.com/rust-lang/crates.io-index"
476 + checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
477 + dependencies = [
478 + "hybrid-array",
479 + ]
480 +
481 + [[package]]
458 482 name = "deadpool"
459 483 version = "0.12.3"
460 484 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -478,12 +502,23 @@ version = "0.10.7"
478 502 source = "registry+https://github.com/rust-lang/crates.io-index"
479 503 checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
480 504 dependencies = [
481 - "block-buffer",
482 - "crypto-common",
505 + "block-buffer 0.10.4",
506 + "crypto-common 0.1.7",
483 507 "subtle",
484 508 ]
485 509
486 510 [[package]]
511 + name = "digest"
512 + version = "0.11.3"
513 + source = "registry+https://github.com/rust-lang/crates.io-index"
514 + checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
515 + dependencies = [
516 + "block-buffer 0.12.1",
517 + "const-oid",
518 + "crypto-common 0.2.2",
519 + ]
520 +
521 + [[package]]
487 522 name = "displaydoc"
488 523 version = "0.2.5"
489 524 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -848,7 +883,7 @@ version = "0.12.1"
848 883 source = "registry+https://github.com/rust-lang/crates.io-index"
849 884 checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
850 885 dependencies = [
851 - "digest",
886 + "digest 0.10.7",
852 887 ]
853 888
854 889 [[package]]
@@ -897,6 +932,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
897 932 checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
898 933
899 934 [[package]]
935 + name = "hybrid-array"
936 + version = "0.4.13"
937 + source = "registry+https://github.com/rust-lang/crates.io-index"
938 + checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c"
939 + dependencies = [
940 + "typenum",
941 + ]
942 +
943 + [[package]]
900 944 name = "hyper"
901 945 version = "1.8.1"
902 946 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1822,7 +1866,7 @@ dependencies = [
1822 1866 "num",
1823 1867 "once_cell",
1824 1868 "serde",
1825 - "sha2",
1869 + "sha2 0.10.9",
1826 1870 "zbus",
1827 1871 ]
1828 1872
@@ -1929,7 +1973,18 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
1929 1973 dependencies = [
1930 1974 "cfg-if",
1931 1975 "cpufeatures 0.2.17",
1932 - "digest",
1976 + "digest 0.10.7",
1977 + ]
1978 +
1979 + [[package]]
1980 + name = "sha2"
1981 + version = "0.11.0"
1982 + source = "registry+https://github.com/rust-lang/crates.io-index"
1983 + checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
1984 + dependencies = [
1985 + "cfg-if",
1986 + "cpufeatures 0.3.0",
1987 + "digest 0.11.3",
1933 1988 ]
1934 1989
1935 1990 [[package]]
@@ -2034,6 +2089,7 @@ dependencies = [
2034 2089 "bytes",
2035 2090 "chacha20poly1305",
2036 2091 "chrono",
2092 + "hex",
2037 2093 "keyring",
2038 2094 "parking_lot",
2039 2095 "rand",
@@ -2041,7 +2097,7 @@ dependencies = [
2041 2097 "rusqlite",
2042 2098 "serde",
2043 2099 "serde_json",
2044 - "sha2",
2100 + "sha2 0.11.0",
2045 2101 "synckit-client",
2046 2102 "thiserror",
2047 2103 "tokio",
@@ -2328,9 +2384,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
2328 2384
2329 2385 [[package]]
2330 2386 name = "typenum"
2331 - version = "1.19.0"
2387 + version = "1.20.1"
2332 2388 source = "registry+https://github.com/rust-lang/crates.io-index"
2333 - checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
2389 + checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
2334 2390
2335 2391 [[package]]
2336 2392 name = "uds_windows"
@@ -2370,7 +2426,7 @@ version = "0.5.1"
2370 2426 source = "registry+https://github.com/rust-lang/crates.io-index"
2371 2427 checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
2372 2428 dependencies = [
2373 - "crypto-common",
2429 + "crypto-common 0.1.7",
2374 2430 "subtle",
2375 2431 ]
2376 2432
@@ -28,7 +28,8 @@ base64 = "0.22"
28 28 zeroize = "1"
29 29
30 30 # PKCE (OAuth2) challenge hashing
31 - sha2 = "0.10"
31 + sha2 = "0.11"
32 + hex = "0.4"
32 33
33 34 # HTTP
34 35 reqwest = { version = "0.13", default-features = false, features = ["json", "native-tls", "stream", "form", "charset", "http2", "system-proxy"] }
@@ -326,7 +326,7 @@ impl SyncKitClient {
326 326 // Verify the content address before the object can be assembled. A
327 327 // mismatch here aborts the session, so a file that changed after the
328 328 // caller hashed it is never stored under the stale address.
329 - let actual = format!("{:x}", hasher.finalize());
329 + let actual = hex::encode(hasher.finalize());
330 330 if actual != hash {
331 331 return Err(SyncKitError::IntegrityFailed {
332 332 expected: hash.to_string(),
@@ -610,7 +610,7 @@ impl SyncKitClient {
610 610 )?;
611 611 }
612 612
613 - let actual = format!("{:x}", Sha256::digest(&plaintext));
613 + let actual = hex::encode(Sha256::digest(&plaintext));
614 614 if actual != expected_hash {
615 615 return Err(SyncKitError::IntegrityFailed {
616 616 expected: expected_hash.to_string(),
@@ -656,7 +656,7 @@ mod tests {
656 656 // The integrity check in blob_download compares against this exact form:
657 657 // lowercase hex of SHA-256, the same string consumers store as the blob
658 658 // hash. If this drifts, every verified download would falsely reject.
659 - let h = format!("{:x}", Sha256::digest(b"hello blob"));
659 + let h = hex::encode(Sha256::digest(b"hello blob"));
660 660 assert_eq!(h.len(), 64);
661 661 assert!(
662 662 h.chars()
@@ -240,7 +240,7 @@ pub async fn download_one<T: BlobTransport>(
240 240 // Verify the plaintext hashes to its address before it lands under that name.
241 241 // The SDK's blob_download also re-verifies; this guards the store regardless
242 242 // of transport (and is what the in-memory tests exercise).
243 - let actual = format!("{:x}", Sha256::digest(&data));
243 + let actual = hex::encode(Sha256::digest(&data));
244 244 if actual != blob.hash {
245 245 return Err(SyncKitError::IntegrityFailed {
246 246 expected: blob.hash.clone(),
@@ -377,7 +377,7 @@ mod tests {
377 377 }
378 378
379 379 fn hash_of(content: &[u8]) -> String {
380 - format!("{:x}", Sha256::digest(content))
380 + hex::encode(Sha256::digest(content))
381 381 }
382 382
383 383 fn tempdir() -> PathBuf {
@@ -228,8 +228,15 @@ mod tests {
228 228 // sha256("salt:key") lowercase hex — the exact audiofiles definition.
229 229 let mut h = Sha256::new();
230 230 h.update(b"salt:key");
231 - let expected = format!("{:x}", h.finalize());
231 + let expected = hex::encode(h.finalize());
232 232 assert_eq!(hash_row_id("salt", "key"), expected);
233 + // Pinned against an independent SHA-256 as well, so the row id stays a
234 + // fixed wire value rather than one that merely agrees with whatever
235 + // hasher this crate happens to link.
236 + assert_eq!(
237 + hash_row_id("salt", "key"),
238 + "c82b2df7a36eec83a4b5a9d83093a209006ae9029b6cb13323500da263d9fd06"
239 + );
233 240 assert_eq!(hash_row_id("salt", "key").len(), 64);
234 241 // Salt actually keys the hash (not just concatenated blindly).
235 242 assert_ne!(hash_row_id("a", "bc"), hash_row_id("ab", "c"));
@@ -234,7 +234,7 @@ mod tests {
234 234 let mut h = Sha256::new();
235 235 h.update(salt.as_bytes());
236 236 h.update(key.as_bytes());
237 - Ok(format!("{:x}", h.finalize()))
237 + Ok(hex::encode(h.finalize()))
238 238 },
239 239 )
240 240 .unwrap();
@@ -492,7 +492,7 @@ async fn blob_upload_url_declares_the_length_the_put_will_carry() {
492 492 let plaintext: Vec<u8> = (0..(synckit_client::crypto::BLOB_CHUNK_SIZE + 500))
493 493 .map(|i| i as u8)
494 494 .collect();
495 - let hash = format!("{:x}", sha2::Sha256::digest(&plaintext));
495 + let hash = hex::encode(sha2::Sha256::digest(&plaintext));
496 496
497 497 let resp = client
498 498 .blob_upload_url(&hash, plaintext.len() as i64)
@@ -578,7 +578,7 @@ async fn blob_download_decrypts_data() {
578 578 // Encrypt data to simulate what S3 would return. A legacy (untagged) blob
579 579 // still decrypts through the AAD-aware reader and must pass the hash check.
580 580 let plaintext = b"decrypted blob content";
581 - let hash = format!("{:x}", sha2::Sha256::digest(plaintext));
581 + let hash = hex::encode(sha2::Sha256::digest(plaintext));
582 582 let encrypted = synckit_client::crypto::encrypt_bytes(plaintext, &key).unwrap();
583 583
584 584 let download_path = "/s3/download";
@@ -1427,7 +1427,7 @@ async fn blob_upload_download_roundtrip() {
1427 1427 client.set_master_key_raw(key);
1428 1428
1429 1429 let plaintext = b"roundtrip blob data with special bytes \x00\xFF\x01";
1430 - let hash = format!("{:x}", sha2::Sha256::digest(plaintext));
1430 + let hash = hex::encode(sha2::Sha256::digest(plaintext));
1431 1431
1432 1432 // Upload
1433 1433 let upload_path = "/s3/roundtrip-upload";
@@ -2616,7 +2616,7 @@ async fn blob_download_retries_on_503() {
2616 2616 client.set_master_key_raw(key);
2617 2617
2618 2618 let plaintext = b"retry download test";
2619 - let hash = format!("{:x}", sha2::Sha256::digest(plaintext));
2619 + let hash = hex::encode(sha2::Sha256::digest(plaintext));
2620 2620 let encrypted = synckit_client::crypto::encrypt_bytes(plaintext, &key).unwrap();
2621 2621
2622 2622 let download_path = "/s3/retry-download";
@@ -3330,7 +3330,7 @@ mod blob_multipart {
3330 3330 let plaintext: Vec<u8> = (0..(synckit_client::crypto::BLOB_CHUNK_SIZE * 3 + 7))
3331 3331 .map(|i| i as u8)
3332 3332 .collect();
3333 - let hash = format!("{:x}", sha2::Sha256::digest(&plaintext));
3333 + let hash = hex::encode(sha2::Sha256::digest(&plaintext));
3334 3334 let file = temp_blob("big.bin", &plaintext);
3335 3335
3336 3336 let cipher_len = synckit_client::crypto::blob_encrypted_len(plaintext.len());
@@ -3438,7 +3438,7 @@ mod blob_multipart {
3438 3438 let key = synckit_client::crypto::generate_master_key();
3439 3439 client.set_master_key_raw(key);
3440 3440
3441 - let hash = format!("{:x}", sha2::Sha256::digest(b""));
3441 + let hash = hex::encode(sha2::Sha256::digest(b""));
3442 3442 let file = temp_blob("empty.bin", b"");
3443 3443 let cipher_len = synckit_client::crypto::blob_encrypted_len(0);
3444 3444 mount_session(&server, cipher_len, 1024 * 1024).await;
@@ -3479,7 +3479,7 @@ mod blob_multipart {
3479 3479 .await;
3480 3480
3481 3481 let plaintext = b"content the server already holds";
3482 - let hash = format!("{:x}", sha2::Sha256::digest(plaintext));
3482 + let hash = hex::encode(sha2::Sha256::digest(plaintext));
3483 3483 let file = temp_blob("dedup.bin", plaintext);
3484 3484
3485 3485 client.blob_upload_streaming(&hash, &file).await.unwrap();
@@ -3505,10 +3505,7 @@ mod blob_multipart {
3505 3505 client.set_master_key_raw(synckit_client::crypto::generate_master_key());
3506 3506
3507 3507 let plaintext = b"the bytes actually on disk";
3508 - let stale_hash = format!(
3509 - "{:x}",
3510 - sha2::Sha256::digest(b"what the caller hashed earlier")
3511 - );
3508 + let stale_hash = hex::encode(sha2::Sha256::digest(b"what the caller hashed earlier"));
3512 3509 let file = temp_blob("changed.bin", plaintext);
3513 3510
3514 3511 let cipher_len = synckit_client::crypto::blob_encrypted_len(plaintext.len());
@@ -3548,7 +3545,7 @@ mod blob_multipart {
3548 3545 client.set_master_key_raw(synckit_client::crypto::generate_master_key());
3549 3546
3550 3547 let plaintext = b"a blob whose part upload will fail";
3551 - let hash = format!("{:x}", sha2::Sha256::digest(plaintext));
3548 + let hash = hex::encode(sha2::Sha256::digest(plaintext));
3552 3549 let file = temp_blob("failing.bin", plaintext);
3553 3550 let cipher_len = synckit_client::crypto::blob_encrypted_len(plaintext.len());
3554 3551