1use cid::multibase::{decode as multibase_decode, Base};
6use cid::Cid;
7use multihash::Multihash;
8use multihash_codetable::{Code, MultihashDigest};
9use sha2::{Digest, Sha256};
10
11pub const DAG_CBOR_CODEC: u64 = 0x71;
13
14pub type ContentHash = [u8; 32];
16
17pub type DagCborMultihash = Multihash<64>;
19
20pub fn sha2_256_content_hash(bytes: &[u8]) -> ContentHash {
22 Sha256::digest(bytes).into()
23}
24
25pub fn dag_cbor_multihash(bytes: &[u8]) -> DagCborMultihash {
27 Code::Sha2_256.digest(bytes)
28}
29
30pub fn compute_cid_dag_cbor(bytes: &[u8]) -> String {
33 let hash = dag_cbor_multihash(bytes);
34 let cid = Cid::new_v1(DAG_CBOR_CODEC, hash);
35 cid.to_string()
36}
37
38pub fn verify_dag_cbor_cid(cid_str: &str, bytes: &[u8]) -> (bool, String, String) {
45 let expected_hash = dag_cbor_multihash(bytes);
46 let expected_cid = Cid::new_v1(DAG_CBOR_CODEC, expected_hash);
47 let expected = expected_cid.to_string();
48 let Some(actual_cid) = parse_verification_cid(cid_str) else {
49 return (false, expected, String::new());
50 };
51 let actual = actual_cid.to_string();
52 (expected_cid == actual_cid, expected, actual)
53}
54
55fn parse_verification_cid(cid_str: &str) -> Option<Cid> {
56 let actual = Cid::try_from(cid_str).ok()?;
57 let Ok((base, _decoded)) = multibase_decode(cid_str) else {
58 return Some(actual);
59 };
60 if rejects_case_variant_base(base) || has_uppercase_payload_for_lowercase_base(base, cid_str) {
61 return None;
62 }
63 Some(actual)
64}
65
66fn rejects_case_variant_base(base: Base) -> bool {
67 matches!(
68 base,
69 Base::Base16Upper
70 | Base::Base32Upper
71 | Base::Base32PadUpper
72 | Base::Base32HexUpper
73 | Base::Base32HexPadUpper
74 | Base::Base36Upper
75 )
76}
77
78fn has_uppercase_payload_for_lowercase_base(base: Base, cid_str: &str) -> bool {
79 if !matches!(
80 base,
81 Base::Base16Lower
82 | Base::Base32Lower
83 | Base::Base32PadLower
84 | Base::Base32HexLower
85 | Base::Base32HexPadLower
86 | Base::Base36Lower
87 ) {
88 return false;
89 }
90 cid_str
91 .get(1..)
92 .is_some_and(|payload| payload.bytes().any(|byte| byte.is_ascii_uppercase()))
93}
94
95pub fn is_valid_cid_string(s: &str) -> bool {
97 Cid::try_from(s).is_ok()
98}
99
100pub fn try_parse_cid(s: &str) -> Option<Cid> {
102 Cid::try_from(s).ok()
103}