provwasm_std/
metadata_address.rs

1use crate::metadata_address::KeyType::{
2    ContractSpecification, Record, RecordSpecification, Scope, ScopeSpecification, Session,
3};
4use bech32::{Bech32, Hrp};
5use cosmwasm_std::StdError;
6use sha2::{Digest, Sha256};
7use uuid::Uuid;
8
9#[derive(Clone, Copy, Debug, PartialEq)]
10pub enum KeyType {
11    Scope = 0x00,
12    Session = 0x01,
13    Record = 0x02,
14    ContractSpecification = 0x03,
15    ScopeSpecification = 0x04,
16    RecordSpecification = 0x05,
17}
18
19impl KeyType {
20    pub fn to_str(&self) -> &str {
21        match self {
22            Scope => "scope",
23            Session => "session",
24            Record => "record",
25            ContractSpecification => "contractspec",
26            ScopeSpecification => "scopespec",
27            RecordSpecification => "recspec",
28        }
29    }
30}
31
32/// Represents a Provenance `MetadataAddress` type
33#[derive(Clone, Debug, PartialEq)]
34pub struct MetadataAddress {
35    pub bech32: String,
36    pub bytes: Vec<u8>,
37    pub key_type: KeyType,
38}
39
40impl MetadataAddress {
41    /// Create a Contract Specification Metadata Address from a `Uuid`
42    ///
43    /// e.g. `contractspec1qw07zlu62ms5zk9g4azsdq9vnesqy4dtgm`
44    pub fn contract_specification(
45        contract_specification_uuid: Uuid,
46    ) -> Result<MetadataAddress, StdError> {
47        let key_type_byte = ContractSpecification as u8;
48        let bytes = [key_type_byte]
49            .iter()
50            .cloned()
51            .chain(Self::hex_encode_uuid(contract_specification_uuid))
52            .collect::<Vec<u8>>();
53
54        let addr = Self::encode_bech32(ContractSpecification, &bytes)?;
55
56        Ok(MetadataAddress {
57            bech32: addr,
58            bytes,
59            key_type: ContractSpecification,
60        })
61    }
62
63    /// Create a Scope Specification Metadata Address from a `Uuid`
64    ///
65    /// e.g. `scopespec1qj07zlu62ms5zk9g4azsdq9vnesqxcv7hd`
66    pub fn scope_specification(
67        scope_specification_uuid: Uuid,
68    ) -> Result<MetadataAddress, StdError> {
69        let key_type_byte = ScopeSpecification as u8;
70        let bytes = [key_type_byte]
71            .iter()
72            .cloned()
73            .chain(Self::hex_encode_uuid(scope_specification_uuid))
74            .collect::<Vec<u8>>();
75
76        let addr = Self::encode_bech32(ScopeSpecification, &bytes)?;
77
78        Ok(MetadataAddress {
79            bech32: addr,
80            bytes,
81            key_type: ScopeSpecification,
82        })
83    }
84
85    /// Create a Scope Metadata Address from a `Uuid`
86    ///
87    /// e.g. `scope1qz07zlu62ms5zk9g4azsdq9vnesqg74ssc`
88    pub fn scope(scope_uuid: Uuid) -> Result<MetadataAddress, StdError> {
89        let key_type_byte = Scope as u8;
90        let bytes = [key_type_byte]
91            .iter()
92            .cloned()
93            .chain(Self::hex_encode_uuid(scope_uuid))
94            .collect::<Vec<u8>>();
95
96        let addr = Self::encode_bech32(Scope, &bytes)?;
97
98        Ok(MetadataAddress {
99            bech32: addr,
100            bytes,
101            key_type: Scope,
102        })
103    }
104
105    /// Create a Record Metadata Address from a `Uuid` and Record name
106    ///
107    /// e.g. `record1q207zlu62ms5zk9g4azsdq9vneswsu3m8wtqu0zfqu9edf8r7l4jugz3hcl`
108    pub fn record(scope_uuid: Uuid, record_name: String) -> Result<MetadataAddress, StdError> {
109        let key_type_byte = Record as u8;
110        let bytes = [key_type_byte]
111            .iter()
112            .cloned()
113            .chain(Self::hex_encode_uuid(scope_uuid))
114            .chain(Self::hash_bytes(record_name))
115            .collect::<Vec<u8>>();
116
117        let addr = Self::encode_bech32(Record, &bytes)?;
118
119        Ok(MetadataAddress {
120            bech32: addr,
121            bytes,
122            key_type: Record,
123        })
124    }
125
126    /// Create a Record Specification Metadata Address from a Contract Specification `Uuid` and Record Specification name
127    ///
128    /// e.g. `recspec1qk07zlu62ms5zk9g4azsdq9vneswsu3m8wtqu0zfqu9edf8r7l4juadl3c0`
129    pub fn record_specification(
130        contract_specification_uuid: Uuid,
131        record_specification_name: String,
132    ) -> Result<MetadataAddress, StdError> {
133        let key_type_byte = RecordSpecification as u8;
134        let bytes = [key_type_byte]
135            .iter()
136            .cloned()
137            .chain(Self::hex_encode_uuid(contract_specification_uuid))
138            .chain(Self::hash_bytes(record_specification_name))
139            .collect::<Vec<u8>>();
140
141        let addr = Self::encode_bech32(RecordSpecification, &bytes)?;
142
143        Ok(MetadataAddress {
144            bech32: addr,
145            bytes,
146            key_type: RecordSpecification,
147        })
148    }
149
150    /// Create a Session Metadata Address from Scope and Session `Uuid`
151    ///
152    /// e.g. `session1qx07zlu62ms5zk9g4azsdq9vnesflctlnftwzs2c4zh52p5q4j0xqrysdme`
153    pub fn session(scope_uuid: Uuid, session_uuid: Uuid) -> Result<MetadataAddress, StdError> {
154        let key_type_byte = Session as u8;
155        let bytes = [key_type_byte]
156            .iter()
157            .cloned()
158            .chain(Self::hex_encode_uuid(scope_uuid))
159            .chain(Self::hex_encode_uuid(session_uuid))
160            .collect::<Vec<u8>>();
161
162        let addr = Self::encode_bech32(Session, &bytes)?;
163
164        Ok(MetadataAddress {
165            bech32: addr,
166            bytes,
167            key_type: Session,
168        })
169    }
170
171    pub fn to_bytes(&self) -> Vec<u8> {
172        self.bech32.as_bytes().to_vec()
173    }
174
175    fn hex_encode_uuid(uuid: Uuid) -> Vec<u8> {
176        hex::decode(uuid.simple().encode_lower(&mut Uuid::encode_buffer())).unwrap()
177    }
178
179    fn encode_bech32(key_type: KeyType, bytes: &[u8]) -> Result<String, StdError> {
180        let hrp =
181            Hrp::parse(key_type.to_str()).map_err(|e| StdError::parse_err("Hrp", e.to_string()))?;
182        let encoded = bech32::encode::<Bech32>(hrp, bytes)
183            .map_err(|e| StdError::generic_err(e.to_string()))?;
184
185        Ok(encoded)
186    }
187
188    pub fn hash_bytes(data: String) -> Vec<u8> {
189        let hash = Sha256::digest(data.trim().to_lowercase().as_bytes());
190        hash[0..16].to_vec()
191    }
192}
193
194#[cfg(test)]
195pub mod test {
196    use std::str::FromStr;
197
198    use uuid::Uuid;
199
200    use crate::metadata_address::{KeyType, MetadataAddress};
201
202    #[test]
203    pub fn new_contract_spec() {
204        let meta_addr = MetadataAddress::contract_specification(
205            Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap(),
206        )
207        .unwrap();
208
209        assert_eq!(
210            meta_addr,
211            MetadataAddress {
212                bech32: "contractspec1qw07zlu62ms5zk9g4azsdq9vnesqy4dtgm".to_string(),
213                bytes: vec![
214                    3, 159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96
215                ],
216                key_type: KeyType::ContractSpecification,
217            }
218        );
219    }
220
221    #[test]
222    pub fn new_record() {
223        let meta_addr = MetadataAddress::record(
224            Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap(),
225            "nft_record_spec_name".to_string(),
226        )
227        .unwrap();
228
229        assert_eq!(
230            meta_addr,
231            MetadataAddress {
232                bech32: "record1q207zlu62ms5zk9g4azsdq9vneswsu3m8wtqu0zfqu9edf8r7l4jugz3hcl"
233                    .to_string(),
234                bytes: vec![
235                    2, 159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96,
236                    232, 114, 59, 59, 150, 14, 60, 73, 7, 11, 150, 164, 227, 247, 235, 46
237                ],
238                key_type: KeyType::Record,
239            }
240        );
241    }
242
243    #[test]
244    pub fn new_record_spec() {
245        let meta_addr = MetadataAddress::record_specification(
246            Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap(),
247            "nft_record_spec_name".to_string(),
248        )
249        .unwrap();
250
251        assert_eq!(
252            meta_addr,
253            MetadataAddress {
254                bech32: "recspec1qk07zlu62ms5zk9g4azsdq9vneswsu3m8wtqu0zfqu9edf8r7l4juadl3c0"
255                    .to_string(),
256                bytes: vec![
257                    5, 159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96,
258                    232, 114, 59, 59, 150, 14, 60, 73, 7, 11, 150, 164, 227, 247, 235, 46
259                ],
260                key_type: KeyType::RecordSpecification,
261            }
262        );
263    }
264
265    #[test]
266    pub fn new_scope() {
267        let meta_addr =
268            MetadataAddress::scope(Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap())
269                .unwrap();
270
271        assert_eq!(
272            meta_addr,
273            MetadataAddress {
274                bech32: "scope1qz07zlu62ms5zk9g4azsdq9vnesqg74ssc".to_string(),
275                bytes: vec![
276                    0, 159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96
277                ],
278                key_type: KeyType::Scope,
279            }
280        );
281    }
282
283    #[test]
284    pub fn new_scope_spec() {
285        let meta_addr = MetadataAddress::scope_specification(
286            Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap(),
287        )
288        .unwrap();
289
290        assert_eq!(
291            meta_addr,
292            MetadataAddress {
293                bech32: "scopespec1qj07zlu62ms5zk9g4azsdq9vnesqxcv7hd".to_string(),
294                bytes: vec![
295                    4, 159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96
296                ],
297                key_type: KeyType::ScopeSpecification,
298            }
299        );
300    }
301
302    #[test]
303    pub fn new_session() {
304        let meta_addr = MetadataAddress::session(
305            Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap(),
306            Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap(),
307        )
308        .unwrap();
309
310        assert_eq!(
311            meta_addr,
312            MetadataAddress {
313                bech32: "session1qx07zlu62ms5zk9g4azsdq9vnesflctlnftwzs2c4zh52p5q4j0xqrysdme"
314                    .to_string(),
315                bytes: vec![
316                    1, 159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96,
317                    159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96
318                ],
319                key_type: KeyType::Session,
320            }
321        );
322    }
323}