oasis_runtime_sdk/modules/rofl/
app_id.rs

1//! ROFL application identifier.
2use std::fmt;
3
4use bech32::{Bech32, Hrp};
5
6use crate::{core::common::crypto::hash::Hash, types::address::Address};
7
8const APP_ID_VERSION_SIZE: usize = 1;
9const APP_ID_DATA_SIZE: usize = 20;
10const APP_ID_SIZE: usize = APP_ID_VERSION_SIZE + APP_ID_DATA_SIZE;
11
12/// V0 identifier version.
13const APP_ID_V0_VERSION: u8 = 0;
14/// Creator/round/index identifier context.
15const APP_ID_CRI_CONTEXT: &[u8] = b"oasis-sdk/rofl: cri app id";
16/// Creator/nonce identifier context.
17const APP_ID_CN_CONTEXT: &[u8] = b"oasis-sdk/rofl: cn app id";
18/// Global name identifier context.
19const APP_ID_GLOBAL_NAME_CONTEXT: &[u8] = b"oasis-sdk/rofl: global name app id";
20
21/// Human readable part for Bech32-encoded application identifier.
22pub const APP_ID_BECH32_HRP: Hrp = Hrp::parse_unchecked("rofl");
23
24/// Error.
25#[derive(thiserror::Error, Debug)]
26pub enum Error {
27    #[error("malformed identifier")]
28    MalformedIdentifier,
29}
30
31/// ROFL application identifier.
32///
33/// The application identifier is similar to an address, but using its own separate namespace and
34/// derivation scheme as it is not meant to be used as an address.
35#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
36pub struct AppId([u8; APP_ID_SIZE]);
37
38impl AppId {
39    /// Size of an application identifier in bytes.
40    pub const SIZE: usize = APP_ID_SIZE;
41
42    /// Creates a new application identifier from a context, version and data.
43    fn new(ctx: &'static [u8], version: u8, data: &[u8]) -> Self {
44        let h = Hash::digest_bytes_list(&[ctx, &[version], data]);
45
46        let mut a = [0; APP_ID_SIZE];
47        a[..APP_ID_VERSION_SIZE].copy_from_slice(&[version]);
48        a[APP_ID_VERSION_SIZE..].copy_from_slice(h.truncated(APP_ID_DATA_SIZE));
49
50        AppId(a)
51    }
52
53    /// Creates a new v0 application identifier from a global name.
54    pub fn from_global_name(name: &str) -> Self {
55        Self::new(
56            APP_ID_GLOBAL_NAME_CONTEXT,
57            APP_ID_V0_VERSION,
58            name.as_bytes(),
59        )
60    }
61
62    /// Creates a new v0 application identifier from creator/round/index tuple.
63    pub fn from_creator_round_index(creator: Address, round: u64, index: u32) -> Self {
64        Self::new(
65            APP_ID_CRI_CONTEXT,
66            APP_ID_V0_VERSION,
67            &[creator.as_ref(), &round.to_be_bytes(), &index.to_be_bytes()].concat(),
68        )
69    }
70
71    /// Creates a new v0 application identifier from creator/nonce tuple.
72    pub fn from_creator_nonce(creator: Address, nonce: u64) -> Self {
73        Self::new(
74            APP_ID_CN_CONTEXT,
75            APP_ID_V0_VERSION,
76            &[creator.as_ref(), &nonce.to_be_bytes()].concat(),
77        )
78    }
79
80    /// Tries to create a new identifier from raw bytes.
81    pub fn from_bytes(data: &[u8]) -> Result<Self, Error> {
82        if data.len() != APP_ID_SIZE {
83            return Err(Error::MalformedIdentifier);
84        }
85
86        let mut a = [0; APP_ID_SIZE];
87        a.copy_from_slice(data);
88
89        Ok(AppId(a))
90    }
91
92    /// Convert the identifier into raw bytes.
93    pub fn into_bytes(self) -> [u8; APP_ID_SIZE] {
94        self.0
95    }
96
97    /// Tries to create a new identifier from Bech32-encoded string.
98    pub fn from_bech32(data: &str) -> Result<Self, Error> {
99        let (hrp, data) = bech32::decode(data).map_err(|_| Error::MalformedIdentifier)?;
100        if hrp != APP_ID_BECH32_HRP {
101            return Err(Error::MalformedIdentifier);
102        }
103
104        Self::from_bytes(&data)
105    }
106
107    /// Converts an identifier to Bech32 representation.
108    pub fn to_bech32(self) -> String {
109        bech32::encode::<Bech32>(APP_ID_BECH32_HRP, &self.0).unwrap()
110    }
111}
112
113impl AsRef<[u8]> for AppId {
114    fn as_ref(&self) -> &[u8] {
115        &self.0
116    }
117}
118
119impl TryFrom<&[u8]> for AppId {
120    type Error = Error;
121
122    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
123        Self::from_bytes(bytes)
124    }
125}
126
127impl From<&'static str> for AppId {
128    fn from(s: &'static str) -> AppId {
129        AppId::from_bech32(s).unwrap()
130    }
131}
132
133impl fmt::LowerHex for AppId {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        for i in &self.0[..] {
136            write!(f, "{i:02x}")?;
137        }
138        Ok(())
139    }
140}
141
142impl fmt::Debug for AppId {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        write!(f, "{}", self.to_bech32())?;
145        Ok(())
146    }
147}
148
149impl fmt::Display for AppId {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        write!(f, "{}", self.to_bech32())?;
152        Ok(())
153    }
154}
155
156impl cbor::Encode for AppId {
157    fn into_cbor_value(self) -> cbor::Value {
158        cbor::Value::ByteString(self.as_ref().to_vec())
159    }
160}
161
162impl cbor::Decode for AppId {
163    fn try_default() -> Result<Self, cbor::DecodeError> {
164        Ok(Default::default())
165    }
166
167    fn try_from_cbor_value(value: cbor::Value) -> Result<Self, cbor::DecodeError> {
168        match value {
169            cbor::Value::ByteString(data) => {
170                Self::from_bytes(&data).map_err(|_| cbor::DecodeError::UnexpectedType)
171            }
172            _ => Err(cbor::DecodeError::UnexpectedType),
173        }
174    }
175}
176
177impl slog::Value for AppId {
178    fn serialize(
179        &self,
180        _record: &slog::Record<'_>,
181        key: slog::Key,
182        serializer: &mut dyn slog::Serializer,
183    ) -> slog::Result {
184        serializer.emit_str(key, &self.to_bech32())
185    }
186}
187
188#[cfg(test)]
189mod test {
190    use super::*;
191    use crate::testing::keys;
192
193    #[test]
194    fn test_identifier_v0() {
195        let creator = keys::alice::address();
196        let app_id = AppId::from_creator_round_index(creator, 42, 0);
197
198        assert_eq!(
199            app_id.to_bech32(),
200            "rofl1qr98wz5t6q4x8ng6a5l5v7rqlx90j3kcnun5dwht"
201        );
202
203        let creator = keys::bob::address();
204        let app_id = AppId::from_creator_round_index(creator, 42, 0);
205
206        assert_eq!(
207            app_id.to_bech32(),
208            "rofl1qrd45eaj4tf6l7mjw5prcukz75wdmwg6kggt6pnp"
209        );
210
211        let creator = keys::bob::address();
212        let app_id = AppId::from_creator_round_index(creator, 1, 0);
213
214        assert_eq!(
215            app_id.to_bech32(),
216            "rofl1qzmuyfwygnmfralgtwrqx8kcm587kwex9y8hf9hf"
217        );
218
219        let creator = keys::bob::address();
220        let app_id = AppId::from_creator_round_index(creator, 42, 1);
221
222        assert_eq!(
223            app_id.to_bech32(),
224            "rofl1qzmh56f52yd0tcqh757fahzc7ec49s8kaguyylvu"
225        );
226
227        let creator = keys::alice::address();
228        let app_id = AppId::from_creator_nonce(creator, 0);
229
230        assert_eq!(
231            app_id.to_bech32(),
232            "rofl1qqxxv77j6qy3rh50ah9kxehsh26e2hf7p5r6kwsq"
233        );
234
235        let creator = keys::alice::address();
236        let app_id = AppId::from_creator_nonce(creator, 1);
237
238        assert_eq!(
239            app_id.to_bech32(),
240            "rofl1qqfuf7u556prwv0wkdt398prhrpat7r3rvr97khf"
241        );
242
243        let creator = keys::alice::address();
244        let app_id = AppId::from_creator_nonce(creator, 42);
245
246        assert_eq!(
247            app_id.to_bech32(),
248            "rofl1qr90w0m8j7h34c2hhpfmg2wgqmtu0q82vyaxv6e0"
249        );
250
251        let creator = keys::bob::address();
252        let app_id = AppId::from_creator_nonce(creator, 0);
253
254        assert_eq!(
255            app_id.to_bech32(),
256            "rofl1qqzuxsh8fkga366kxrze8vpltdjge3rc7qg6tlrg"
257        );
258
259        let app_id = AppId::from_global_name("test global app");
260
261        assert_eq!(
262            app_id.to_bech32(),
263            "rofl1qrev5wq76npkmcv5wxkdxxcu4dhmu704yyl30h43"
264        );
265    }
266}