trustchain_ion/
sidetree.rs

1//! Data structures for deserializing Sidetree IPFS data.
2use crate::ion::IONTest as ION;
3use did_ion::sidetree::{Delta, Sidetree, SuffixData};
4use serde::{Deserialize, Serialize};
5use trustchain_core::{commitment::CommitmentError, utils::get_did_suffix};
6
7/// Data structure for suffix data of create operations within a [Core Index File](https://identity.foundation/sidetree/spec/#core-index-file).
8#[derive(Clone, Debug, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct CreateSuffixData {
11    /// DID Suffix data.
12    pub suffix_data: SuffixData,
13}
14/// Data structure for suffix data of recover and deactivate operations within a [Core Index File](https://identity.foundation/sidetree/spec/#core-index-file).
15#[derive(Clone, Debug, Serialize, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct OtherOperationSuffixData {
18    /// DID suffix.
19    pub did_suffix: String,
20    /// Reveal value for operation.
21    pub reveal_value: String,
22}
23
24/// Data structure for operations contained within a [Core Index File](https://identity.foundation/sidetree/spec/#core-index-file).
25#[derive(Clone, Debug, Serialize, Deserialize)]
26#[serde(rename_all = "camelCase")]
27pub struct CoreIndexFileOperations {
28    /// Suffix data associated with create operations.
29    pub create: Option<Vec<CreateSuffixData>>,
30    /// Suffix data associated with recover operations.
31    pub recover: Option<Vec<OtherOperationSuffixData>>,
32    /// Suffix data associated with deactivate operations.
33    pub deactivate: Option<Vec<OtherOperationSuffixData>>,
34}
35/// Data structure for a Sidetree [Core Index File](https://identity.foundation/sidetree/spec/#core-index-file).
36#[derive(Clone, Debug, Serialize, Deserialize)]
37#[serde(rename_all = "camelCase")]
38pub struct CoreIndexFile {
39    /// URI of associated [core proof file](https://identity.foundation/sidetree/spec/#core-proof-file).
40    pub core_proof_file_uri: Option<String>,
41    /// URI of associated [provisional index file](https://identity.foundation/sidetree/spec/#provisional-index-file).
42    pub provisional_index_file_uri: Option<String>,
43    /// Optional [writer lock property](https://identity.foundation/sidetree/spec/#writer-lock-property).
44    pub writer_lock_id: Option<String>,
45    /// Data associated with any create, recover or deactivate operations.
46    pub operations: Option<CoreIndexFileOperations>,
47}
48
49impl CoreIndexFile {
50    /// Returns a vector of DID suffixes being created in the core index file.
51    pub fn created_did_suffixes(&self) -> Option<Vec<String>> {
52        Some(
53            self.operations
54                .as_ref()?
55                .create
56                .as_ref()?
57                .iter()
58                .filter_map(|create_suffix_data| {
59                    Some(
60                        ION::serialize_suffix_data(&create_suffix_data.suffix_data)
61                            .ok()?
62                            .to_string(),
63                    )
64                })
65                .collect::<Vec<_>>(),
66        )
67    }
68    /// Returns the index of the create operation for the given DID.
69    pub fn did_create_operation_index(&self, did: &str) -> Result<usize, CommitmentError> {
70        // TODO: to be generalized to roots that have been updated
71        let did_suffix = get_did_suffix(did);
72        self.created_did_suffixes()
73            .ok_or(CommitmentError::FailedContentVerification(
74                did.to_string(),
75                serde_json::to_string(self).unwrap(),
76            ))?
77            .into_iter()
78            .position(|v| v == did_suffix)
79            .ok_or(CommitmentError::FailedContentVerification(
80                did.to_string(),
81                serde_json::to_string(self).unwrap(),
82            ))
83    }
84}
85
86/// Data structure for operations contained within a [Provisional Index File](https://identity.foundation/sidetree/spec/#provisional-index-file).
87#[derive(Clone, Debug, Serialize, Deserialize)]
88#[serde(rename_all = "camelCase")]
89pub struct ProvisionalIndexFileOperations {
90    /// Suffix data associated with update operations.
91    pub update: Vec<OtherOperationSuffixData>,
92}
93
94/// Data structure for Chunk File URI.
95#[derive(Clone, Debug, Serialize, Deserialize)]
96#[serde(rename_all = "camelCase")]
97pub struct ChunkFileUri {
98    /// Chunk file URI.
99    pub chunk_file_uri: String,
100}
101
102/// Data structure for a Sidetree [Provisional Index File](https://identity.foundation/sidetree/spec/#provisional-index-file).
103#[derive(Clone, Debug, Serialize, Deserialize)]
104#[serde(rename_all = "camelCase")]
105pub struct ProvisionalIndexFile {
106    /// [Provisional Proof File](https://identity.foundation/sidetree/spec/#provisional-proof-file) URI associated with any update operations.
107    pub provisional_proof_file_uri: Option<String>,
108    /// Array of associated Chunk File URI.
109    pub chunks: Option<Vec<ChunkFileUri>>,
110    /// Data for any update operations.
111    pub operations: Option<ProvisionalIndexFileOperations>,
112}
113
114/// Data structure for a Sidetree [Chunk File](https://identity.foundation/sidetree/spec/#chunk-files).
115#[derive(Clone, Debug, Serialize, Deserialize)]
116#[serde(rename_all = "camelCase")]
117pub struct ChunkFile {
118    /// Array of [Delta Entry](https://identity.foundation/sidetree/spec/#chunk-file-delta-entry) objects.
119    pub deltas: Vec<Delta>,
120}
121
122#[cfg(test)]
123mod tests {
124    use crate::data::{
125        TEST_CHUNK_FILE_CONTENT, TEST_CORE_INDEX_FILE_CONTENT, TEST_PROVISIONAL_INDEX_FILE_CONTENT,
126    };
127
128    use super::*;
129
130    /// Example data structure from [sidetree](https://identity.foundation/sidetree/spec/#core-index-file).
131    const CORE_INDEX_FILE_STRUCTURE: &str = r#"
132    {
133        "coreProofFileUri": "CAS_URI",
134        "provisionalIndexFileUri": "CAS_URI",
135        "writerLockId": "OPTIONAL_LOCKING_VALUE",
136        "operations": {
137          "create": [
138            {
139              "suffixData": {
140                "type": "TYPE_STRING",
141                "deltaHash": "DELTA_HASH",
142                "recoveryCommitment": "COMMITMENT_HASH"
143              }
144            }
145          ],
146          "recover": [
147            {
148              "didSuffix": "SUFFIX_STRING",
149              "revealValue": "MULTIHASH_OF_JWK"
150            }
151          ],
152          "deactivate": [
153            {
154              "didSuffix": "SUFFIX_STRING",
155              "revealValue": "MULTIHASH_OF_JWK"
156            }
157          ]
158        }
159    }"#;
160
161    /// Example data structure from [sidetree](https://identity.foundation/sidetree/spec/#provisional-index-file).
162    const PROVISIONAL_INDEX_FILE_STRUCTURE: &str = r#"
163    {
164        "provisionalProofFileUri": "CAS_URI",
165        "chunks": [
166          { "chunkFileUri": "CAS_URI" }
167        ],
168        "operations": {
169          "update": [
170            {
171              "didSuffix": "SUFFIX_STRING",
172              "revealValue": "MULTIHASH_OF_JWK"
173            }
174          ]
175        }
176      }
177      "#;
178
179    #[test]
180    fn test_parse_core_index_file_from_sidetree() {
181        let core_index_file: CoreIndexFile =
182            serde_json::from_str(CORE_INDEX_FILE_STRUCTURE).unwrap();
183        assert!(serde_json::to_string_pretty(&core_index_file).is_ok());
184    }
185    #[test]
186    fn test_parse_core_index_file_from_data() {
187        let core_index_file: CoreIndexFile =
188            serde_json::from_str(TEST_CORE_INDEX_FILE_CONTENT).unwrap();
189        assert!(serde_json::to_string_pretty(&core_index_file).is_ok());
190    }
191    #[test]
192    fn test_parse_provisional_index_file_from_sidetree() {
193        let provisional_index_file: ProvisionalIndexFile =
194            serde_json::from_str(PROVISIONAL_INDEX_FILE_STRUCTURE).unwrap();
195        assert!(serde_json::to_string_pretty(&provisional_index_file).is_ok());
196    }
197    #[test]
198    fn test_parse_provisional_index_file_from_data() {
199        let provisional_index_file: ProvisionalIndexFile =
200            serde_json::from_str(TEST_PROVISIONAL_INDEX_FILE_CONTENT).unwrap();
201        assert!(serde_json::to_string_pretty(&provisional_index_file).is_ok());
202    }
203    #[test]
204    fn test_parse_chunk_file_from_data() {
205        let chunk_file: ChunkFile = serde_json::from_str(TEST_CHUNK_FILE_CONTENT).unwrap();
206        assert!(serde_json::to_string_pretty(&chunk_file).is_ok());
207    }
208    #[test]
209    fn test_created_did_suffixes() {
210        let core_index_file: CoreIndexFile =
211            serde_json::from_str(TEST_CORE_INDEX_FILE_CONTENT).unwrap();
212        let expected = vec![
213            "EiCClfEdkTv_aM3UnBBhlOV89LlGhpQAbfeZLFdFxVFkEg",
214            "EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A",
215            "EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q",
216        ];
217        let actual = core_index_file.created_did_suffixes().unwrap();
218        assert_eq!(expected, actual);
219    }
220
221    #[test]
222    fn test_extract_suffix_idx() {
223        let core_index_file: CoreIndexFile =
224            serde_json::from_str(TEST_CORE_INDEX_FILE_CONTENT).unwrap();
225        let expected = 1;
226        let actual = core_index_file
227            .did_create_operation_index("EiBVpjUxXeSRJpvj2TewlX9zNF3GKMCKWwGmKBZqF6pk_A")
228            .unwrap();
229        assert_eq!(expected, actual);
230    }
231}