From 7a2f694011ac5bed2172a26338db99950f02fab4 Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Fri, 11 Feb 2022 17:04:51 -0500 Subject: [PATCH] Implement partially verified operations --- did-ion/src/sidetree.rs | 314 ++++++++++++++++++++++++++++------------ 1 file changed, 223 insertions(+), 91 deletions(-) diff --git a/did-ion/src/sidetree.rs b/did-ion/src/sidetree.rs index b50b43727..402e836b7 100644 --- a/did-ion/src/sidetree.rs +++ b/did-ion/src/sidetree.rs @@ -469,35 +469,195 @@ pub enum Operation { Deactivate(DeactivateOperation), } +#[derive(Debug, Clone)] +pub struct PartiallyVerifiedCreateOperation { + did_suffix: DIDSuffix, + r#type: Option, + recovery_commitment: String, + anchor_origin: Option, + hashed_delta: Delta, +} + +#[derive(Debug, Clone)] +pub struct PartiallyVerifiedUpdateOperation { + reveal_value: String, + signed_delta: Delta, + signed_update_key: PublicKeyJwk, +} + +#[derive(Debug, Clone)] +pub struct PartiallyVerifiedRecoverOperation { + reveal_value: String, + signed_delta: Delta, + signed_recovery_commitment: String, + signed_recovery_key: PublicKeyJwk, + signed_anchor_origin: Option, +} + +#[derive(Debug, Clone)] +pub struct PartiallyVerifiedDeactivateOperation { + signed_did_suffix: DIDSuffix, + reveal_value: String, + signed_recovery_key: PublicKeyJwk, +} + +#[derive(Debug, Clone)] +pub enum PartiallyVerifiedOperation { + Create(PartiallyVerifiedCreateOperation), + Update(PartiallyVerifiedUpdateOperation), + Recover(PartiallyVerifiedRecoverOperation), + Deactivate(PartiallyVerifiedDeactivateOperation), +} + trait SidetreeOperation { - type Claims; - fn partial_verify(&self) -> Result; + type PartiallyVerifiedForm; + fn partial_verify(self) -> Result; } impl SidetreeOperation for Operation { - type Claims = (); - fn partial_verify(&self) -> Result { - match self { - Operation::Create(_create_operation) => {} - Operation::Update(op) => { + type PartiallyVerifiedForm = PartiallyVerifiedOperation; + + fn partial_verify(self) -> Result { + Ok(match self { + Operation::Create(op) => PartiallyVerifiedOperation::Create( op.partial_verify::() - .context("Partial verify Update operation")?; - } - Operation::Recover(op) => { + .context("Partial verify Create operation")?, + ), + Operation::Update(op) => PartiallyVerifiedOperation::Update( op.partial_verify::() - .context("Partial verify Recover operation")?; - } - Operation::Deactivate(op) => { + .context("Partial verify Update operation")?, + ), + Operation::Recover(op) => PartiallyVerifiedOperation::Recover( op.partial_verify::() - .context("Partial verify Deactivate operation")?; + .context("Partial verify Recover operation")?, + ), + Operation::Deactivate(op) => PartiallyVerifiedOperation::Deactivate( + op.partial_verify::() + .context("Partial verify Deactivate operation")?, + ), + }) + } +} + +fn ensure_reveal_commitment( + recovery_commitment: &str, + reveal_value: &str, + pk: &PublicKeyJwk, +) -> Result<()> { + let canonicalized_public_key = + S::json_canonicalization_scheme(&pk).context("Canonicalize JWK")?; + let commitment_value = canonicalized_public_key.as_bytes(); + let computed_reveal_value = S::reveal_value(&commitment_value); + ensure!(&computed_reveal_value == reveal_value); + let computed_commitment = + S::commitment_scheme(&pk).context("Unable to compute public key commitment")?; + ensure!(&computed_commitment == recovery_commitment); + Ok(()) +} + +impl PartiallyVerifiedOperation { + pub fn update_commitment(&self) -> Option<&str> { + match self { + PartiallyVerifiedOperation::Create(create) => { + Some(&create.hashed_delta.update_commitment) + } + PartiallyVerifiedOperation::Update(update) => { + Some(&update.signed_delta.update_commitment) + } + PartiallyVerifiedOperation::Recover(recover) => { + Some(&recover.signed_delta.update_commitment) + } + PartiallyVerifiedOperation::Deactivate(_) => None, + } + } + + pub fn recovery_commitment(&self) -> Option<&str> { + match self { + PartiallyVerifiedOperation::Create(create) => Some(&create.recovery_commitment), + PartiallyVerifiedOperation::Update(_) => None, + PartiallyVerifiedOperation::Recover(recover) => { + Some(&recover.signed_recovery_commitment) + } + PartiallyVerifiedOperation::Deactivate(_) => None, + } + } + + pub fn follows(&self, previous: &PartiallyVerifiedOperation) -> Result<()> { + match self { + PartiallyVerifiedOperation::Create(_) => { + bail!("Create operation cannot follow another operation"); + } + PartiallyVerifiedOperation::Update(update) => { + let update_commitment = previous + .update_commitment() + .ok_or(anyhow!("No update commitment"))?; + ensure_reveal_commitment::( + &update_commitment, + &update.reveal_value, + &update.signed_update_key, + )?; + } + PartiallyVerifiedOperation::Recover(recover) => { + let recovery_commitment = previous + .recovery_commitment() + .ok_or(anyhow!("No recovery commitment"))?; + ensure_reveal_commitment::( + &recovery_commitment, + &recover.reveal_value, + &recover.signed_recovery_key, + )?; + } + PartiallyVerifiedOperation::Deactivate(deactivate) => { + if let PartiallyVerifiedOperation::Create(create) = previous { + ensure!( + deactivate.signed_did_suffix == create.did_suffix, + "DID Suffix mismatch" + ); + } else { + // Note: Recover operations do not sign over the DID suffix. If the deactivate + // operation follows a recover operation rather than a create operation, the + // DID Suffix must be verified by the caller. + } + let recovery_commitment = previous + .recovery_commitment() + .ok_or(anyhow!("No recovery commitment"))?; + ensure_reveal_commitment::( + &recovery_commitment, + &deactivate.reveal_value, + &deactivate.signed_recovery_key, + )?; } } Ok(()) } } +impl SidetreeOperation for CreateOperation { + type PartiallyVerifiedForm = PartiallyVerifiedCreateOperation; + + fn partial_verify(self) -> Result { + let did = SidetreeDID::::from_create_operation(&self) + .context("Unable to derive DID from create operation")?; + let did_suffix = DIDSuffix::from(did); + let delta_string = S::json_canonicalization_scheme(&self.delta) + .context("Unable to Canonicalize Update Operation Delta Object")?; + let delta_hash = S::hash(delta_string.as_bytes()); + ensure!( + delta_hash == self.suffix_data.delta_hash, + "Delta hash mismatch" + ); + Ok(PartiallyVerifiedCreateOperation { + did_suffix, + r#type: self.suffix_data.r#type, + recovery_commitment: self.suffix_data.recovery_commitment, + anchor_origin: self.suffix_data.anchor_origin, + hashed_delta: self.delta, + }) + } +} + impl SidetreeOperation for UpdateOperation { - type Claims = UpdateClaims; + type PartiallyVerifiedForm = PartiallyVerifiedUpdateOperation; /// Partially verify an [UpdateOperation] /// @@ -512,9 +672,8 @@ impl SidetreeOperation for UpdateOperation { /// The [DID Suffix](UpdateOperation::did_suffix), and the delta values, are **not** verified /// by this function. The correspondence of the reveal value's hash to the previous update /// commitment is not checked either, since that is not known from this function. - /// - /// On success, returns the [claims object](UpdateClaims) decoded from the operation's [signed data](UpdateOperation::signed_data). - fn partial_verify(&self) -> Result { + + fn partial_verify(self) -> Result { // Verify JWS against public key in payload. // Then check public key against its hash (reveal value). let (header, claims) = @@ -539,17 +698,20 @@ impl SidetreeOperation for UpdateOperation { .context("Canonicalize Update Operation Delta Object")?; let delta_hash = S::hash(delta_string.as_bytes()); ensure!(claims.delta_hash == delta_hash, "Delta hash mismatch"); - Ok(claims) + // Note: did_suffix is dropped, since it's not signed over. + Ok(PartiallyVerifiedUpdateOperation { + reveal_value: self.reveal_value, + signed_delta: self.delta, + signed_update_key: claims.update_key, + }) } } impl SidetreeOperation for RecoverOperation { - type Claims = RecoveryClaims; + type PartiallyVerifiedForm = PartiallyVerifiedRecoverOperation; /// Partially verify a [RecoverOperation] - /// - /// On success, returns the [claims object](RecoveryClaims) decoded from the operation's [signed data](RecoverOperation::signed_data). - fn partial_verify(&self) -> Result { + fn partial_verify(self) -> Result { // Verify JWS against public key in payload. // Then check public key against its hash (reveal value). let (header, claims) = @@ -574,17 +736,22 @@ impl SidetreeOperation for RecoverOperation { .context("Canonicalize Recover Operation Delta Object")?; let delta_hash = S::hash(delta_string.as_bytes()); ensure!(claims.delta_hash == delta_hash, "Delta hash mismatch"); - Ok(claims) + // Note: did_suffix is dropped, since it's not signed over. + Ok(PartiallyVerifiedRecoverOperation { + reveal_value: self.reveal_value, + signed_delta: self.delta, + signed_recovery_commitment: claims.recovery_commitment, + signed_recovery_key: claims.recovery_key, + signed_anchor_origin: claims.anchor_origin, + }) } } impl SidetreeOperation for DeactivateOperation { - type Claims = DeactivateClaims; + type PartiallyVerifiedForm = PartiallyVerifiedDeactivateOperation; /// Partially verify a [DeactivateOperation] - /// - /// On success, returns the [claims object](DeactivateClaims) decoded from the operation's [signed data](DeactivateOperation::signed_data). - fn partial_verify(&self) -> Result { + fn partial_verify(self) -> Result { // Verify JWS against public key in payload. // Then check public key against its hash (reveal value). @@ -607,7 +774,11 @@ impl SidetreeOperation for DeactivateOperation { self.reveal_value, ); ensure!(self.did_suffix == claims.did_suffix, "DID Suffix mismatch"); - Ok(claims) + Ok(PartiallyVerifiedDeactivateOperation { + signed_did_suffix: claims.did_suffix, + reveal_value: self.reveal_value, + signed_recovery_key: claims.recovery_key, + }) } } @@ -1714,81 +1885,42 @@ mod tests { assert_eq!(did.to_string(), LONGFORM_DID); } - fn ensure_reveal_commitment(recovery_commitment: &str, reveal_value: &str, pk: &PublicKeyJwk) { - let canonicalized_public_key = Example::json_canonicalization_scheme(&pk) - .context("Canonicalize JWK") - .unwrap(); - let commitment_value = canonicalized_public_key.as_bytes(); - let computed_reveal_value = Example::reveal_value(&commitment_value); - assert_eq!(&computed_reveal_value, reveal_value); - let computed_commitment = Example::commitment_scheme(&pk).unwrap(); - assert_eq!(&computed_commitment, recovery_commitment); - } - #[test] fn test_update_verify_reveal() { - let update_operation = match &*UPDATE_OPERATION { - Operation::Update(op) => op, - _ => panic!("Expected Update Operation"), - }; - let revealed_pk = update_operation + let create_pvo = CREATE_OPERATION + .clone() .partial_verify::() - .unwrap() - .update_key; - let create_operation = match &*CREATE_OPERATION { - Operation::Create(op) => op, - _ => panic!("Expected Create Operation"), - }; - ensure_reveal_commitment( - &create_operation.delta.update_commitment, - &update_operation.reveal_value, - &revealed_pk, - ); + .unwrap(); + let update_pvo = UPDATE_OPERATION + .clone() + .partial_verify::() + .unwrap(); + update_pvo.follows::(&create_pvo).unwrap(); } #[test] fn test_recover_verify_reveal() { - let recover_operation = match &*RECOVER_OPERATION { - Operation::Recover(op) => op, - _ => panic!("Expected Recover Operation"), - }; - let revealed_pk = recover_operation + let create_pvo = CREATE_OPERATION + .clone() .partial_verify::() - .unwrap() - .recovery_key; - let create_operation = match &*CREATE_OPERATION { - Operation::Create(op) => op, - _ => panic!("Expected Create Operation"), - }; - ensure_reveal_commitment( - &create_operation.suffix_data.recovery_commitment, - &recover_operation.reveal_value, - &revealed_pk, - ); + .unwrap(); + let recover_pvo = RECOVER_OPERATION + .clone() + .partial_verify::() + .unwrap(); + recover_pvo.follows::(&create_pvo).unwrap(); } #[test] fn test_deactivate_verify_reveal() { - let deactivate_operation = match &*DEACTIVATE_OPERATION { - Operation::Deactivate(op) => op, - _ => panic!("Expected Deactivate Operation"), - }; - let revealed_pk = deactivate_operation + let recover_pvo = RECOVER_OPERATION + .clone() .partial_verify::() - .unwrap() - .recovery_key; - let recover_operation = match &*RECOVER_OPERATION { - Operation::Recover(op) => op, - _ => panic!("Expected Recover Operation"), - }; - let recovery_commitment = recover_operation + .unwrap(); + let deactivate_pvo = DEACTIVATE_OPERATION + .clone() .partial_verify::() - .unwrap() - .recovery_commitment; - ensure_reveal_commitment( - &recovery_commitment, - &deactivate_operation.reveal_value, - &revealed_pk, - ); + .unwrap(); + deactivate_pvo.follows::(&recover_pvo).unwrap(); } }