trustchain_core/
verifier.rs

1//! DID verifier API and default implementation.
2use std::error::Error;
3
4use crate::chain::{Chain, ChainError, DIDChain};
5use crate::commitment::{CommitmentError, DIDCommitment, TimestampCommitment};
6use crate::resolver::{ResolverError, TrustchainResolver};
7use async_trait::async_trait;
8use ssi::did_resolve::DIDResolver;
9use thiserror::Error;
10
11/// An error relating to Trustchain verification.
12#[derive(Error, Debug)]
13pub enum VerifierError {
14    /// Invalid payload in proof compared to resolved document.
15    #[error("Invalid payload provided in proof for dDID: {0}.")]
16    InvalidPayload(String),
17    /// Invalid payload in proof compared to resolved document.
18    #[error("Invalid signature for proof in dDID: {0}.")]
19    InvalidSignature(String),
20    /// Invalid root DID after self-controller reached in path.
21    #[error("Invalid root DID error: {0}")]
22    InvalidRoot(Box<dyn std::error::Error + Send + Sync>),
23    /// Invalid root with error:
24    #[error("Invalid root DID ({0}) with timestamp: {1}.")]
25    InvalidRootTimestamp(String, Timestamp),
26    /// Failed to build DID chain.
27    #[error("Failed to build chain: {0}.")]
28    ChainBuildFailure(String),
29    /// Chain verification failed.
30    #[error("Chain verification failed: {0}.")]
31    InvalidChain(String),
32    /// Invalid PoW hash.
33    #[error("Invalid PoW hash: {0}.")]
34    InvalidProofOfWorkHash(String),
35    /// Failed to get DID operation.
36    #[error("Error getting {0} DID operation.")]
37    FailureToGetDIDOperation(String),
38    /// Failed to get DID content.
39    #[error("Error getting {0} DID content.")]
40    FailureToGetDIDContent(String),
41    /// Failed to recognise/handle DID content.
42    #[error("Unrecognised DID content found at: {0}")]
43    UnrecognisedDIDContent(String),
44    /// Failed to read DID content.
45    #[error("Error reading DID content found at: {0}")]
46    FailureToReadDIDContent(String),
47    /// Failed to parse DID content.
48    #[error("Error parsing DID content: {0}")]
49    FailureToParseDIDContent(String),
50    /// Failed to verify DID content.
51    #[error("Error verifying DID content.")]
52    FailureToVerifyDIDContent,
53    /// Failed to parse timestamp.
54    #[error("Error parsing timestamp data.")]
55    FailureToParseTimestamp,
56    /// Invalid block hash.
57    #[error("Invalid block hash: {0}")]
58    InvalidBlockHash(String),
59    /// Invalid block height.
60    #[error("Invalid block height: {0}")]
61    InvalidBlockHeight(i64),
62    /// Invalid transaction index.
63    #[error("Invalid transaction index: {0}")]
64    InvalidTransactionIndex(i32),
65    /// Failed to get the block hash for DID.
66    #[error("Failed to get block hash for DID: {0}")]
67    FailureToGetBlockHash(String),
68    /// Failed to get the block height for DID.
69    #[error("Failed to get block height for DID: {0}")]
70    FailureToGetBlockHeight(String),
71    /// Failed to get the block header for block hash.
72    #[error("Failed to get block header for block hash: {0}")]
73    FailureToGetBlockHeader(String),
74    /// Failure of API call to PoW ledger client.
75    #[error("Failed API call to PoW ledger client: {0}")]
76    LedgerClientError(String),
77    /// Detected multiple DID content identifiers.
78    #[error("Detected multiple DID content identifiers in tx: {0}")]
79    MultipleDIDContentIdentifiers(String),
80    /// No DID content identifier was found.
81    #[error("No DID content identifier was found in tx: {0}")]
82    NoDIDContentIdentifier(String),
83    /// Failed verification of DID-related content hash.
84    #[error("Content hash {0} does not match expected: {1}")]
85    FailedContentHashVerification(String, String),
86    /// Unhandled DID content.
87    #[error("Unhandled DID content: {0}")]
88    UnhandledDIDContent(String),
89    /// Failed to resolve DID for verification.
90    #[error("Failed to resolve DID: {0} with associated resolution metadata: {1}")]
91    // Note: ResolverError boxed to remove large Err-variant lint: <https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err>
92    DIDResolutionError(String, Box<ResolverError>),
93    /// Failed to parse DID Document metadata.
94    #[error("Failed to parse DID Document metadata.")]
95    DIDMetadataError,
96    /// Failed to find expected key in verified DID content.
97    #[error("Key not found in verified content for DID: {0}")]
98    KeyNotFoundInVerifiedContent(String),
99    /// Failed to find expected key in verified DID content.
100    #[error("No keys found in verified content for DID: {0}")]
101    NoKeysFoundInVerifiedContent(String),
102    /// Failed to find expected service endpoint in verified DID content.
103    #[error("Endpoint not found in verified content for DID: {0}")]
104    EndpointNotFoundInVerifiedContent(String),
105    /// No endpoints found in verified DID content.
106    #[error("No endpoints found in verified content for DID: {0}")]
107    NoEndpointsFoundInVerifiedContent(String),
108    /// Found duplicate update commitments in different DID operations.
109    #[error("Duplicate update commitments: {0}")]
110    DuplicateDIDUpdateCommitments(String),
111    /// Failed to verify PoW hashes.
112    #[error("PoW hashes do not match: {0}, {1}")]
113    FailedProofOfWorkHashVerification(String, String),
114    /// Failed to verify transaction timestamp.
115    #[error("Timestamp verification failed for transaction: {0}")]
116    FailedTransactionTimestampVerification(String),
117    /// Failed block hash verification.
118    #[error("Block hash verification failed for DID: {0}.")]
119    FailedBlockHashVerification(String),
120    /// Failed DID timestamp verification.
121    #[error("Timestamp verification failed for DID: {0}.")]
122    TimestampVerificationError(String),
123    /// Error fetching verification material.
124    #[error("Error fetching verification material: {0}. Error: {1}")]
125    ErrorFetchingVerificationMaterial(String, Box<dyn Error + Send + Sync>),
126    /// Failed to fetch verification material.
127    #[error("Failed to fetch verification material: {0}")]
128    FailureToFetchVerificationMaterial(String),
129    /// Attempt to access verification material before it has been fetched.
130    #[error("Verification material not yet fetched for DID: {0}.")]
131    VerificationMaterialNotYetFetched(String),
132    /// Wrapped commitment error.
133    #[error("A commitment error during verification: {0}")]
134    CommitmentFailure(CommitmentError),
135    /// Wrapped resolver error.
136    #[error("A resolver error during verification: {0}")]
137    ResolverFailure(ResolverError),
138    /// Wrapped chain error.
139    #[error("A chain error during verification: {0}")]
140    ChainFailure(ChainError),
141    /// Wrapped serde JSON deserialization error.
142    #[error("Failed to deserialize: {0}")]
143    FailedToDeserialize(serde_json::Error),
144}
145
146impl From<CommitmentError> for VerifierError {
147    fn from(err: CommitmentError) -> Self {
148        VerifierError::CommitmentFailure(err)
149    }
150}
151
152impl From<ResolverError> for VerifierError {
153    fn from(err: ResolverError) -> Self {
154        VerifierError::ResolverFailure(err)
155    }
156}
157
158impl From<ChainError> for VerifierError {
159    fn from(err: ChainError) -> Self {
160        VerifierError::ChainFailure(err)
161    }
162}
163
164impl From<serde_json::Error> for VerifierError {
165    fn from(err: serde_json::Error) -> Self {
166        VerifierError::FailedToDeserialize(err)
167    }
168}
169
170/// A Unix timestamp.
171pub type Timestamp = u64;
172
173/// A verifiably-timestamped DID.
174pub trait VerifiableTimestamp {
175    /// Gets the wrapped DIDCommitment.
176    fn did_commitment(&self) -> &dyn DIDCommitment;
177    /// Gets the wrapped TimestampCommitment.
178    fn timestamp_commitment(&self) -> &dyn TimestampCommitment;
179    /// Gets the Timestamp.
180    fn timestamp(&self) -> Timestamp {
181        self.timestamp_commitment().timestamp()
182    }
183    /// Verifies both the DIDCommitment and the TimestampCommitment against the same target.
184    fn verify(&self, target: &str) -> Result<(), CommitmentError> {
185        // The expected data in the TimestampCommitment is the timestamp, while in the
186        // DIDCommitment the expected data are the public keys & service endpoints.
187        // By verifying both commitments using the same target, we confirm that the *same*
188        // hash commits to *both* the DID Document data and the timestamp.
189        self.did_commitment().verify(target)?;
190        self.timestamp_commitment().verify(target)?;
191        Ok(())
192    }
193}
194
195/// A verifier of root and downstream DIDs.
196#[async_trait]
197pub trait Verifier<T: Sync + Send + DIDResolver> {
198    /// Verifies a downstream DID by tracing its chain back to the root.
199    async fn verify(
200        &self,
201        did: &str,
202        root_timestamp: Timestamp,
203    ) -> Result<DIDChain, VerifierError> {
204        // Build a chain from the given DID to the root.
205        let resolver = self.resolver();
206        let chain = DIDChain::new(did, resolver).await?;
207
208        // Verify the proofs in the chain.
209        chain.verify_proofs()?;
210
211        // Verify the root timestamp.
212        let root = chain.root();
213
214        let verifiable_timestamp = self.verifiable_timestamp(root, root_timestamp).await?;
215
216        // Verify that the root DID content (keys & endpoints) and the timestamp share a common
217        // commitment target.
218        verifiable_timestamp.verify(&verifiable_timestamp.timestamp_commitment().hash()?)?;
219
220        // Validate the PoW on the common target hash.
221        self.validate_pow_hash(&verifiable_timestamp.timestamp_commitment().hash()?)?;
222
223        // Verify explicitly that the return value from the timestamp method equals the expected
224        // root timestamp (in case the default timestamp method implementation has been overridden).
225        if !verifiable_timestamp.timestamp().eq(&root_timestamp) {
226            Err(VerifierError::InvalidRootTimestamp(
227                root.to_string(),
228                verifiable_timestamp.timestamp(),
229            ))
230        } else {
231            Ok(chain)
232        }
233    }
234
235    /// Constructs a verifiable timestamp for the given DID, including an expected
236    /// value for the timestamp retrieved from a local PoW network node.
237    async fn verifiable_timestamp(
238        &self,
239        did: &str,
240        expected_timestamp: Timestamp,
241    ) -> Result<Box<dyn VerifiableTimestamp>, VerifierError>;
242
243    /// Gets a block hash (PoW) Commitment for the given DID.
244    async fn did_commitment(&self, did: &str) -> Result<Box<dyn DIDCommitment>, VerifierError>;
245
246    /// Queries a local PoW node to get the expected timestamp for a given PoW hash.
247    fn validate_pow_hash(&self, hash: &str) -> Result<(), VerifierError>;
248
249    /// Gets the resolver used for DID verification.
250    fn resolver(&self) -> &dyn TrustchainResolver;
251}
252
253#[cfg(test)]
254mod tests {}