did_ion/sidetree/operation/
mod.rs1mod 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#[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#[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
96pub trait SidetreeOperation {
100 type PartiallyVerifiedForm;
102
103 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 }
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#[derive(thiserror::Error, Debug)]
264pub enum JWSDecodeVerifyError {
265 #[error("Unable to split JWS")]
267 SplitJWS(#[source] ssi_jws::Error),
268 #[error("Unable to decode JWS parts")]
270 DecodeJWSParts(#[source] ssi_jws::Error),
271 #[error("Deserialize JWS payload")]
273 DeserializeJWSPayload(#[source] serde_json::Error),
274 #[error("Unable to convert PublicKeyJwk to JWK")]
276 JWKFromPublicKeyJwk(#[source] JWKFromPublicKeyJwkError),
277 #[error("Unable to verify JWS")]
279 VerifyJWS(#[source] ssi_jws::Error),
280}
281
282pub 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}