trustchain_core/
commitment.rs

1//! Commitment scheme API with default implementation.
2use crate::utils::{json_contains, HasEndpoints, HasKeys};
3use crate::verifier::Timestamp;
4use serde::Serialize;
5use serde_json::{json, Value};
6use ssi::{
7    did::{Document, ServiceEndpoint},
8    jwk::JWK,
9};
10use std::fmt::Display;
11use thiserror::Error;
12
13/// Type for commitment result.
14pub type CommitmentResult<T> = Result<T, CommitmentError>;
15
16/// An error relating to Commitment verification.
17#[derive(Error, Debug)]
18pub enum CommitmentError {
19    /// Data decoding failure.
20    #[error("Data decoding failed.")]
21    DataDecodingFailure,
22    /// Data decoding error.
23    #[error("Data decoding error: {0}")]
24    DataDecodingError(String),
25    /// Failed to compute hash.
26    #[error("Failed to compute hash: {0}")]
27    FailedToComputeHash(String),
28    /// Failed hash verification.
29    #[error("Failed hash verification.")]
30    FailedHashVerification(String),
31    /// Failed content verification.
32    #[error("Failed content verification. Expected data {0} not found in candidate: {1}.")]
33    FailedContentVerification(String, String),
34    /// Empty iterated commitment.
35    #[error("Failed verification. Empty iterated commitment.")]
36    EmptyChainedCommitment,
37    /// No expected data present.
38    #[error("Failed retrieval of expected data. Empty expected data.")]
39    EmptyExpectedData,
40    /// Wrapped serde JSON deserialization error.
41    #[error("Failed to deserialize.")]
42    FailedToDeserialize(serde_json::Error),
43}
44
45impl From<serde_json::Error> for CommitmentError {
46    fn from(err: serde_json::Error) -> Self {
47        CommitmentError::FailedToDeserialize(err)
48    }
49}
50
51/// A cryptographic commitment with no expected data content.
52pub trait TrivialCommitment<T = Value> {
53    /// Gets the hasher (as a function pointer).
54    fn hasher(&self) -> fn(&[u8]) -> CommitmentResult<String>;
55    /// Gets the candidate data.
56    fn candidate_data(&self) -> &[u8];
57    /// Gets the candidate data decoder (function).
58    fn decode_candidate_data(&self) -> fn(&[u8]) -> CommitmentResult<Value>;
59    /// A closure for filtering candidate data. By default there is no filtering.
60    fn filter(&self) -> Option<Box<dyn Fn(&serde_json::Value) -> CommitmentResult<Value>>> {
61        None
62    }
63    /// Computes the hash (commitment). This method should not be overridden by implementors.
64    fn hash(&self) -> CommitmentResult<String> {
65        // Call the hasher on the candidate data.
66        self.hasher()(self.candidate_data())
67    }
68    /// Gets the data content that the hash verifiably commits to. This method should not be overridden by implementors.
69    fn commitment_content(&self) -> CommitmentResult<Value> {
70        let unfiltered_candidate_data = self.decode_candidate_data()(self.candidate_data())?;
71        // Optionally filter the candidate data.
72        let candidate_data = match self.filter() {
73            Some(filter) => filter(&unfiltered_candidate_data).map_err(|e| {
74                CommitmentError::DataDecodingError(format!(
75                    "Error filtering commitment content: {}",
76                    e
77                ))
78            }),
79            None => Ok(unfiltered_candidate_data.clone()),
80        }?;
81
82        // Check that the unfiltered candidate data contains the filtered data
83        // (to ensure no pollution from the filter closure).
84        if self.filter().is_some() && !json_contains(&unfiltered_candidate_data, &candidate_data) {
85            return Err(CommitmentError::DataDecodingError(
86                "Filtering of candidate data injects pollution.".to_string(),
87            ));
88        }
89        Ok(candidate_data)
90    }
91    // See https://users.rust-lang.org/t/is-there-a-way-to-move-a-trait-object/707 for Box<Self> hint.
92    /// Converts this TrivialCommitment to a Commitment.
93    fn to_commitment(self: Box<Self>, expected_data: T) -> Box<dyn Commitment<T>>;
94}
95
96/// A cryptographic commitment with expected data content.
97pub trait Commitment<T = Value>: TrivialCommitment<T>
98where
99    T: Serialize + Display,
100{
101    /// Gets the expected data.
102    fn expected_data(&self) -> &T;
103
104    /// Verifies that the expected data is found in the candidate data.
105    fn verify_content(&self) -> CommitmentResult<()> {
106        // Get the decoded candidate data.
107        let candidate_data = self.commitment_content()?;
108
109        // Verify the content.
110        // Note the call `json!(self.expected_data())` acts as the identity function when called on
111        // a `Value` type as it is simply serialized by the underlying methods.
112        if !json_contains(&candidate_data, &json!(self.expected_data())) {
113            return Err(CommitmentError::FailedContentVerification(
114                self.expected_data().to_string(),
115                candidate_data.to_string(),
116            ));
117        }
118        Ok(())
119    }
120
121    /// Verifies the commitment.
122    fn verify(&self, target: &str) -> CommitmentResult<()> {
123        // Verify the content.
124        self.verify_content()?;
125        // Verify the target by comparing with the computed hash.
126        if self.hash()?.ne(target) {
127            return Err(CommitmentError::FailedHashVerification(
128                "Computed hash not equal to target.".to_string(),
129            ));
130        }
131        Ok(())
132    }
133}
134
135/// A chain of commitments in which the target in the n'th commitment
136/// is identical to the expected data in the (n+1)'th commitment
137pub trait CommitmentChain: Commitment {
138    /// Gets the sequence of commitments.
139    fn commitments(&self) -> &Vec<Box<dyn Commitment>>;
140
141    /// Gets the sequence of commitments as a mutable reference.
142    fn mut_commitments(&mut self) -> &mut Vec<Box<dyn Commitment>>;
143
144    /// Appends a TrivialCommitment to extend this IterableCommitment.
145    ///
146    /// The appended commitment must be endowed with expected data identical
147    /// to the hash of this commitment, so the resulting iterable
148    /// commitment is itself a commitment to the same expected data.
149    fn append(&mut self, trivial_commitment: Box<dyn TrivialCommitment>) -> CommitmentResult<()>;
150}
151
152/// A chain of commitments in which the hash of the n'th commitment
153/// is identical to the expected data in the (n+1)'th commitment.
154pub struct ChainedCommitment {
155    commitments: Vec<Box<dyn Commitment>>,
156}
157
158impl ChainedCommitment {
159    pub fn new(commitment: Box<dyn Commitment>) -> Self {
160        let commitments: Vec<Box<dyn Commitment>> = vec![commitment];
161        Self { commitments }
162    }
163}
164
165impl TrivialCommitment for ChainedCommitment {
166    fn hasher(&self) -> fn(&[u8]) -> CommitmentResult<String> {
167        // The hasher of a chained commitment is that of the last in the sequence.
168        self.commitments()
169            .last()
170            .expect("Unexpected empty commitment chain.")
171            .hasher()
172    }
173
174    fn candidate_data(&self) -> &[u8] {
175        // Use as_ref to avoid consuming the Some() value from first().
176        self.commitments
177            .first()
178            .as_ref()
179            .expect("Unexpected empty commitment chain.")
180            .candidate_data()
181    }
182
183    fn decode_candidate_data(&self) -> fn(&[u8]) -> CommitmentResult<Value> {
184        self.commitments()
185            .first()
186            .expect("Unexpected empty commitment chain.")
187            .decode_candidate_data()
188    }
189
190    fn hash(&self) -> CommitmentResult<String> {
191        // The hash of a chained commitment is that of the last in the sequence.
192        self.commitments()
193            .last()
194            .ok_or(CommitmentError::EmptyChainedCommitment)?
195            .hash()
196    }
197
198    fn to_commitment(self: Box<Self>, _expected_data: serde_json::Value) -> Box<dyn Commitment> {
199        self
200    }
201}
202
203impl Commitment for ChainedCommitment {
204    fn expected_data(&self) -> &serde_json::Value {
205        // The chained commitment commits to the expected data in the first of the
206        // sequence of commitments. Must cast here to avoid infinite recursion.
207        self.commitments().first().unwrap().expected_data()
208    }
209
210    /// Verifies an IteratedCommitment by verifying each of its constituent commitments.
211    fn verify(&self, target: &str) -> CommitmentResult<()> {
212        // Verify the content.
213        self.verify_content()?;
214
215        // Verify each commitment in the sequence.
216        let commitments = self.commitments();
217        if commitments.is_empty() {
218            return Err(CommitmentError::EmptyChainedCommitment);
219        }
220        let mut it = self.commitments().iter();
221        let mut commitment = it.next().unwrap();
222
223        while let Some(&next) = it.next().as_ref() {
224            // The target for the current commitment is the expected data of the next one.
225            let this_target = match next.expected_data() {
226                serde_json::Value::String(x) => x,
227                _ => {
228                    return Err(CommitmentError::DataDecodingError(
229                        "Unhandled JSON Value variant. Expected String.".to_string(),
230                    ));
231                }
232            };
233            commitment.verify(this_target)?;
234            commitment = next;
235        }
236        // Verify the last commitment in the sequence against the given target.
237        commitment.verify(target)?;
238        Ok(())
239    }
240}
241
242impl CommitmentChain for ChainedCommitment {
243    fn commitments(&self) -> &Vec<Box<dyn Commitment>> {
244        &self.commitments
245    }
246
247    fn mut_commitments(&mut self) -> &mut Vec<Box<dyn Commitment>> {
248        &mut self.commitments
249    }
250
251    fn append(&mut self, trivial_commitment: Box<dyn TrivialCommitment>) -> CommitmentResult<()> {
252        // Set the expected data in the appended commitment to be the hash of this commitment.
253        // This ensures that the composition still commits to the expected data.
254        let expected_data = json!(self.hash()?);
255        let new_commitment = trivial_commitment.to_commitment(expected_data);
256
257        self.mut_commitments().push(new_commitment);
258        Ok(())
259    }
260}
261
262pub trait DIDCommitment: Commitment {
263    /// Gets the DID.
264    fn did(&self) -> &str;
265    /// Gets the DID Document.
266    fn did_document(&self) -> &Document;
267    /// Gets the keys in the candidate data.
268    fn candidate_keys(&self) -> Option<Vec<JWK>> {
269        self.did_document().get_keys()
270    }
271    /// Gets the endpoints in the candidate data.
272    fn candidate_endpoints(&self) -> Option<Vec<ServiceEndpoint>> {
273        self.did_document().get_endpoints()
274    }
275    fn as_any(&self) -> &dyn std::any::Any;
276}
277
278/// A Commitment whose expected data is a Unix time.
279pub trait TimestampCommitment: Commitment<Timestamp> {
280    /// Gets the timestamp as a Unix time.
281    fn timestamp(&self) -> Timestamp {
282        self.expected_data().to_owned()
283    }
284}