did_ion/sidetree/operation/
mod.rs

1mod create;
2mod deactivate;
3mod recover;
4mod update;
5
6pub use create::*;
7pub use deactivate::*;
8pub use recover::*;
9use serde::{de::DeserializeOwned, Deserialize, Serialize};
10use ssi_jwk::JWK;
11use ssi_jws::DecodedSigningBytes;
12pub use update::*;
13
14use super::{
15    json_canonicalization_scheme, DIDSuffix, JWKFromPublicKeyJwkError, PublicKeyJwk, Sidetree,
16};
17
18/// Sidetree DID operation
19///
20/// ### References
21/// - <https://identity.foundation/sidetree/spec/v1.0.0/#did-operations>
22/// - <https://identity.foundation/sidetree/spec/v1.0.0/#sidetree-operations>
23/// - <https://identity.foundation/sidetree/api/#sidetree-operations>
24#[derive(Debug, Serialize, Deserialize, Clone)]
25#[serde(tag = "type")]
26#[serde(rename_all = "camelCase")]
27pub enum Operation {
28    Create(CreateOperation),
29    Update(UpdateOperation),
30    Recover(RecoverOperation),
31    Deactivate(DeactivateOperation),
32}
33
34#[derive(Debug, thiserror::Error)]
35pub enum OperationFromTransactionError {
36    #[error("missing `sidetreeOperation` property")]
37    MissingSidetreeOperation,
38
39    #[error("invalid `sidetreeOperation` value")]
40    InvalidSidetreeOperation,
41}
42
43impl Operation {
44    pub fn from_transaction(
45        mut transaction: serde_json::Value,
46    ) -> Result<Self, OperationFromTransactionError> {
47        let op_value = transaction
48            .as_object_mut()
49            .ok_or(OperationFromTransactionError::MissingSidetreeOperation)?
50            .remove("sidetreeOperation")
51            .ok_or(OperationFromTransactionError::MissingSidetreeOperation)?;
52        let op: Operation = serde_json::from_value(op_value)
53            .map_err(|_| OperationFromTransactionError::InvalidSidetreeOperation)?;
54        Ok(op)
55    }
56
57    pub fn into_transaction(self) -> serde_json::Value {
58        let value = serde_json::to_value(self).unwrap();
59        serde_json::json!({ "sidetreeOperation": value })
60    }
61}
62
63/// Partially verified Sidetree DID operation
64///
65/// Converted from [Operation].
66///
67/// Operation verification is described in [Sidetree §10.2.1 Operation Verification][ov].
68///
69/// [ov]: https://identity.foundation/sidetree/spec/v1.0.0/#operation-verification
70#[derive(Debug, Clone)]
71pub enum PartiallyVerifiedOperation {
72    Create(PartiallyVerifiedCreateOperation),
73    Update(PartiallyVerifiedUpdateOperation),
74    Recover(PartiallyVerifiedRecoverOperation),
75    Deactivate(PartiallyVerifiedDeactivateOperation),
76}
77
78#[derive(Debug, thiserror::Error)]
79pub enum PartialVerificationError {
80    #[error("invalid signature algorithm")]
81    InvalidSignatureAlgorithm,
82
83    #[error("reveal value mismatch (computed: {computed:?}, found: {found:?})")]
84    RevealValueMismatch { computed: String, found: String },
85
86    #[error("delta hash mismatch")]
87    DeltaHashMismatch,
88
89    #[error("DID suffix mismatch")]
90    DIDSuffixMismatch,
91
92    #[error(transparent)]
93    JWSDecodeVerifyError(#[from] JWSDecodeVerifyError),
94}
95
96/// A Sidetree operation
97///
98/// See also the enum [Operation] which implements this trait.
99pub trait SidetreeOperation {
100    /// The result of [partially verifying][Self::partial_verify] the operation.
101    type PartiallyVerifiedForm;
102
103    /// Partially verify the operation.
104    ///
105    /// Operation verification is described in [Sidetree §10.2.1 Operation Verification][ov].
106    ///
107    /// This function verifies the internal consistency (including signatures and hashes) of the operation,
108    /// and returns the integrity-verified data.
109    /// Public key commitment values are not checked; that is, the signature is verified, but
110    /// whether the public key is the correct reveal value is not checked, since that depends on
111    /// what the previous operation was. The DID suffix is also not checked, except for a Create
112    /// operation, since it is otherwise in reference to an earlier (Create) opeation.
113    ///
114    /// [ov]: https://identity.foundation/sidetree/spec/v1.0.0/#operation-verification
115    fn partial_verify<S: Sidetree>(
116        self,
117    ) -> Result<Self::PartiallyVerifiedForm, PartialVerificationError>;
118}
119
120impl SidetreeOperation for Operation {
121    type PartiallyVerifiedForm = PartiallyVerifiedOperation;
122
123    fn partial_verify<S: Sidetree>(
124        self,
125    ) -> Result<Self::PartiallyVerifiedForm, PartialVerificationError> {
126        match self {
127            Operation::Create(op) => op
128                .partial_verify::<S>()
129                .map(PartiallyVerifiedOperation::Create),
130            Operation::Update(op) => op
131                .partial_verify::<S>()
132                .map(PartiallyVerifiedOperation::Update),
133            Operation::Recover(op) => op
134                .partial_verify::<S>()
135                .map(PartiallyVerifiedOperation::Recover),
136            Operation::Deactivate(op) => op
137                .partial_verify::<S>()
138                .map(PartiallyVerifiedOperation::Deactivate),
139        }
140    }
141}
142
143impl PartiallyVerifiedOperation {
144    pub fn update_commitment(&self) -> Option<&str> {
145        match self {
146            PartiallyVerifiedOperation::Create(create) => {
147                Some(&create.hashed_delta.update_commitment)
148            }
149            PartiallyVerifiedOperation::Update(update) => {
150                Some(&update.signed_delta.update_commitment)
151            }
152            PartiallyVerifiedOperation::Recover(recover) => {
153                Some(&recover.signed_delta.update_commitment)
154            }
155            PartiallyVerifiedOperation::Deactivate(_) => None,
156        }
157    }
158
159    pub fn recovery_commitment(&self) -> Option<&str> {
160        match self {
161            PartiallyVerifiedOperation::Create(create) => Some(&create.recovery_commitment),
162            PartiallyVerifiedOperation::Update(_) => None,
163            PartiallyVerifiedOperation::Recover(recover) => {
164                Some(&recover.signed_recovery_commitment)
165            }
166            PartiallyVerifiedOperation::Deactivate(_) => None,
167        }
168    }
169
170    pub fn follows<S: Sidetree>(
171        &self,
172        previous: &PartiallyVerifiedOperation,
173    ) -> Result<(), FollowsError> {
174        match self {
175            PartiallyVerifiedOperation::Create(_) => Err(FollowsError::CreateCannotFollow),
176            PartiallyVerifiedOperation::Update(update) => {
177                let update_commitment = previous
178                    .update_commitment()
179                    .ok_or(FollowsError::MissingUpdateCommitment)?;
180                ensure_reveal_commitment::<S>(
181                    update_commitment,
182                    &update.reveal_value,
183                    &update.signed_update_key,
184                )
185            }
186            PartiallyVerifiedOperation::Recover(recover) => {
187                let recovery_commitment = previous
188                    .recovery_commitment()
189                    .ok_or(FollowsError::MissingRecoveryCommitment)?;
190                ensure_reveal_commitment::<S>(
191                    recovery_commitment,
192                    &recover.reveal_value,
193                    &recover.signed_recovery_key,
194                )
195            }
196            PartiallyVerifiedOperation::Deactivate(deactivate) => {
197                if let PartiallyVerifiedOperation::Create(create) = previous {
198                    return Err(FollowsError::DIDSuffixMismatch {
199                        expected: create.did_suffix.clone(),
200                        actual: deactivate.signed_did_suffix.clone(),
201                    });
202                } else {
203                    // Note: Recover operations do not sign over the DID suffix. If the deactivate
204                    // operation follows a recover operation rather than a create operation, the
205                    // DID Suffix must be verified by the caller.
206                }
207                let recovery_commitment = previous
208                    .recovery_commitment()
209                    .ok_or(FollowsError::MissingRecoveryCommitment)?;
210                ensure_reveal_commitment::<S>(
211                    recovery_commitment,
212                    &deactivate.reveal_value,
213                    &deactivate.signed_recovery_key,
214                )
215            }
216        }
217    }
218}
219
220fn ensure_reveal_commitment<S: Sidetree>(
221    recovery_commitment: &str,
222    reveal_value: &str,
223    pk: &PublicKeyJwk,
224) -> Result<(), FollowsError> {
225    let canonicalized_public_key = json_canonicalization_scheme(&pk).unwrap();
226    let commitment_value = canonicalized_public_key.as_bytes();
227    let computed_reveal_value = S::reveal_value(commitment_value);
228    if computed_reveal_value != reveal_value {
229        return Err(FollowsError::RevealValueMismatch);
230    }
231    let computed_commitment = S::commitment_scheme(pk);
232    if computed_commitment != recovery_commitment {
233        return Err(FollowsError::CommitmentMismatch);
234    }
235    Ok(())
236}
237
238#[derive(Debug, thiserror::Error)]
239pub enum FollowsError {
240    #[error("create cannot follow")]
241    CreateCannotFollow,
242
243    #[error("missing update commitment")]
244    MissingUpdateCommitment,
245
246    #[error("missing recovery commitment")]
247    MissingRecoveryCommitment,
248
249    #[error("DID suffix mismatch (expected {expected:?}, found {actual:?})")]
250    DIDSuffixMismatch {
251        expected: DIDSuffix,
252        actual: DIDSuffix,
253    },
254
255    #[error("reveal value mismatch")]
256    RevealValueMismatch,
257
258    #[error("commitment mismatch")]
259    CommitmentMismatch,
260}
261
262/// An error resulting from [jws_decode_verify_inner]
263#[derive(thiserror::Error, Debug)]
264pub enum JWSDecodeVerifyError {
265    /// Unable to split JWS
266    #[error("Unable to split JWS")]
267    SplitJWS(#[source] ssi_jws::Error),
268    /// Unable to decode JWS parts
269    #[error("Unable to decode JWS parts")]
270    DecodeJWSParts(#[source] ssi_jws::Error),
271    /// Deserialize JWS payload
272    #[error("Deserialize JWS payload")]
273    DeserializeJWSPayload(#[source] serde_json::Error),
274    /// Unable to convert PublicKeyJwk to JWK
275    #[error("Unable to convert PublicKeyJwk to JWK")]
276    JWKFromPublicKeyJwk(#[source] JWKFromPublicKeyJwkError),
277    /// Unable to verify JWS
278    #[error("Unable to verify JWS")]
279    VerifyJWS(#[source] ssi_jws::Error),
280}
281
282/// Decode and verify JWS with public key inside payload
283///
284/// Similar to [ssi_jwt::decode_verify] or [ssi_jws::decode_verify], but for when the payload (claims) must be parsed to
285/// determine the public key.
286///
287/// This function decodes and verifies a JWS/JWT, where the public key is expected to be found
288/// within the payload (claims). Before verification, the deserialized claims object is passed to
289/// the provided `get_key` function. The public key returned from the `get_key` function is then
290/// used to verify the signature. The verified claims and header object are returned on successful
291/// verification, along with the public key that they were verified against (as returned by the
292/// `get_key` function).
293///
294/// The `get_key` function uses [PublicKeyJwk], for the convenience of this crate, but this
295/// function converts it to [ssi_jwk::JWK] internally.
296pub fn jws_decode_verify_inner<Claims: DeserializeOwned>(
297    jwt: &str,
298    get_key: impl FnOnce(&Claims) -> &PublicKeyJwk,
299) -> Result<(ssi_jws::Header, Claims), JWSDecodeVerifyError> {
300    use ssi_jws::{decode_jws_parts, split_jws, verify_bytes, DecodedJws};
301    let (header_b64, payload_enc, signature_b64) =
302        split_jws(jwt).map_err(JWSDecodeVerifyError::SplitJWS)?;
303    let DecodedJws {
304        signing_bytes:
305            DecodedSigningBytes {
306                bytes: signing_bytes,
307                header,
308                payload,
309            },
310        signature,
311    } = decode_jws_parts(header_b64, payload_enc.as_bytes(), signature_b64)
312        .map_err(JWSDecodeVerifyError::DecodeJWSParts)?;
313    let claims: Claims =
314        serde_json::from_slice(&payload).map_err(JWSDecodeVerifyError::DeserializeJWSPayload)?;
315    let pk = get_key(&claims);
316    let pk = JWK::try_from(pk.clone()).map_err(JWSDecodeVerifyError::JWKFromPublicKeyJwk)?;
317    verify_bytes(header.algorithm, &signing_bytes, &pk, &signature)
318        .map_err(JWSDecodeVerifyError::VerifyJWS)?;
319    Ok((header, claims))
320}