use crate::config::HashAlgorithm; use cid::Cid; use multihash::Multihash; /// CID codec for raw binary data const RAW_CODEC: u64 = 0x55; /// Maximum multihash size (64 bytes for SHA3-512) const MAX_HASH_SIZE: usize = 64; /// Convert raw hash bytes to proper CIDv1 format /// /// CID structure: /// - Version: 1 /// - Codec: 0x55 (raw binary data) /// - Multihash: hash_code (varint) + hash_length (varint) + hash_bytes /// /// Returns base32lower encoded CID with 'b' multibase prefix pub fn to_cid(hash: &[u8], algo: HashAlgorithm) -> String { let hash_code = algo.multicodec(); // Create multihash: wrap hash bytes with algorithm code let multihash: Multihash = Multihash::wrap(hash_code, hash).expect("Hash size exceeds maximum"); // Create CIDv1 with raw codec let cid = Cid::new_v1(RAW_CODEC, multihash); // Return base32lower string (multibase 'b' prefix is default for CIDv1) cid.to_string() } /// Returns the multihash code (the algorithm) of a bare CID string, or None /// if it doesn't parse. Used to tell which digests a record already carries /// in its bare-CID key-set (cids/), since the algorithm is no longer in /// a field name. pub fn multicodec_of(cid_str: &str) -> Option { Cid::try_from(cid_str).ok().map(|c| c.hash().code()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_cid_format() { // Test with a known SHA-256 hash let hash = [0u8; 32]; // 32 zero bytes let cid = to_cid(&hash, HashAlgorithm::Sha256); // CIDv1 base32lower starts with 'b' assert!(cid.starts_with('b'), "CID should start with 'b' multibase prefix"); // Verify we can parse it back let parsed = Cid::try_from(cid.as_str()).expect("Should parse valid CID"); assert_eq!(parsed.version(), cid::Version::V1); assert_eq!(parsed.codec(), RAW_CODEC); assert_eq!(parsed.hash().code(), 0x12); // SHA-256 code } #[test] fn test_all_algorithms() { let test_data = b"test data for hashing"; for algo in HashAlgorithm::all() { // Create a fake hash of appropriate size let hash_size = match algo { HashAlgorithm::Sha256 | HashAlgorithm::Sha3_256 | HashAlgorithm::Midhash256 => 32, HashAlgorithm::Sha1 => 20, HashAlgorithm::Md5 => 16, HashAlgorithm::Crc32 => 4, HashAlgorithm::Sha3_384 => 48, HashAlgorithm::BtPiecesRoot | HashAlgorithm::BtInfoHash => 32, }; let hash = vec![0xab; hash_size]; let cid = to_cid(&hash, algo); // All CIDs should start with 'b' (base32lower) assert!( cid.starts_with('b'), "CID for {:?} should start with 'b'", algo ); // All CIDs should be parseable let parsed = Cid::try_from(cid.as_str()) .expect(&format!("Should parse valid CID for {:?}", algo)); assert_eq!(parsed.version(), cid::Version::V1); assert_eq!(parsed.codec(), RAW_CODEC); assert_eq!( parsed.hash().code(), algo.multicodec(), "Multicodec mismatch for {:?}", algo ); } } #[test] fn test_cid_determinism() { // Same hash should always produce same CID let hash = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; let cid1 = to_cid(&hash, HashAlgorithm::Md5); let cid2 = to_cid(&hash, HashAlgorithm::Md5); assert_eq!(cid1, cid2); } #[test] fn test_different_algorithms_different_cids() { // Same hash bytes with different algorithms should produce different CIDs let hash = [0u8; 32]; let sha256_cid = to_cid(&hash, HashAlgorithm::Sha256); let sha3_256_cid = to_cid(&hash, HashAlgorithm::Sha3_256); assert_ne!(sha256_cid, sha3_256_cid); } }