Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add revocation and unrevocation
  • Loading branch information
ntn-x2 committed Aug 2, 2022
commit 974a16e36adfdb0adc3b7d925d56d0c3d9e1a6f5
2 changes: 2 additions & 0 deletions pallets/public-credentials/src/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ pub struct Credential<CtypeHash, SubjectIdentifier, Claims> {
pub struct CredentialEntry<Attester, BlockNumber, AccountId, Balance,> {
/// The attester of the credential.
pub attester: Attester,
/// A flag indicating the revocation status of the credential
pub revoked: bool,
/// The block number in which the credential tx was evaluated and included
/// in the block.
pub block_number: BlockNumber,
Expand Down
97 changes: 78 additions & 19 deletions pallets/public-credentials/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,7 @@ pub mod pallet {
use sp_std::vec::Vec;

use ctype::CtypeHashOf;
use kilt_support::{
traits::CallSources,
};
use kilt_support::traits::CallSources;

/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
Expand Down Expand Up @@ -99,11 +97,7 @@ pub mod pallet {
pub(crate) type CredentialIdOf<T> = <<T as Config>::CredentialHash as sp_runtime::traits::Hash>::Output;

/// The type of a public credential as the pallet expects it.
pub type InputCredentialOf<T> = Credential<
CtypeHashOf<T>,
InputSubjectIdOf<T>,
InputClaimsContentOf<T>,
>;
pub type InputCredentialOf<T> = Credential<CtypeHashOf<T>, InputSubjectIdOf<T>, InputClaimsContentOf<T>>;

#[pallet::config]
pub trait Config: frame_system::Config + ctype::Config {
Expand Down Expand Up @@ -188,6 +182,16 @@ pub mod pallet {
/// The id of the removed credential.
credential_id: CredentialIdOf<T>,
},
/// A public credentials has been revoked.
CredentialRevoked {
/// The id of the revoked credential.
credential_id: CredentialIdOf<T>,
},
/// A public credentials has been unrevoked.
CredentialUnrevoked {
/// The id of the unrevoked credential.
credential_id: CredentialIdOf<T>,
},
}

#[pallet::error]
Expand Down Expand Up @@ -234,7 +238,8 @@ pub mod pallet {
} = credential.clone();

// Credential ID = H(<encoded_credential_input> || <attester_identifier>)
let credential_id = T::CredentialHash::hash(&[&credential.encode()[..], &attester.encode()[..]].concat()[..]);
let credential_id =
T::CredentialHash::hash(&[&credential.encode()[..], &attester.encode()[..]].concat()[..]);

// Try to decode subject ID to something structured
let subject = T::SubjectId::try_from(subject.into_inner()).map_err(|_| Error::<T>::InvalidInput)?;
Expand All @@ -256,12 +261,14 @@ pub mod pallet {
Credentials::<T>::insert(
&subject,
&credential_id,
CredentialEntryOf::<T> { attester, deposit, block_number },
);
CredentialSubjects::<T>::insert(
&credential_id,
subject.clone()
CredentialEntryOf::<T> {
revoked: false,
attester,
deposit,
block_number,
},
);
CredentialSubjects::<T>::insert(&credential_id, subject.clone());

Self::deposit_event(Event::CredentialStored {
subject_id: subject,
Expand All @@ -287,10 +294,7 @@ pub mod pallet {
///
/// Emits `CredentialRemoved`.
#[pallet::weight(<T as pallet::Config>::WeightInfo::remove())]
pub fn remove(
origin: OriginFor<T>,
credential_id: CredentialIdOf<T>,
) -> DispatchResult {
pub fn remove(origin: OriginFor<T>, credential_id: CredentialIdOf<T>) -> DispatchResult {
let source = <T as Config>::EnsureOrigin::ensure_origin(origin)?;
let attester = source.subject();

Expand All @@ -301,6 +305,62 @@ pub mod pallet {
Ok(())
}

/// Revokes a public credential.
///
/// If a credential was already revoked, this function does not fail but simply results
/// in a noop.
///
/// Emits `CredentialRevoked`.
#[pallet::weight(0)]
pub fn revoke(origin: OriginFor<T>, credential_id: CredentialIdOf<T>) -> DispatchResult {
let source = <T as Config>::EnsureOrigin::ensure_origin(origin)?;
let attester = source.subject();

let credential_subject =
CredentialSubjects::<T>::get(&credential_id).ok_or(Error::<T>::CredentialNotFound)?;

Credentials::<T>::try_mutate(&credential_subject, &credential_id, |credential_entry| {
if let Some(credential) = credential_entry {
credential.revoked = true;
Ok(())
} else {
Err(DispatchError::from(Error::<T>::CredentialNotFound))
}
})?;

Self::deposit_event(Event::CredentialRevoked { credential_id });

Ok(())
}

/// Unrevokes a public credential.
///
/// If a credential was not revoked, this function does not fail but simply results
/// in a noop.
///
/// Emits `CredentialUnrevoked`.
#[pallet::weight(0)]
pub fn unrevoke(origin: OriginFor<T>, credential_id: CredentialIdOf<T>) -> DispatchResult {
let source = <T as Config>::EnsureOrigin::ensure_origin(origin)?;
let attester = source.subject();

let credential_subject =
CredentialSubjects::<T>::get(&credential_id).ok_or(Error::<T>::CredentialNotFound)?;

Credentials::<T>::try_mutate(&credential_subject, &credential_id, |credential_entry| {
if let Some(credential) = credential_entry {
credential.revoked = false;
Ok(())
} else {
Err(DispatchError::from(Error::<T>::CredentialNotFound))
}
})?;

Self::deposit_event(Event::CredentialUnrevoked { credential_id });

Ok(())
}

/// Performs the same function as the `remove` extrinsic, with the
/// difference that the caller of this function must be the original
/// payer of the deposit, and not the original attester of the
Expand Down Expand Up @@ -336,7 +396,6 @@ pub mod pallet {
.ok_or(Error::<T>::InternalError)
}


// Simple wrapper to remove entries from both storages when deleting a
// credential.
fn remove_credential_entry(
Expand Down
20 changes: 13 additions & 7 deletions pallets/public-credentials/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub fn generate_base_public_credential_creation_op<T: Config>(

pub fn generate_credential_id<T: Config>(
input_credential: &InputCredentialOf<T>,
attester: &AttesterOf<T>
attester: &AttesterOf<T>,
) -> CredentialIdOf<T> {
T::CredentialHash::hash(&[&input_credential.encode()[..], &attester.encode()[..]].concat()[..])
}
Expand Down Expand Up @@ -67,14 +67,11 @@ pub(crate) mod runtime {
use kilt_support::{
deposit::Deposit,
mock::{mock_origin, SubjectId},
signature::EqualVerify,
};

use ctype::{CtypeCreatorOf, CtypeHashOf};

use crate::{
BalanceOf, CredentialEntryOf, Credentials, CredentialSubjects, CurrencyOf, Error, InputSubjectIdOf,
};
use crate::{BalanceOf, CredentialEntryOf, CredentialSubjects, Credentials, CurrencyOf, Error, InputSubjectIdOf};

pub(crate) type BlockNumber = u64;
pub(crate) type Balance = u128;
Expand Down Expand Up @@ -143,6 +140,7 @@ pub(crate) mod runtime {
attester: T::AttesterId,
) -> CredentialEntryOf<T> {
CredentialEntryOf::<T> {
revoked: false,
attester,
block_number,
deposit: Deposit::<T::AccountId, BalanceOf<T>> {
Expand Down Expand Up @@ -276,7 +274,11 @@ pub(crate) mod runtime {
ctypes: Vec<(CtypeHashOf<Test>, CtypeCreatorOf<Test>)>,
/// endowed accounts with balances
balances: Vec<(AccountId, Balance)>,
public_credentials: Vec<(<Test as Config>::SubjectId, CredentialIdOf<Test>, CredentialEntryOf<Test>)>,
public_credentials: Vec<(
<Test as Config>::SubjectId,
CredentialIdOf<Test>,
CredentialEntryOf<Test>,
)>,
}

impl ExtBuilder {
Expand All @@ -295,7 +297,11 @@ pub(crate) mod runtime {
#[must_use]
pub fn with_public_credentials(
mut self,
credentials: Vec<(<Test as Config>::SubjectId, CredentialIdOf<Test>, CredentialEntryOf<Test>)>,
credentials: Vec<(
<Test as Config>::SubjectId,
CredentialIdOf<Test>,
CredentialEntryOf<Test>,
)>,
) -> Self {
self.public_credentials = credentials;
self
Expand Down
87 changes: 72 additions & 15 deletions pallets/public-credentials/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ fn add_successful() {
.expect("Public credential details should be present on chain.");

// Test this pallet logic
assert_eq!(stored_public_credential_details.attester, attester);
assert!(!stored_public_credential_details.revoked);
assert_eq!(stored_public_credential_details.block_number, 0);
assert_eq!(CredentialSubjects::<Test>::get(&credential_id_1), Some(subject_id));

Expand Down Expand Up @@ -101,6 +103,8 @@ fn add_successful() {
.expect("Public credential #2 details should be present on chain.");

// Test this pallet logic
assert_eq!(stored_public_credential_details.attester, attester);
assert!(!stored_public_credential_details.revoked);
assert_eq!(stored_public_credential_details.block_number, 1);
assert_eq!(CredentialSubjects::<Test>::get(&credential_id_2), Some(subject_id));

Expand Down Expand Up @@ -140,29 +144,82 @@ fn add_not_enough_balance() {
});
}

// revoke

#[test]
fn add_ctype_not_found() {
fn revoke_successful() {
let attester = sr25519_did_from_seed(&ALICE_SEED);
let subject_id = SUBJECT_ID_00;
let ctype_hash = get_ctype_hash::<Test>(true);
let new_credential = generate_base_public_credential_creation_op::<Test>(
subject_id.into(),
ctype_hash,
InputClaimsContentOf::<Test>::default(),
);
let subject_id: <Test as Config>::SubjectId = SUBJECT_ID_00;
let new_credential = generate_base_credential_entry::<Test>(ACCOUNT_00, 0, attester.clone());
let credential_id: CredentialIdOf<Test> = CredentialIdOf::<Test>::default();
let deposit: Balance = <Test as Config>::Deposit::get();

ExtBuilder::default()
.with_balances(vec![(ACCOUNT_00, deposit)])
.with_public_credentials(vec![(subject_id, credential_id, new_credential)])
.build()
.execute_with(|| {
assert_noop!(
PublicCredentials::add(
DoubleOrigin(ACCOUNT_00, attester.clone()).into(),
new_credential
),
ctype::Error::<Test>::CTypeNotFound
);
assert_ok!(PublicCredentials::revoke(
DoubleOrigin(ACCOUNT_00, attester.clone()).into(),
credential_id,
));

let stored_public_credential_details = Credentials::<Test>::get(&subject_id, &credential_id)
.expect("Public credential details should be present on chain.");

// Test this pallet logic
assert!(stored_public_credential_details.revoked);

// Revoking the same credential does nothing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: Maybe we actually want to throw when double revoking?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I thought about that, but I really do not see any issues in revoking something that is already revoked. It won't basically change anything, so I thought it would be fine to simply proceed. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah its fine. I just wanted to raise awareness such that we are all on the same page. It really does not matter much and makes it easier to implement in the SDK.

assert_ok!(PublicCredentials::revoke(
DoubleOrigin(ACCOUNT_00, attester.clone()).into(),
credential_id,
));

let stored_public_credential_details_2 = Credentials::<Test>::get(&subject_id, &credential_id)
.expect("Public credential details should be present on chain.");

assert_eq!(stored_public_credential_details, stored_public_credential_details_2);
});
}

// revoke

#[test]
fn unrevoke_successful() {
let attester = sr25519_did_from_seed(&ALICE_SEED);
let subject_id: <Test as Config>::SubjectId = SUBJECT_ID_00;
let mut new_credential = generate_base_credential_entry::<Test>(ACCOUNT_00, 0, attester.clone());
new_credential.revoked = true;
let credential_id: CredentialIdOf<Test> = CredentialIdOf::<Test>::default();
let deposit: Balance = <Test as Config>::Deposit::get();

ExtBuilder::default()
.with_balances(vec![(ACCOUNT_00, deposit)])
.with_public_credentials(vec![(subject_id, credential_id, new_credential)])
.build()
.execute_with(|| {
assert_ok!(PublicCredentials::unrevoke(
DoubleOrigin(ACCOUNT_00, attester.clone()).into(),
credential_id,
));

let stored_public_credential_details = Credentials::<Test>::get(&subject_id, &credential_id)
.expect("Public credential details should be present on chain.");

// Test this pallet logic
assert!(!stored_public_credential_details.revoked);

// Unrevoking the same credential does nothing
assert_ok!(PublicCredentials::unrevoke(
DoubleOrigin(ACCOUNT_00, attester.clone()).into(),
credential_id,
));

let stored_public_credential_details_2 = Credentials::<Test>::get(&subject_id, &credential_id)
.expect("Public credential details should be present on chain.");

assert_eq!(stored_public_credential_details, stored_public_credential_details_2);
});
}

Expand Down