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