Skip to main content

scrybe_core/
content.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Shawn Hartsock and contributors
3
4//! Content addressing — BLAKE3 CIDs and the ContentAddressable trait.
5//!
6//! Follows the same pattern as `kyln-core`: BLAKE3 hash encoded as
7//! lowercase hex, used as a stable content identifier across languages.
8
9use serde::{Deserialize, Serialize};
10
11/// A BLAKE3 content identifier.
12///
13/// Stable across serialization formats (JSON, CBOR). Two `ContentId`s
14/// are equal iff the content they identify is byte-for-byte identical.
15#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
16pub struct ContentId(String);
17
18impl ContentId {
19    /// Computes the CID of *content*.
20    pub fn of(content: &[u8]) -> Self {
21        let hash = blake3::hash(content);
22        Self(hex::encode(hash.as_bytes()))
23    }
24
25    /// Returns the hex-encoded BLAKE3 digest.
26    pub fn as_hex(&self) -> &str {
27        &self.0
28    }
29
30    /// Verifies that *content* matches this CID.
31    pub fn verify(&self, content: &[u8]) -> bool {
32        Self::of(content) == *self
33    }
34}
35
36impl std::fmt::Display for ContentId {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        f.write_str(&self.0)
39    }
40}
41
42/// Trait for types that can produce a stable content identifier.
43pub trait ContentAddressable {
44    /// Returns the content identifier for this value.
45    fn content_id(&self) -> ContentId;
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn test_cid_deterministic() {
54        let a = ContentId::of(b"hello scrybe");
55        let b = ContentId::of(b"hello scrybe");
56        assert_eq!(a, b);
57    }
58
59    #[test]
60    fn test_cid_differs_for_different_content() {
61        let a = ContentId::of(b"foo");
62        let b = ContentId::of(b"bar");
63        assert_ne!(a, b);
64    }
65
66    #[test]
67    fn test_verify_roundtrip() {
68        let content = b"verifiable content";
69        let cid = ContentId::of(content);
70        assert!(cid.verify(content));
71        assert!(!cid.verify(b"different content"));
72    }
73}