diff --git a/Cargo.lock b/Cargo.lock index 72536ed0ce..6e6cd06648 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2000,6 +2000,7 @@ dependencies = [ "aries_vcx_ledger", "aries_vcx_wallet", "async-trait", + "bs58", "chrono", "did_resolver", "log", diff --git a/aries/aries_vcx/src/handlers/util.rs b/aries/aries_vcx/src/handlers/util.rs index 2ed30d0c75..332496ff5a 100644 --- a/aries/aries_vcx/src/handlers/util.rs +++ b/aries/aries_vcx/src/handlers/util.rs @@ -47,7 +47,7 @@ macro_rules! matches_opt_thread_id { #[rustfmt::skip] // This macro results in some false positives and formatting makes it harder to read macro_rules! get_attach_as_string { ($attachments:expr) => {{ - let __attach = $attachments.get(0).as_ref().map(|a| &a.data.content); + let __attach = $attachments.first().as_ref().map(|a| &a.data.content); let err_fn = |attach: Option<&messages::decorators::attachment::Attachment>| { Err(AriesVcxError::from_msg( AriesVcxErrorKind::SerializationError, @@ -55,9 +55,9 @@ macro_rules! get_attach_as_string { )) }; - let Some(messages::decorators::attachment::AttachmentType::Base64(encoded_attach)) = __attach else { return err_fn($attachments.get(0)); }; - let Ok(bytes) = base64::engine::Engine::decode(&base64::engine::general_purpose::STANDARD, &encoded_attach) else { return err_fn($attachments.get(0)); }; - let Ok(attach_string) = String::from_utf8(bytes) else { return err_fn($attachments.get(0)); }; + let Some(messages::decorators::attachment::AttachmentType::Base64(encoded_attach)) = __attach else { return err_fn($attachments.first()); }; + let Ok(bytes) = base64::engine::Engine::decode(&base64::engine::general_purpose::STANDARD, &encoded_attach) else { return err_fn($attachments.first()); }; + let Ok(attach_string) = String::from_utf8(bytes) else { return err_fn($attachments.first()); }; attach_string }}; diff --git a/did_core/did_methods/did_resolver_sov/Cargo.toml b/did_core/did_methods/did_resolver_sov/Cargo.toml index 091f1e6738..a34a921f58 100644 --- a/did_core/did_methods/did_resolver_sov/Cargo.toml +++ b/did_core/did_methods/did_resolver_sov/Cargo.toml @@ -13,6 +13,7 @@ chrono = { version = "0.4.24", default-features = false } thiserror = "1.0.40" url = "2.3.1" log = "0.4.16" +bs58 = "0.5.0" [dev-dependencies] mockall = "0.13.0" diff --git a/did_core/did_methods/did_resolver_sov/src/resolution/utils.rs b/did_core/did_methods/did_resolver_sov/src/resolution/utils.rs index f4314b3eed..65de49d405 100644 --- a/did_core/did_methods/did_resolver_sov/src/resolution/utils.rs +++ b/did_core/did_methods/did_resolver_sov/src/resolution/utils.rs @@ -53,6 +53,28 @@ fn unix_to_datetime(posix_timestamp: i64) -> Option> { DateTime::from_timestamp(posix_timestamp, 0) } +fn expand_abbreviated_verkey(nym: &str, verkey: &str) -> Result { + if let Some(stripped_verkey) = verkey.strip_prefix('~') { + let mut decoded_nym = bs58::decode(nym).into_vec().map_err(|e| { + DidSovError::ParsingError(ParsingErrorSource::LedgerResponseParsingError(format!( + "Failed to decode did from base58: {} (error: {})", + nym, e + ))) + })?; + let decoded_stripped_verkey = bs58::decode(stripped_verkey).into_vec().map_err(|e| { + DidSovError::ParsingError(ParsingErrorSource::LedgerResponseParsingError(format!( + "Failed to decode verkey from base58: {} (error: {})", + stripped_verkey, e + ))) + })?; + decoded_nym.extend(&decoded_stripped_verkey); + + Ok(bs58::encode(decoded_nym).into_string()) + } else { + Ok(verkey.to_string()) + } +} + pub(super) fn is_valid_sovrin_did_id(id: &str) -> bool { if id.len() < 21 || id.len() > 22 { return false; @@ -69,29 +91,52 @@ pub(super) async fn ledger_response_to_ddo( log::info!("ledger_response_to_ddo >> did: {did}, verkey: {verkey}, resp: {resp}"); let (service_id, ddo_id) = prepare_ids(did)?; - let service_data = get_data_from_response(resp)?; - log::info!("ledger_response_to_ddo >> service_data: {service_data:?}"); - let endpoint: EndpointDidSov = serde_json::from_value(service_data["endpoint"].clone())?; - - let txn_time = get_txn_time_from_response(resp)?; - let datetime = unix_to_datetime(txn_time); - - let service_types: Vec = endpoint - .types - .into_iter() - .map(|t| match t { - DidSovServiceType::Endpoint => ServiceType::AIP1, - DidSovServiceType::DidCommunication => ServiceType::DIDCommV1, - DidSovServiceType::DIDComm => ServiceType::DIDCommV2, - DidSovServiceType::Unknown => ServiceType::Other("Unknown".to_string()), - }) - .collect(); - let service = Service::new( - service_id, - endpoint.endpoint, - OneOrList::List(service_types), - Default::default(), - ); + let service_data = match get_data_from_response(resp) { + Ok(data) => data, + Err(e) => { + log::warn!("Failed to get service data: {}", e); + serde_json::Value::Null + } + }; + + let mut ddo = DidDocument::new(ddo_id.clone()); + + let mut services = Vec::new(); + + if !service_data.is_null() { + let endpoint: EndpointDidSov = serde_json::from_value(service_data["endpoint"].clone())?; + + let service_types: Vec = endpoint + .types + .into_iter() + .map(|t| match t { + DidSovServiceType::Endpoint => ServiceType::AIP1, + DidSovServiceType::DidCommunication => ServiceType::DIDCommV1, + DidSovServiceType::DIDComm => ServiceType::DIDCommV2, + DidSovServiceType::Unknown => ServiceType::Other("Unknown".to_string()), + }) + .collect(); + let service = Service::new( + service_id, + endpoint.endpoint, + OneOrList::List(service_types), + Default::default(), + ); + services.push(service); + } + + ddo.set_service(services); + + let txn_time_result = get_txn_time_from_response(resp); + let datetime = match txn_time_result { + Ok(txn_time) => unix_to_datetime(txn_time), + Err(e) => { + log::warn!("Failed to parse txnTime: {}", e); + None + } + }; + + let expanded_verkey = expand_abbreviated_verkey(ddo_id.id(), &verkey)?; // TODO: Use multibase instead of base58 let verification_method = VerificationMethod::builder() @@ -99,12 +144,10 @@ pub(super) async fn ledger_response_to_ddo( .controller(did.to_string().try_into()?) .verification_method_type(VerificationMethodType::Ed25519VerificationKey2018) .public_key(PublicKeyField::Base58 { - public_key_base58: verkey, + public_key_base58: expanded_verkey, }) .build(); - let mut ddo = DidDocument::new(ddo_id); - ddo.add_service(service); ddo.add_verification_method(verification_method); ddo.add_key_agreement_ref(DidUrl::parse("#1".to_string())?); @@ -112,7 +155,7 @@ pub(super) async fn ledger_response_to_ddo( let mut metadata_builder = DidDocumentMetadata::builder().deactivated(false); if let Some(datetime) = datetime { metadata_builder = metadata_builder.updated(datetime); - }; + } metadata_builder.build() }; @@ -216,4 +259,27 @@ mod tests { panic!("Unexpected public key type"); } } + + #[test] + fn test_expand_abbreviated_verkey_with_abbreviation() { + let nym = "7Sqc3ne5NfUVxMTrHahxz3"; + let abbreviated_verkey = "~DczaFTexiEYv5abkEUZeZt"; + let expected_full_verkey = "4WkksEAXsewRbDYDz66aTdjtVF2LBxbqEMyF2WEjTBKk"; + + assert_eq!( + expand_abbreviated_verkey(nym, abbreviated_verkey).unwrap(), + expected_full_verkey + ); + } + + #[test] + fn test_expand_abbreviated_verkey_without_abbreviation() { + let nym = "123456789abcdefghi"; + let full_verkey = "123456789abcdefghixyz123"; + + assert_eq!( + expand_abbreviated_verkey(nym, full_verkey).unwrap(), + full_verkey + ); + } } diff --git a/did_core/did_methods/did_resolver_sov/tests/resolution.rs b/did_core/did_methods/did_resolver_sov/tests/resolution.rs index 8a309caab1..626978780c 100644 --- a/did_core/did_methods/did_resolver_sov/tests/resolution.rs +++ b/did_core/did_methods/did_resolver_sov/tests/resolution.rs @@ -2,7 +2,7 @@ use std::{thread, time::Duration}; use aries_vcx::common::ledger::{service_didsov::EndpointDidSov, transactions::write_endpoint}; use aries_vcx_ledger::ledger::base_ledger::IndyLedgerWrite; -use aries_vcx_wallet::wallet::base_wallet::BaseWallet; +use aries_vcx_wallet::wallet::base_wallet::{did_wallet::DidWallet, BaseWallet}; use did_resolver::{ did_doc::schema::service::typed::ServiceType, did_parser_nom::Did, @@ -58,3 +58,35 @@ async fn test_error_handling_during_resolution() { assert!(result.is_err()); } + +#[tokio::test] +async fn write_new_nym_and_get_did_doc() { + let profile = build_setup_profile().await; + let did_data = profile + .wallet + .create_and_store_my_did(None, None) + .await + .unwrap(); + + profile + .ledger_write + .publish_nym( + &profile.wallet, + &profile.institution_did, + &did_data.did().parse().unwrap(), + Some(did_data.verkey()), + None, + None, + ) + .await + .unwrap(); + + let resolver = DidSovResolver::new(profile.ledger_read); + let did = format!("did:sov:{}", did_data.did()); + + let DidResolutionOutput { did_document, .. } = resolver + .resolve(&Did::parse(did.clone()).unwrap(), &()) + .await + .unwrap(); + assert_eq!(did_document.id().to_string(), did); +}