concordium_rust_sdk/
web3id.rs1use crate::{
4 cis4::{Cis4Contract, Cis4QueryError},
5 v2::{self, BlockIdentifier, IntoBlockIdentifier},
6};
7pub use concordium_base::web3id::*;
8use concordium_base::{
9 base::CredentialRegistrationID,
10 cis4_types::CredentialStatus,
11 contracts_common::AccountAddress,
12 id::{constants::ArCurve, types::IpIdentity},
13 web3id,
14};
15use futures::TryStreamExt;
16
17#[derive(thiserror::Error, Debug)]
18pub enum CredentialLookupError {
19 #[error("Credential network not supported.")]
20 IncorrectNetwork,
21 #[error("Credential issuer not as stated: {stated} != {actual}.")]
22 InconsistentIssuer {
23 stated: IpIdentity,
24 actual: IpIdentity,
25 },
26 #[error("Unable to look up account: {0}")]
27 QueryError(#[from] v2::QueryError),
28 #[error("Unable to query CIS4 contract: {0}")]
29 Cis4QueryError(#[from] Cis4QueryError),
30 #[error("Credential {cred_id} no longer present or of unknown type on account: {account}")]
31 CredentialNotPresentOrUnknown {
32 cred_id: CredentialRegistrationID,
33 account: AccountAddress,
34 },
35 #[error("Initial credential {cred_id} cannot be used.")]
36 InitialCredential { cred_id: CredentialRegistrationID },
37 #[error("Unexpected response from the node: {0}")]
38 InvalidResponse(String),
39 #[error("Unknown stored credential for {cred_id}. Updating the rust-sdk to a version compatible with the node will resolve this issue.")]
40 UnknownCredential { cred_id: CredentialRegistrationID },
41}
42
43pub struct CredentialWithMetadata {
46 pub status: CredentialStatus,
48 pub inputs: CredentialsInputs<ArCurve>,
50}
51
52pub async fn verify_credential_metadata(
71 mut client: v2::Client,
72 network: web3id::did::Network,
73 metadata: &ProofMetadata,
74 bi: impl IntoBlockIdentifier,
75) -> Result<CredentialWithMetadata, CredentialLookupError> {
76 if metadata.network != network {
77 return Err(CredentialLookupError::IncorrectNetwork);
78 }
79 let bi = bi.into_block_identifier();
80 match metadata.cred_metadata {
81 CredentialMetadata::Account { issuer, cred_id } => {
82 let ai = client
83 .get_account_info(&cred_id.into(), BlockIdentifier::LastFinal)
84 .await?;
85 let Some(cred) = ai.response.account_credentials.values().find(|cred| {
86 cred.value
87 .as_ref()
88 .is_known_and(|c| c.cred_id() == cred_id.as_ref())
89 }) else {
90 return Err(CredentialLookupError::CredentialNotPresentOrUnknown {
91 cred_id,
92 account: ai.response.account_address,
93 });
94 };
95 let c = cred
96 .value
97 .as_ref()
98 .known_or(CredentialLookupError::UnknownCredential { cred_id })?;
99 if c.issuer() != issuer {
100 return Err(CredentialLookupError::InconsistentIssuer {
101 stated: issuer,
102 actual: c.issuer(),
103 });
104 }
105 match &c {
106 concordium_base::id::types::AccountCredentialWithoutProofs::Initial { .. } => {
107 Err(CredentialLookupError::InitialCredential { cred_id })
108 }
109 concordium_base::id::types::AccountCredentialWithoutProofs::Normal {
110 cdv,
111 commitments,
112 } => {
113 let now = client.get_block_info(bi).await?.response.block_slot_time;
114 let valid_from = cdv.policy.created_at.lower().ok_or_else(|| {
115 CredentialLookupError::InvalidResponse(
116 "Credential creation date is not valid.".into(),
117 )
118 })?;
119 let valid_until = cdv.policy.valid_to.upper().ok_or_else(|| {
120 CredentialLookupError::InvalidResponse(
121 "Credential creation date is not valid.".into(),
122 )
123 })?;
124 let status = if valid_from > now {
125 CredentialStatus::NotActivated
126 } else if valid_until < now {
127 CredentialStatus::Expired
128 } else {
129 CredentialStatus::Active
130 };
131 let inputs = CredentialsInputs::Account {
132 commitments: commitments.cmm_attributes.clone(),
133 };
134
135 Ok(CredentialWithMetadata { status, inputs })
136 }
137 }
138 }
139 CredentialMetadata::Web3Id { contract, holder } => {
140 let mut contract_client = Cis4Contract::create(client, contract).await?;
141 let issuer_pk = contract_client.issuer(bi).await?;
142
143 let inputs = CredentialsInputs::Web3 { issuer_pk };
144
145 let status = contract_client.credential_status(holder, bi).await?;
146
147 Ok(CredentialWithMetadata { status, inputs })
148 }
149 }
150}
151
152pub async fn get_public_data(
161 client: &mut v2::Client,
162 network: web3id::did::Network,
163 presentation: &web3id::Presentation<ArCurve, web3id::Web3IdAttribute>,
164 bi: impl IntoBlockIdentifier,
165) -> Result<Vec<CredentialWithMetadata>, CredentialLookupError> {
166 let block = bi.into_block_identifier();
167 let stream = presentation
168 .metadata()
169 .map(|meta| {
170 let mainnet_client = client.clone();
171 async move { verify_credential_metadata(mainnet_client, network, &meta, block).await }
172 })
173 .collect::<futures::stream::FuturesOrdered<_>>();
174 stream.try_collect().await
175}