use cid::{multihash::Multihash, Cid};
use sha2::{Digest, Sha256};
pub const SHA2_256_CODE: u64 = 0x12;
pub const RAW_CODEC: u64 = 0x55;
pub const DAG_PB_CODEC: u64 = 0x70;
pub fn parse_persisted_cid(cid_str: &str) -> Result<Cid, String> {
let parsed = Cid::try_from(cid_str).map_err(|e| format!("invalid CID '{cid_str}': {e}"))?;
if !matches!(parsed.codec(), RAW_CODEC | DAG_PB_CODEC) {
return Err(format!("unsupported CID codec for {cid_str}: 0x{:x}", parsed.codec()));
}
Ok(parsed)
}
pub fn validate_persisted_multihash(cid: &Cid) -> Result<(), String> {
if cid.hash().code() == SHA2_256_CODE && cid.hash().digest().len() == 32 {
Ok(())
} else {
Err(format!(
"unsupported multihash for CID {cid}; only SHA2-256 is supported"
))
}
}
pub fn sha2_256_multihash(data: &[u8]) -> Multihash<64> {
let digest = Sha256::digest(data);
Multihash::<64>::wrap(SHA2_256_CODE, digest.as_slice()).expect("sha256 fits in multihash")
}
pub fn cid_v1_raw(data: &[u8]) -> String {
cid_v1_raw_cid(data).to_string()
}
pub fn cid_v1_raw_cid(data: &[u8]) -> Cid {
Cid::new_v1(RAW_CODEC, sha2_256_multihash(data))
}
pub fn verify_cid_multihash(cid: &Cid, data: &[u8]) -> Result<(), String> {
validate_persisted_multihash(cid)?;
if cid.hash() != &sha2_256_multihash(data) {
return Err(format!(
"CID multihash mismatch: downloaded data hash does not match CID '{cid}'"
));
}
Ok(())
}
pub fn verify_raw_cid(cid: &Cid, data: &[u8]) -> Result<(), String> {
if cid.codec() != RAW_CODEC {
return Err(format!("CID {cid} does not use the raw codec"));
}
verify_cid_multihash(cid, data)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cid_v1_raw_roundtrips_through_verify() {
let data = b"newton policy wasm bytes";
let cid = cid_v1_raw(data);
assert!(cid.starts_with('b'), "expected base32 CIDv1, got {cid}");
verify_raw_cid(&Cid::try_from(cid.as_str()).unwrap(), data).unwrap();
}
#[test]
fn cid_v1_raw_is_deterministic() {
assert_eq!(cid_v1_raw(b"abc"), cid_v1_raw(b"abc"));
assert_ne!(cid_v1_raw(b"abc"), cid_v1_raw(b"abd"));
}
#[test]
fn cid_v1_raw_rejects_mismatched_data() {
let cid = cid_v1_raw(b"expected");
assert!(verify_raw_cid(&Cid::try_from(cid.as_str()).unwrap(), b"tampered").is_err());
}
#[test]
fn raw_verification_rejects_cid_v0() {
let cid = "QmaozNR7DZHQK1ZcU9p7QdrshMvXqWK6gpu5rmrkPdT3L4";
assert!(verify_raw_cid(&Cid::try_from(cid).unwrap(), b"hello world").is_err());
}
#[test]
fn persisted_cid_parser_returns_codec_information() {
let cidv0 = "QmaozNR7DZHQK1ZcU9p7QdrshMvXqWK6gpu5rmrkPdT3L4";
assert_eq!(parse_persisted_cid(cidv0).unwrap().codec(), DAG_PB_CODEC);
let digest = Sha256::digest(b"dag-pb block");
let mh = Multihash::<64>::wrap(SHA2_256_CODE, digest.as_slice()).unwrap();
let cidv1 = Cid::new_v1(DAG_PB_CODEC, mh).to_string();
assert_eq!(parse_persisted_cid(&cidv1).unwrap().codec(), DAG_PB_CODEC);
}
#[test]
fn persisted_cid_parser_rejects_invalid_cid() {
let result = parse_persisted_cid("not-a-valid-cid!!!");
assert!(result.is_err());
assert!(result.unwrap_err().contains("invalid CID"));
}
}