#[cfg(feature = "data-integrity")]
use affinidi_data_integrity::{
DataIntegrityError, DataIntegrityProof, verification_proof::VerificationProof,
};
use affinidi_did_resolver_cache_sdk::{DIDCacheClient, config::DIDCacheConfigBuilder};
#[cfg(feature = "messaging")]
use affinidi_messaging_sdk::ATM;
#[cfg(feature = "messaging")]
use affinidi_messaging_sdk::config::ATMConfigBuilder;
use affinidi_secrets_resolver::{SecretsResolver, ThreadedSecretsResolver};
use affinidi_tdk_common::{
TDKSharedState, create_http_client, environments::TDKEnvironments, errors::Result,
profiles::TDKProfile, tasks::authentication::AuthenticationCache,
};
use common::{config::TDKConfig, environments::TDKEnvironment};
#[cfg(feature = "data-integrity")]
use serde::Serialize;
use std::sync::Arc;
pub mod dids;
pub mod secrets;
#[cfg(feature = "meeting-place")]
pub use affinidi_meeting_place as meeting_place;
pub use affinidi_messaging_didcomm as didcomm;
#[cfg(feature = "messaging")]
pub use affinidi_messaging_sdk as messaging;
#[cfg(feature = "data-integrity")]
pub use affinidi_data_integrity as data_integrity;
pub use affinidi_crypto;
pub use affinidi_did_authentication as did_authentication;
pub use affinidi_did_common as did_common;
pub use affinidi_secrets_resolver as secrets_resolver;
pub use affinidi_tdk_common as common;
#[derive(Clone)]
pub struct TDK {
pub(crate) inner: Arc<TDKSharedState>,
#[cfg(feature = "messaging")]
pub atm: Option<ATM>,
#[cfg(feature = "meeting-place")]
pub meeting_place: Option<meeting_place::MeetingPlace>,
}
impl TDK {
pub async fn new(
config: TDKConfig,
#[cfg(feature = "messaging")] atm: Option<ATM>,
) -> Result<Self> {
let client = create_http_client();
let did_resolver = if let Some(did_resolver) = &config.did_resolver {
did_resolver.to_owned()
} else if let Some(did_resolver_config) = &config.did_resolver_config {
DIDCacheClient::new(did_resolver_config.to_owned()).await?
} else {
DIDCacheClient::new(DIDCacheConfigBuilder::default().build()).await?
};
let secrets_resolver = if let Some(secrets_resolver) = &config.secrets_resolver {
secrets_resolver.to_owned()
} else {
ThreadedSecretsResolver::new(None).await.0
};
let (authentication, _) = AuthenticationCache::new(
config.authentication_cache_limit as u64,
&did_resolver,
secrets_resolver.clone(),
&client,
config.custom_auth_handlers.clone(),
);
authentication.start().await;
let environment = if config.load_environment {
let mut environment = TDKEnvironments::fetch_from_file(
Some(&config.environment_path),
&config.environment_name,
)?;
for (_, profile) in environment.profiles.iter_mut() {
secrets_resolver
.insert_vec(profile.secrets.as_slice())
.await;
profile.secrets.clear();
}
environment
} else {
TDKEnvironment::default()
};
let shared_state = Arc::new(TDKSharedState {
config,
did_resolver,
secrets_resolver,
client,
environment,
authentication,
});
#[cfg(feature = "messaging")]
let atm = if shared_state.config.use_atm {
if let Some(atm) = atm {
Some(atm.to_owned())
} else {
Some(ATM::new(ATMConfigBuilder::default().build()?, shared_state.clone()).await?)
}
} else {
None
};
Ok(TDK {
inner: shared_state,
#[cfg(feature = "messaging")]
atm,
#[cfg(feature = "meeting-place")]
meeting_place: None,
})
}
pub fn get_shared_state(&self) -> Arc<TDKSharedState> {
self.inner.clone()
}
pub async fn add_profile(&self, profile: &TDKProfile) {
self.inner
.secrets_resolver
.insert_vec(&profile.secrets)
.await;
}
pub fn did_resolver(&self) -> &DIDCacheClient {
&self.inner.did_resolver
}
#[cfg(feature = "data-integrity")]
pub async fn verify_data<S>(
&self,
signed_doc: &S,
context: Option<Vec<String>>,
proof: &DataIntegrityProof,
) -> Result<VerificationProof>
where
S: Serialize,
{
use affinidi_data_integrity::verification_proof::verify_data_with_public_key;
use affinidi_did_common::document::DocumentExt;
use affinidi_tdk_common::errors::TDKError;
let did = if let Some((did, _)) = proof.verification_method.split_once("#") {
did
} else {
use affinidi_tdk_common::errors::TDKError;
return Err(TDKError::DataIntegrity(DataIntegrityError::InputDataError(
"Invalid proof:verificationMethod. Must be DID#key-id format".to_string(),
)));
};
let resolved = self.inner.did_resolver.resolve(did).await?;
let public_bytes = if let Some(vm) = resolved
.doc
.get_verification_method(&proof.verification_method)
{
vm.get_public_key_bytes().map_err(|e| {
DataIntegrityError::InputDataError(format!(
"Failed to get public key bytes from verification method: {e}"
))
})?
} else {
return Err(TDKError::DataIntegrity(DataIntegrityError::InputDataError(
format!(
"Couldn't find key-id ({}) in resolved DID Document",
proof.verification_method
),
)));
};
verify_data_with_public_key(signed_doc, context, proof, public_bytes.as_slice())
.map_err(TDKError::DataIntegrity)
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use affinidi_data_integrity::{DataIntegrityError, crypto_suites::CryptoSuite};
use affinidi_tdk_common::{config::TDKConfig, errors::TDKError};
use crate::TDK;
#[tokio::test]
async fn invalid_verification_method() {
let proof = crate::DataIntegrityProof {
type_: "DataIntegrityProof".to_string(),
cryptosuite: CryptoSuite::EddsaJcs2022,
created: Some("2025-01-01T00:00:00Z".to_string()),
verification_method: "test".to_string(),
proof_purpose: "test".to_string(),
proof_value: Some("z2RPk8MWLoULfcbtpULoEsgfDsaAvyfD1PvQC2v3BjqqNtzGu8YJ4Nxq8CmJCZpPqA49uJhkxmxSztUQhBxqnVrYj".to_string()),
context: None,
};
let tdk = TDK::new(
TDKConfig::builder()
.with_load_environment(false)
.build()
.unwrap(),
None,
)
.await
.unwrap();
let result = tdk
.verify_data(&HashMap::<String, String>::new(), None, &proof)
.await;
assert!(result.is_err());
match result {
Err(TDKError::DataIntegrity(DataIntegrityError::InputDataError(txt))) => {
assert_eq!(
txt,
"Invalid proof:verificationMethod. Must be DID#key-id format".to_string()
);
}
_ => panic!("Invalid return type"),
}
}
#[tokio::test]
async fn invalid_verification_method_2() {
let proof = crate::DataIntegrityProof {
type_: "DataIntegrityProof".to_string(),
cryptosuite: CryptoSuite::EddsaJcs2022,
created: Some("2025-01-01T00:00:00Z".to_string()),
verification_method: "did:key:not_a_key".to_string(),
proof_purpose: "test".to_string(),
proof_value: Some("z2RPk8MWLoULfcbtpULoEsgfDsaAvyfD1PvQC2v3BjqqNtzGu8YJ4Nxq8CmJCZpPqA49uJhkxmxSztUQhBxqnVrYj".to_string()),
context: None,
};
let tdk = TDK::new(
TDKConfig::builder()
.with_load_environment(false)
.build()
.unwrap(),
None,
)
.await
.unwrap();
let result = tdk
.verify_data(&HashMap::<String, String>::new(), None, &proof)
.await;
assert!(result.is_err());
match result {
Err(TDKError::DataIntegrity(DataIntegrityError::InputDataError(txt))) => {
assert_eq!(
txt,
"Invalid proof:verificationMethod. Must be DID#key-id format".to_string()
);
}
_ => panic!("Invalid return type"),
}
}
#[tokio::test]
async fn invalid_verification_method_3() {
let proof = crate::DataIntegrityProof {
type_: "DataIntegrityProof".to_string(),
cryptosuite: CryptoSuite::EddsaJcs2022,
created: Some("2025-01-01T00:00:00Z".to_string()),
verification_method: "did:key:test#test".to_string(),
proof_purpose: "test".to_string(),
proof_value: Some("z2RPk8MWLoULfcbtpULoEsgfDsaAvyfD1PvQC2v3BjqqNtzGu8YJ4Nxq8CmJCZpPqA49uJhkxmxSztUQhBxqnVrYj".to_string()),
context: None,
};
let tdk = TDK::new(
TDKConfig::builder()
.with_load_environment(false)
.build()
.unwrap(),
None,
)
.await
.unwrap();
let result = tdk
.verify_data(&HashMap::<String, String>::new(), None, &proof)
.await;
assert!(result.is_err());
match result {
Err(TDKError::DIDResolver(txt)) => {
assert_eq!(
txt,
"DID error: Failed to parse DID: Invalid method-specific ID: invalid did:key encoding: Invalid multibase prefix: expected 'z' (base58btc), got 't'"
.to_string()
);
}
_ => panic!("Invalid return type {result:#?}"),
}
}
}