borderless_id_types/
lib.rs

1//! # Borderless ID-Types
2//!
3//! This library contains various types that are used as IDs for different things in the borderless ecosystem.
4
5use core::fmt;
6use std::fmt::Display;
7
8use borderless_hash::Hash256;
9use bytes::{Buf, BufMut, Bytes, BytesMut};
10use serde::{Deserialize, Serialize};
11
12pub use uuid::Uuid;
13
14/// Generic macro to define wrapper types around u128 and uuid.
15macro_rules! impl_uuid {
16    ($type:ident, $prefix_upper:literal, $prefix_lower:literal) => {
17        impl $type {
18            /// Generates a new ID
19            #[cfg(any(all(feature = "generate_ids", not(target_arch = "wasm32")), test))]
20            pub fn generate() -> Self {
21                // Start by generating a v4 uuid
22                let uuid = uuid::Uuid::new_v4();
23                // Then convert it to a valid $type id
24                uuid.into()
25            }
26
27            /// Converts the ID into a [`uuid::Uuid`]
28            pub fn into_uuid(self) -> uuid::Uuid {
29                self.0
30            }
31
32            // NOTE: I am not sure; we have two options here. Either we just construct a uuid based on the bytes,
33            // but then it may not be a valid $type. Or, we enforce our bit-pattern here, so we know, that we have a valid $type,
34            // BUT this changes the roundtrip encoding (bytes -> $type -> bytes), because we modified the array here.
35            pub fn from_bytes(mut bytes: [u8; 16]) -> Self {
36                bytes[0] = bytes[0] & $prefix_upper | $prefix_lower;
37                $type(uuid::Uuid::new_v8(bytes))
38            }
39
40            pub fn from_slice(slice: &[u8]) -> Result<Self, std::array::TryFromSliceError> {
41                slice.try_into()
42            }
43
44            /// Returns the underlying bytes
45            pub fn into_bytes(self) -> [u8; 16] {
46                self.0.into_bytes()
47            }
48
49            /// Returns a reference to the underlying bytes
50            pub fn as_bytes(&self) -> &[u8; 16] {
51                self.0.as_bytes()
52            }
53
54            /// Parses an ID from a `&str`
55            pub fn parse_str(s: &str) -> Result<Self, uuid::Error> {
56                let uuid = Uuid::parse_str(s)?;
57                Ok(uuid.into())
58            }
59
60            /// Merges two IDs commutatively
61            ///
62            /// Can be used to construct database keys.
63            pub fn merge(&self, other: &Self) -> [u8; 16] {
64                let mut out = [0; 16];
65                for i in 0..16 {
66                    out[i] = self.as_bytes()[i] ^ other.as_bytes()[i];
67                }
68                out
69            }
70
71            /// Merges and Compacts two IDs into a `u64`
72            ///
73            /// Can be used to construct database keys.
74            pub fn merge_compact(&self, other: &Self) -> u64 {
75                let merged = self.merge(other);
76                let mut out = [0; 8];
77                for i in 0..8 {
78                    out[i] = merged[i] ^ merged[i + 8];
79                }
80                u64::from_be_bytes(out)
81            }
82
83            /// Compacts the current ID into a `u64`
84            ///
85            /// Can be used to construct database keys.
86            pub fn compact(&self) -> u64 {
87                let mut out = [0; 8];
88                for i in 0..8 {
89                    out[i] = self.as_bytes()[i] ^ self.as_bytes()[i + 8];
90                }
91                u64::from_be_bytes(out)
92            }
93        }
94
95        impl<'de> serde::Deserialize<'de> for $type {
96            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
97            where
98                D: serde::Deserializer<'de>,
99            {
100                // NOTE: We delegate to the From<uuid> here
101                let uuid = uuid::Uuid::deserialize(deserializer)?;
102                Ok($type::from(uuid))
103            }
104        }
105
106        impl From<u128> for $type {
107            fn from(value: u128) -> Self {
108                let mut bytes = value.to_be_bytes();
109                bytes[0] = bytes[0] & $prefix_upper | $prefix_lower;
110                $type(uuid::Uuid::new_v8(bytes))
111            }
112        }
113
114        impl From<uuid::Uuid> for $type {
115            fn from(value: uuid::Uuid) -> Self {
116                let mut bytes = value.into_bytes();
117                bytes[0] = bytes[0] & $prefix_upper | $prefix_lower;
118                $type(uuid::Uuid::new_v8(bytes))
119            }
120        }
121
122        impl From<$type> for uuid::Uuid {
123            fn from(value: $type) -> uuid::Uuid {
124                value.0
125            }
126        }
127
128        impl From<$type> for u128 {
129            fn from(value: $type) -> u128 {
130                value.0.as_u128()
131            }
132        }
133
134        impl TryFrom<&str> for $type {
135            type Error = uuid::Error;
136
137            fn try_from(value: &str) -> Result<Self, Self::Error> {
138                uuid::Uuid::parse_str(&value).map(Into::into)
139            }
140        }
141
142        impl TryFrom<String> for $type {
143            type Error = uuid::Error;
144
145            fn try_from(value: String) -> Result<Self, Self::Error> {
146                $type::try_from(value.as_str())
147            }
148        }
149
150        impl TryFrom<&[u8]> for $type {
151            type Error = std::array::TryFromSliceError;
152
153            fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
154                let bytes = value.try_into()?;
155                Ok($type::from_bytes(bytes))
156            }
157        }
158
159        impl std::str::FromStr for $type {
160            type Err = uuid::Error;
161
162            fn from_str(s: &str) -> Result<Self, Self::Err> {
163                uuid::Uuid::parse_str(s).map(Into::into)
164            }
165        }
166
167        impl std::fmt::Display for $type {
168            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169                write!(f, "{}", self.0)
170            }
171        }
172
173        impl From<$type> for String {
174            fn from(value: $type) -> String {
175                value.0.to_string()
176            }
177        }
178
179        impl From<&$type> for String {
180            fn from(value: &$type) -> String {
181                value.0.to_string()
182            }
183        }
184
185        impl AsRef<[u8]> for $type {
186            fn as_ref(&self) -> &[u8] {
187                self.0.as_bytes()
188            }
189        }
190
191        impl AsRef<[u8; 16]> for $type {
192            fn as_ref(&self) -> &[u8; 16] {
193                self.0.as_bytes()
194            }
195        }
196    };
197}
198
199/// An agent-id used to identify software-agents in the borderless-ecosystem.
200///
201/// These ids are version 8 [uuids](https://en.wikipedia.org/wiki/Universally_unique_identifier), where
202/// the first four bits of the uuid are set to `0xa`, to indicate that it is an agent-id and not another uuid based id.
203///
204/// Example:
205/// ```sh
206/// afc23cb3-f447-8107-8f93-9bfb8e1d157d
207/// ```
208///
209/// All uuid-based ids used in the borderless-ecosystem have a different prefix, based on what the id is used for.
210/// This mechanism ensures that you cannot mistake an agent-id for e.g. a contract-id and vice versa. Even if you convert the agent-id
211/// back into a uuid and the result into a contract-id, the results are different.
212///
213/// The implementation of the IDs is compliant with [RFC9562](https://www.ietf.org/rfc/rfc9562.html#name-uuid-version-8),
214/// as we utilize standard version 8 uuids.
215#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Hash, PartialOrd, Ord)]
216pub struct AgentId(uuid::Uuid);
217impl_uuid!(AgentId, 0xaf, 0xa0);
218
219/// The borderless-id used to identify participants in the borderless-network.
220///
221/// These ids are version 8 [uuids](https://en.wikipedia.org/wiki/Universally_unique_identifier), where
222/// the first four bits of the uuid are set to `0xb`, to indicate that it is a borderless-id and not another uuid based id.
223///
224/// Example:
225/// ```sh
226/// 0bc23cb3-f447-8107-8f93-9bfb8e1d157d
227/// ```
228///
229/// All uuid-based ids used in the borderless-ecosystem have a different prefix, based on what the id is used for.
230/// This mechanism ensures that you cannot mistake a participant-id for e.g. a contract-id and vice versa. Even if you convert the participant-id
231/// back into a uuid and the result into a contract-id, the results are different.
232///
233/// The implementation of the IDs is compliant with [RFC9562](https://www.ietf.org/rfc/rfc9562.html#name-uuid-version-8),
234/// as we utilize standard version 8 uuids.
235#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Hash, PartialOrd, Ord)]
236pub struct BorderlessId(uuid::Uuid);
237impl_uuid!(BorderlessId, 0xbf, 0xb0);
238
239/// A contract-id used to itentify different SmartContracts in the borderless-ecosystem.
240///
241/// These ids are version 8 [uuids](https://en.wikipedia.org/wiki/Universally_unique_identifier), where
242/// the first four bits of the uuid are set to `0xc`, to indicate that it is a contract-id and not another uuid based id.
243///
244/// Example:
245/// ```sh
246/// cfc23cb3-f447-8107-8f93-9bfb8e1d157d
247/// ```
248///
249/// All uuid-based ids used in the borderless-ecosystem have a different prefix, based on what the id is used for.
250/// This mechanism ensures that you cannot mistake a contract-id for e.g. a process-id and vice versa. Even if you convert the contract-id
251/// back into a uuid and the result into a process-id, the results are different.
252///
253/// The implementation of the IDs is compliant with [RFC9562](https://www.ietf.org/rfc/rfc9562.html#name-uuid-version-8),
254/// as we utilize standard version 8 uuids.
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Hash, PartialOrd, Ord)]
256pub struct ContractId(uuid::Uuid);
257impl_uuid!(ContractId, 0xcf, 0xc0);
258
259/// A decentralized-id used to itentify different assets and documents in the borderless-ecosystem.
260///
261/// These ids are version 8 [uuids](https://en.wikipedia.org/wiki/Universally_unique_identifier), where
262/// the first four bits of the uuid are set to `0xd`, to indicate that it is a decentralized-id and not another uuid based id.
263///
264/// Example:
265/// ```sh
266/// dfc23cb3-f447-8107-8f93-9bfb8e1d157d
267/// ```
268///
269/// All uuid-based ids used in the borderless-ecosystem have a different prefix, based on what the id is used for.
270/// This mechanism ensures that you cannot mistake a contract-id for e.g. a process-id and vice versa. Even if you convert the contract-id
271/// back into a uuid and the result into a process-id, the results are different.
272///
273/// The implementation of the IDs is compliant with [RFC9562](https://www.ietf.org/rfc/rfc9562.html#name-uuid-version-8),
274/// as we utilize standard version 8 uuids.
275#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Hash, PartialOrd, Ord)]
276pub struct Did(uuid::Uuid);
277impl_uuid!(Did, 0xdf, 0xd0);
278
279/// An external-id used to identify external entities, that are not in the borderless-network.
280///
281/// These ids are version 8 [uuids](https://en.wikipedia.org/wiki/Universally_unique_identifier), where
282/// the first four bits of the uuid are set to `0xe`, to indicate that it is an external-id and not another uuid based id.
283///
284/// Example:
285/// ```sh
286/// ebc23cb3-f447-8107-8f93-9bfb8e1d157d
287/// ```
288///
289/// All uuid-based ids used in the borderless-ecosystem have a different prefix, based on what the id is used for.
290/// This mechanism ensures that you cannot mistake an participant-id for e.g. a contract-id and vice versa. Even if you convert the participant-id
291/// back into a uuid and the result into a contract-id, the results are different.
292///
293/// The implementation of the IDs is compliant with [RFC9562](https://www.ietf.org/rfc/rfc9562.html#name-uuid-version-8),
294/// as we utilize standard version 8 uuids.
295#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Hash, PartialOrd, Ord)]
296pub struct ExternalId(uuid::Uuid);
297impl_uuid!(ExternalId, 0xef, 0xe0);
298// TODO: Add tests for external ID and prefix checks
299
300/// A flow-id used to identify `Flows` in a contract.
301///
302/// These ids are version 8 [uuids](https://en.wikipedia.org/wiki/Universally_unique_identifier), where
303/// the first four bits of the uuid are set to `0xf`, to indicate that it is a flow-id and not another uuid based id.
304///
305/// Example:
306/// ```sh
307/// fbc23cb3-f447-8107-8f93-9bfb8e1d157d
308/// ```
309///
310/// All uuid-based ids used in the borderless-ecosystem have a different prefix, based on what the id is used for.
311/// This mechanism ensures that you cannot mistake an participant-id for e.g. a contract-id and vice versa. Even if you convert the participant-id
312/// back into a uuid and the result into a contract-id, the results are different.
313///
314/// The implementation of the IDs is compliant with [RFC9562](https://www.ietf.org/rfc/rfc9562.html#name-uuid-version-8),
315/// as we utilize standard version 8 uuids.
316#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Hash, PartialOrd, Ord)]
317pub struct FlowId(uuid::Uuid);
318impl_uuid!(FlowId, 0xff, 0xf0);
319
320/// Check weather or not an array of bytes contains the prefix of an [`AgentId`].
321///
322/// Useful for filtering in key-value storages, when multiple ID types are used as keys or key-prefixes.
323pub fn aid_prefix(bytes: impl AsRef<[u8]>) -> bool {
324    let bytes = bytes.as_ref();
325    if bytes.is_empty() {
326        return false;
327    }
328    bytes[0] | 0x0f == 0xaf
329}
330
331/// Check weather or not an array of bytes contains the prefix of a [`BorderlessId`].
332///
333/// Useful for filtering in key-value storages, when multiple ID types are used as keys or key-prefixes.
334pub fn bid_prefix(bytes: impl AsRef<[u8]>) -> bool {
335    let bytes = bytes.as_ref();
336    if bytes.is_empty() {
337        return false;
338    }
339    bytes[0] | 0x0f == 0xbf
340}
341
342/// Check weather or not an array of bytes contains the prefix of a [`ContractId`].
343///
344/// Useful for filtering in key-value storages, when multiple ID types are used as keys or key-prefixes.
345pub fn cid_prefix(bytes: impl AsRef<[u8]>) -> bool {
346    let bytes = bytes.as_ref();
347    if bytes.is_empty() {
348        return false;
349    }
350    bytes[0] | 0x0f == 0xcf
351}
352
353/// Check weather or not an array of bytes contains the prefix of a [`Did`].
354///
355/// Useful for filtering in key-value storages, when multiple ID types are used as keys or key-prefixes.
356pub fn did_prefix(bytes: impl AsRef<[u8]>) -> bool {
357    let bytes = bytes.as_ref();
358    if bytes.is_empty() {
359        return false;
360    }
361    bytes[0] | 0x0f == 0xdf
362}
363
364/// Check weather or not an array of bytes contains the prefix of an [`ExternalId`].
365///
366/// Useful for filtering in key-value storages, when multiple ID types are used as keys or key-prefixes.
367pub fn eid_prefix(bytes: impl AsRef<[u8]>) -> bool {
368    let bytes = bytes.as_ref();
369    if bytes.is_empty() {
370        return false;
371    }
372    bytes[0] | 0x0f == 0xef
373}
374
375/// Check weather or not an array of bytes contains the prefix of a [`FlowId`].
376///
377/// Useful for filtering in key-value storages, when multiple ID types are used as keys or key-prefixes.
378pub fn fid_prefix(bytes: impl AsRef<[u8]>) -> bool {
379    let bytes = bytes.as_ref();
380    if bytes.is_empty() {
381        return false;
382    }
383    bytes[0] | 0x0f == 0xff
384}
385
386/// Type used to identify blocks.
387///
388/// In principle a block is uniquely defined by its hash, however it may be useful to know where to find the block.
389/// A `BlockIdentifier` adds the chain-id and block-number to the hash, so we can easily lookup a block based on its identifier.
390#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
391pub struct BlockIdentifier {
392    /// Chain-ID of the block.
393    pub chain_id: u32,
394    /// Number of the block.
395    pub number: u64,
396    /// Hash of the block.
397    ///
398    /// Note: The hash of a block is not the hash over the serialized data.
399    /// It is the sum of three hashes: `Hash(Header) + MerkleRoot(Txs) + MerkleRoot(SignatureWithKeys)`
400    pub hash: Hash256,
401}
402
403impl BlockIdentifier {
404    /// Constructs a new `BlockIdentifier` from its raw parts
405    pub fn new(chain_id: u32, number: u64, hash: Hash256) -> Self {
406        Self {
407            chain_id,
408            number,
409            hash,
410        }
411    }
412
413    pub fn genesis(chain_id: u32) -> Self {
414        Self::new(chain_id, 0, Hash256::empty())
415    }
416
417    /// Decodes a `BlockIdentifier` from bytes
418    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, InvalidBlockIdentifier> {
419        if bytes.len() != ENCODED_BLOCK_ID_LEN {
420            Err(InvalidBlockIdentifier(bytes.len()))
421        } else {
422            let mut bytes = Bytes::from(bytes);
423            let chain_id = bytes.get_u32();
424            let number = bytes.get_u64();
425            let mut hash_slice = [0u8; 32];
426            bytes.copy_to_slice(&mut hash_slice);
427            Ok(BlockIdentifier {
428                chain_id,
429                number,
430                hash: hash_slice.into(),
431            })
432        }
433    }
434
435    /// Encodes a `BlockIdentifier` to bytes
436    pub fn to_bytes(&self) -> Vec<u8> {
437        let mut buf = BytesMut::with_capacity(std::mem::size_of::<BlockIdentifier>());
438        buf.put_u32(self.chain_id);
439        buf.put_u64(self.number);
440        buf.put_slice(self.hash.as_ref());
441        buf.into()
442    }
443}
444
445/// Lenght of an encoded TxIdentifier
446///
447/// 4 bytes for the chain-id (u32)
448/// 8 bytes for the number (u64)
449/// 32 bytes for the hash (which is basically [u8; 32])
450const ENCODED_BLOCK_ID_LEN: usize = 4 + 8 + 32;
451
452#[derive(Debug, PartialEq)]
453pub struct InvalidBlockIdentifier(pub usize);
454impl Display for InvalidBlockIdentifier {
455    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
456        write!(
457            f,
458            "failed to decode block identifier - expected {ENCODED_BLOCK_ID_LEN} bytes, got {}",
459            self.0
460        )
461    }
462}
463impl std::error::Error for InvalidBlockIdentifier {}
464
465// This is necessary for the update functionality.
466// We need a lexicographical sort of the incoming block-ids,
467// sorted by their hash.
468impl PartialOrd for BlockIdentifier {
469    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
470        Some(self.cmp(other))
471    }
472}
473
474impl Ord for BlockIdentifier {
475    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
476        self.hash.cmp(&other.hash)
477    }
478}
479
480impl Display for BlockIdentifier {
481    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
482        write!(f, "{}.{}.{}", self.chain_id, self.number, self.hash)
483    }
484}
485
486/// Type used to identify transactions.
487///
488/// In principal a transaction is uniquely defined by its hash, however it may be useful to know where to find the transaction.
489/// An identifier adds the chain-id and block-number to the hash, so we can easily lookup a transaction based on its identifier.
490#[allow(dead_code)]
491#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
492pub struct TxIdentifier {
493    /// Chain-ID of the transaction.
494    pub chain_id: u32,
495    /// Block-Number of the block that holds the transaction.
496    pub number: u64,
497    /// Hash of the transaction.
498    pub hash: Hash256,
499}
500
501impl TxIdentifier {
502    /// Constructs a new [`TxIdentifier`]
503    pub fn new(chain_id: u32, number: u64, hash: Hash256) -> Self {
504        TxIdentifier {
505            chain_id,
506            number,
507            hash,
508        }
509    }
510
511    /// Decodes a `TxIdentifier` from bytes
512    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, InvalidTxIdentifier> {
513        if bytes.len() != ENCODED_TX_ID_LEN {
514            Err(InvalidTxIdentifier(bytes.len()))
515        } else {
516            let mut bytes = Bytes::from(bytes);
517            let chain_id = bytes.get_u32();
518            let number = bytes.get_u64();
519            let mut hash_slice = [0u8; 32];
520            bytes.copy_to_slice(&mut hash_slice);
521            Ok(TxIdentifier {
522                chain_id,
523                number,
524                hash: hash_slice.into(),
525            })
526        }
527    }
528
529    /// Encodes a `TxIdentifier` as bytes
530    pub fn to_bytes(&self) -> Vec<u8> {
531        let mut buf = BytesMut::with_capacity(std::mem::size_of::<TxIdentifier>());
532        buf.put_u32(self.chain_id);
533        buf.put_u64(self.number);
534        buf.put_slice(self.hash.as_ref());
535        buf.into()
536    }
537}
538
539/// Lenght of an encoded TxIdentifier
540///
541/// 4 bytes for the chain-id (u32)
542/// 8 bytes for the number (u64)
543/// 32 bytes for the hash (which is basically [u8; 32])
544const ENCODED_TX_ID_LEN: usize = 4 + 8 + 32;
545
546#[derive(Debug, PartialEq)]
547pub struct InvalidTxIdentifier(pub usize);
548impl Display for InvalidTxIdentifier {
549    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
550        write!(
551            f,
552            "failed to decode transaction identifier - expected {ENCODED_TX_ID_LEN} bytes, got {}",
553            self.0
554        )
555    }
556}
557impl std::error::Error for InvalidTxIdentifier {}
558
559impl Display for TxIdentifier {
560    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
561        write!(f, "{}.{}.{}", self.chain_id, self.number, self.hash)
562    }
563}
564
565#[cfg(test)]
566mod tests {
567    use super::*;
568    use serde::de::DeserializeOwned;
569    use uuid::Uuid;
570
571    #[test]
572    fn tx_id_encode_decode() {
573        let tx_id = TxIdentifier::new(1, 2, Hash256::empty());
574        let bytes = tx_id.to_bytes();
575        let result = TxIdentifier::from_bytes(bytes);
576        assert!(result.is_ok(), "decoding failed: {}", result.unwrap_err());
577        assert_eq!(result.unwrap(), tx_id);
578    }
579
580    #[test]
581    fn tx_id_display() {
582        let tx_id = TxIdentifier::new(1, 2, Hash256::empty());
583        assert_eq!(format!("{tx_id}"), "1.2.a7ffc6f8");
584    }
585
586    #[test]
587    fn block_id_encode_decode() {
588        let block_id = BlockIdentifier::new(1, 2, Hash256::empty());
589        let bytes = block_id.to_bytes();
590        let result = BlockIdentifier::from_bytes(bytes);
591        assert!(result.is_ok(), "decoding failed: {}", result.unwrap_err());
592        assert_eq!(result.unwrap(), block_id);
593    }
594
595    #[test]
596    fn block_id_display() {
597        let block_id = BlockIdentifier::new(1, 2, Hash256::empty());
598        assert_eq!(format!("{block_id}"), "1.2.a7ffc6f8");
599    }
600
601    #[test]
602    fn block_id_ordering() {
603        // Simple test
604        let first = BlockIdentifier::new(10, 20, Hash256::zero());
605        let last = BlockIdentifier::new(1, 2, Hash256::empty());
606        assert!(first < last);
607
608        // Do some fuzzing
609        for i in 0..1_000u64 {
610            let h1 = Hash256::digest(&i.to_be_bytes());
611            let h2 = Hash256::digest(&i.to_le_bytes());
612            let b1 = BlockIdentifier::new(i as u32, i, h1);
613            let b2 = BlockIdentifier::new(i as u32 + 1, i + 1, h2);
614            assert_eq!(
615                h1 < h2,
616                b1 < b2,
617                "block identifiers must be sorted based on their hash"
618            );
619        }
620    }
621
622    #[test]
623    fn agent_id_prefix() {
624        for _ in 0..1_000_000 {
625            let base_id = Uuid::new_v4();
626            let process_id = AgentId::from(base_id);
627            let pid_string = process_id.to_string();
628            assert_eq!(
629                pid_string.chars().next(),
630                Some('a'),
631                "Process-IDs must be prefixed with 'a' in string representation"
632            );
633            let back_to_uuid: Uuid = process_id.into();
634            assert_ne!(base_id, back_to_uuid);
635            // Check, if first four bits match 'a'
636            // NOTE: Bit-level-hacking here: bits abcdefgh & 11110000 = abcd0000
637            // -> so i can match on byte level agains abcd0000 (in this case 0xb0)
638            assert_eq!(back_to_uuid.as_bytes()[0] & 0xF0, 0xa0);
639        }
640    }
641
642    #[test]
643    fn borderless_id_prefix() {
644        for _ in 0..1_000_000 {
645            let base_id = Uuid::new_v4();
646            let borderless_id = BorderlessId::from(base_id);
647            let pid_string = borderless_id.to_string();
648            assert_eq!(
649                pid_string.chars().next(),
650                Some('b'),
651                "Borderless-IDs must be prefixed with 'b' in string representation"
652            );
653            let back_to_uuid: Uuid = borderless_id.into();
654            assert_ne!(base_id, back_to_uuid);
655            // Check, if first four bits match 'b'
656            // NOTE: Bit-level-hacking here: bits abcdefgh & 11110000 = abcd0000
657            // -> so i can match on byte level agains abcd0000 (in this case 0x00)
658            assert_eq!(back_to_uuid.as_bytes()[0] & 0xF0, 0xb0);
659        }
660    }
661
662    #[test]
663    fn contract_id_prefix() {
664        for _ in 0..1_000_000 {
665            let base_id = Uuid::new_v4();
666            let contract_id = ContractId::from(base_id);
667            let cid_string = contract_id.to_string();
668            assert_eq!(
669                cid_string.chars().next(),
670                Some('c'),
671                "Contract-IDs must be prefixed with 'c' in string representation"
672            );
673            let back_to_uuid: Uuid = contract_id.into();
674            assert_ne!(base_id, back_to_uuid);
675            // Check, if first four bits match 'c'
676            // NOTE: Bit-level-hacking here: bits abcdefgh & 11110000 = abcd0000
677            // -> so i can match on byte level agains abcd0000 (in this case 0xc0)
678            assert_eq!(back_to_uuid.as_bytes()[0] & 0xF0, 0xc0);
679        }
680    }
681
682    #[test]
683    fn decentralized_id_prefix() {
684        for _ in 0..1_000_000 {
685            let base_id = Uuid::new_v4();
686            let asset_id = Did::from(base_id);
687            let aid_string = asset_id.to_string();
688            assert_eq!(
689                aid_string.chars().next(),
690                Some('d'),
691                "Decentralized-IDs must be prefixed with 'd' in string representation"
692            );
693            let back_to_uuid: Uuid = asset_id.into();
694            assert_ne!(base_id, back_to_uuid);
695            // Check, if first four bits match 'd'
696            // NOTE: Bit-level-hacking here: bits abcdefgh & 11110000 = abcd0000
697            // -> so i can match on byte level agains abcd0000 (in this case 0xd0)
698            assert_eq!(back_to_uuid.as_bytes()[0] & 0xF0, 0xd0);
699        }
700    }
701
702    #[test]
703    fn external_id_prefix() {
704        for _ in 0..1_000_000 {
705            let base_id = Uuid::new_v4();
706            let asset_id = ExternalId::from(base_id);
707            let aid_string = asset_id.to_string();
708            assert_eq!(
709                aid_string.chars().next(),
710                Some('e'),
711                "External-IDs must be prefixed with 'e' in string representation"
712            );
713            let back_to_uuid: Uuid = asset_id.into();
714            assert_ne!(base_id, back_to_uuid);
715            // Check, if first four bits match 'e'
716            // NOTE: Bit-level-hacking here: bits abcdefgh & 11110000 = abcd0000
717            // -> so i can match on byte level agains abcd0000 (in this case 0xe0)
718            assert_eq!(back_to_uuid.as_bytes()[0] & 0xF0, 0xe0);
719        }
720    }
721
722    #[test]
723    fn differentiate_id_types() {
724        // This test ensures, that all ID types generate different bit-level representations.
725        // In other words: They do not match, even if I use the same uuid to generate them.
726        // This allows us to easily spot, which ID type we have and prevents cross-matches.
727        for _ in 0..1_000_000 {
728            let base_id = Uuid::new_v4();
729            let agent_id: Uuid = AgentId::from(base_id).into();
730            let borderless_id: Uuid = BorderlessId::from(base_id).into();
731            let contract_id: Uuid = ContractId::from(base_id).into();
732            let did: Uuid = Did::from(base_id).into();
733            let external_id: Uuid = ExternalId::from(base_id).into();
734            let flow_id: Uuid = FlowId::from(base_id).into();
735            let ids = [
736                base_id,
737                agent_id,
738                borderless_id,
739                contract_id,
740                did,
741                external_id,
742                flow_id,
743            ];
744            // NOTE: Check all permutations, just to be sure:
745            for i in 0..ids.len() {
746                for j in i..ids.len() {
747                    assert_eq!(ids[i] == ids[j], i == j);
748                }
749            }
750        }
751    }
752
753    #[test]
754    fn agent_id_construction() {
755        for _ in 0..1_000_000 {
756            let base_id = Uuid::new_v4();
757            let base_u128 = base_id.as_u128();
758            let from_uuid = AgentId::from(base_id);
759            let from_u128 = AgentId::from(base_u128);
760            assert_eq!(from_uuid, from_u128);
761            let back_to_uuid: Uuid = from_uuid.into();
762            let back_to_u128: u128 = from_u128.into();
763            assert_eq!(back_to_uuid, Uuid::from_u128(back_to_u128));
764            assert_eq!(back_to_uuid.as_u128(), back_to_u128); // this is redundant - but let's stay paranoid
765            assert_ne!(base_id, back_to_uuid);
766            assert_ne!(base_id.as_u128(), back_to_u128);
767        }
768    }
769
770    #[test]
771    fn borderless_id_construction() {
772        for _ in 0..1_000_000 {
773            let base_id = Uuid::new_v4();
774            let base_u128 = base_id.as_u128();
775            let from_uuid = BorderlessId::from(base_id);
776            let from_u128 = BorderlessId::from(base_u128);
777            assert_eq!(from_uuid, from_u128);
778            let back_to_uuid: Uuid = from_uuid.into();
779            let back_to_u128: u128 = from_u128.into();
780            assert_eq!(back_to_uuid, Uuid::from_u128(back_to_u128));
781            assert_eq!(back_to_uuid.as_u128(), back_to_u128); // this is redundant - but let's stay paranoid
782            assert_ne!(base_id, back_to_uuid);
783            assert_ne!(base_id.as_u128(), back_to_u128);
784        }
785    }
786
787    #[test]
788    fn contract_id_construction() {
789        for _ in 0..1_000_000 {
790            let base_id = Uuid::new_v4();
791            let base_u128 = base_id.as_u128();
792            let from_uuid = ContractId::from(base_id);
793            let from_u128 = ContractId::from(base_u128);
794            assert_eq!(from_uuid, from_u128);
795            let back_to_uuid: Uuid = from_uuid.into();
796            let back_to_u128: u128 = from_u128.into();
797            assert_eq!(back_to_uuid, Uuid::from_u128(back_to_u128));
798            assert_eq!(back_to_uuid.as_u128(), back_to_u128); // this is redundant - but let's stay paranoid
799            assert_ne!(base_id, back_to_uuid);
800            assert_ne!(base_id.as_u128(), back_to_u128);
801        }
802    }
803
804    #[test]
805    fn did_construction() {
806        for _ in 0..1_000_000 {
807            let base_id = Uuid::new_v4();
808            let base_u128 = base_id.as_u128();
809            let from_uuid = Did::from(base_id);
810            let from_u128 = Did::from(base_u128);
811            assert_eq!(from_uuid, from_u128);
812            let back_to_uuid: Uuid = from_uuid.into();
813            let back_to_u128: u128 = from_u128.into();
814            assert_eq!(back_to_uuid, Uuid::from_u128(back_to_u128));
815            assert_eq!(back_to_uuid.as_u128(), back_to_u128); // this is redundant - but let's stay paranoid
816            assert_ne!(base_id, back_to_uuid);
817            assert_ne!(base_id.as_u128(), back_to_u128);
818        }
819    }
820
821    #[test]
822    fn external_id_construction() {
823        for _ in 0..1_000_000 {
824            let base_id = Uuid::new_v4();
825            let base_u128 = base_id.as_u128();
826            let from_uuid = ExternalId::from(base_id);
827            let from_u128 = ExternalId::from(base_u128);
828            assert_eq!(from_uuid, from_u128);
829            let back_to_uuid: Uuid = from_uuid.into();
830            let back_to_u128: u128 = from_u128.into();
831            assert_eq!(back_to_uuid, Uuid::from_u128(back_to_u128));
832            assert_eq!(back_to_uuid.as_u128(), back_to_u128); // this is redundant - but let's stay paranoid
833            assert_ne!(base_id, back_to_uuid);
834            assert_ne!(base_id.as_u128(), back_to_u128);
835        }
836    }
837
838    #[test]
839    fn check_aid_prefix() {
840        for _ in 0..1_000_000 {
841            assert!(aid_prefix(&AgentId::generate()));
842            assert!(!aid_prefix(&BorderlessId::generate()));
843            assert!(!aid_prefix(&ContractId::generate()));
844            assert!(!aid_prefix(&Did::generate()));
845            assert!(!aid_prefix(&ExternalId::generate()));
846            assert!(!aid_prefix(&FlowId::generate()));
847            assert!(!aid_prefix(&[]));
848        }
849    }
850
851    #[test]
852    fn check_bid_prefix() {
853        for _ in 0..1_000_000 {
854            assert!(!bid_prefix(&AgentId::generate()));
855            assert!(bid_prefix(&BorderlessId::generate()));
856            assert!(!bid_prefix(&ContractId::generate()));
857            assert!(!bid_prefix(&Did::generate()));
858            assert!(!bid_prefix(&ExternalId::generate()));
859            assert!(!bid_prefix(&FlowId::generate()));
860            assert!(!bid_prefix(&[]));
861        }
862    }
863
864    #[test]
865    fn check_cid_prefix() {
866        for _ in 0..1_000_000 {
867            assert!(!cid_prefix(&AgentId::generate()));
868            assert!(!cid_prefix(&BorderlessId::generate()));
869            assert!(cid_prefix(&ContractId::generate()));
870            assert!(!cid_prefix(&Did::generate()));
871            assert!(!cid_prefix(&ExternalId::generate()));
872            assert!(!cid_prefix(&FlowId::generate()));
873            assert!(!cid_prefix(&[]));
874        }
875    }
876
877    #[test]
878    fn check_did_prefix() {
879        for _ in 0..1_000_000 {
880            assert!(!did_prefix(&AgentId::generate()));
881            assert!(!did_prefix(&BorderlessId::generate()));
882            assert!(!did_prefix(&ContractId::generate()));
883            assert!(did_prefix(&Did::generate()));
884            assert!(!did_prefix(&ExternalId::generate()));
885            assert!(!did_prefix(&FlowId::generate()));
886            assert!(!did_prefix(&[]));
887        }
888    }
889
890    #[test]
891    fn check_eid_prefix() {
892        for _ in 0..1_000_000 {
893            assert!(!eid_prefix(&AgentId::generate()));
894            assert!(!eid_prefix(&BorderlessId::generate()));
895            assert!(!eid_prefix(&ContractId::generate()));
896            assert!(!eid_prefix(&Did::generate()));
897            assert!(eid_prefix(&ExternalId::generate()));
898            assert!(!eid_prefix(&FlowId::generate()));
899            assert!(!eid_prefix(&[]));
900        }
901    }
902
903    #[test]
904    fn check_fid_prefix() {
905        for _ in 0..1_000_000 {
906            assert!(!fid_prefix(&AgentId::generate()));
907            assert!(!fid_prefix(&BorderlessId::generate()));
908            assert!(!fid_prefix(&ContractId::generate()));
909            assert!(!fid_prefix(&Did::generate()));
910            assert!(!fid_prefix(&ExternalId::generate()));
911            assert!(fid_prefix(&FlowId::generate()));
912            assert!(!fid_prefix(&[]));
913        }
914    }
915
916    #[test]
917    fn string_representation() {
918        fn parse<T>(id: T)
919        where
920            T: Into<String> + TryFrom<String> + fmt::Debug + Eq + Copy,
921        {
922            let s1: String = id.into();
923            let a1: Result<T, _> = s1.try_into();
924            assert!(a1.is_ok());
925            let id2 = unsafe { a1.unwrap_unchecked() };
926            assert_eq!(id, id2);
927        }
928        parse(AgentId::generate());
929        parse(BorderlessId::generate());
930        parse(ContractId::generate());
931        parse(Did::generate());
932        parse(ExternalId::generate());
933        parse(FlowId::generate());
934    }
935
936    #[test]
937    fn uuid_conversion() {
938        let uid = Uuid::new_v4();
939        let aid: AgentId = uid.into();
940        let bid: BorderlessId = uid.into();
941        let cid: ContractId = uid.into();
942        let did: Did = uid.into();
943        let eid: ExternalId = uid.into();
944        let fid: FlowId = uid.into();
945        // They must never match the uuid, because of the prefix
946        assert_ne!(aid.into_bytes(), uid.into_bytes());
947        assert_ne!(bid.into_bytes(), uid.into_bytes());
948        assert_ne!(cid.into_bytes(), uid.into_bytes());
949        assert_ne!(did.into_bytes(), uid.into_bytes());
950        assert_ne!(eid.into_bytes(), uid.into_bytes());
951        assert_ne!(fid.into_bytes(), uid.into_bytes());
952        assert_ne!(aid.into_uuid(), uid);
953        assert_ne!(bid.into_uuid(), uid);
954        assert_ne!(cid.into_uuid(), uid);
955        assert_ne!(did.into_uuid(), uid);
956        assert_ne!(eid.into_uuid(), uid);
957        assert_ne!(fid.into_uuid(), uid);
958
959        // But roundtrip will work
960        assert_eq!(AgentId::from(aid.into_uuid()), aid);
961        assert_eq!(BorderlessId::from(aid.into_uuid()), bid);
962        assert_eq!(ContractId::from(aid.into_uuid()), cid);
963        assert_eq!(Did::from(aid.into_uuid()), did);
964        assert_eq!(ExternalId::from(aid.into_uuid()), eid);
965        assert_eq!(FlowId::from(aid.into_uuid()), fid);
966    }
967
968    #[test]
969    fn merge_is_commutative() {
970        for _ in 0..1_000_000 {
971            let bid_1 = BorderlessId::generate();
972            let bid_2 = BorderlessId::generate();
973            assert_eq!(bid_1.merge(&bid_2), bid_2.merge(&bid_1));
974            // Merge with self is basically 0
975            assert_eq!(bid_1.merge(&bid_1), [0; 16]);
976        }
977    }
978
979    #[test]
980    fn merge_compact_is_commutative() {
981        for _ in 0..1_000_000 {
982            let bid_1 = BorderlessId::generate();
983            let bid_2 = BorderlessId::generate();
984            assert_eq!(bid_1.merge_compact(&bid_2), bid_2.merge_compact(&bid_1));
985            // Merge with self is 0
986            assert_eq!(bid_1.merge_compact(&bid_1), 0);
987        }
988    }
989
990    #[test]
991    fn compact() {
992        for _ in 0..1_000_000 {
993            let bid_1 = BorderlessId::generate();
994            let bid_2 = BorderlessId::generate();
995            assert_ne!(bid_1.compact(), bid_2.compact());
996            // Compacting is not creating a 0
997            assert_ne!(bid_1.compact(), 0);
998        }
999    }
1000
1001    #[test]
1002    fn compact_merge() {
1003        for _ in 0..1_000_000 {
1004            let bid_1 = BorderlessId::generate();
1005            let bid_2 = BorderlessId::generate();
1006            let merge_compact = bid_1.merge_compact(&bid_2);
1007            let merged_bytes = bid_1.merge(&bid_2);
1008            let merged_id = BorderlessId::from_bytes(merged_bytes);
1009            let compacted = merged_id.compact();
1010            // Merging and then compacting creates the same result, as calling merge_compact
1011            // ONLY IF the byte-array would be left untouched (which is not really the case)
1012            assert_eq!(
1013                compacted == merge_compact,
1014                merged_bytes == *merged_id.as_bytes()
1015            );
1016            // If we would do it manually however, it will work fine:
1017            let mut manual_compact = [0; 8];
1018            for i in 0..8 {
1019                manual_compact[i] = merged_bytes[i] ^ merged_bytes[i + 8];
1020            }
1021            assert_eq!(merge_compact, u64::from_be_bytes(manual_compact));
1022        }
1023    }
1024
1025    #[test]
1026    fn serde_conversion_is_ensured() {
1027        fn check_id<T: DeserializeOwned + From<Uuid> + Into<Uuid> + std::fmt::Debug + Eq>(
1028            base_id: Uuid,
1029        ) {
1030            let json_id = serde_json::to_string(&base_id).unwrap();
1031            let result: Result<T, _> = serde_json::from_str(&json_id);
1032            assert!(result.is_ok(), "{}", result.unwrap_err());
1033            let bid = result.unwrap();
1034            assert_eq!(bid, T::from(base_id));
1035            assert_ne!(bid.into(), base_id);
1036        }
1037        for _ in 0..100_000 {
1038            let base_id = Uuid::new_v4();
1039            check_id::<AgentId>(base_id);
1040            check_id::<BorderlessId>(base_id);
1041            check_id::<ContractId>(base_id);
1042            check_id::<Did>(base_id);
1043            check_id::<ExternalId>(base_id);
1044            check_id::<FlowId>(base_id);
1045        }
1046    }
1047}