oasis_runtime_sdk/modules/rofl/
app_id.rs1use 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
12const APP_ID_V0_VERSION: u8 = 0;
14const APP_ID_CRI_CONTEXT: &[u8] = b"oasis-sdk/rofl: cri app id";
16const APP_ID_CN_CONTEXT: &[u8] = b"oasis-sdk/rofl: cn app id";
18const APP_ID_GLOBAL_NAME_CONTEXT: &[u8] = b"oasis-sdk/rofl: global name app id";
20
21pub const APP_ID_BECH32_HRP: Hrp = Hrp::parse_unchecked("rofl");
23
24#[derive(thiserror::Error, Debug)]
26pub enum Error {
27 #[error("malformed identifier")]
28 MalformedIdentifier,
29}
30
31#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
36pub struct AppId([u8; APP_ID_SIZE]);
37
38impl AppId {
39 pub const SIZE: usize = APP_ID_SIZE;
41
42 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 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 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 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 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 pub fn into_bytes(self) -> [u8; APP_ID_SIZE] {
94 self.0
95 }
96
97 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 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}