Skip to content

Commit

Permalink
Implement partially verified operations
Browse files Browse the repository at this point in the history
  • Loading branch information
clehner committed Feb 11, 2022
1 parent 1cd9f2c commit 7a2f694
Showing 1 changed file with 223 additions and 91 deletions.
314 changes: 223 additions & 91 deletions did-ion/src/sidetree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,35 +469,195 @@ pub enum Operation {
Deactivate(DeactivateOperation),
}

#[derive(Debug, Clone)]
pub struct PartiallyVerifiedCreateOperation {
did_suffix: DIDSuffix,
r#type: Option<String>,
recovery_commitment: String,
anchor_origin: Option<String>,
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<String>,
}

#[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<S: Sidetree>(&self) -> Result<Self::Claims>;
type PartiallyVerifiedForm;
fn partial_verify<S: Sidetree>(self) -> Result<Self::PartiallyVerifiedForm>;
}

impl SidetreeOperation for Operation {
type Claims = ();
fn partial_verify<S: Sidetree>(&self) -> Result<Self::Claims> {
match self {
Operation::Create(_create_operation) => {}
Operation::Update(op) => {
type PartiallyVerifiedForm = PartiallyVerifiedOperation;

fn partial_verify<S: Sidetree>(self) -> Result<Self::PartiallyVerifiedForm> {
Ok(match self {
Operation::Create(op) => PartiallyVerifiedOperation::Create(
op.partial_verify::<S>()
.context("Partial verify Update operation")?;
}
Operation::Recover(op) => {
.context("Partial verify Create operation")?,
),
Operation::Update(op) => PartiallyVerifiedOperation::Update(
op.partial_verify::<S>()
.context("Partial verify Recover operation")?;
}
Operation::Deactivate(op) => {
.context("Partial verify Update operation")?,
),
Operation::Recover(op) => PartiallyVerifiedOperation::Recover(
op.partial_verify::<S>()
.context("Partial verify Deactivate operation")?;
.context("Partial verify Recover operation")?,
),
Operation::Deactivate(op) => PartiallyVerifiedOperation::Deactivate(
op.partial_verify::<S>()
.context("Partial verify Deactivate operation")?,
),
})
}
}

fn ensure_reveal_commitment<S: Sidetree>(
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<S: Sidetree>(&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::<S>(
&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::<S>(
&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::<S>(
&recovery_commitment,
&deactivate.reveal_value,
&deactivate.signed_recovery_key,
)?;
}
}
Ok(())
}
}

impl SidetreeOperation for CreateOperation {
type PartiallyVerifiedForm = PartiallyVerifiedCreateOperation;

fn partial_verify<S: Sidetree>(self) -> Result<PartiallyVerifiedCreateOperation> {
let did = SidetreeDID::<S>::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]
///
Expand All @@ -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<S: Sidetree>(&self) -> Result<UpdateClaims> {
fn partial_verify<S: Sidetree>(self) -> Result<PartiallyVerifiedUpdateOperation> {
// Verify JWS against public key in payload.
// Then check public key against its hash (reveal value).
let (header, claims) =
Expand All @@ -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<S: Sidetree>(&self) -> Result<RecoveryClaims> {
fn partial_verify<S: Sidetree>(self) -> Result<PartiallyVerifiedRecoverOperation> {
// Verify JWS against public key in payload.
// Then check public key against its hash (reveal value).
let (header, claims) =
Expand All @@ -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<S: Sidetree>(&self) -> Result<DeactivateClaims> {
fn partial_verify<S: Sidetree>(self) -> Result<PartiallyVerifiedDeactivateOperation> {
// Verify JWS against public key in payload.
// Then check public key against its hash (reveal value).

Expand All @@ -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,
})
}
}

Expand Down Expand Up @@ -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::<Example>()
.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::<Example>()
.unwrap();
update_pvo.follows::<Example>(&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::<Example>()
.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::<Example>()
.unwrap();
recover_pvo.follows::<Example>(&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::<Example>()
.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::<Example>()
.unwrap()
.recovery_commitment;
ensure_reveal_commitment(
&recovery_commitment,
&deactivate_operation.reveal_value,
&revealed_pk,
);
.unwrap();
deactivate_pvo.follows::<Example>(&recover_pvo).unwrap();
}
}

0 comments on commit 7a2f694

Please sign in to comment.