identity_iota_core/rebased/assets/
public_available_vc.rs1use std::ops::Deref;
5
6use anyhow::Context as _;
7use identity_credential::credential::Credential;
8use identity_credential::credential::Jwt;
9use identity_credential::credential::JwtCredential;
10use identity_jose::jwt::JwtHeader;
11use identity_jose::jwu;
12use iota_interaction::types::base_types::ObjectID;
13use iota_interaction::IotaKeySignature;
14use iota_interaction::IotaVerifiableCredential;
15use iota_interaction::OptionalSync;
16use itertools::Itertools;
17use product_common::core_client::CoreClientReadOnly;
18use secret_storage::Signer;
19
20use crate::rebased::client::IdentityClient;
21use crate::rebased::client::IdentityClientReadOnly;
22
23use super::AuthenticatedAsset;
24use super::AuthenticatedAssetBuilder;
25
26#[derive(Debug, Clone)]
28pub struct PublicAvailableVC {
29 asset: AuthenticatedAsset<IotaVerifiableCredential>,
30 credential: Credential,
31}
32
33impl Deref for PublicAvailableVC {
34 type Target = Credential;
35 fn deref(&self) -> &Self::Target {
36 &self.credential
37 }
38}
39
40impl PublicAvailableVC {
41 pub fn object_id(&self) -> ObjectID {
43 self.asset.id()
44 }
45
46 pub fn jwt(&self) -> Jwt {
48 String::from_utf8(self.asset.content().data().clone())
49 .map(Jwt::new)
50 .expect("JWT is valid UTF8")
51 }
52
53 pub async fn new<S>(jwt: Jwt, gas_budget: Option<u64>, client: &IdentityClient<S>) -> Result<Self, anyhow::Error>
58 where
59 S: Signer<IotaKeySignature> + OptionalSync,
60 {
61 let jwt_bytes = String::from(jwt).into_bytes();
62 let credential = parse_jwt_credential(&jwt_bytes)?;
63 let tx_builder = AuthenticatedAssetBuilder::new(IotaVerifiableCredential::new(jwt_bytes))
64 .transferable(false)
65 .mutable(true)
66 .deletable(true)
67 .finish(client);
68
69 let tx_builder = if let Some(gas_budget) = gas_budget {
70 tx_builder.with_gas_budget(gas_budget)
71 } else {
72 tx_builder
73 };
74
75 let asset = tx_builder.build_and_execute(client).await?.output;
76
77 Ok(Self { credential, asset })
78 }
79
80 pub async fn get_by_id(id: ObjectID, client: &IdentityClientReadOnly) -> Result<Self, crate::rebased::Error> {
82 let asset = client
83 .get_object_by_id::<AuthenticatedAsset<IotaVerifiableCredential>>(id)
84 .await?;
85
86 Self::try_from_asset(asset).map_err(|e| {
87 crate::rebased::Error::ObjectLookup(format!(
88 "object at address {id} is not a valid publicly available VC: {e}"
89 ))
90 })
91 }
92
93 fn try_from_asset(asset: AuthenticatedAsset<IotaVerifiableCredential>) -> Result<Self, anyhow::Error> {
94 let credential = parse_jwt_credential(asset.content().data())?;
95 Ok(Self { asset, credential })
96 }
97}
98
99fn parse_jwt_credential(bytes: &[u8]) -> Result<Credential, anyhow::Error> {
100 let [header, payload, _signature]: [Vec<u8>; 3] = bytes
101 .split(|c| *c == b'.')
102 .map(jwu::decode_b64)
103 .try_collect::<_, Vec<_>, _>()?
104 .try_into()
105 .map_err(|_| anyhow::anyhow!("invalid JWT"))?;
106 let _header = serde_json::from_slice::<JwtHeader>(&header)?;
107 let credential_claims = serde_json::from_slice::<JwtCredential>(&payload)?;
108 credential_claims.try_into().context("invalid jwt credential claims")
109}