Skip to main content

rns_core/
destination.rs

1use alloc::string::String;
2use core::fmt;
3
4use crate::constants;
5use crate::hash;
6
7#[derive(Debug)]
8pub enum DestinationError {
9    DotInAppName,
10    DotInAspect,
11}
12
13impl fmt::Display for DestinationError {
14    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15        match self {
16            DestinationError::DotInAppName => write!(f, "Dots can't be used in app names"),
17            DestinationError::DotInAspect => write!(f, "Dots can't be used in aspects"),
18        }
19    }
20}
21
22/// Expand name: "app_name.aspect1.aspect2[.hexhash]"
23///
24/// If identity_hash is provided, appends its hex representation.
25pub fn expand_name(
26    app_name: &str,
27    aspects: &[&str],
28    identity_hash: Option<&[u8; 16]>,
29) -> Result<String, DestinationError> {
30    if app_name.contains('.') {
31        return Err(DestinationError::DotInAppName);
32    }
33
34    let mut name = String::from(app_name);
35    for aspect in aspects {
36        if aspect.contains('.') {
37            return Err(DestinationError::DotInAspect);
38        }
39        name.push('.');
40        name.push_str(aspect);
41    }
42
43    if let Some(hash) = identity_hash {
44        name.push('.');
45        for b in hash {
46            use core::fmt::Write;
47            write!(name, "{:02x}", b).unwrap();
48        }
49    }
50
51    Ok(name)
52}
53
54/// Compute name hash from app_name and aspects.
55///
56/// = SHA-256("app_name.aspect1.aspect2".as_bytes())[:10]
57pub fn name_hash(
58    app_name: &str,
59    aspects: &[&str],
60) -> [u8; constants::NAME_HASH_LENGTH / 8] {
61    hash::name_hash(app_name, aspects)
62}
63
64/// Compute destination hash.
65///
66/// 1. name_hash = SHA256(expand_name(None, app_name, aspects))[:10]
67/// 2. addr_material = name_hash || identity_hash (if present)
68/// 3. destination_hash = SHA256(addr_material)[:16]
69pub fn destination_hash(
70    app_name: &str,
71    aspects: &[&str],
72    identity_hash: Option<&[u8; 16]>,
73) -> [u8; constants::TRUNCATED_HASHLENGTH / 8] {
74    let nh = name_hash(app_name, aspects);
75
76    let mut addr_material = alloc::vec::Vec::new();
77    addr_material.extend_from_slice(&nh);
78    if let Some(ih) = identity_hash {
79        addr_material.extend_from_slice(ih);
80    }
81
82    hash::truncated_hash(&addr_material)
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn test_expand_name_basic() {
91        let name = expand_name("app", &["aspect"], None).unwrap();
92        assert_eq!(name, "app.aspect");
93    }
94
95    #[test]
96    fn test_expand_name_multiple_aspects() {
97        let name = expand_name("app", &["a", "b"], None).unwrap();
98        assert_eq!(name, "app.a.b");
99    }
100
101    #[test]
102    fn test_expand_name_with_identity() {
103        let hash = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
104                     0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10];
105        let name = expand_name("app", &["a", "b"], Some(&hash)).unwrap();
106        assert_eq!(name, "app.a.b.0102030405060708090a0b0c0d0e0f10");
107    }
108
109    #[test]
110    fn test_expand_name_dot_in_app_name() {
111        assert!(expand_name("app.bad", &["aspect"], None).is_err());
112    }
113
114    #[test]
115    fn test_expand_name_dot_in_aspect() {
116        assert!(expand_name("app", &["bad.aspect"], None).is_err());
117    }
118
119    #[test]
120    fn test_destination_hash_plain() {
121        // PLAIN destination: no identity hash
122        let dh = destination_hash("app", &["aspect"], None);
123        assert_eq!(dh.len(), 16);
124
125        // Should be deterministic
126        let dh2 = destination_hash("app", &["aspect"], None);
127        assert_eq!(dh, dh2);
128    }
129
130    #[test]
131    fn test_destination_hash_with_identity() {
132        let id_hash = [0x42; 16];
133        let dh = destination_hash("app", &["aspect"], Some(&id_hash));
134        assert_eq!(dh.len(), 16);
135
136        // Different identity hash should give different destination hash
137        let id_hash2 = [0x43; 16];
138        let dh2 = destination_hash("app", &["aspect"], Some(&id_hash2));
139        assert_ne!(dh, dh2);
140    }
141
142    #[test]
143    fn test_destination_hash_computation() {
144        // Manually verify the computation
145        let nh = name_hash("app", &["aspect"]);
146        let id_hash = [0xAA; 16];
147
148        let mut material = alloc::vec::Vec::new();
149        material.extend_from_slice(&nh);
150        material.extend_from_slice(&id_hash);
151
152        let expected = crate::hash::truncated_hash(&material);
153        let actual = destination_hash("app", &["aspect"], Some(&id_hash));
154        assert_eq!(actual, expected);
155    }
156}