From 7229ece8a0e1ce67f9f5e7b23609097a72adbae8 Mon Sep 17 00:00:00 2001 From: CEbbinghaus Date: Thu, 23 Apr 2026 17:22:58 +1000 Subject: [PATCH 1/3] Moved everything to SHA256 --- client/src/main.rs | 18 ++++++++-------- common/src/archive.rs | 10 ++++----- common/src/hash.rs | 50 +++++++++++++++++++++---------------------- common/src/object.rs | 4 ++-- 4 files changed, 41 insertions(+), 41 deletions(-) diff --git a/client/src/main.rs b/client/src/main.rs index 6117f6b..4a52dfa 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -11,7 +11,7 @@ use common::{ read_object_into_headers_sync, Hash, Header, Mode, ObjectType, BLOB_KEY, INDEX_KEY, TREE_KEY, }; use rayon::prelude::*; -use sha2::{Digest, Sha512}; +use sha2::{Digest, Sha256}; use std::{ collections::HashMap, fs::{create_dir, create_dir_all, read_dir, File}, @@ -202,7 +202,7 @@ impl<'a> CacheObject<'a> { let mode = Mode::from_str(mode).expect("valid mode"); - let mut hash: [u8; 64] = [0; 64]; + let mut hash: [u8; 32] = [0; 32]; file.read_exact(&mut hash).expect("file to contain hash"); let hash = Hash::from(hash); @@ -306,7 +306,7 @@ impl Object for Index { fn get_hash(&self) -> Hash { let body = self.get_body(); - let mut hasher = Sha512::new(); + let mut hasher = Sha256::new(); write!(hasher, "{}{}", self.get_prefix(), body).unwrap(); Hash::from(hasher) } @@ -425,7 +425,7 @@ impl Object for Tree { fn get_hash(&self) -> Hash { let body = self.get_body(); - let mut hasher = Sha512::new(); + let mut hasher = Sha256::new(); write!(hasher, "{}", self.get_prefix()).unwrap(); hasher .write_all(&body) @@ -487,7 +487,7 @@ impl Blob { let size = src.metadata().unwrap().len(); let prefix = format!("{} {}\0", BLOB_KEY, size); - let mut hasher = Sha512::new(); + let mut hasher = Sha256::new(); hasher.write_all(prefix.as_bytes()).unwrap(); let f = File::open(src).unwrap(); @@ -543,7 +543,7 @@ impl Object for Blob { } fn get_hash(&self) -> Hash { - let mut hasher = Sha512::new(); + let mut hasher = Sha256::new(); hasher.write_all(self.get_prefix().as_bytes()).unwrap(); let f = File::open(&self.file).unwrap(); @@ -1034,7 +1034,7 @@ fn unpack_archive(cache: &Path, path: &Path) -> anyhow::Result<()> { let index_data = archive.index.to_data(); let index_header = Header::new(ObjectType::Index, index_data.len() as u64); - let mut hasher = Sha512::new(); + let mut hasher = Sha256::new(); hasher.write_all(index_header.to_string().as_bytes())?; hasher.write_all(&index_data)?; assert!(Hash::from(hasher) == archive.hash); @@ -1383,7 +1383,7 @@ mod tests { // archive.index without needing a separate reference build. let index_data = archive.index.to_data(); let index_header = Header::new(ObjectType::Index, index_data.len() as u64); - let mut hasher = Sha512::new(); + let mut hasher = Sha256::new(); hasher .write_all(index_header.to_string().as_bytes()) .unwrap(); @@ -1391,7 +1391,7 @@ mod tests { assert_eq!( archive.hash, Hash::from(hasher), - "archive hash must equal sha512(index header + body)" + "archive hash must equal Sha256(index header + body)" ); // Body has 1 tree + 3 blobs = 4 entries (index is in the archive header, not body). diff --git a/common/src/archive.rs b/common/src/archive.rs index 363fc0f..124e190 100644 --- a/common/src/archive.rs +++ b/common/src/archive.rs @@ -11,7 +11,7 @@ use anyhow::anyhow; use futures::AsyncReadExt; use lzma_rust2::LzmaOptions; use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha512}; +use sha2::{Digest, Sha256}; use crate::{ object_body::{Index, Object}, @@ -229,7 +229,7 @@ where .try_into() .map_err(|_| anyhow!("Invalid Compression"))?; - let mut hash: [u8; 64] = [0; 64]; + let mut hash: [u8; 32] = [0; 32]; reader.read_exact(&mut hash)?; let hash: Hash = hash.into(); @@ -444,7 +444,7 @@ where break; } - let mut hash: [u8; 64] = [0; 64]; + let mut hash: [u8; 32] = [0; 32]; reader.read_exact(&mut hash)?; let hash: Hash = hash.into(); @@ -476,7 +476,7 @@ where let mut data: Vec = vec![0; amount as usize]; reader.read_exact(&mut data[..])?; - let mut hasher = Sha512::new(); + let mut hasher = Sha256::new(); hasher.write_all(&data)?; assert!(Hash::from(hasher) == entry.hash); @@ -499,7 +499,7 @@ mod tests { use std::collections::HashMap; fn empty_archive(compression: CompressionAlgorithm) -> Archive { - let zero = Hash::from([0u8; 64]); + let zero = Hash::from([0u8; 32]); Archive { header: HEADER, compression, diff --git a/common/src/hash.rs b/common/src/hash.rs index 7a5cb6e..9a7a34a 100644 --- a/common/src/hash.rs +++ b/common/src/hash.rs @@ -3,7 +3,7 @@ use serde::de::{self, Visitor}; use serde::{Deserialize, Deserializer, Serialize}; use sha2::digest::FixedOutput; -use sha2::Sha512; +use sha2::Sha256; use std::fmt::{self, Debug, Display}; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -11,9 +11,9 @@ use std::str::FromStr; #[derive(Clone, Serialize)] pub struct Hash { - // Sha512 Hash value + // Sha256 Hash value #[serde(skip)] - pub hash: [u8; 64], + pub hash: [u8; 32], hash_string: String, } @@ -27,13 +27,13 @@ impl Hash { } pub fn from_string(value: &str) -> Option { - if value.len() != 128 { + if value.len() != 64 { return None; } let hash = hex::decode(value).ok()?; - if hash.len() != 64 { + if hash.len() != 32 { return None; } @@ -56,7 +56,7 @@ impl Hash { return None; } - if filename.len() != 126 { + if filename.len() != 62 { return None; } @@ -90,13 +90,13 @@ impl TryFrom for Hash { type Error = anyhow::Error; fn try_from(value: String) -> Result { - if value.len() != 128 { + if value.len() != 64 { return Err(anyhow!( - "Invalid length. Hash has to be 128 characters long" + "Invalid length. Hash has to be 64 characters long" )); } - let mut hash = [0u8; 64]; + let mut hash = [0u8; 32]; hex::decode_to_slice(&value, &mut hash)?; Ok(Self { @@ -110,13 +110,13 @@ impl TryFrom<&str> for Hash { type Error = anyhow::Error; fn try_from(value: &str) -> Result { - if value.len() != 128 { + if value.len() != 64 { return Err(anyhow!( - "Invalid length. Hash has to be 128 characters long" + "Invalid length. Hash has to be 64 characters long" )); } - let mut hash = [0u8; 64]; + let mut hash = [0u8; 32]; hex::decode_to_slice(value, &mut hash)?; Ok(Self { @@ -130,11 +130,11 @@ impl TryFrom<&[u8]> for Hash { type Error = anyhow::Error; fn try_from(value: &[u8]) -> Result { - if value.len() != 64 { - return Err(anyhow!("Invalid length. Slice must be 64 bytes long")); + if value.len() != 32 { + return Err(anyhow!("Invalid length. Slice must be 32 bytes long")); } - let data: [u8; 64] = value.try_into()?; + let data: [u8; 32] = value.try_into()?; Ok(Self { hash_string: hex::encode(value), hash: data, @@ -142,8 +142,8 @@ impl TryFrom<&[u8]> for Hash { } } -impl From<[u8; 64]> for Hash { - fn from(value: [u8; 64]) -> Self { +impl From<[u8; 32]> for Hash { + fn from(value: [u8; 32]) -> Self { Self { hash_string: hex::encode(value), hash: value, @@ -151,9 +151,9 @@ impl From<[u8; 64]> for Hash { } } -impl From for Hash { - fn from(value: Sha512) -> Self { - Self::from(Into::<[u8; 64]>::into(value.finalize_fixed())) +impl From for Hash { + fn from(value: Sha256) -> Self { + Self::from(Into::<[u8; 32]>::into(value.finalize_fixed())) } } @@ -180,17 +180,17 @@ impl<'de> Deserialize<'de> for Hash { type Value = Hash; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("Sha512 hex string Hash") + formatter.write_str("Sha256 hex string Hash") } fn visit_str(self, value: &str) -> Result where E: serde::de::Error, { - if value.len() != 128 { + if value.len() != 64 { return Err(de::Error::invalid_length( value.len(), - &"A hex string with 128 characters", + &"A hex string with 64 characters", )); } @@ -201,10 +201,10 @@ impl<'de> Deserialize<'de> for Hash { where E: serde::de::Error, { - if value.len() != 128 { + if value.len() != 64 { return Err(de::Error::invalid_length( value.len(), - &"A hex string with 128 characters", + &"A hex string with 64 characters", )); } diff --git a/common/src/object.rs b/common/src/object.rs index ca330a7..c977209 100644 --- a/common/src/object.rs +++ b/common/src/object.rs @@ -1,6 +1,6 @@ use crate::{Hash, Header}; use anyhow::{anyhow, Result}; -use sha2::{Digest, Sha512}; +use sha2::{Digest, Sha256}; use std::io::{BufReader, Read, Write}; pub struct Object { @@ -10,7 +10,7 @@ pub struct Object { impl Object { pub fn to_hash(&self) -> Hash { - let mut hasher = Sha512::new(); + let mut hasher = Sha256::new(); hasher .write_all(self.header.to_string().as_bytes()) .expect("Out of Memory"); From c3c38569b1bd8ad88c504310fce3ffb04172d7a4 Mon Sep 17 00:00:00 2001 From: CEbbinghaus Date: Thu, 23 Apr 2026 17:35:29 +1000 Subject: [PATCH 2/3] Updated Docs and comments --- client/src/main.rs | 5 +---- docs/designs/design.md | 4 ++-- server/src/main.rs | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/client/src/main.rs b/client/src/main.rs index 4a52dfa..1adfccb 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1377,10 +1377,7 @@ mod tests { let mut reader = BufReader::new(f); let archive = Archive::::from_data(&mut reader).expect("archive to parse"); - // Archive hash must match the SHA-512 of the index header + body bytes — - // the same integrity invariant `unpack_archive` enforces when restoring - // into a store. This catches mismatches between archive.hash and - // archive.index without needing a separate reference build. + // Archive hash must match the SHA-256 of the index header + body bytes let index_data = archive.index.to_data(); let index_header = Header::new(ObjectType::Index, index_data.len() as u64); let mut hasher = Sha256::new(); diff --git a/docs/designs/design.md b/docs/designs/design.md index 1616883..2423519 100644 --- a/docs/designs/design.md +++ b/docs/designs/design.md @@ -1,6 +1,6 @@ # Design -The ArtifactRepository design is based on a SHA2-512 Merkel Tree which serves as the foundation of the Artifact. Each file within an Artifact is content addressed by its sha512 hash and directories are stored in an identical format to git. In general the object structure closely matches that of git. +The ArtifactRepository design is based on a SHA2-256 Merkel Tree which serves as the foundation of the Artifact. Each file within an Artifact is content addressed by its sha256 hash and directories are stored in an identical format to git. In general the object structure closely matches that of git. The very top level of an Artifact is called an **Index** (git calls it a commit). This defines the artifact and contains any and all relevant metadata, such as the timestamp of creation. But since it simply contains any and all metadata in a simple key value format similar to HTTP headers, it allows for the same level of flexibility and metadata to be attached to an index. Some possibilities could include: @@ -45,7 +45,7 @@ It is structured as follows: | [u8; 4] | header / magic number | | [u8; 1] | version | | [u8; 2] | compression method | -| [u8; 64] | SHA2 512 index hash | +| [u8; 32] | SHA2 256 index hash | | [u8; N] | index data | | [u8; 1] | null byte | | [u8; N] | data (compressed with above method) | diff --git a/server/src/main.rs b/server/src/main.rs index 48f5c10..d36bd0f 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -144,7 +144,7 @@ impl From for ErrorResult { // let file = File::create(path).await?; // let mut writer = BufWriter::new(file).compat_write(); -// let mut hasher = Sha512::new(); +// let mut hasher = Sha256::new(); // header.write_to(&mut hasher)?; // header.write_to_async(&mut writer).await?; From 6055c70f9d18c9e941717f26c3f214189436fcb3 Mon Sep 17 00:00:00 2001 From: CEbbinghaus Date: Thu, 23 Apr 2026 17:37:30 +1000 Subject: [PATCH 3/3] Formatted --- common/src/hash.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/common/src/hash.rs b/common/src/hash.rs index 9a7a34a..e67089f 100644 --- a/common/src/hash.rs +++ b/common/src/hash.rs @@ -91,9 +91,7 @@ impl TryFrom for Hash { fn try_from(value: String) -> Result { if value.len() != 64 { - return Err(anyhow!( - "Invalid length. Hash has to be 64 characters long" - )); + return Err(anyhow!("Invalid length. Hash has to be 64 characters long")); } let mut hash = [0u8; 32]; @@ -111,9 +109,7 @@ impl TryFrom<&str> for Hash { fn try_from(value: &str) -> Result { if value.len() != 64 { - return Err(anyhow!( - "Invalid length. Hash has to be 64 characters long" - )); + return Err(anyhow!("Invalid length. Hash has to be 64 characters long")); } let mut hash = [0u8; 32];