use crate::{
cis4::{Cis4Contract, Cis4QueryError},
v2::{self, BlockIdentifier, IntoBlockIdentifier},
};
pub use concordium_base::web3id::*;
use concordium_base::{
base::CredentialRegistrationID,
cis4_types::CredentialStatus,
contracts_common::AccountAddress,
id::{constants::ArCurve, types::IpIdentity},
web3id,
};
use futures::TryStreamExt;
pub mod v1;
#[derive(thiserror::Error, Debug)]
pub enum CredentialLookupError {
#[error("Credential network not supported.")]
IncorrectNetwork,
#[error("Credential issuer not as stated: {stated} != {actual}.")]
InconsistentIssuer {
stated: IpIdentity,
actual: IpIdentity,
},
#[error("Unable to look up account: {0}")]
QueryError(#[from] v2::QueryError),
#[error("Unable to query CIS4 contract: {0}")]
Cis4QueryError(#[from] Cis4QueryError),
#[error("Credential {cred_id} no longer present or of unknown type on account: {account}")]
CredentialNotPresentOrUnknown {
cred_id: CredentialRegistrationID,
account: AccountAddress,
},
#[error("Initial credential {cred_id} cannot be used.")]
InitialCredential { cred_id: CredentialRegistrationID },
#[error("Unexpected response from the node: {0}")]
InvalidResponse(String),
#[error("Unknown stored credential for {cred_id}. Updating the rust-sdk to a version compatible with the node will resolve this issue.")]
UnknownCredential { cred_id: CredentialRegistrationID },
}
pub struct CredentialWithMetadata {
pub status: CredentialStatus,
pub inputs: CredentialsInputs<ArCurve>,
}
pub async fn verify_credential_metadata(
mut client: v2::Client,
network: web3id::did::Network,
metadata: &ProofMetadata,
bi: impl IntoBlockIdentifier,
) -> Result<CredentialWithMetadata, CredentialLookupError> {
if metadata.network != network {
return Err(CredentialLookupError::IncorrectNetwork);
}
let bi = bi.into_block_identifier();
match metadata.cred_metadata {
CredentialMetadata::Account { issuer, cred_id } => {
let ai = client
.get_account_info(&cred_id.into(), BlockIdentifier::LastFinal)
.await?;
let Some(cred) = ai.response.account_credentials.values().find(|cred| {
cred.value
.as_ref()
.is_known_and(|c| c.cred_id() == cred_id.as_ref())
}) else {
return Err(CredentialLookupError::CredentialNotPresentOrUnknown {
cred_id,
account: ai.response.account_address,
});
};
let c = cred
.value
.as_ref()
.known_or(CredentialLookupError::UnknownCredential { cred_id })?;
if c.issuer() != issuer {
return Err(CredentialLookupError::InconsistentIssuer {
stated: issuer,
actual: c.issuer(),
});
}
match &c {
concordium_base::id::types::AccountCredentialWithoutProofs::Initial { .. } => {
Err(CredentialLookupError::InitialCredential { cred_id })
}
concordium_base::id::types::AccountCredentialWithoutProofs::Normal {
cdv,
commitments,
} => {
let now = client.get_block_info(bi).await?.response.block_slot_time;
let valid_from = cdv.policy.created_at.lower().ok_or_else(|| {
CredentialLookupError::InvalidResponse(
"Credential creation date is not valid.".into(),
)
})?;
let valid_until = cdv.policy.valid_to.upper().ok_or_else(|| {
CredentialLookupError::InvalidResponse(
"Credential creation date is not valid.".into(),
)
})?;
let status = if valid_from > now {
CredentialStatus::NotActivated
} else if valid_until < now {
CredentialStatus::Expired
} else {
CredentialStatus::Active
};
let inputs = CredentialsInputs::Account {
commitments: commitments.cmm_attributes.clone(),
};
Ok(CredentialWithMetadata { status, inputs })
}
}
}
CredentialMetadata::Web3Id { contract, holder } => {
let mut contract_client = Cis4Contract::create(client, contract).await?;
let issuer_pk = contract_client.issuer(bi).await?;
let inputs = CredentialsInputs::Web3 { issuer_pk };
let status = contract_client.credential_status(holder, bi).await?;
Ok(CredentialWithMetadata { status, inputs })
}
}
}
pub async fn get_public_data(
client: &mut v2::Client,
network: web3id::did::Network,
presentation: &web3id::Presentation<ArCurve, web3id::Web3IdAttribute>,
bi: impl IntoBlockIdentifier,
) -> Result<Vec<CredentialWithMetadata>, CredentialLookupError> {
let block = bi.into_block_identifier();
let stream = presentation
.metadata()
.map(|meta| {
let mainnet_client = client.clone();
async move { verify_credential_metadata(mainnet_client, network, &meta, block).await }
})
.collect::<futures::stream::FuturesOrdered<_>>();
stream.try_collect().await
}