ssi_data_integrity_core/proof/configuration/
expansion.rs

1use ::linked_data::{LinkedDataResource, LinkedDataSubject};
2use iref::IriBuf;
3use linked_data::LinkedData;
4use rdf_types::{
5    dataset::{BTreeGraph, IndexedBTreeDataset, PatternMatchingDataset},
6    interpretation::{ReverseTermInterpretation, WithGenerator},
7    vocabulary::IriVocabulary,
8    Id, InterpretationMut, LexicalQuad, Quad, Term, Triple, Vocabulary, VocabularyMut,
9};
10use serde::Serialize;
11use ssi_json_ld::{
12    CompactJsonLd, Expandable, ExpandedDocument, JsonLdError, JsonLdLoaderProvider,
13    JsonLdNodeObject, JsonLdObject, JsonLdTypes, Loader,
14};
15use ssi_rdf::{urdna2015, Interpretation, IntoNQuads, LdEnvironment};
16use std::{borrow::Cow, hash::Hash};
17
18use crate::{suite::SerializeCryptographicSuite, CryptographicSuite, ProofConfigurationRef};
19
20impl<'a, S: CryptographicSuite> ProofConfigurationRef<'a, S> {
21    pub fn embed<'d>(
22        self,
23        document: &'d impl JsonLdNodeObject,
24    ) -> EmbeddedProofConfigurationRef<'d, 'a, S> {
25        EmbeddedProofConfigurationRef {
26            context: document.json_ld_context(),
27            type_: document.json_ld_type(),
28            proof: self,
29        }
30    }
31
32    pub async fn expand(
33        self,
34        environment: &impl JsonLdLoaderProvider,
35        document: &impl JsonLdNodeObject,
36    ) -> Result<ExpandedProofConfiguration, ConfigurationExpansionError>
37    where
38        S: SerializeCryptographicSuite,
39    {
40        let embedded = self.embed(document);
41        let expanded = embedded.expand(environment.loader()).await?;
42        let mut interpretation = WithGenerator::new((), ssi_rdf::generator::Blank::new());
43        expanded.extract(&mut (), &mut interpretation)
44    }
45}
46
47#[derive(Debug, thiserror::Error)]
48pub enum ConfigurationExpansionError {
49    #[error("JSON-LD expansion failed: {0}")]
50    Expansion(#[from] JsonLdError),
51
52    #[error(transparent)]
53    IntoQuads(#[from] ::linked_data::IntoQuadsError),
54
55    #[error("missing proof configuration")]
56    MissingProof,
57
58    #[error("missing proof configuration value")]
59    InvalidProofValue,
60
61    #[error("invalid proof type")]
62    InvalidProofType,
63
64    #[error("missing proof configuration graph")]
65    MissingProofGraph,
66
67    #[error("invalid JSON-LD context")]
68    InvalidContext,
69}
70
71#[derive(Serialize)]
72#[serde(bound = "S: SerializeCryptographicSuite")]
73pub struct EmbeddedProofConfigurationRef<'d, 'a, S: CryptographicSuite> {
74    #[serde(rename = "@context", default, skip_serializing_if = "Option::is_none")]
75    context: Option<Cow<'d, ssi_json_ld::syntax::Context>>,
76
77    #[serde(rename = "type", skip_serializing_if = "JsonLdTypes::is_empty")]
78    type_: JsonLdTypes<'d>,
79
80    proof: ProofConfigurationRef<'a, S>,
81}
82
83impl<S: CryptographicSuite> JsonLdObject for EmbeddedProofConfigurationRef<'_, '_, S> {
84    fn json_ld_context(&self) -> Option<Cow<ssi_json_ld::syntax::Context>> {
85        self.context.as_deref().map(Cow::Borrowed)
86    }
87}
88
89impl<S: CryptographicSuite> JsonLdNodeObject for EmbeddedProofConfigurationRef<'_, '_, S> {
90    fn json_ld_type(&self) -> JsonLdTypes {
91        self.type_.reborrow()
92    }
93}
94
95impl<S: SerializeCryptographicSuite> Expandable for EmbeddedProofConfigurationRef<'_, '_, S> {
96    type Error = ConfigurationExpansionError;
97    type Expanded<I, V>
98        = ExpandedEmbeddedProofConfiguration<V::Iri, V::BlankId>
99    where
100        I: Interpretation,
101        V: VocabularyMut,
102        V::Iri: LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
103        V::BlankId: LinkedDataResource<I, V> + LinkedDataSubject<I, V>;
104
105    async fn expand_with<I, V>(
106        &self,
107        ld: &mut LdEnvironment<V, I>,
108        loader: &impl Loader,
109    ) -> Result<Self::Expanded<I, V>, Self::Error>
110    where
111        I: Interpretation,
112        V: VocabularyMut,
113        V::Iri: Clone + Eq + Hash + LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
114        V::BlankId: Clone + Eq + Hash + LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
115    {
116        let json = json_syntax::to_value(self).unwrap();
117        let mut expanded = CompactJsonLd(json).expand_with(ld, loader).await?;
118        expanded.canonicalize();
119        Ok(ExpandedEmbeddedProofConfiguration(expanded))
120    }
121}
122
123pub struct ExpandedEmbeddedProofConfiguration<I, B>(ssi_json_ld::ExpandedDocument<I, B>);
124
125impl<I, B> ExpandedEmbeddedProofConfiguration<I, B>
126where
127    I: Clone + Eq + Hash,
128    B: Clone + Eq + Hash,
129{
130    pub fn extract<V, R>(
131        self,
132        vocabulary: &mut V,
133        interpretation: &mut R,
134    ) -> Result<ExpandedProofConfiguration, ConfigurationExpansionError>
135    where
136        R: InterpretationMut<V>
137            + ReverseTermInterpretation<Iri = I, BlankId = B, Literal = V::Literal>,
138        R::Resource: Clone,
139        V: VocabularyMut<Iri = I, BlankId = B>,
140        I: LinkedDataResource<R, V> + LinkedDataSubject<R, V>,
141        B: LinkedDataResource<R, V> + LinkedDataSubject<R, V>,
142    {
143        match self.0.into_main_node() {
144            Some(node) => {
145                // Get the proof type IRI.
146                let proof_prop = ssi_json_ld::Id::iri(
147                    vocabulary
148                        .get(ssi_security::PROOF)
149                        .ok_or(ConfigurationExpansionError::MissingProof)?,
150                );
151                let proof_type = match node.get_any(&proof_prop) {
152                    Some(proof) => match proof.as_node() {
153                        Some(proof) => match &proof.graph {
154                            Some(proof_graph) => match proof_graph.first() {
155                                Some(proof) => match proof.as_node() {
156                                    Some(proof) => match proof.types() {
157                                        [ssi_json_ld::Id::Valid(Id::Iri(iri))] => {
158                                            Ok(vocabulary.iri(iri).unwrap().to_owned())
159                                        }
160                                        _ => Err(ConfigurationExpansionError::InvalidProofType),
161                                    },
162                                    None => Err(ConfigurationExpansionError::InvalidProofValue),
163                                },
164                                None => Err(ConfigurationExpansionError::MissingProof),
165                            },
166                            None => Err(ConfigurationExpansionError::InvalidProofValue),
167                        },
168                        None => Err(ConfigurationExpansionError::InvalidProofValue),
169                    },
170                    None => Err(ConfigurationExpansionError::MissingProof),
171                }?;
172
173                let (subject, quads) = ::linked_data::to_lexical_subject_quads_with(
174                    vocabulary,
175                    interpretation,
176                    None,
177                    &node,
178                )?;
179
180                let subject = Term::Id(subject);
181
182                let mut dataset: IndexedBTreeDataset = quads
183                    .into_iter()
184                    .map(|q| Quad(Term::Id(q.0), Term::iri(q.1), q.2, q.3.map(Term::Id)))
185                    .collect();
186
187                let proof_prop = Term::iri(ssi_security::PROOF.to_owned());
188                match dataset.quad_objects(None, &subject, &proof_prop).next() {
189                    Some(Term::Id(proof_id)) => {
190                        let proof_id = Term::Id(proof_id.clone());
191                        match dataset.remove_graph(Some(&proof_id)) {
192                            Some(graph) => Ok(ExpandedProofConfiguration {
193                                type_iri: proof_type,
194                                graph,
195                            }),
196                            None => Err(ConfigurationExpansionError::MissingProofGraph),
197                        }
198                    }
199                    Some(Term::Literal(_)) => Err(ConfigurationExpansionError::InvalidProofValue),
200                    None => Err(ConfigurationExpansionError::MissingProof),
201                }
202            }
203            None => Err(ConfigurationExpansionError::InvalidContext),
204        }
205    }
206}
207
208impl<I: Interpretation, V: Vocabulary> LinkedData<I, V>
209    for ExpandedEmbeddedProofConfiguration<V::Iri, V::BlankId>
210where
211    ExpandedDocument<V::Iri, V::BlankId>: LinkedData<I, V>,
212{
213    fn visit<S>(&self, visitor: S) -> Result<S::Ok, S::Error>
214    where
215        S: linked_data::Visitor<I, V>,
216    {
217        self.0.visit(visitor)
218    }
219}
220
221/// Linked-Data proof configuration.
222pub struct ExpandedProofConfiguration {
223    pub type_iri: IriBuf,
224    pub graph: BTreeGraph,
225}
226
227impl ExpandedProofConfiguration {
228    /// Returns the quads of the proof configuration, in canonical form.
229    pub fn quads(&self) -> impl '_ + Iterator<Item = LexicalQuad> {
230        let quads = self.graph.iter().map(|Triple(s, p, o)| {
231            Quad(
232                s.as_lexical_term_ref().into_id().unwrap(),
233                p.as_lexical_term_ref().into_iri().unwrap(),
234                o.as_lexical_term_ref(),
235                None,
236            )
237        });
238
239        urdna2015::normalize(quads)
240    }
241
242    /// Returns the quads of the proof configuration, in canonical form.
243    pub fn nquads_lines(&self) -> Vec<String> {
244        self.quads().into_nquads_lines()
245    }
246
247    /// Returns the quads of the proof configuration, in canonical form.
248    pub fn nquads(&self) -> String {
249        self.quads().into_nquads()
250    }
251}