identity_iota_core/rebased/client/
full_client.rs1use std::ops::Deref;
5
6use crate::iota_interaction_adapter::IotaClientAdapter;
7use crate::rebased::client::QueryControlledDidsError;
8use crate::rebased::iota::move_calls;
9use crate::rebased::iota::package::identity_package_id;
10use crate::rebased::migration::CreateIdentity;
11use crate::IotaDID;
12use crate::IotaDocument;
13use crate::StateMetadataDocument;
14use crate::StateMetadataEncoding;
15use async_trait::async_trait;
16use identity_verification::jwk::Jwk;
17use iota_interaction::move_types::language_storage::StructTag;
18use iota_interaction::rpc_types::IotaObjectData;
19use iota_interaction::rpc_types::IotaObjectDataFilter;
20use iota_interaction::rpc_types::IotaObjectResponseQuery;
21use iota_interaction::rpc_types::IotaTransactionBlockEffects;
22use iota_interaction::types::base_types::IotaAddress;
23use iota_interaction::types::base_types::ObjectRef;
24use iota_interaction::types::crypto::PublicKey;
25use iota_interaction::types::transaction::ProgrammableTransaction;
26use product_common::core_client::CoreClient;
27use product_common::core_client::CoreClientReadOnly;
28use product_common::network_name::NetworkName;
29use product_common::transaction::transaction_builder::Transaction;
30use product_common::transaction::transaction_builder::TransactionBuilder;
31use secret_storage::Signer;
32use serde::de::DeserializeOwned;
33use tokio::sync::OnceCell;
34
35use super::get_object_id_from_did;
36use crate::rebased::assets::AuthenticatedAssetBuilder;
37use crate::rebased::migration::Identity;
38use crate::rebased::migration::IdentityBuilder;
39use crate::rebased::Error;
40use iota_interaction::types::base_types::ObjectID;
41use iota_interaction::IotaClientTrait;
42use iota_interaction::IotaKeySignature;
43use iota_interaction::MoveType;
44use iota_interaction::OptionalSync;
45
46use super::IdentityClientReadOnly;
47
48#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
50pub struct KeyId(String);
51
52impl KeyId {
53 pub fn new(id: impl Into<String>) -> Self {
55 Self(id.into())
56 }
57
58 pub fn as_str(&self) -> &str {
60 &self.0
61 }
62}
63
64impl std::fmt::Display for KeyId {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 f.write_str(&self.0)
67 }
68}
69
70impl From<KeyId> for String {
71 fn from(value: KeyId) -> Self {
72 value.0
73 }
74}
75
76#[derive(Clone)]
78pub struct IdentityClient<S> {
79 read_client: IdentityClientReadOnly,
81 public_key: PublicKey,
83 signer: S,
85}
86
87impl<S> Deref for IdentityClient<S> {
88 type Target = IdentityClientReadOnly;
89 fn deref(&self) -> &Self::Target {
90 &self.read_client
91 }
92}
93
94impl<S> IdentityClient<S>
95where
96 S: Signer<IotaKeySignature>,
97{
98 pub async fn new(client: IdentityClientReadOnly, signer: S) -> Result<Self, Error> {
100 let public_key = signer
101 .public_key()
102 .await
103 .map_err(|e| Error::InvalidKey(e.to_string()))?;
104
105 Ok(Self {
106 public_key,
107 read_client: client,
108 signer,
109 })
110 }
111}
112
113impl<S> IdentityClient<S> {
114 pub fn create_identity(&self, iota_document: IotaDocument) -> IdentityBuilder {
116 IdentityBuilder::new(iota_document)
117 }
118
119 pub fn create_authenticated_asset<T>(&self, content: T) -> AuthenticatedAssetBuilder<T>
121 where
122 T: MoveType + DeserializeOwned + Send + Sync + PartialEq,
123 {
124 AuthenticatedAssetBuilder::new(content)
125 }
126
127 #[inline(always)]
129 pub fn address(&self) -> IotaAddress {
130 IotaAddress::from(&self.public_key)
131 }
132
133 pub async fn controlled_dids(&self) -> Result<Vec<IotaDID>, QueryControlledDidsError> {
135 self.dids_controlled_by(self.address()).await
136 }
137}
138
139impl<S> IdentityClient<S>
140where
141 S: Signer<IotaKeySignature> + OptionalSync,
142{
143 pub fn publish_did_document(&self, document: IotaDocument) -> TransactionBuilder<PublishDidDocument> {
145 TransactionBuilder::new(PublishDidDocument::new(document, self.sender_address()))
146 }
147
148 pub async fn publish_did_document_update(
151 &self,
152 document: IotaDocument,
153 gas_budget: u64,
154 ) -> Result<IotaDocument, Error> {
155 let mut oci =
156 if let Identity::FullFledged(value) = self.get_identity(get_object_id_from_did(document.id())?).await? {
157 value
158 } else {
159 return Err(Error::Identity("only new identities can be updated".to_string()));
160 };
161
162 let controller_token = oci.get_controller_token(self).await?.ok_or_else(|| {
163 Error::Identity(format!(
164 "address {} has no control over Identity {}",
165 self.sender_address(),
166 oci.id()
167 ))
168 })?;
169
170 oci
171 .update_did_document(document.clone(), &controller_token)
172 .finish(self)
173 .await?
174 .with_gas_budget(gas_budget)
175 .build_and_execute(self)
176 .await
177 .map_err(|e| Error::TransactionUnexpectedResponse(e.to_string()))?;
178
179 Ok(document)
180 }
181
182 pub async fn deactivate_did_output(&self, did: &IotaDID, gas_budget: u64) -> Result<(), Error> {
184 let mut oci = if let Identity::FullFledged(value) = self.get_identity(get_object_id_from_did(did)?).await? {
185 value
186 } else {
187 return Err(Error::Identity("only new identities can be deactivated".to_string()));
188 };
189
190 let controller_token = oci.get_controller_token(self).await?.ok_or_else(|| {
191 Error::Identity(format!(
192 "address {} has no control over Identity {}",
193 self.sender_address(),
194 oci.id()
195 ))
196 })?;
197
198 oci
199 .deactivate_did(&controller_token)
200 .finish(self)
201 .await?
202 .with_gas_budget(gas_budget)
203 .build_and_execute(self)
204 .await
205 .map_err(|e| Error::TransactionUnexpectedResponse(e.to_string()))?;
206
207 Ok(())
208 }
209
210 pub async fn find_owned_ref<P>(&self, tag: StructTag, predicate: P) -> Result<Option<ObjectRef>, Error>
213 where
214 P: Fn(&IotaObjectData) -> bool,
215 {
216 let filter = IotaObjectResponseQuery::new_with_filter(IotaObjectDataFilter::StructType(tag));
217
218 let mut cursor = None;
219 loop {
220 let mut page = self
221 .read_api()
222 .get_owned_objects(self.sender_address(), Some(filter.clone()), cursor, None)
223 .await?;
224 let obj_ref = std::mem::take(&mut page.data)
225 .into_iter()
226 .filter_map(|res| res.data)
227 .find(|obj| predicate(obj))
228 .map(|obj_data| obj_data.object_ref());
229 cursor = page.next_cursor;
230
231 if obj_ref.is_some() {
232 return Ok(obj_ref);
233 }
234 if !page.has_next_page {
235 break;
236 }
237 }
238
239 Ok(None)
240 }
241}
242
243#[cfg_attr(feature = "send-sync", async_trait)]
244#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
245impl<S> CoreClientReadOnly for IdentityClient<S>
246where
247 S: OptionalSync,
248{
249 fn client_adapter(&self) -> &IotaClientAdapter {
250 &self.read_client
251 }
252
253 fn package_id(&self) -> ObjectID {
254 self.read_client.package_id()
255 }
256
257 fn package_history(&self) -> Vec<ObjectID> {
258 self.read_client.package_history()
259 }
260
261 fn network_name(&self) -> &NetworkName {
262 self.read_client.network()
263 }
264}
265
266impl<S> CoreClient<S> for IdentityClient<S>
267where
268 S: Signer<IotaKeySignature> + OptionalSync,
269{
270 fn sender_address(&self) -> IotaAddress {
271 IotaAddress::from(&self.public_key)
272 }
273
274 fn signer(&self) -> &S {
275 &self.signer
276 }
277
278 fn sender_public_key(&self) -> &PublicKey {
279 &self.public_key
280 }
281}
282
283pub fn get_sender_public_key(sender_public_jwk: &Jwk) -> Result<Vec<u8>, Error> {
285 let public_key_base_64 = &sender_public_jwk
286 .try_okp_params()
287 .map_err(|err| Error::InvalidKey(format!("key not of type `Okp`; {err}")))?
288 .x;
289
290 identity_jose::jwu::decode_b64(public_key_base_64)
291 .map_err(|err| Error::InvalidKey(format!("could not decode base64 public key; {err}")))
292}
293
294#[derive(Debug, Clone)]
297pub struct PublishDidDocument {
298 did_document: IotaDocument,
299 controller: IotaAddress,
300 cached_ptb: OnceCell<ProgrammableTransaction>,
301}
302
303impl PublishDidDocument {
304 pub fn new(did_document: IotaDocument, controller: IotaAddress) -> Self {
306 Self {
307 did_document,
308 controller,
309 cached_ptb: OnceCell::new(),
310 }
311 }
312
313 async fn make_ptb(&self, client: &impl CoreClientReadOnly) -> Result<ProgrammableTransaction, Error> {
314 let package = identity_package_id(client).await?;
315 let did_doc = StateMetadataDocument::from(self.did_document.clone())
316 .pack(StateMetadataEncoding::Json)
317 .map_err(|e| Error::TransactionBuildingFailed(e.to_string()))?;
318
319 let programmable_tx_bcs =
320 move_calls::identity::new_with_controllers(Some(&did_doc), [(self.controller, 1, false)], 1, package).await?;
321 Ok(bcs::from_bytes(&programmable_tx_bcs)?)
322 }
323}
324
325#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
326#[cfg_attr(feature = "send-sync", async_trait)]
327impl Transaction for PublishDidDocument {
328 type Output = IotaDocument;
329 type Error = Error;
330
331 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
332 where
333 C: CoreClientReadOnly + OptionalSync,
334 {
335 self.cached_ptb.get_or_try_init(|| self.make_ptb(client)).await.cloned()
336 }
337
338 async fn apply<C>(self, effects: &mut IotaTransactionBlockEffects, client: &C) -> Result<Self::Output, Self::Error>
339 where
340 C: CoreClientReadOnly + OptionalSync,
341 {
342 let tx = {
343 let builder = IdentityBuilder::new(self.did_document)
344 .threshold(1)
345 .controller(self.controller, 1);
346 CreateIdentity::new(builder)
347 };
348
349 tx.apply(effects, client).await.map(IotaDocument::from)
350 }
351}