diff --git a/Cargo.lock b/Cargo.lock index d5d6eee4b8..5b6f2ca1af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1435,6 +1435,7 @@ dependencies = [ "serde", "serde_json", "thiserror", + "typed-builder", "uniresid", "url", ] diff --git a/aries/agents/node/.gitignore b/aries/agents/node/.gitignore index c7c0c162f2..90cdfd1cf4 100644 --- a/aries/agents/node/.gitignore +++ b/aries/agents/node/.gitignore @@ -70,6 +70,6 @@ typings/ # End of https://www.gitignore.io/api/node -vcxagent-core/storage-proofs/ +vcxagent-core/storage-* **/test/tmp diff --git a/aries/agents/rust/aries-vcx-agent/src/error/convertors.rs b/aries/agents/rust/aries-vcx-agent/src/error/convertors.rs index 252ed485ce..a37605549a 100644 --- a/aries/agents/rust/aries-vcx-agent/src/error/convertors.rs +++ b/aries/agents/rust/aries-vcx-agent/src/error/convertors.rs @@ -1,11 +1,12 @@ use std::convert::From; use aries_vcx::{ - did_doc::error::{DidDocumentBuilderError, DidDocumentSovError}, + did_doc::error::DidDocumentBuilderError, errors::error::{AriesVcxError, AriesVcxErrorKind}, protocols::did_exchange::state_machine::generic::GenericDidExchange, }; use aries_vcx_core::errors::error::AriesVcxCoreError; +use did_resolver_sov::did_resolver::did_doc::schema::utils::error::DidDocumentLookupError; use crate::error::*; @@ -29,7 +30,6 @@ impl From for AgentError { } } -// TODO impl From for AgentError { fn from(err: AriesVcxCoreError) -> Self { let kind = AgentErrorKind::GenericAriesVcxError; @@ -38,14 +38,6 @@ impl From for AgentError { } } -impl From for AgentError { - fn from(err: DidDocumentSovError) -> Self { - let kind = AgentErrorKind::GenericAriesVcxError; - let message = format!("DidDocumentSovError; err: {:?}", err.to_string()); - AgentError { message, kind } - } -} - impl From for AgentError { fn from(err: DidDocumentBuilderError) -> Self { let kind = AgentErrorKind::GenericAriesVcxError; @@ -93,3 +85,10 @@ impl From<(GenericDidExchange, AriesVcxError)> for AgentError { AgentError { message, kind } } } +impl From for AgentError { + fn from(err: DidDocumentLookupError) -> Self { + let kind = AgentErrorKind::GenericAriesVcxError; + let message = format!("DidDocumentLookupError; err: {:?}", err.to_string()); + AgentError { message, kind } + } +} diff --git a/aries/agents/rust/aries-vcx-agent/src/http.rs b/aries/agents/rust/aries-vcx-agent/src/http.rs index 41371446fd..e141a865d1 100644 --- a/aries/agents/rust/aries-vcx-agent/src/http.rs +++ b/aries/agents/rust/aries-vcx-agent/src/http.rs @@ -6,7 +6,7 @@ pub struct VcxHttpClient; #[async_trait] impl Transport for VcxHttpClient { - async fn send_message(&self, msg: Vec, service_endpoint: Url) -> VcxResult<()> { + async fn send_message(&self, msg: Vec, service_endpoint: &Url) -> VcxResult<()> { shared::http_client::post_message(msg, service_endpoint).await?; Ok(()) } diff --git a/aries/agents/rust/aries-vcx-agent/src/services/did_exchange.rs b/aries/agents/rust/aries-vcx-agent/src/services/did_exchange.rs index 9037c393ca..205c67d4bb 100644 --- a/aries/agents/rust/aries-vcx-agent/src/services/did_exchange.rs +++ b/aries/agents/rust/aries-vcx-agent/src/services/did_exchange.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use aries_vcx::{ + did_doc::schema::{service::typed::ServiceType, types::uri::Uri}, messages::{ msg_fields::protocols::{ did_exchange::{ @@ -25,7 +26,6 @@ use aries_vcx_core::wallet::{base_wallet::BaseWallet, indy::IndySdkWallet}; use did_peer::peer_did::{numalgos::numalgo2::Numalgo2, PeerDid}; use did_resolver_registry::ResolverRegistry; use did_resolver_sov::did_resolver::did_doc::schema::did_doc::DidDocument; -use url::Url; use super::connection::ServiceEndpoint; use crate::{ @@ -83,10 +83,17 @@ impl ServiceDidExchange { .thid; let ddo_their = requester.their_did_doc(); let ddo_our = requester.our_did_document(); - let encryption_envelope = - pairwise_encrypt(ddo_our, ddo_their, self.wallet.as_ref(), &request.into()).await?; + let service = ddo_their.get_service_of_type(&ServiceType::DIDCommV1)?; + let encryption_envelope = pairwise_encrypt( + ddo_our, + ddo_their, + self.wallet.as_ref(), + &request.into(), + service.id(), + ) + .await?; VcxHttpClient - .send_message(encryption_envelope.0, get_first_endpoint(ddo_their)?) + .send_message(encryption_envelope.0, service.service_endpoint()) .await?; self.did_exchange.insert(&request_id, requester.clone()) } @@ -127,10 +134,17 @@ impl ServiceDidExchange { .await?; let ddo_their = responder.their_did_doc(); let ddo_our = responder.our_did_document(); - let encryption_envelope = - pairwise_encrypt(ddo_our, ddo_their, self.wallet.as_ref(), &response.into()).await?; + let service = ddo_their.get_service_of_type(&ServiceType::DIDCommV1)?; + let encryption_envelope = pairwise_encrypt( + ddo_our, + ddo_their, + self.wallet.as_ref(), + &response.into(), + service.id(), + ) + .await?; VcxHttpClient - .send_message(encryption_envelope.0, get_first_endpoint(ddo_their)?) + .send_message(encryption_envelope.0, service.service_endpoint()) .await?; self.did_exchange.insert(&request_id, responder.clone()) } @@ -144,10 +158,17 @@ impl ServiceDidExchange { .await?; let ddo_their = requester.their_did_doc(); let ddo_our = requester.our_did_document(); - let encryption_envelope = - pairwise_encrypt(ddo_our, ddo_their, self.wallet.as_ref(), &complete.into()).await?; + let service = ddo_their.get_service_of_type(&ServiceType::DIDCommV1)?; + let encryption_envelope = pairwise_encrypt( + ddo_our, + ddo_their, + self.wallet.as_ref(), + &complete.into(), + service.id(), + ) + .await?; VcxHttpClient - .send_message(encryption_envelope.0, get_first_endpoint(ddo_their)?) + .send_message(encryption_envelope.0, service.service_endpoint()) .await?; self.did_exchange.insert(&thread_id, requester.clone()) } @@ -187,25 +208,19 @@ impl ServiceDidExchange { } } -pub fn get_first_endpoint(did_document: &DidDocument) -> AgentResult { - let service = did_document.service().first().ok_or(AgentError::from_msg( - AgentErrorKind::InvalidState, - "No service found", - ))?; - Ok(service.service_endpoint().clone()) -} - pub async fn pairwise_encrypt( our_did_doc: &DidDocument, their_did_doc: &DidDocument, wallet: &impl BaseWallet, message: &AriesMessage, + their_service_id: &Uri, ) -> AgentResult { EncryptionEnvelope::create( wallet, serde_json::json!(message).to_string().as_bytes(), our_did_doc, their_did_doc, + their_service_id, ) .await .map_err(|err| { diff --git a/aries/agents/rust/mediator/src/aries_agent/utils.rs b/aries/agents/rust/mediator/src/aries_agent/utils.rs index fbefebdd12..f074c078e4 100644 --- a/aries/agents/rust/mediator/src/aries_agent/utils.rs +++ b/aries/agents/rust/mediator/src/aries_agent/utils.rs @@ -55,7 +55,7 @@ pub struct MockTransport; #[async_trait] impl Transport for MockTransport { - async fn send_message(&self, _msg: Vec, _service_endpoint: url::Url) -> VcxResult<()> { + async fn send_message(&self, _msg: Vec, _service_endpoint: &url::Url) -> VcxResult<()> { Ok(()) } } diff --git a/aries/aries_vcx/src/errors/mapping_others.rs b/aries/aries_vcx/src/errors/mapping_others.rs index c0f5c1e82c..2e2372437b 100644 --- a/aries/aries_vcx/src/errors/mapping_others.rs +++ b/aries/aries_vcx/src/errors/mapping_others.rs @@ -1,6 +1,7 @@ use std::sync::PoisonError; use aries_vcx_core::errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind}; +use did_doc::schema::{types::uri::UriWrapperError, utils::error::DidDocumentLookupError}; use shared::errors::http_error::HttpError; use crate::{ @@ -45,8 +46,8 @@ impl From for AriesVcxError { } } -impl From for AriesVcxError { - fn from(err: did_doc::error::DidDocumentSovError) -> Self { +impl From for AriesVcxError { + fn from(err: DidDocumentLookupError) -> Self { AriesVcxError::from_msg(AriesVcxErrorKind::InvalidState, err.to_string()) } } @@ -75,7 +76,12 @@ impl From for AriesVcxError { } } -// TODO +impl From for AriesVcxError { + fn from(err: UriWrapperError) -> Self { + AriesVcxError::from_msg(AriesVcxErrorKind::InvalidInput, err.to_string()) + } +} + impl From for AriesVcxError { fn from(err: AriesVcxCoreError) -> Self { let kind = match err.kind() { diff --git a/aries/aries_vcx/src/handlers/mediated_connection/util.rs b/aries/aries_vcx/src/handlers/mediated_connection/util.rs index 6ae9185794..0c64cf5458 100644 --- a/aries/aries_vcx/src/handlers/mediated_connection/util.rs +++ b/aries/aries_vcx/src/handlers/mediated_connection/util.rs @@ -30,7 +30,7 @@ pub async fn send_message( post_message( envelope, - did_doc.get_endpoint().ok_or_else(|| { + &did_doc.get_endpoint().ok_or_else(|| { AriesVcxError::from_msg(AriesVcxErrorKind::InvalidUrl, "No URL in DID Doc") })?, ) diff --git a/aries/aries_vcx/src/protocols/connection/generic/mod.rs b/aries/aries_vcx/src/protocols/connection/generic/mod.rs index 6443f964ce..60c911929c 100644 --- a/aries/aries_vcx/src/protocols/connection/generic/mod.rs +++ b/aries/aries_vcx/src/protocols/connection/generic/mod.rs @@ -207,7 +207,7 @@ impl GenericConnection { let service_endpoint = did_doc.get_endpoint().ok_or_else(|| { AriesVcxError::from_msg(AriesVcxErrorKind::InvalidUrl, "No URL in DID Doc") })?; - transport.send_message(msg, service_endpoint).await + transport.send_message(msg, &service_endpoint).await } } @@ -341,7 +341,7 @@ mod connection_serde_tests { #[async_trait] impl Transport for MockTransport { - async fn send_message(&self, _msg: Vec, _service_endpoint: Url) -> VcxResult<()> { + async fn send_message(&self, _msg: Vec, _service_endpoint: &Url) -> VcxResult<()> { Ok(()) } } diff --git a/aries/aries_vcx/src/protocols/connection/mod.rs b/aries/aries_vcx/src/protocols/connection/mod.rs index 15a15cdafe..863b25dcf0 100644 --- a/aries/aries_vcx/src/protocols/connection/mod.rs +++ b/aries/aries_vcx/src/protocols/connection/mod.rs @@ -137,7 +137,7 @@ where let service_endpoint = self.their_did_doc().get_endpoint().ok_or_else(|| { AriesVcxError::from_msg(AriesVcxErrorKind::InvalidUrl, "No URL in DID Doc") })?; - transport.send_message(msg, service_endpoint).await + transport.send_message(msg, &service_endpoint).await } } diff --git a/aries/aries_vcx/src/protocols/did_exchange/mod.rs b/aries/aries_vcx/src/protocols/did_exchange/mod.rs index 8f902f2245..e5d4f198e4 100644 --- a/aries/aries_vcx/src/protocols/did_exchange/mod.rs +++ b/aries/aries_vcx/src/protocols/did_exchange/mod.rs @@ -35,7 +35,7 @@ pub async fn resolve_enc_key_from_invitation( })?; info!("DID resolution output {:?}", output); Ok(output - .did_document() + .did_document .verification_method() .first() .ok_or_else(|| { diff --git a/aries/aries_vcx/src/protocols/did_exchange/state_machine/helpers.rs b/aries/aries_vcx/src/protocols/did_exchange/state_machine/helpers.rs index f8c2e3a19a..bd271e25c8 100644 --- a/aries/aries_vcx/src/protocols/did_exchange/state_machine/helpers.rs +++ b/aries/aries_vcx/src/protocols/did_exchange/state_machine/helpers.rs @@ -5,7 +5,7 @@ use base64::engine::general_purpose::URL_SAFE_NO_PAD; use chrono::Utc; use did_doc::schema::{ did_doc::DidDocument, - service::{extra_fields::ServiceKeyKind, typed::didcommv1::ServiceDidCommV1, Service}, + service::{service_key_kind::ServiceKeyKind, typed::didcommv1::ServiceDidCommV1, Service}, types::uri::Uri, verification_method::{VerificationMethod, VerificationMethodType}, }; @@ -246,17 +246,9 @@ mod tests { assert_eq!(&ddo_service.id().to_string(), "#service-0"); assert_eq!( ddo_service.service_type(), - &OneOrList::List(vec![ServiceType::DIDCommV1]) + &OneOrList::One(ServiceType::DIDCommV1) ); assert_eq!(ddo_service.service_endpoint(), &service_endpoint); - let recipient_keys = ddo_service - .extra_field_recipient_keys() - .map(|keys| { - keys.into_iter() - .map(|key| key.to_string()) - .collect::>() - }) - .unwrap(); assert_eq!( ddo_service.extra_field_routing_keys().unwrap(), vec![ @@ -265,7 +257,6 @@ mod tests { ] ); - println!("did_doc: {}", did_doc); assert_eq!(did_doc.key_agreement().len(), 1); match did_doc.key_agreement().first().unwrap() { VerificationMethodKind::Resolved(key_agreement) => { diff --git a/aries/aries_vcx/src/protocols/did_exchange/state_machine/requester/helpers.rs b/aries/aries_vcx/src/protocols/did_exchange/state_machine/requester/helpers.rs index 7262685350..67af6f0483 100644 --- a/aries/aries_vcx/src/protocols/did_exchange/state_machine/requester/helpers.rs +++ b/aries/aries_vcx/src/protocols/did_exchange/state_machine/requester/helpers.rs @@ -6,7 +6,10 @@ use messages::{ timing::Timing, }, msg_fields::protocols::{ - did_exchange::request::{Request, RequestContent, RequestDecorators}, + did_exchange::{ + complete::{Complete, CompleteDecorators}, + request::{Request, RequestContent, RequestDecorators}, + }, out_of_band::invitation::{Invitation, OobService}, }, }; @@ -40,6 +43,22 @@ pub fn construct_request(invitation_id: String, our_did: String) -> Request { .build() } +pub fn construct_didexchange_complete(request_id: String, invitation_id: String) -> Complete { + let decorators = CompleteDecorators::builder() + .thread( + Thread::builder() + .thid(request_id) + .pthid(invitation_id) + .build(), + ) + .timing(Timing::builder().out_time(Utc::now()).build()) + .build(); + Complete::builder() + .id(Uuid::new_v4().to_string()) + .decorators(decorators) + .build() +} + /// We are going to support only DID service values in did-exchange protocol unless there's explicit /// good reason to keep support for "embedded" type of service value. /// This function returns first found DID based service value from invitation. diff --git a/aries/aries_vcx/src/protocols/did_exchange/state_machine/requester/request_sent/mod.rs b/aries/aries_vcx/src/protocols/did_exchange/state_machine/requester/request_sent/mod.rs index afbfeb9e10..97d439aee7 100644 --- a/aries/aries_vcx/src/protocols/did_exchange/state_machine/requester/request_sent/mod.rs +++ b/aries/aries_vcx/src/protocols/did_exchange/state_machine/requester/request_sent/mod.rs @@ -1,20 +1,13 @@ use std::sync::Arc; -use chrono::Utc; use did_parser::Did; use did_peer::{ peer_did::{numalgos::numalgo2::Numalgo2, PeerDid}, - resolver::{options::PublicKeyEncoding, PeerDidResolver}, + resolver::options::PublicKeyEncoding, }; -use did_resolver::traits::resolvable::DidResolvable; use did_resolver_registry::ResolverRegistry; -use messages::{ - decorators::{thread::Thread, timing::Timing}, - msg_fields::protocols::did_exchange::{ - complete::{Complete as CompleteMessage, Complete, CompleteDecorators}, - request::Request, - response::Response, - }, +use messages::msg_fields::protocols::did_exchange::{ + complete::Complete as CompleteMessage, request::Request, response::Response, }; use uuid::Uuid; @@ -24,11 +17,12 @@ use crate::{ protocols::did_exchange::{ state_machine::{ helpers::{attachment_to_diddoc, to_transition_error}, - requester::helpers::construct_request, + requester::helpers::{construct_didexchange_complete, construct_request}, }, states::{completed::Completed, requester::request_sent::RequestSent}, transition::{transition_error::TransitionError, transition_result::TransitionResult}, }, + utils::didcomm_utils::resolve_didpeer2, }; impl DidExchangeRequester { @@ -40,11 +34,8 @@ impl DidExchangeRequester { let their_did_document = resolver_registry .resolve(their_did, &Default::default()) .await? - .did_document() - .clone(); - let our_did_document = our_peer_did - .to_did_doc_builder(PublicKeyEncoding::Base58)? - .build(); + .did_document; + let our_did_document = resolve_didpeer2(our_peer_did, PublicKeyEncoding::Base58).await?; let invitation_id = Uuid::new_v4().to_string(); let request = construct_request(invitation_id.clone(), our_peer_did.to_string()); @@ -81,33 +72,17 @@ impl DidExchangeRequester { let did_document = if let Some(ddo) = response.content.did_doc { attachment_to_diddoc(ddo).map_err(to_transition_error(self.clone()))? } else { - PeerDidResolver::new() - .resolve( - &response - .content - .did - .parse() - .map_err(to_transition_error(self.clone()))?, - &Default::default(), - ) + let peer_did = PeerDid::::parse(response.content.did) + .map_err(to_transition_error(self.clone()))?; + resolve_didpeer2(&peer_did, PublicKeyEncoding::Base58) .await .map_err(to_transition_error(self.clone()))? - .did_document() - .to_owned() }; - let decorators = CompleteDecorators::builder() - .thread( - Thread::builder() - .thid(self.state.request_id.clone()) - .pthid(self.state.invitation_id.clone()) - .build(), - ) - .timing(Timing::builder().out_time(Utc::now()).build()) - .build(); - let complete_message = Complete::builder() - .id(Uuid::new_v4().to_string()) - .decorators(decorators) - .build(); + + let complete_message = construct_didexchange_complete( + self.state.request_id.clone(), + self.state.invitation_id.clone(), + ); Ok(TransitionResult { state: DidExchangeRequester::from_parts( diff --git a/aries/aries_vcx/src/protocols/did_exchange/state_machine/responder/response_sent/mod.rs b/aries/aries_vcx/src/protocols/did_exchange/state_machine/responder/response_sent/mod.rs index 13c1093960..62fcf436fe 100644 --- a/aries/aries_vcx/src/protocols/did_exchange/state_machine/responder/response_sent/mod.rs +++ b/aries/aries_vcx/src/protocols/did_exchange/state_machine/responder/response_sent/mod.rs @@ -22,6 +22,7 @@ use crate::{ states::{completed::Completed, responder::response_sent::ResponseSent}, transition::{transition_error::TransitionError, transition_result::TransitionResult}, }, + utils::didcomm_utils::resolve_didpeer2, }; impl DidExchangeResponder { @@ -32,18 +33,15 @@ impl DidExchangeResponder { our_peer_did: &PeerDid, invitation_key: Key, ) -> Result, Response>, AriesVcxError> { - let their_ddo = resolve_their_ddo(&resolver_registry, &request).await?; - let our_did_document = our_peer_did - .to_did_doc_builder(PublicKeyEncoding::Base58)? - .build(); - + let their_ddo = resolve_ddo_from_request(&resolver_registry, &request).await?; + let our_did_document = resolve_didpeer2(our_peer_did, PublicKeyEncoding::Base58).await?; // TODO: Response should sign the new *did* with invitation_key only if key was rotated // In practice if the invitation was public, we definitely will be rotating to - // peer:did if the invitation was peer2peer, we probably keep our original invitation - // keys The outstanding question is how, and on what level, we gonna do this - // detection TODO: Check amendment made to did-exchange protocol in terms of - // rotating keys. When keys are rotated, there's a new decorator which conveys - // that + // peer:did + // If the invitation was peer2peer, we probably keep our original invitation keys + // The outstanding question is how, and on what level, we gonna do this detection + // TODO: Check amendment made to did-exchange protocol in terms of rotating keys. + // When keys are rotated, there's a new decorator which conveys that let signed_attach = jws_sign_attach( ddo_to_attach(our_did_document.clone())?, invitation_key, @@ -88,7 +86,7 @@ impl DidExchangeResponder { } } -async fn resolve_their_ddo( +async fn resolve_ddo_from_request( resolver_registry: &Arc, request: &Request, ) -> Result { @@ -102,7 +100,6 @@ async fn resolve_their_ddo( resolver_registry .resolve(&request.content.did.parse()?, &Default::default()) .await? - .did_document() - .to_owned(), + .did_document, )) } diff --git a/aries/aries_vcx/src/transport.rs b/aries/aries_vcx/src/transport.rs index 016c9945b2..8d14bb0bae 100644 --- a/aries/aries_vcx/src/transport.rs +++ b/aries/aries_vcx/src/transport.rs @@ -7,7 +7,7 @@ use crate::errors::error::VcxResult; /// [`crate::protocols::connection::Connection`]. #[async_trait] pub trait Transport: Send + Sync { - async fn send_message(&self, msg: Vec, service_endpoint: Url) -> VcxResult<()>; + async fn send_message(&self, msg: Vec, service_endpoint: &Url) -> VcxResult<()>; } // While in many cases the auto-dereferencing does the trick, @@ -18,7 +18,7 @@ impl Transport for &T where T: Transport + ?Sized, { - async fn send_message(&self, msg: Vec, service_endpoint: Url) -> VcxResult<()> { + async fn send_message(&self, msg: Vec, service_endpoint: &Url) -> VcxResult<()> { self.send_message(msg, service_endpoint).await } } diff --git a/aries/aries_vcx/src/utils/didcomm_utils.rs b/aries/aries_vcx/src/utils/didcomm_utils.rs index 834728934c..c6a9fb0fcd 100644 --- a/aries/aries_vcx/src/utils/didcomm_utils.rs +++ b/aries/aries_vcx/src/utils/didcomm_utils.rs @@ -1,11 +1,34 @@ use did_doc::schema::{ - did_doc::{diddoc_resolve_first_key_agreement, DidDocument}, - service::extra_fields::ServiceKeyKind, + did_doc::DidDocument, service::service_key_kind::ServiceKeyKind, types::uri::Uri, + verification_method::VerificationMethodType, +}; +use did_peer::{ + peer_did::{numalgos::numalgo2::Numalgo2, PeerDid}, + resolver::{options::PublicKeyEncoding, PeerDidResolutionOptions, PeerDidResolver}, +}; +use did_resolver::{ + error::GenericError, + traits::resolvable::{resolution_output::DidResolutionOutput, DidResolvable}, }; use public_key::Key; use crate::errors::error::{AriesVcxError, AriesVcxErrorKind, VcxResult}; +pub(crate) async fn resolve_didpeer2( + did_peer: &PeerDid, + encoding: PublicKeyEncoding, +) -> Result { + let DidResolutionOutput { did_document, .. } = PeerDidResolver::new() + .resolve( + did_peer.did(), + &PeerDidResolutionOptions { + encoding: Some(encoding), + }, + ) + .await?; + Ok(did_document) +} + fn resolve_service_key_to_typed_key( key: &ServiceKeyKind, did_document: &DidDocument, @@ -35,57 +58,24 @@ fn resolve_service_key_to_typed_key( } pub fn resolve_base58_key_agreement(did_document: &DidDocument) -> VcxResult { - // note: we possibly don't want to support this, instead rely on key_agreement field - // let service = did_document - // .service() - // .first() - // .ok_or_else(|| { - // AriesVcxError::from_msg( - // AriesVcxErrorKind::InvalidState, - // "No Service object found on our did document", - // ) - // })? - // .clone(); - // let key_base58 = match service.extra_field_recipient_keys() { - // Ok(recipient_keys) => { - // match recipient_keys.first() { - // None => { - // return Err(AriesVcxError::from_msg( - // AriesVcxErrorKind::InvalidState, - // "Recipient key field but did not have any keys", - // )) - // } - // Some(key) => { - // // service_key_to_naked_key(&key, did_document)? - // unimplemented!("Support for 'recipientKeys' has been dropped") - // } - // } - // } - // Err(_err) => { - // - // } - // }; - let key_base58 = diddoc_resolve_first_key_agreement(did_document)?; - Ok(key_base58.base58()) + let key_types = [ + VerificationMethodType::Ed25519VerificationKey2018, + VerificationMethodType::Ed25519VerificationKey2020, + VerificationMethodType::X25519KeyAgreementKey2019, + VerificationMethodType::X25519KeyAgreementKey2020, + ]; + let key_base58 = did_document.get_key_agreement_of_type(&key_types)?; + Ok(key_base58.public_key()?.base58()) } -pub fn get_routing_keys(our_did_doc: &DidDocument) -> VcxResult> { - let service = our_did_doc - .service() - .first() - .ok_or_else(|| { - AriesVcxError::from_msg( - AriesVcxErrorKind::InvalidState, - "No Service object found on our did document", - ) - })? - .clone(); +pub fn get_routing_keys(their_did_doc: &DidDocument, service_id: &Uri) -> VcxResult> { + let service = their_did_doc.get_service_by_id(service_id)?; match service.extra_field_routing_keys() { Ok(routing_keys) => { let mut naked_routing_keys = Vec::new(); for key in routing_keys.iter() { naked_routing_keys - .push(resolve_service_key_to_typed_key(key, our_did_doc)?.base58()); + .push(resolve_service_key_to_typed_key(key, their_did_doc)?.base58()); } Ok(naked_routing_keys) } diff --git a/aries/aries_vcx/src/utils/encryption_envelope.rs b/aries/aries_vcx/src/utils/encryption_envelope.rs index 7e668537ac..d2399e9fb5 100644 --- a/aries/aries_vcx/src/utils/encryption_envelope.rs +++ b/aries/aries_vcx/src/utils/encryption_envelope.rs @@ -1,6 +1,6 @@ use agency_client::testing::mocking::AgencyMockDecrypted; use aries_vcx_core::{global::settings::VERKEY, wallet::base_wallet::BaseWallet}; -use did_doc::schema::did_doc::DidDocument; +use did_doc::schema::{did_doc::DidDocument, types::uri::Uri}; use diddoc_legacy::aries::diddoc::AriesDidDoc; use messages::{ msg_fields::protocols::routing::{Forward, ForwardContent}, @@ -43,16 +43,28 @@ impl EncryptionEnvelope { Self::create_from_keys(wallet, data, sender_vk, recipient_key, routing_keys).await } + /// Create encrypted message based on key agreement keys of our did document, counterparties + /// did document and their specific service, identified by id, which must be part of their + /// did document + /// + /// # Arguments + /// + /// * `our_did_doc` - Our did_document, which the counterparty should already be in possession + /// of + /// * `their_did_doc` - The did document of the counterparty, the recipient of the encrypted + /// message + /// * `their_service_id` - Id of service where message will be sent. The counterparty did + /// document must contain Service object identified with such value. pub async fn create( wallet: &impl BaseWallet, data: &[u8], our_did_doc: &DidDocument, their_did_doc: &DidDocument, + their_service_id: &Uri, ) -> VcxResult { - // get first service, from service get (possibly resolve) recipient key and routing keys let sender_vk = resolve_base58_key_agreement(our_did_doc)?; let recipient_key = resolve_base58_key_agreement(their_did_doc)?; - let routing_keys = get_routing_keys(their_did_doc)?; + let routing_keys = get_routing_keys(their_did_doc, their_service_id)?; EncryptionEnvelope::create_from_keys( wallet, diff --git a/aries/aries_vcx/tests/test_did_exchange.rs b/aries/aries_vcx/tests/test_did_exchange.rs index dd53b8674d..86f43fc67e 100644 --- a/aries/aries_vcx/tests/test_did_exchange.rs +++ b/aries/aries_vcx/tests/test_did_exchange.rs @@ -18,15 +18,16 @@ use aries_vcx::{ }; use aries_vcx_core::ledger::indy_vdr_ledger::DefaultIndyLedgerRead; use did_doc::schema::{ - did_doc::{diddoc_resolve_first_key_agreement, DidDocument}, - service::typed::didcommv1::ServiceDidCommV1, + did_doc::DidDocument, + service::typed::{didcommv1::ServiceDidCommV1, ServiceType}, types::uri::Uri, }; use did_parser::Did; use did_peer::{ peer_did::{numalgos::numalgo2::Numalgo2, PeerDid}, - resolver::{options::PublicKeyEncoding, PeerDidResolver}, + resolver::{options::PublicKeyEncoding, PeerDidResolutionOptions, PeerDidResolver}, }; +use did_resolver::traits::resolvable::{resolution_output::DidResolutionOutput, DidResolvable}; use did_resolver_registry::ResolverRegistry; use did_resolver_sov::resolution::DidSovResolver; use log::info; @@ -42,9 +43,25 @@ use crate::utils::test_agent::{ pub mod utils; +pub(crate) async fn resolve_didpeer2( + did_peer: &PeerDid, + encoding: PublicKeyEncoding, +) -> DidDocument { + let DidResolutionOutput { did_document, .. } = PeerDidResolver::new() + .resolve( + did_peer.did(), + &PeerDidResolutionOptions { + encoding: Some(encoding), + }, + ) + .await + .unwrap(); + did_document +} + fn assert_key_agreement(a: DidDocument, b: DidDocument) { - let a_key = diddoc_resolve_first_key_agreement(&a).unwrap(); - let b_key = diddoc_resolve_first_key_agreement(&b).unwrap(); + let a_key = resolve_base58_key_agreement(&a).unwrap(); + let b_key = resolve_base58_key_agreement(&b).unwrap(); assert_eq!(a_key, b_key); } @@ -137,9 +154,7 @@ async fn did_exchange_test() -> Result<(), Box> { let responders_peer_did = PeerDid::::from_did_doc(responders_did_document.clone())?; info!("Responder prepares their peer:did: {responders_peer_did}"); - let check_diddoc: DidDocument = responders_peer_did - .to_did_doc_builder(PublicKeyEncoding::Base58)? - .build(); + let check_diddoc = resolve_didpeer2(&responders_peer_did, PublicKeyEncoding::Base58).await; info!("Responder decodes constructed peer:did as did document: {check_diddoc}"); let TransitionResult { @@ -183,11 +198,15 @@ async fn did_exchange_test() -> Result<(), Box> { ); let data = "Hello world"; + let service = requester + .their_did_doc() + .get_service_of_type(&ServiceType::DIDCommV1)?; let m = EncryptionEnvelope::create( &agent_invitee.wallet, data.as_bytes(), requester.our_did_doc(), requester.their_did_doc(), + service.id(), ) .await?; diff --git a/aries/aries_vcx/tests/utils/scenarios/connection.rs b/aries/aries_vcx/tests/utils/scenarios/connection.rs index bbe3b50602..5668877a58 100644 --- a/aries/aries_vcx/tests/utils/scenarios/connection.rs +++ b/aries/aries_vcx/tests/utils/scenarios/connection.rs @@ -51,7 +51,7 @@ async fn establish_connection_from_invite( #[async_trait] impl Transport for DummyHttpClient { - async fn send_message(&self, _msg: Vec, _service_endpoint: Url) -> VcxResult<()> { + async fn send_message(&self, _msg: Vec, _service_endpoint: &Url) -> VcxResult<()> { Ok(()) } } diff --git a/aries/misc/legacy/agency_client/src/internal/messaging.rs b/aries/misc/legacy/agency_client/src/internal/messaging.rs index be433873b2..55a4d0068e 100644 --- a/aries/misc/legacy/agency_client/src/internal/messaging.rs +++ b/aries/misc/legacy/agency_client/src/internal/messaging.rs @@ -36,7 +36,7 @@ impl AgencyClient { ); return Ok(mocked_response); } - post_message(body_content, url).await.map_err(|err| { + post_message(body_content, &url).await.map_err(|err| { AgencyClientError::from_msg( AgencyClientErrorKind::PostMessageFailed, format!("Cannot send message to agency: {err}"), diff --git a/aries/misc/legacy/libvcx_core/src/api_vcx/api_handle/connection.rs b/aries/misc/legacy/libvcx_core/src/api_vcx/api_handle/connection.rs index c0e60fbb30..693a9e2dff 100644 --- a/aries/misc/legacy/libvcx_core/src/api_vcx/api_handle/connection.rs +++ b/aries/misc/legacy/libvcx_core/src/api_vcx/api_handle/connection.rs @@ -30,7 +30,7 @@ pub struct HttpClient; #[async_trait] impl Transport for HttpClient { - async fn send_message(&self, msg: Vec, service_endpoint: Url) -> VcxResult<()> { + async fn send_message(&self, msg: Vec, service_endpoint: &Url) -> VcxResult<()> { post_message(msg, service_endpoint).await?; Ok(()) } diff --git a/aries/misc/shared/src/http_client.rs b/aries/misc/shared/src/http_client.rs index 855ea1fb62..8b37051ed1 100644 --- a/aries/misc/shared/src/http_client.rs +++ b/aries/misc/shared/src/http_client.rs @@ -21,10 +21,10 @@ lazy_static! { }; } -pub async fn post_message(body_content: Vec, url: Url) -> HttpResult> { +pub async fn post_message(body_content: Vec, url: &Url) -> HttpResult> { debug!("post_message >> http client sending request POST {}", &url); - let response = send_post_request(&url, body_content).await?; + let response = send_post_request(url, body_content).await?; process_response(response).await } diff --git a/aries/wrappers/uniffi-aries-vcx/core/src/core/http_client.rs b/aries/wrappers/uniffi-aries-vcx/core/src/core/http_client.rs index 99c6fb4f28..0e29279c83 100644 --- a/aries/wrappers/uniffi-aries-vcx/core/src/core/http_client.rs +++ b/aries/wrappers/uniffi-aries-vcx/core/src/core/http_client.rs @@ -6,7 +6,7 @@ pub struct HttpClient; #[async_trait] impl Transport for HttpClient { - async fn send_message(&self, msg: Vec, service_endpoint: Url) -> VcxResult<()> { + async fn send_message(&self, msg: Vec, service_endpoint: &Url) -> VcxResult<()> { post_message(msg, service_endpoint).await?; Ok(()) } diff --git a/did_core/did_doc/Cargo.toml b/did_core/did_doc/Cargo.toml index 3c5fcfb1f5..5743f444a7 100644 --- a/did_core/did_doc/Cargo.toml +++ b/did_core/did_doc/Cargo.toml @@ -18,3 +18,4 @@ url = { version = "2.3.1", features = ["serde"] } display_as_json = { path = "../../misc/display_as_json" } did_key = { path = "../did_methods/did_key" } thiserror = "1.0.40" +typed-builder = "0.16.0" diff --git a/did_core/did_doc/src/error.rs b/did_core/did_doc/src/error.rs index 1a41c6047a..26e5f9e720 100644 --- a/did_core/did_doc/src/error.rs +++ b/did_core/did_doc/src/error.rs @@ -1,19 +1,11 @@ -use thiserror::Error; -use url::ParseError; - -use crate::schema::verification_method::VerificationMethodType; +use crate::schema::verification_method::{error::KeyDecodingError, VerificationMethodType}; #[derive(Debug)] pub enum DidDocumentBuilderError { CustomError(String), - InvalidInput(String), - MissingField(String), - UnsupportedPublicKeyField(&'static str), + MissingField(&'static str), JsonError(serde_json::Error), - PemError(pem::PemError), - Base58DecodeError(bs58::decode::Error), - Base64DecodeError(base64::DecodeError), - HexDecodeError(hex::FromHexError), + KeyDecodingError(KeyDecodingError), UnsupportedVerificationMethodType(VerificationMethodType), PublicKeyError(public_key::PublicKeyError), } @@ -21,30 +13,12 @@ pub enum DidDocumentBuilderError { impl std::fmt::Display for DidDocumentBuilderError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - DidDocumentBuilderError::InvalidInput(input) => { - write!(f, "Invalid input: {}", input) - } DidDocumentBuilderError::MissingField(field) => { write!(f, "Missing field: {}", field) } - DidDocumentBuilderError::UnsupportedPublicKeyField(field) => { - write!(f, "Unsupported public key field: {}", field) - } DidDocumentBuilderError::JsonError(error) => { write!(f, "(De)serialization error: {}", error) } - DidDocumentBuilderError::PemError(error) => { - write!(f, "PEM error: {}", error) - } - DidDocumentBuilderError::Base58DecodeError(error) => { - write!(f, "Base58 decode error: {}", error) - } - DidDocumentBuilderError::Base64DecodeError(error) => { - write!(f, "Base64 decode error: {}", error) - } - DidDocumentBuilderError::HexDecodeError(error) => { - write!(f, "Hex decode error: {}", error) - } DidDocumentBuilderError::UnsupportedVerificationMethodType(vm_type) => { write!(f, "Unsupported verification method type: {}", vm_type) } @@ -54,6 +28,9 @@ impl std::fmt::Display for DidDocumentBuilderError { DidDocumentBuilderError::CustomError(string) => { write!(f, "Custom DidDocumentBuilderError: {}", string) } + DidDocumentBuilderError::KeyDecodingError(error) => { + write!(f, "Key decoding error: {}", error) + } } } } @@ -62,10 +39,6 @@ impl std::error::Error for DidDocumentBuilderError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { DidDocumentBuilderError::JsonError(error) => Some(error), - DidDocumentBuilderError::PemError(error) => Some(error), - DidDocumentBuilderError::Base58DecodeError(error) => Some(error), - DidDocumentBuilderError::Base64DecodeError(error) => Some(error), - DidDocumentBuilderError::HexDecodeError(error) => Some(error), DidDocumentBuilderError::PublicKeyError(error) => Some(error), _ => None, } @@ -78,54 +51,14 @@ impl From for DidDocumentBuilderError { } } -impl From for DidDocumentBuilderError { - fn from(error: pem::PemError) -> Self { - DidDocumentBuilderError::PemError(error) - } -} - -impl From for DidDocumentBuilderError { - fn from(error: bs58::decode::Error) -> Self { - DidDocumentBuilderError::Base58DecodeError(error) - } -} - -impl From for DidDocumentBuilderError { - fn from(error: base64::DecodeError) -> Self { - DidDocumentBuilderError::Base64DecodeError(error) - } -} - -impl From for DidDocumentBuilderError { - fn from(error: hex::FromHexError) -> Self { - DidDocumentBuilderError::HexDecodeError(error) - } -} - -impl From for DidDocumentBuilderError { - fn from(error: ParseError) -> Self { - DidDocumentBuilderError::InvalidInput(error.to_string()) - } -} - impl From for DidDocumentBuilderError { fn from(error: public_key::PublicKeyError) -> Self { DidDocumentBuilderError::PublicKeyError(error) } } -#[derive(Debug, Error)] -pub enum DidDocumentSovError { - #[error("Attempted to access empty collection: {0}")] - EmptyCollection(&'static str), - #[error("DID document builder error: {0}")] - DidDocumentBuilderError(#[from] DidDocumentBuilderError), - #[error("Unexpected service type: {0}")] - UnexpectedServiceType(String), - #[error("Index out of bounds: {0}")] - IndexOutOfBounds(usize), - #[error("JSON error")] - JsonError(#[from] serde_json::Error), - #[error("Parsing err {0}")] - ParsingError(String), +impl From for DidDocumentBuilderError { + fn from(error: KeyDecodingError) -> Self { + DidDocumentBuilderError::KeyDecodingError(error) + } } diff --git a/did_core/did_doc/src/schema/did_doc.rs b/did_core/did_doc/src/schema/did_doc.rs index ad44d088f7..4b150d41e8 100644 --- a/did_core/did_doc/src/schema/did_doc.rs +++ b/did_core/did_doc/src/schema/did_doc.rs @@ -2,7 +2,6 @@ use std::collections::HashMap; use did_parser::{Did, DidUrl}; use display_as_json::Display; -use public_key::Key; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -13,8 +12,6 @@ use super::{ }; use crate::{error::DidDocumentBuilderError, schema::service::Service}; -pub type ControllerAlias = OneOrList; - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Display)] #[serde(default)] #[serde(rename_all = "camelCase")] @@ -23,7 +20,7 @@ pub struct DidDocument { #[serde(skip_serializing_if = "Vec::is_empty")] also_known_as: Vec, #[serde(skip_serializing_if = "Option::is_none")] - controller: Option, + controller: Option>, #[serde(skip_serializing_if = "Vec::is_empty")] verification_method: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] @@ -108,32 +105,6 @@ impl DidDocument { } } -pub fn diddoc_resolve_first_key_agreement( - did_document: &DidDocument, -) -> Result { - let vm = did_document.key_agreement().first().ok_or_else(|| { - DidDocumentBuilderError::CustomError( - "Expected to find key agreement on did document".to_string(), - ) - })?; - - let key = match vm { - VerificationMethodKind::Resolved(verification_method) => { - verification_method.public_key()? - } - VerificationMethodKind::Resolvable(reference) => { - match did_document.dereference_key(reference) { - None => Err(DidDocumentBuilderError::CustomError(format!( - "Unable to dereference key: {}", - reference - )))?, - Some(verification_method) => verification_method.public_key()?, - } - } - }; - Ok(key) -} - #[derive(Default, Debug)] pub struct DidDocumentBuilder { id: Did, diff --git a/did_core/did_doc/src/schema/legacy.rs b/did_core/did_doc/src/schema/legacy.rs index 6e7a6f363c..4bc71bf0da 100644 --- a/did_core/did_doc/src/schema/legacy.rs +++ b/did_core/did_doc/src/schema/legacy.rs @@ -205,7 +205,7 @@ pub fn deserialize_legacy_or_new_diddoc_str(val: String) -> Result, - routing_keys: Vec, - #[serde(default)] - accept: Vec, -} - -impl ExtraFieldsDidCommV1 { - pub fn builder() -> ExtraFieldsDidCommV1Builder { - ExtraFieldsDidCommV1Builder::default() - } - - pub fn priority(&self) -> u32 { - self.priority - } - - pub fn recipient_keys(&self) -> &[ServiceKeyKind] { - self.recipient_keys.as_ref() - } - - pub fn routing_keys(&self) -> &[ServiceKeyKind] { - self.routing_keys.as_ref() - } - - pub fn accept(&self) -> &[ServiceAcceptType] { - self.accept.as_ref() - } -} - -pub struct ExtraFieldsDidCommV1Builder { - priority: u32, - recipient_keys: Vec, - routing_keys: Vec, - accept: Vec, -} - -impl Default for ExtraFieldsDidCommV1Builder { - fn default() -> Self { - Self { - priority: 0, - recipient_keys: Vec::new(), - routing_keys: Vec::new(), - accept: vec![ServiceAcceptType::DIDCommV1], - } - } -} - -impl ExtraFieldsDidCommV1Builder { - pub fn set_priority(mut self, priority: u32) -> Self { - self.priority = priority; - self - } - - pub fn set_recipient_keys(mut self, recipient_keys: Vec) -> Self { - self.recipient_keys = recipient_keys; - self - } - - pub fn set_routing_keys(mut self, routing_keys: Vec) -> Self { - self.routing_keys = routing_keys; - self - } - - pub fn set_accept(mut self, accept: Vec) -> Self { - self.accept = accept; - self - } - - pub fn add_accept(mut self, accept: ServiceAcceptType) -> Self { - self.accept.push(accept); - self - } - - pub fn build(self) -> ExtraFieldsDidCommV1 { - ExtraFieldsDidCommV1 { - priority: self.priority, - recipient_keys: self.recipient_keys, - routing_keys: self.routing_keys, - accept: self.accept, - } - } -} diff --git a/did_core/did_doc/src/schema/service/extra_fields/didcommv2.rs b/did_core/did_doc/src/schema/service/extra_fields/didcommv2.rs deleted file mode 100644 index c113f0c9f5..0000000000 --- a/did_core/did_doc/src/schema/service/extra_fields/didcommv2.rs +++ /dev/null @@ -1,66 +0,0 @@ -use display_as_json::Display; -use serde::{Deserialize, Serialize}; - -use crate::schema::service::extra_fields::{ServiceAcceptType, ServiceKeyKind}; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Display)] -#[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] -pub struct ExtraFieldsDidCommV2 { - #[serde(skip_serializing_if = "Vec::is_empty")] - routing_keys: Vec, - #[serde(default)] - accept: Vec, -} - -impl ExtraFieldsDidCommV2 { - pub fn builder() -> ExtraFieldsDidCommV2Builder { - ExtraFieldsDidCommV2Builder::default() - } - - pub fn accept(&self) -> &[ServiceAcceptType] { - self.accept.as_ref() - } - - pub fn routing_keys(&self) -> &[ServiceKeyKind] { - self.routing_keys.as_ref() - } -} - -pub struct ExtraFieldsDidCommV2Builder { - routing_keys: Vec, - accept: Vec, -} - -impl Default for ExtraFieldsDidCommV2Builder { - fn default() -> Self { - Self { - routing_keys: Vec::new(), - accept: vec![ServiceAcceptType::DIDCommV2], - } - } -} - -impl ExtraFieldsDidCommV2Builder { - pub fn set_routing_keys(mut self, routing_keys: Vec) -> Self { - self.routing_keys = routing_keys; - self - } - - pub fn set_accept(mut self, accept: Vec) -> Self { - self.accept = accept; - self - } - - pub fn add_accept(mut self, accept: ServiceAcceptType) -> Self { - self.accept.push(accept); - self - } - - pub fn build(self) -> ExtraFieldsDidCommV2 { - ExtraFieldsDidCommV2 { - routing_keys: self.routing_keys, - accept: self.accept, - } - } -} diff --git a/did_core/did_doc/src/schema/service/extra_fields/mod.rs b/did_core/did_doc/src/schema/service/extra_fields/mod.rs deleted file mode 100644 index 08c22c7689..0000000000 --- a/did_core/did_doc/src/schema/service/extra_fields/mod.rs +++ /dev/null @@ -1,150 +0,0 @@ -pub mod aip1; -pub mod didcommv1; -pub mod didcommv2; -pub mod legacy; - -use std::fmt::Display; - -use did_key::DidKey; -use did_parser::DidUrl; -use serde::{Deserialize, Deserializer, Serialize}; - -use crate::error::DidDocumentSovError; - -#[derive(Clone, Debug, Eq, PartialEq, Hash)] -pub enum ServiceAcceptType { - DIDCommV1, - DIDCommV2, - Other(String), -} - -impl From<&str> for ServiceAcceptType { - fn from(s: &str) -> Self { - match s { - "didcomm/aip2;env=rfc19" => ServiceAcceptType::DIDCommV1, - "didcomm/v2" => ServiceAcceptType::DIDCommV2, - _ => ServiceAcceptType::Other(s.to_string()), - } - } -} - -impl Display for ServiceAcceptType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ServiceAcceptType::DIDCommV1 => write!(f, "didcomm/aip2;env=rfc19"), - ServiceAcceptType::DIDCommV2 => write!(f, "didcomm/v2"), - ServiceAcceptType::Other(other) => write!(f, "{}", other), - } - } -} - -impl<'de> Deserialize<'de> for ServiceAcceptType { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - match s.as_str() { - "didcomm/aip2;env=rfc19" => Ok(ServiceAcceptType::DIDCommV1), - "didcomm/v2" => Ok(ServiceAcceptType::DIDCommV2), - _ => Ok(ServiceAcceptType::Other(s)), - } - } -} - -impl Serialize for ServiceAcceptType { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match self { - ServiceAcceptType::DIDCommV1 => serializer.serialize_str("didcomm/aip2;env=rfc19"), - ServiceAcceptType::DIDCommV2 => serializer.serialize_str("didcomm/v2"), - ServiceAcceptType::Other(other) => serializer.serialize_str(other), - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(untagged)] -pub enum ServiceKeyKind { - DidKey(DidKey), - Reference(DidUrl), - Value(String), -} - -impl Display for ServiceKeyKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ServiceKeyKind::Reference(did_url) => write!(f, "{}", did_url), - ServiceKeyKind::Value(value) => write!(f, "{}", value), - ServiceKeyKind::DidKey(did_key) => write!(f, "{}", did_key), - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, display_as_json::Display)] -#[serde(untagged)] -pub enum ExtraFieldsSov { - DIDCommV1(didcommv1::ExtraFieldsDidCommV1), - DIDCommV2(didcommv2::ExtraFieldsDidCommV2), - AIP1(aip1::ExtraFieldsAIP1), - Legacy(legacy::ExtraFieldsLegacy), -} - -impl Default for ExtraFieldsSov { - fn default() -> Self { - ExtraFieldsSov::AIP1(aip1::ExtraFieldsAIP1::default()) - } -} - -impl ExtraFieldsSov { - pub fn recipient_keys(&self) -> Result<&[ServiceKeyKind], DidDocumentSovError> { - match self { - ExtraFieldsSov::DIDCommV1(extra) => Ok(extra.recipient_keys()), - ExtraFieldsSov::Legacy(extra) => Ok(extra.recipient_keys()), - ExtraFieldsSov::AIP1(_) | ExtraFieldsSov::DIDCommV2(_) => { - Err(DidDocumentSovError::EmptyCollection("recipient_keys")) - } - } - } - - pub fn routing_keys(&self) -> Result<&[ServiceKeyKind], DidDocumentSovError> { - match self { - ExtraFieldsSov::DIDCommV1(extra) => Ok(extra.routing_keys()), - ExtraFieldsSov::DIDCommV2(extra) => Ok(extra.routing_keys()), - ExtraFieldsSov::Legacy(extra) => Ok(extra.routing_keys()), - ExtraFieldsSov::AIP1(_) => Err(DidDocumentSovError::EmptyCollection("routing_keys")), - } - } - - pub fn first_recipient_key(&self) -> Result<&ServiceKeyKind, DidDocumentSovError> { - self.recipient_keys()? - .first() - .ok_or(DidDocumentSovError::EmptyCollection("recipient_keys")) - } - - pub fn first_routing_key(&self) -> Result<&ServiceKeyKind, DidDocumentSovError> { - self.routing_keys()? - .first() - .ok_or(DidDocumentSovError::EmptyCollection("routing_keys")) - } - - pub fn accept(&self) -> Result<&[ServiceAcceptType], DidDocumentSovError> { - match self { - ExtraFieldsSov::DIDCommV1(extra) => Ok(extra.accept()), - ExtraFieldsSov::DIDCommV2(extra) => Ok(extra.accept()), - ExtraFieldsSov::AIP1(_) | ExtraFieldsSov::Legacy(_) => { - Err(DidDocumentSovError::EmptyCollection("accept")) - } - } - } - - pub fn priority(&self) -> Result { - match self { - ExtraFieldsSov::DIDCommV1(extra) => Ok(extra.priority()), - ExtraFieldsSov::Legacy(extra) => Ok(extra.priority()), - _ => Err(DidDocumentSovError::EmptyCollection("priority")), - } - } -} diff --git a/did_core/did_doc/src/schema/service/mod.rs b/did_core/did_doc/src/schema/service/mod.rs index 39b0ed4064..f1bab97f70 100644 --- a/did_core/did_doc/src/schema/service/mod.rs +++ b/did_core/did_doc/src/schema/service/mod.rs @@ -3,21 +3,17 @@ use std::collections::HashMap; use display_as_json::Display; use serde::{Deserialize, Serialize}; use serde_json::Value; +use service_accept_type::ServiceAcceptType; +use service_key_kind::ServiceKeyKind; use url::Url; use crate::{ error::DidDocumentBuilderError, - schema::{ - service::{ - extra_fields::{ServiceAcceptType, ServiceKeyKind}, - typed::ServiceType, - }, - types::uri::Uri, - utils::OneOrList, - }, + schema::{service::typed::ServiceType, types::uri::Uri, utils::OneOrList}, }; -pub mod extra_fields; +pub mod service_accept_type; +pub mod service_key_kind; pub mod typed; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Display)] @@ -55,10 +51,10 @@ impl Service { &self.service_type } - pub fn service_types(&self) -> Vec { + pub fn service_types(&self) -> &[ServiceType] { match &self.service_type { - OneOrList::One(service_type) => vec![service_type.clone()], - OneOrList::List(service_types) => service_types.clone(), + OneOrList::One(service_type) => std::slice::from_ref(service_type), + OneOrList::List(service_types) => service_types.as_slice(), } } @@ -90,10 +86,10 @@ impl Service { fn _expected_extra_field_type serde::Deserialize<'de>>( &self, - key: &str, + key: &'static str, ) -> Result { match self.extra_field_as_as::(key) { - None => Err(DidDocumentBuilderError::MissingField(String::from(key))), + None => Err(DidDocumentBuilderError::MissingField(key)), Some(value) => value, } } @@ -171,9 +167,8 @@ mod tests { use crate::schema::{ service::{ - extra_fields::{ServiceAcceptType, ServiceKeyKind}, - typed::ServiceType, - Service, + service_accept_type::ServiceAcceptType, service_key_kind::ServiceKeyKind, + typed::ServiceType, Service, }, types::uri::Uri, utils::OneOrList, @@ -183,18 +178,19 @@ mod tests { fn test_service_builder() { let uri_id = Uri::new("http://example.com").unwrap(); let service_endpoint = "http://example.com/endpoint"; - let service_type = OneOrList::One(ServiceType::DIDCommV2); + let service_type = ServiceType::DIDCommV2; let service = Service::new( uri_id.clone(), service_endpoint.try_into().unwrap(), - service_type.clone(), + OneOrList::One(service_type.clone()), HashMap::default(), ); assert_eq!(service.id(), &uri_id); assert_eq!(service.service_endpoint().as_ref(), service_endpoint); - assert_eq!(service.service_type(), &service_type); + assert_eq!(service.service_types(), vec!(service_type.clone())); + assert_eq!(service.service_type(), &OneOrList::One(service_type)); } #[test] diff --git a/did_core/did_doc/src/schema/service/service_accept_type.rs b/did_core/did_doc/src/schema/service/service_accept_type.rs new file mode 100644 index 0000000000..a92c7ec067 --- /dev/null +++ b/did_core/did_doc/src/schema/service/service_accept_type.rs @@ -0,0 +1,57 @@ +use std::fmt::Display; + +use serde::{Deserialize, Deserializer, Serialize}; + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub enum ServiceAcceptType { + DIDCommV1, + DIDCommV2, + Other(String), +} + +impl From<&str> for ServiceAcceptType { + fn from(s: &str) -> Self { + match s { + "didcomm/aip2;env=rfc19" => ServiceAcceptType::DIDCommV1, + "didcomm/v2" => ServiceAcceptType::DIDCommV2, + _ => ServiceAcceptType::Other(s.to_string()), + } + } +} + +impl Display for ServiceAcceptType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ServiceAcceptType::DIDCommV1 => write!(f, "didcomm/aip2;env=rfc19"), + ServiceAcceptType::DIDCommV2 => write!(f, "didcomm/v2"), + ServiceAcceptType::Other(other) => write!(f, "{}", other), + } + } +} + +impl<'de> Deserialize<'de> for ServiceAcceptType { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + match s.as_str() { + "didcomm/aip2;env=rfc19" => Ok(ServiceAcceptType::DIDCommV1), + "didcomm/v2" => Ok(ServiceAcceptType::DIDCommV2), + _ => Ok(ServiceAcceptType::Other(s)), + } + } +} + +impl Serialize for ServiceAcceptType { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + ServiceAcceptType::DIDCommV1 => serializer.serialize_str("didcomm/aip2;env=rfc19"), + ServiceAcceptType::DIDCommV2 => serializer.serialize_str("didcomm/v2"), + ServiceAcceptType::Other(other) => serializer.serialize_str(other), + } + } +} diff --git a/did_core/did_doc/src/schema/service/service_key_kind.rs b/did_core/did_doc/src/schema/service/service_key_kind.rs new file mode 100644 index 0000000000..9f16100a89 --- /dev/null +++ b/did_core/did_doc/src/schema/service/service_key_kind.rs @@ -0,0 +1,23 @@ +use std::fmt::Display; + +use did_key::DidKey; +use did_parser::DidUrl; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(untagged)] +pub enum ServiceKeyKind { + DidKey(DidKey), + Reference(DidUrl), + Value(String), +} + +impl Display for ServiceKeyKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ServiceKeyKind::Reference(did_url) => write!(f, "{}", did_url), + ServiceKeyKind::Value(value) => write!(f, "{}", value), + ServiceKeyKind::DidKey(did_key) => write!(f, "{}", did_key), + } + } +} diff --git a/did_core/did_doc/src/schema/service/extra_fields/aip1.rs b/did_core/did_doc/src/schema/service/typed/aip1.rs similarity index 76% rename from did_core/did_doc/src/schema/service/extra_fields/aip1.rs rename to did_core/did_doc/src/schema/service/typed/aip1.rs index 4f1d13ee57..d2ec34348f 100644 --- a/did_core/did_doc/src/schema/service/extra_fields/aip1.rs +++ b/did_core/did_doc/src/schema/service/typed/aip1.rs @@ -1,6 +1,7 @@ use display_as_json::Display; use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Display)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Display, TypedBuilder)] #[serde(deny_unknown_fields)] pub struct ExtraFieldsAIP1 {} diff --git a/did_core/did_doc/src/schema/service/typed/didcommv1.rs b/did_core/did_doc/src/schema/service/typed/didcommv1.rs index 1a19616b87..5408c88a4d 100644 --- a/did_core/did_doc/src/schema/service/typed/didcommv1.rs +++ b/did_core/did_doc/src/schema/service/typed/didcommv1.rs @@ -1,13 +1,16 @@ use std::collections::HashMap; -use serde::Serialize; +use display_as_json::Display; +use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; use url::Url; use crate::{ - error::DidDocumentSovError, + error::DidDocumentBuilderError, schema::{ service::{ - extra_fields::{didcommv1::ExtraFieldsDidCommV1, ServiceKeyKind}, + service_accept_type::ServiceAcceptType, + service_key_kind::ServiceKeyKind, typed::{ServiceType, TypedService}, Service, }, @@ -22,6 +25,17 @@ pub struct ServiceDidCommV1 { service: TypedService, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Display, TypedBuilder)] +#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] +pub struct ExtraFieldsDidCommV1 { + priority: u32, + recipient_keys: Vec, + routing_keys: Vec, + #[serde(default)] + accept: Vec, +} + impl ServiceDidCommV1 { pub fn new( id: Uri, @@ -31,14 +45,15 @@ impl ServiceDidCommV1 { routing_keys: Vec, ) -> Self { let extra = ExtraFieldsDidCommV1::builder() - .set_priority(priority) - .set_recipient_keys(recipient_keys) - .set_routing_keys(routing_keys) + .priority(priority) + .recipient_keys(recipient_keys) + .routing_keys(routing_keys) + .accept(vec![ServiceAcceptType::DIDCommV1]) .build(); Self { service: TypedService:: { id, - service_type: OneOrList::One(ServiceType::DIDCommV1.to_string()), + service_type: ServiceType::DIDCommV1, service_endpoint, extra, }, @@ -49,10 +64,6 @@ impl ServiceDidCommV1 { self.service.id() } - pub fn service_type(&self) -> ServiceType { - ServiceType::DIDCommV1 - } - pub fn service_endpoint(&self) -> Url { self.service.service_endpoint().clone() } @@ -63,7 +74,7 @@ impl ServiceDidCommV1 { } impl TryFrom for Service { - type Error = DidDocumentSovError; + type Error = DidDocumentBuilderError; fn try_from(did_comm_service: ServiceDidCommV1) -> Result { let mut extra_fields = HashMap::new(); @@ -87,8 +98,26 @@ impl TryFrom for Service { Ok(Service::new( did_comm_service.id().clone(), did_comm_service.service_endpoint(), - OneOrList::List(vec![ServiceType::DIDCommV1]), + OneOrList::One(ServiceType::DIDCommV1), extra_fields, )) } } + +impl ExtraFieldsDidCommV1 { + pub fn priority(&self) -> u32 { + self.priority + } + + pub fn recipient_keys(&self) -> &[ServiceKeyKind] { + self.recipient_keys.as_ref() + } + + pub fn routing_keys(&self) -> &[ServiceKeyKind] { + self.routing_keys.as_ref() + } + + pub fn accept(&self) -> &[ServiceAcceptType] { + self.accept.as_ref() + } +} diff --git a/did_core/did_doc/src/schema/service/typed/didcommv2.rs b/did_core/did_doc/src/schema/service/typed/didcommv2.rs index ee523940b3..1f0bcdbbd1 100644 --- a/did_core/did_doc/src/schema/service/typed/didcommv2.rs +++ b/did_core/did_doc/src/schema/service/typed/didcommv2.rs @@ -1,13 +1,15 @@ -use serde::Serialize; +use display_as_json::Display; +use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; use url::Url; use crate::schema::{ service::{ - extra_fields::{didcommv2::ExtraFieldsDidCommV2, ServiceAcceptType, ServiceKeyKind}, + service_accept_type::ServiceAcceptType, + service_key_kind::ServiceKeyKind, typed::{ServiceType, TypedService}, }, types::uri::Uri, - utils::OneOrList, }; #[derive(Serialize, Clone, Debug, PartialEq)] @@ -24,13 +26,13 @@ impl ServiceDidCommV2 { accept: Vec, ) -> Self { let extra: ExtraFieldsDidCommV2 = ExtraFieldsDidCommV2::builder() - .set_routing_keys(routing_keys) - .set_accept(accept) + .routing_keys(routing_keys) + .accept(accept) .build(); Self { service: TypedService:: { id, - service_type: OneOrList::One(ServiceType::DIDCommV2.to_string()), + service_type: ServiceType::DIDCommV2, service_endpoint, extra, }, @@ -41,10 +43,6 @@ impl ServiceDidCommV2 { self.service.id() } - pub fn service_type(&self) -> ServiceType { - ServiceType::DIDCommV2 - } - pub fn service_endpoint(&self) -> Url { self.service.service_endpoint().clone() } @@ -53,3 +51,23 @@ impl ServiceDidCommV2 { self.service.extra() } } + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Display, TypedBuilder)] +#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] +pub struct ExtraFieldsDidCommV2 { + #[serde(skip_serializing_if = "Vec::is_empty")] + routing_keys: Vec, + #[serde(default)] + accept: Vec, +} + +impl ExtraFieldsDidCommV2 { + pub fn routing_keys(&self) -> &[ServiceKeyKind] { + self.routing_keys.as_ref() + } + + pub fn accept(&self) -> &[ServiceAcceptType] { + self.accept.as_ref() + } +} diff --git a/did_core/did_doc/src/schema/service/extra_fields/legacy.rs b/did_core/did_doc/src/schema/service/typed/legacy.rs similarity index 84% rename from did_core/did_doc/src/schema/service/extra_fields/legacy.rs rename to did_core/did_doc/src/schema/service/typed/legacy.rs index 9a5f2143be..af97658857 100644 --- a/did_core/did_doc/src/schema/service/extra_fields/legacy.rs +++ b/did_core/did_doc/src/schema/service/typed/legacy.rs @@ -1,9 +1,10 @@ use display_as_json::Display; use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; -use crate::schema::service::extra_fields::ServiceKeyKind; +use crate::schema::service::service_key_kind::ServiceKeyKind; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Display)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Display, TypedBuilder)] #[serde(rename_all = "camelCase")] #[serde(deny_unknown_fields)] pub struct ExtraFieldsLegacy { diff --git a/did_core/did_doc/src/schema/service/typed/mod.rs b/did_core/did_doc/src/schema/service/typed/mod.rs index d368904d28..795a820cfe 100644 --- a/did_core/did_doc/src/schema/service/typed/mod.rs +++ b/did_core/did_doc/src/schema/service/typed/mod.rs @@ -1,21 +1,20 @@ +pub mod aip1; pub mod didcommv1; pub mod didcommv2; +pub mod legacy; use std::{fmt::Display, str::FromStr}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use url::Url; -use crate::{ - error::DidDocumentBuilderError, - schema::{types::uri::Uri, utils::OneOrList}, -}; +use crate::{error::DidDocumentBuilderError, schema::types::uri::Uri}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct TypedService { +pub(crate) struct TypedService { id: Uri, #[serde(rename = "type")] - service_type: OneOrList, + service_type: ServiceType, service_endpoint: Url, #[serde(flatten)] extra: E, @@ -26,10 +25,6 @@ impl TypedService { &self.id } - pub fn service_type(&self) -> &OneOrList { - &self.service_type - } - pub fn service_endpoint(&self) -> &Url { &self.service_endpoint } diff --git a/did_core/did_doc/src/schema/types/jsonwebkey.rs b/did_core/did_doc/src/schema/types/jsonwebkey.rs index ce33744901..34bc41a4a2 100644 --- a/did_core/did_doc/src/schema/types/jsonwebkey.rs +++ b/did_core/did_doc/src/schema/types/jsonwebkey.rs @@ -1,13 +1,30 @@ use std::{ collections::HashMap, + error::Error, fmt::{self, Display, Formatter}, str::FromStr, }; use serde::{Deserialize, Serialize}; use serde_json::Value; +use thiserror::Error; -use crate::error::DidDocumentBuilderError; +#[derive(Debug, Error)] +pub struct JsonWebKeyError { + reason: &'static str, + #[source] + source: Box, +} + +impl Display for JsonWebKeyError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "JsonWebKeyError, reason: {}, source: {}", + self.reason, self.source + ) + } +} #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] // TODO: Introduce proper custom type @@ -24,17 +41,24 @@ pub struct JsonWebKey { } impl JsonWebKey { - pub fn new(jwk: &str) -> Result { - Ok(serde_json::from_str(jwk)?) + // todo: More future-proof way would be creating custom error type, but seems as overkill atm? + pub fn new(jwk: &str) -> Result { + serde_json::from_str(jwk).map_err(|err| JsonWebKeyError { + reason: "Parsing JWK failed", + source: Box::new(err), + }) } - pub fn to_vec(&self) -> Result, DidDocumentBuilderError> { - serde_json::to_vec(self).map_err(|e| e.into()) + pub fn to_vec(&self) -> Result, JsonWebKeyError> { + serde_json::to_vec(self).map_err(|err| JsonWebKeyError { + reason: "Serializing JWK to vector failed", + source: Box::new(err), + }) } } impl FromStr for JsonWebKey { - type Err = DidDocumentBuilderError; + type Err = JsonWebKeyError; fn from_str(s: &str) -> Result { Self::new(s) diff --git a/did_core/did_doc/src/schema/types/multibase.rs b/did_core/did_doc/src/schema/types/multibase.rs index fdc5d642e5..2509411d68 100644 --- a/did_core/did_doc/src/schema/types/multibase.rs +++ b/did_core/did_doc/src/schema/types/multibase.rs @@ -1,12 +1,29 @@ use std::{ + error::Error, fmt::{self, Display, Formatter}, str::FromStr, }; use multibase::{decode, Base}; use serde::{Deserialize, Serialize}; +use thiserror::Error; -use crate::error::DidDocumentBuilderError; +#[derive(Debug, Error)] +pub struct MultibaseWrapperError { + reason: &'static str, + #[source] + source: Box, +} + +impl Display for MultibaseWrapperError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "MultibaseWrapperError, reason: {}, source: {}", + self.reason, self.source + ) + } +} // https://datatracker.ietf.org/doc/html/draft-multiformats-multibase-07 #[derive(Clone, Debug, PartialEq)] @@ -16,9 +33,10 @@ pub struct Multibase { } impl Multibase { - pub fn new(multibase: String) -> Result { - let (base, bytes) = decode(multibase).map_err(|err| { - DidDocumentBuilderError::InvalidInput(format!("Invalid multibase key: {}", err)) + pub fn new(multibase: String) -> Result { + let (base, bytes) = decode(multibase).map_err(|err| MultibaseWrapperError { + reason: "Decoding multibase value failed", + source: Box::new(err), })?; Ok(Self { base, bytes }) } @@ -52,7 +70,7 @@ impl<'de> Deserialize<'de> for Multibase { } impl FromStr for Multibase { - type Err = DidDocumentBuilderError; + type Err = MultibaseWrapperError; fn from_str(s: &str) -> Result { Self::new(s.to_string()) @@ -118,7 +136,14 @@ mod tests { #[test] fn test_multibase_from_str_invalid() { let multibase = "invalidmultibasekey".parse::(); - assert!(multibase.is_err()); + let err = multibase.expect_err("Error was expected."); + assert!(err + .source() + .expect("Error was expected to has source set up.") + .is::()); + assert!(err + .to_string() + .contains("Decoding multibase value failed, source: ")); } #[test] diff --git a/did_core/did_doc/src/schema/types/uri.rs b/did_core/did_doc/src/schema/types/uri.rs index ef1d0b518e..494ee6c60f 100644 --- a/did_core/did_doc/src/schema/types/uri.rs +++ b/did_core/did_doc/src/schema/types/uri.rs @@ -4,22 +4,32 @@ use std::{ }; use serde::{Deserialize, Serialize}; - -use crate::error::DidDocumentBuilderError; +use thiserror::Error; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] pub struct Uri(uniresid::Uri); +#[derive(Debug, Error)] +pub struct UriWrapperError { + reason: uniresid::Error, +} + +impl Display for UriWrapperError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "UriWrapperError: {}", self.reason) + } +} + impl Uri { - pub fn new(uri: &str) -> Result { - Ok(Self(uniresid::Uri::try_from(uri).map_err(|e| { - DidDocumentBuilderError::InvalidInput(format!("Invalid URI: {}", e)) - })?)) + pub fn new(uri: &str) -> Result { + Ok(Self( + uniresid::Uri::try_from(uri).map_err(|e| UriWrapperError { reason: e })?, + )) } } impl FromStr for Uri { - type Err = DidDocumentBuilderError; + type Err = UriWrapperError; fn from_str(s: &str) -> Result { Self::new(s) diff --git a/did_core/did_doc/src/schema/utils/error.rs b/did_core/did_doc/src/schema/utils/error.rs new file mode 100644 index 0000000000..a8d7640eba --- /dev/null +++ b/did_core/did_doc/src/schema/utils/error.rs @@ -0,0 +1,20 @@ +use std::fmt::{self, Display, Formatter}; + +use thiserror::Error; + +#[derive(Debug, Error)] +pub struct DidDocumentLookupError { + reason: String, +} + +impl DidDocumentLookupError { + pub fn new(reason: String) -> Self { + Self { reason } + } +} + +impl Display for DidDocumentLookupError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "DiddocLookupError: {}", self.reason) + } +} diff --git a/did_core/did_doc/src/schema/utils/mod.rs b/did_core/did_doc/src/schema/utils/mod.rs index db7dec277c..99c2120fc6 100644 --- a/did_core/did_doc/src/schema/utils/mod.rs +++ b/did_core/did_doc/src/schema/utils/mod.rs @@ -1,7 +1,17 @@ +pub mod error; + use std::fmt::{Debug, Display}; use serde::{Deserialize, Serialize}; +use crate::schema::{ + did_doc::DidDocument, + service::{typed::ServiceType, Service}, + types::uri::Uri, + utils::error::DidDocumentLookupError, + verification_method::{VerificationMethod, VerificationMethodKind, VerificationMethodType}, +}; + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(untagged)] pub enum OneOrList { @@ -26,3 +36,116 @@ impl Display for OneOrList { } } } + +impl DidDocument { + pub fn get_key_agreement_of_type( + &self, + key_types: &[VerificationMethodType], + ) -> Result { + for verification_method_kind in self.key_agreement() { + let verification_method = match verification_method_kind { + VerificationMethodKind::Resolved(verification_method) => verification_method, + VerificationMethodKind::Resolvable(reference) => { + match self.dereference_key(reference) { + None => { + return Err(DidDocumentLookupError::new(format!( + "Unable to resolve key agreement key by reference: {}", + reference + ))) + } + Some(verification_method) => verification_method, + } + } + }; + for key_type in key_types { + if verification_method.verification_method_type() == key_type { + return Ok(verification_method.clone()); + } + } + } + Err(DidDocumentLookupError::new( + "No supported key_agreement keys have been found".to_string(), + )) + } + + pub fn get_service_of_type( + &self, + service_type: &ServiceType, + ) -> Result { + self.service() + .iter() + .find(|service| service.service_types().contains(service_type)) + .cloned() + .ok_or(DidDocumentLookupError::new(format!( + "Failed to look up service object by type {}", + service_type + ))) + } + + pub fn get_service_by_id(&self, id: &Uri) -> Result { + self.service() + .iter() + .find(|service| service.id() == id) + .cloned() + .ok_or(DidDocumentLookupError::new(format!( + "Failed to look up service object by id {}", + id + ))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::schema::verification_method::VerificationMethodType; + + const DID_DOC: &str = r##" + { + "@context": [ + "https://w3.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1" + ], + "id": "did:web:did-actor-alice", + "alsoKnownAs": [ + "https://example.com/user-profile/123" + ], + "keyAgreement": [ + { + "id": "#foo", + "type": "Bls12381G2Key2020", + "controller": "did:web:did-actor-alice", + "publicKeyBase58": "CaSHXEvLKS6SfN9aBfkVGBpp15jSnaHazqHgLHp8KZ3Y" + }, + { + "id": "#bar", + "type": "X25519KeyAgreementKey2020", + "controller": "did:web:did-actor-alice", + "publicKeyBase58": "CaSHXEvLKS6SfN9aBfkVGBpp15jSnaHazqHgLHp8KZ3Y" + } + ] + } + "##; + + #[test] + fn should_resolve_key_agreement() { + let did_document: DidDocument = serde_json::from_str(DID_DOC).unwrap(); + let methods = &vec![ + VerificationMethodType::Ed25519VerificationKey2020, + VerificationMethodType::X25519KeyAgreementKey2020, + ]; + let key = did_document.get_key_agreement_of_type(methods).unwrap(); + assert_eq!(key.id().to_string(), "#bar") + } + + #[test] + fn should_not_resolve_key_agreement() { + let did_document: DidDocument = serde_json::from_str(DID_DOC).unwrap(); + let methods = &vec![VerificationMethodType::Bls12381G1Key2020]; + let err = did_document + .get_key_agreement_of_type(methods) + .expect_err("expected error"); + assert!(err + .to_string() + .contains("No supported key_agreement keys have been found")) + } +} diff --git a/did_core/did_doc/src/schema/verification_method/error.rs b/did_core/did_doc/src/schema/verification_method/error.rs new file mode 100644 index 0000000000..235d175e09 --- /dev/null +++ b/did_core/did_doc/src/schema/verification_method/error.rs @@ -0,0 +1,92 @@ +use std::{ + error::Error, + fmt, + fmt::{Display, Formatter}, +}; + +use thiserror::Error; + +use crate::schema::types::{jsonwebkey::JsonWebKeyError, multibase::MultibaseWrapperError}; + +#[derive(Debug, Error)] +pub struct KeyDecodingError { + reason: &'static str, + #[source] + source: Option>, +} + +impl KeyDecodingError { + pub fn new(reason: &'static str) -> Self { + KeyDecodingError { + reason, + source: None, + } + } +} + +impl Display for KeyDecodingError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match &self.source { + Some(source) => write!( + f, + "KeyDecodingError, reason: {}, source: {}", + self.reason, source + ), + None => write!(f, "KeyDecodingError, reason: {}", self.reason), + } + } +} + +impl From for KeyDecodingError { + fn from(error: pem::PemError) -> Self { + KeyDecodingError { + reason: "Failed to decode PEM", + source: Some(Box::new(error)), + } + } +} + +impl From for KeyDecodingError { + fn from(error: bs58::decode::Error) -> Self { + KeyDecodingError { + reason: "Failed to decode base58", + source: Some(Box::new(error)), + } + } +} + +impl From for KeyDecodingError { + fn from(error: base64::DecodeError) -> Self { + KeyDecodingError { + reason: "Failed to decode base64", + source: Some(Box::new(error)), + } + } +} + +impl From for KeyDecodingError { + fn from(error: hex::FromHexError) -> Self { + KeyDecodingError { + reason: "Failed to decode hex value", + source: Some(Box::new(error)), + } + } +} + +impl From for KeyDecodingError { + fn from(error: MultibaseWrapperError) -> Self { + KeyDecodingError { + reason: "Failed to decode multibase value", + source: Some(Box::new(error)), + } + } +} + +impl From for KeyDecodingError { + fn from(error: JsonWebKeyError) -> Self { + KeyDecodingError { + reason: "Failed to decode JWK", + source: Some(Box::new(error)), + } + } +} diff --git a/did_core/did_doc/src/schema/verification_method/mod.rs b/did_core/did_doc/src/schema/verification_method/mod.rs index df5ab7c02f..013e1078fc 100644 --- a/did_core/did_doc/src/schema/verification_method/mod.rs +++ b/did_core/did_doc/src/schema/verification_method/mod.rs @@ -1,4 +1,5 @@ -mod public_key; +pub mod error; +pub mod public_key; mod verification_method_kind; mod verification_method_type; diff --git a/did_core/did_doc/src/schema/verification_method/public_key.rs b/did_core/did_doc/src/schema/verification_method/public_key.rs index 96238b7f10..cc05514fb9 100644 --- a/did_core/did_doc/src/schema/verification_method/public_key.rs +++ b/did_core/did_doc/src/schema/verification_method/public_key.rs @@ -3,9 +3,9 @@ use std::str::FromStr; use base64::{engine::general_purpose, Engine}; use serde::{Deserialize, Serialize}; -use crate::{ - error::DidDocumentBuilderError, - schema::types::{jsonwebkey::JsonWebKey, multibase::Multibase}, +use crate::schema::{ + types::{jsonwebkey::JsonWebKey, multibase::Multibase}, + verification_method::error::KeyDecodingError, }; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] @@ -29,7 +29,7 @@ pub enum PublicKeyField { } impl PublicKeyField { - pub fn key_decoded(&self) -> Result, DidDocumentBuilderError> { + pub fn key_decoded(&self) -> Result, KeyDecodingError> { match self { PublicKeyField::Multibase { public_key_multibase, @@ -37,7 +37,7 @@ impl PublicKeyField { let multibase = Multibase::from_str(public_key_multibase)?; Ok(multibase.as_ref().to_vec()) } - PublicKeyField::Jwk { public_key_jwk } => public_key_jwk.to_vec(), + PublicKeyField::Jwk { public_key_jwk } => Ok(public_key_jwk.to_vec()?), PublicKeyField::Base58 { public_key_base58 } => { Ok(bs58::decode(public_key_base58).into_vec()?) } @@ -48,20 +48,22 @@ impl PublicKeyField { PublicKeyField::Pem { public_key_pem } => { Ok(pem::parse(public_key_pem.as_bytes())?.contents().to_vec()) } - PublicKeyField::Pgp { public_key_pgp: _ } => Err( - DidDocumentBuilderError::UnsupportedPublicKeyField("publicKeyPgp"), - ), + PublicKeyField::Pgp { public_key_pgp: _ } => Err(KeyDecodingError::new( + "PGP public key decoding not supported", + )), } } // TODO: Other formats - pub fn base58(&self) -> Result { + pub fn base58(&self) -> Result { Ok(bs58::encode(self.key_decoded()?).into_string()) } } #[cfg(test)] mod tests { + use std::error::Error; + use super::*; static PUBLIC_KEY_MULTIBASE: &str = "z6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc"; @@ -115,4 +117,32 @@ mod tests { PUBLIC_KEY_BYTES.to_vec() ); } + + #[test] + fn test_b58_fails() { + let public_key_field = PublicKeyField::Base58 { + public_key_base58: "abcdefghijkl".to_string(), + }; + let err = public_key_field.key_decoded().expect_err("Expected error"); + println!("Error: {}", err); + assert!(err + .source() + .expect("Error was expected to has source set up.") + .is::()); + assert!(err.to_string().contains("Failed to decode base58")); + } + + #[test] + fn test_pem_fails() { + let public_key_field = PublicKeyField::Pem { + public_key_pem: "abcdefghijkl".to_string(), + }; + let err = public_key_field.key_decoded().unwrap_err(); + println!("Error: {}", err); + assert!(err + .source() + .expect("Error was expected to has source set up.") + .is::()); + assert!(err.to_string().contains("Failed to decode PEM")); + } } diff --git a/did_core/did_methods/did_peer/examples/demo.rs b/did_core/did_methods/did_peer/examples/demo.rs index 82b7c51ed1..a02f0cf842 100644 --- a/did_core/did_methods/did_peer/examples/demo.rs +++ b/did_core/did_methods/did_peer/examples/demo.rs @@ -5,16 +5,21 @@ use did_doc::schema::{ verification_method::{VerificationMethod, VerificationMethodType}, }; use did_parser::{Did, DidUrl}; -use did_peer::peer_did::{ - numalgos::{numalgo2::Numalgo2, numalgo3::Numalgo3}, - PeerDid, +use did_peer::{ + peer_did::{ + numalgos::{numalgo2::Numalgo2, numalgo3::Numalgo3}, + PeerDid, + }, + resolver::{options::PublicKeyEncoding, PeerDidResolutionOptions, PeerDidResolver}, }; +use did_resolver::traits::resolvable::{resolution_output::DidResolutionOutput, DidResolvable}; -fn main() -> Result<(), Box> { - demo() +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<(), Box> { + demo().await } -fn demo() -> Result<(), Box> { +async fn demo() -> Result<(), Box> { let did_url = DidUrl::parse("did:foo:bar#key-1".into())?; let did = Did::parse("did:foo:bar".into())?; let verification_method = VerificationMethod::builder( @@ -42,18 +47,24 @@ fn demo() -> Result<(), Box> { peer_did_3_v2 ); - let decoded_did_doc = peer_did_2 - .to_did_doc_builder(did_peer::resolver::options::PublicKeyEncoding::Base58)? - .build(); + let DidResolutionOutput { did_document, .. } = PeerDidResolver::new() + .resolve( + peer_did_2.did(), + &PeerDidResolutionOptions { + encoding: Some(PublicKeyEncoding::Base58), + }, + ) + .await + .unwrap(); println!( "Decoded did document: \n{}", - serde_json::to_string_pretty(&decoded_did_doc)? + serde_json::to_string_pretty(&did_document)? ); Ok(()) } -#[test] -fn demo_test() -> Result<(), Box> { - demo() +#[tokio::test] +async fn demo_test() -> Result<(), Box> { + demo().await } diff --git a/did_core/did_methods/did_peer/src/error.rs b/did_core/did_methods/did_peer/src/error.rs index 15a333bb65..11f1576110 100644 --- a/did_core/did_methods/did_peer/src/error.rs +++ b/did_core/did_methods/did_peer/src/error.rs @@ -1,6 +1,9 @@ use std::convert::Infallible; -use did_doc::{error::DidDocumentSovError, schema::verification_method::VerificationMethodType}; +use did_doc::schema::{ + types::uri::UriWrapperError, + verification_method::{error::KeyDecodingError, VerificationMethodType}, +}; use crate::peer_did::numalgos::kind::NumalgoKind; @@ -18,8 +21,6 @@ pub enum DidPeerError { InvalidKeyReference(String), #[error("Invalid service: {0}")] InvalidService(String), - #[error("Sovrin DID document builder error: {0}")] - DidDocumentSovBuilderError(#[from] DidDocumentSovError), #[error("Unsupported numalgo: {0}")] UnsupportedNumalgo(NumalgoKind), #[error("Invalid numalgo character: {0}")] @@ -30,6 +31,8 @@ pub enum DidPeerError { UnsupportedVerificationMethodType(VerificationMethodType), #[error("Base 64 decoding error")] Base64DecodingError(#[from] base64::DecodeError), + #[error("Key decoding error")] + KeyDecodingError(#[from] KeyDecodingError), #[error("JSON error: {0}")] JsonError(#[from] serde_json::Error), #[error("Regex error: {0}")] @@ -43,3 +46,9 @@ impl From for DidPeerError { panic!("Attempted to convert an Infallible error") } } + +impl From for DidPeerError { + fn from(error: UriWrapperError) -> Self { + DidPeerError::ParsingError(error.to_string()) + } +} diff --git a/did_core/did_methods/did_peer/src/helpers.rs b/did_core/did_methods/did_peer/src/helpers.rs index e12231df6b..9c8725da97 100644 --- a/did_core/did_methods/did_peer/src/helpers.rs +++ b/did_core/did_methods/did_peer/src/helpers.rs @@ -1,17 +1,17 @@ use std::collections::HashMap; -use did_doc::error::DidDocumentSovError; +use did_doc::error::DidDocumentBuilderError; use serde::Serialize; use serde_json::Value; pub fn convert_to_hashmap( value: &T, -) -> Result, DidDocumentSovError> { +) -> Result, DidDocumentBuilderError> { let serialized_value = serde_json::to_value(value)?; match serialized_value { Value::Object(map) => Ok(map.into_iter().collect()), - _ => Err(DidDocumentSovError::ParsingError( + _ => Err(DidDocumentBuilderError::CustomError( "Expected JSON object".to_string(), )), } diff --git a/did_core/did_methods/did_peer/src/peer_did/generic.rs b/did_core/did_methods/did_peer/src/peer_did/generic.rs index 27b8c1592c..b3e8d63005 100644 --- a/did_core/did_methods/did_peer/src/peer_did/generic.rs +++ b/did_core/did_methods/did_peer/src/peer_did/generic.rs @@ -77,8 +77,7 @@ mod tests { .VzXwpBnMdCm1cLmKuzgESn29nqnonp1ioqrQMRHNsmjMyppzx8xB2pv7cw8q1PdDacSrdWE3dtB9f7Nxk886mdzNFoPtY\ .SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0"; - const INVALID_PEER_DID_NUMALGO2: &str = "did:peer:2\ - .SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX1"; + const INVALID_PEER_DID_NUMALGO2: &str = "did:peer:2.Qqqq"; const VALID_PEER_DID_NUMALGO3: &str = "did:peer:3.d8da5079c166b183cf815ee27747f34e116977103d8b23c96dcba9a9d9429688"; diff --git a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/encoding.rs b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/encoding.rs index e8c25c1152..fceb6ecfd2 100644 --- a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/encoding.rs +++ b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/encoding.rs @@ -127,8 +127,8 @@ fn append_key_to_did(mut did: String, key: Key, purpose: ElementPurpose) -> Stri mod tests { use did_doc::schema::{ service::{ - extra_fields::{didcommv2::ExtraFieldsDidCommV2, ServiceKeyKind}, - typed::ServiceType, + service_key_kind::ServiceKeyKind, + typed::{didcommv2::ExtraFieldsDidCommV2, ServiceType}, Service, }, types::uri::Uri, @@ -186,17 +186,17 @@ mod tests { assert_eq!(did, did_full); } - #[test] - fn test_append_encoded_service_segment() { + #[tokio::test] + async fn test_append_encoded_service_segment() { let did = "did:peer:2"; let service = "eyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0"; let did_expected = format!("{}.S{}", did, service); let extra = ExtraFieldsDidCommV2::builder() - .set_routing_keys(vec![ServiceKeyKind::Reference( + .routing_keys(vec![ServiceKeyKind::Reference( "did:example:somemediator#somekey".parse().unwrap(), )]) - .add_accept("didcomm/aip2;env=rfc587".into()) + .accept(vec!["didcomm/v2".into(), "didcomm/aip2;env=rfc587".into()]) .build(); let service = Service::new( diff --git a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/helpers.rs b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/helpers.rs index 80296d8857..05e0758e25 100644 --- a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/helpers.rs +++ b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/helpers.rs @@ -117,6 +117,7 @@ fn process_key_element( #[cfg(test)] mod tests { + use did_doc::schema::service::typed::ServiceType; use pretty_assertions::assert_eq; use super::*; @@ -193,10 +194,7 @@ mod tests { assert_eq!(built_ddo.service().len(), 1); let service = built_ddo.service().first().unwrap(); assert_eq!(service.id().to_string(), "#service-0".to_string()); - assert_eq!( - service.service_type().to_string(), - "DIDCommMessaging".to_string() - ); + assert_eq!(service.service_types(), vec!(ServiceType::DIDCommV2)); assert_eq!( service.service_endpoint().to_string(), "https://example.com/endpoint".to_string() diff --git a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/mod.rs b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/mod.rs index 56a69c7c3d..4afed44c4c 100644 --- a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/mod.rs +++ b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/mod.rs @@ -33,11 +33,10 @@ impl PeerDid { PeerDid::::parse(format!("did:peer:3.{}", numalgoless_id_hashed)) } - pub fn to_did_doc_builder( + pub(crate) fn to_did_doc_builder( &self, public_key_encoding: PublicKeyEncoding, ) -> Result { - // resolve_numalgo2(self.did(), public_key_encoding) let mut did_doc_builder: DidDocumentBuilder = DidDocument::builder(self.did().clone()); did_doc_builder = didpeer_elements_to_diddoc(did_doc_builder, self.did(), public_key_encoding)?; @@ -55,6 +54,7 @@ impl Numalgo for Numalgo2 { #[cfg(test)] mod test { use did_doc::schema::did_doc::DidDocument; + use pretty_assertions::assert_eq; use serde_json::{from_value, json}; use crate::{ @@ -64,12 +64,13 @@ mod test { #[test] fn test_peer_did_2_encode_decode() { + let expected_did_peer = "did:peer:2.Ez6MkkukgyKAdBN46UAHvia2nxmioo74F6YdvW1nBT1wfKKha.Vz6MkfoapUdLHHgSMq5PYhdHYCoqGuRku2i17cQ9zAoR5cLSm.SeyJpZCI6IiNmb29iYXIiLCJ0IjpbImRpZC1jb21tdW5pY2F0aW9uIl0sInMiOiJodHRwOi8vZHVtbXl1cmwub3JnLyIsInIiOlsiIzZNa2t1a2d5Il0sImEiOlsiZGlkY29tbS9haXAyO2Vudj1yZmMxOSJdfQ"; let value = json!({ - "id": "did:peer:2.Ez6MkkukgyKAdBN46UAHvia2nxmioo74F6YdvW1nBT1wfKKha.Vz6MkfoapUdLHHgSMq5PYhdHYCoqGuRku2i17cQ9zAoR5cLSm.SeyJpZCI6IiMwIiwidCI6WyJkaWQtY29tbXVuaWNhdGlvbiJdLCJzIjoiaHR0cDovL2R1bW15dXJsLm9yZy8iLCJhIjpbImRpZGNvbW0vYWlwMjtlbnY9cmZjMTkiXX0", + "id": expected_did_peer, "verificationMethod": [ { "id": "#6MkfoapU", - "controller": "did:example:123456789abcdefghi", + "controller": expected_did_peer, "type": "Ed25519VerificationKey2020", "publicKeyBase58": "2MKmtP5qx8wtiaYr24KhMiHH5rV3cpkkvPF4LXT4h7fP" } @@ -77,31 +78,29 @@ mod test { "keyAgreement": [ { "id": "#6Mkkukgy", - "controller": "did:example:123456789abcdefghi", + "controller": expected_did_peer, "type": "Ed25519VerificationKey2020", "publicKeyBase58": "7TVeP4vBqpZdMfTE314x7gAoyXnPgfPZozsFcjyeQ6vC" } ], "service": [ { - "id": "#0", + "id": "#foobar", "type": [ "did-communication" ], "serviceEndpoint": "http://dummyurl.org/", - "routingKeys": [], + "routingKeys": ["#6Mkkukgy"], "accept": [ "didcomm/aip2;env=rfc19" ], - "priority": 0, - "recipientKeys": [ - "did:key:z6MkkukgyKAdBN46UAHvia2nxmioo74F6YdvW1nBT1wfKKha" - ] } ] }); let ddo_original: DidDocument = from_value(value).unwrap(); let did_peer: PeerDid = PeerDid::from_did_doc(ddo_original.clone()).unwrap(); + assert_eq!(did_peer.to_string(), expected_did_peer); + let ddo_decoded: DidDocument = did_peer .to_did_doc_builder(PublicKeyEncoding::Base58) .unwrap() diff --git a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/service_abbreviation.rs b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/service_abbreviation.rs index 525da227d2..d3810e22e7 100644 --- a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/service_abbreviation.rs +++ b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/service_abbreviation.rs @@ -2,10 +2,10 @@ use std::{collections::HashMap, str::FromStr}; use did_doc::schema::{ service::{ - extra_fields::{ServiceAcceptType, ServiceKeyKind}, - typed::ServiceType, - Service, + service_accept_type::ServiceAcceptType, service_key_kind::ServiceKeyKind, + typed::ServiceType, Service, }, + types::uri::Uri, utils::OneOrList, }; use serde::{Deserialize, Serialize}; @@ -19,7 +19,7 @@ pub struct ServiceAbbreviatedDidPeer2 { // https://identity.foundation/peer-did-method-spec/#generating-a-didpeer2 // > For use with did:peer:2, service id attributes MUST be relative. // > The service MAY omit the id; however, this is NOT RECOMMEDED (clarified). - id: Option, + id: Option, #[serde(rename = "t")] service_type: OneOrList, #[serde(rename = "s")] @@ -36,7 +36,7 @@ pub struct ServiceAbbreviatedDidPeer2 { impl ServiceAbbreviatedDidPeer2 { pub fn new( - id: Option, + id: Option, service_type: OneOrList, service_endpoint: Url, routing_keys: Vec, @@ -68,6 +68,8 @@ impl ServiceAbbreviatedDidPeer2 { } } +// todo: This is encoding is lossy but shouldn't be. +// Right now any unrecognized field will not be included in the abbreviated form pub(crate) fn abbreviate_service( service: &Service, ) -> Result { @@ -125,7 +127,7 @@ pub(crate) fn abbreviate_service( } }; Ok(ServiceAbbreviatedDidPeer2::new( - Some(service.id().to_string()), + Some(service.id().clone()), service_types_abbreviated, service_endpoint, routing_keys, @@ -138,8 +140,6 @@ pub(crate) fn deabbreviate_service( index: usize, ) -> Result { let service_type = match abbreviated.service_type().clone() { - // todo: get rid of internal OneOrList representation - just have list - // also don't expose OneOrList to users, it's just matter of de/serialization OneOrList::One(service_type) => { let typed = match service_type.as_str() { "dm" => ServiceType::DIDCommV2, @@ -160,7 +160,10 @@ pub(crate) fn deabbreviate_service( } }; - let id = format!("#service-{}", index).parse()?; + let id = abbreviated + .id + .clone() + .unwrap_or(format!("#service-{}", index).parse()?); let mut service = Service::new( id, @@ -183,10 +186,10 @@ pub(crate) fn deabbreviate_service( mod tests { use did_doc::schema::{ service::{ - extra_fields::{ServiceAcceptType, ServiceKeyKind}, - typed::ServiceType, - Service, + service_accept_type::ServiceAcceptType, service_key_kind::ServiceKeyKind, + typed::ServiceType, Service, }, + types::uri::Uri, utils::OneOrList, }; use serde_json::json; @@ -199,7 +202,7 @@ mod tests { #[test] fn test_deabbreviate_service_type_value_dm() { let service_abbreviated = ServiceAbbreviatedDidPeer2 { - id: Some("#service-0".into()), + id: Some(Uri::new("#service-0").unwrap()), service_type: OneOrList::One("dm".to_string()), service_endpoint: Url::parse("https://example.org").unwrap(), routing_keys: vec![], @@ -220,9 +223,9 @@ mod tests { let accept = vec![ServiceAcceptType::DIDCommV1]; let service_endpoint = Url::parse("https://example.com/endpoint").unwrap(); let service_type = OneOrList::One(ServiceType::Other("foobar".to_string())); - let service_id = "#service-0"; + let service_id = Uri::new("#service-0").unwrap(); let service_abbreviated = ServiceAbbreviatedDidPeer2 { - id: Some(service_id.into()), + id: Some(service_id), service_type: OneOrList::One("foobar".to_string()), service_endpoint: service_endpoint.clone(), routing_keys: routing_keys.clone(), diff --git a/did_core/did_methods/did_peer/src/peer_did/numalgos/traits.rs b/did_core/did_methods/did_peer/src/peer_did/numalgos/traits.rs index 97a157221d..ac96e7e523 100644 --- a/did_core/did_methods/did_peer/src/peer_did/numalgos/traits.rs +++ b/did_core/did_methods/did_peer/src/peer_did/numalgos/traits.rs @@ -1,5 +1,4 @@ use did_doc::schema::did_doc::DidDocument; -use did_doc_sov::extra_fields::ExtraFieldsSov; use did_parser::Did; use crate::{ diff --git a/did_core/did_methods/did_peer/src/peer_did/validate.rs b/did_core/did_methods/did_peer/src/peer_did/validate.rs index 6d80c22d61..bdff2d22eb 100644 --- a/did_core/did_methods/did_peer/src/peer_did/validate.rs +++ b/did_core/did_methods/did_peer/src/peer_did/validate.rs @@ -6,7 +6,7 @@ use crate::error::DidPeerError; static GROUP_NUMALGO_0_AND_1: &str = r"([01](z)([1-9a-km-zA-HJ-NP-Z]{5,200}))"; static GROUP_NUMALGO_2: &str = - r"(2((.[AEVID](z)([1-9a-km-zA-HJ-NP-Z]{5,200}))+(.(S)[0-9a-zA-Z=]*)?))"; + r"(2((.[AEVID](z)([1-9a-km-zA-HJ-NP-Z]{5,200}))*(.(S)[0-9a-zA-Z=]*)?))"; static GROUP_NUMALGO_3: &str = r"(3\.[0-9a-fA-F]{64})"; pub static PEER_DID_REGEX: Lazy = Lazy::new(|| { diff --git a/did_core/did_methods/did_peer/src/resolver/mod.rs b/did_core/did_methods/did_peer/src/resolver/mod.rs index d2e3b5cfed..d7b8c195c4 100644 --- a/did_core/did_methods/did_peer/src/resolver/mod.rs +++ b/did_core/did_methods/did_peer/src/resolver/mod.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use did_doc::schema::did_doc::DidDocumentBuilder; use did_parser::Did; use did_resolver::{ error::GenericError, @@ -28,7 +29,6 @@ impl PeerDidResolver { pub struct PeerDidResolutionOptions { pub encoding: Option, } - #[async_trait] impl DidResolvable for PeerDidResolver { type DidResolutionOptions = PeerDidResolutionOptions; @@ -41,8 +41,9 @@ impl DidResolvable for PeerDidResolver { let peer_did = AnyPeerDid::parse(did.to_owned())?; match peer_did { AnyPeerDid::Numalgo2(peer_did) => { - let did_doc = peer_did - .to_did_doc_builder(options.encoding.unwrap_or(PublicKeyEncoding::Multibase))? + let encoding = options.encoding.unwrap_or(PublicKeyEncoding::Multibase); + let builder: DidDocumentBuilder = peer_did.to_did_doc_builder(encoding)?; + let did_doc = builder .add_also_known_as(peer_did.to_numalgo3()?.to_string().parse()?) .build(); let resolution_metadata = DidResolutionMetadata::builder() diff --git a/did_core/did_methods/did_peer/tests/resolve_positive.rs b/did_core/did_methods/did_peer/tests/resolve_positive.rs index e9732e5119..6ef2d684ea 100644 --- a/did_core/did_methods/did_peer/tests/resolve_positive.rs +++ b/did_core/did_methods/did_peer/tests/resolve_positive.rs @@ -14,12 +14,11 @@ use crate::fixtures::{ async fn resolve_positive_test(did_doc: &str, peer_did: &str, options: PeerDidResolutionOptions) { let did_document_expected = serde_json::from_str::(did_doc).unwrap(); - let ddo = PeerDidResolver + let resolution = PeerDidResolver .resolve(&peer_did.parse().unwrap(), &options) .await .unwrap(); - let did_document_actual = ddo.did_document().clone(); - assert_eq!(did_document_actual, did_document_expected); + assert_eq!(resolution.did_document, did_document_expected); } #[test] diff --git a/did_core/did_methods/did_resolver_sov/src/dereferencing/utils.rs b/did_core/did_methods/did_resolver_sov/src/dereferencing/utils.rs index 2e69c37d27..3af8aef24e 100644 --- a/did_core/did_methods/did_resolver_sov/src/dereferencing/utils.rs +++ b/did_core/did_methods/did_resolver_sov/src/dereferencing/utils.rs @@ -74,9 +74,9 @@ pub(crate) fn dereference_did_document( resolution_output: &DidResolutionOutput, did_url: &DidUrl, ) -> Result>>, DidSovError> { - let content_stream = content_stream_from(resolution_output.did_document(), did_url)?; + let content_stream = content_stream_from(&resolution_output.did_document, did_url)?; - let content_metadata = resolution_output.did_document_metadata().clone(); + let content_metadata = resolution_output.did_document_metadata.clone(); let dereferencing_metadata = DidDereferencingMetadata::builder() .content_type("application/did+json".to_string()) @@ -176,7 +176,7 @@ mod tests { assert_eq!(content_value, expected); let content_metadata = dereferencing_output.content_metadata(); - assert_eq!(content_metadata, resolution_output.did_document_metadata()); + assert_eq!(content_metadata, &resolution_output.did_document_metadata); let dereferencing_metadata = dereferencing_output.dereferencing_metadata(); assert_eq!( diff --git a/did_core/did_methods/did_resolver_sov/src/error/mod.rs b/did_core/did_methods/did_resolver_sov/src/error/mod.rs index fc08f94bb9..a05803b5ba 100644 --- a/did_core/did_methods/did_resolver_sov/src/error/mod.rs +++ b/did_core/did_methods/did_resolver_sov/src/error/mod.rs @@ -2,10 +2,11 @@ pub mod parsing; mod resolution; use aries_vcx_core::errors::error::AriesVcxCoreError; -use did_resolver::did_doc::error::DidDocumentBuilderError; +use did_resolver::did_doc::{error::DidDocumentBuilderError, schema::types::uri::UriWrapperError}; use thiserror::Error; use self::parsing::ParsingErrorSource; +use crate::error::DidSovError::ParsingError; // TODO: DIDDocumentBuilderError should do key validation and the error // should me mapped accordingly @@ -34,3 +35,9 @@ pub enum DidSovError { #[error(transparent)] Other(#[from] Box), } + +impl From for DidSovError { + fn from(error: UriWrapperError) -> Self { + ParsingError(ParsingErrorSource::DidDocumentParsingUriError(error)) + } +} diff --git a/did_core/did_methods/did_resolver_sov/src/error/parsing.rs b/did_core/did_methods/did_resolver_sov/src/error/parsing.rs index 623b8e0e91..40bd133cd0 100644 --- a/did_core/did_methods/did_resolver_sov/src/error/parsing.rs +++ b/did_core/did_methods/did_resolver_sov/src/error/parsing.rs @@ -1,4 +1,4 @@ -use did_resolver::did_parser; +use did_resolver::{did_doc::schema::types::uri::UriWrapperError, did_parser}; use thiserror::Error; use super::DidSovError; @@ -7,6 +7,8 @@ use super::DidSovError; pub enum ParsingErrorSource { #[error("DID document parsing error: {0}")] DidDocumentParsingError(#[from] did_parser::ParseError), + #[error("DID document parsing URI error: {0}")] + DidDocumentParsingUriError(#[from] UriWrapperError), #[error("Serde error: {0}")] SerdeError(#[from] serde_json::Error), #[error("Ledger response parsing error: {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 d5da81605b..dc649059b7 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 @@ -186,8 +186,11 @@ mod tests { } }"#; let verkey = "9wvq2i4xUa5umXoThe83CDgx1e5bsjZKJL4DEWvTP9qe".to_string(); - let resolution_output = ledger_response_to_ddo(did, resp, verkey).await.unwrap(); - let ddo = resolution_output.did_document(); + let DidResolutionOutput { + did_document: ddo, + did_resolution_metadata, + did_document_metadata, + } = ledger_response_to_ddo(did, resp, verkey).await.unwrap(); assert_eq!(ddo.id().to_string(), "did:example:1234567890"); assert_eq!(ddo.service()[0].id().to_string(), "did:example:1234567890"); assert_eq!( @@ -195,14 +198,11 @@ mod tests { "https://example.com/" ); assert_eq!( - resolution_output.did_document_metadata().updated().unwrap(), - chrono::Utc.timestamp_opt(1629272938, 0).unwrap() + did_document_metadata.updated().unwrap(), + Utc.timestamp_opt(1629272938, 0).unwrap() ); assert_eq!( - resolution_output - .did_resolution_metadata() - .content_type() - .unwrap(), + did_resolution_metadata.content_type().unwrap(), "application/did+json" ); if let PublicKeyField::Base58 { public_key_base58 } = 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 0ae84e2a5c..c5e026355f 100644 --- a/did_core/did_methods/did_resolver_sov/tests/resolution.rs +++ b/did_core/did_methods/did_resolver_sov/tests/resolution.rs @@ -3,8 +3,9 @@ use std::{thread, time::Duration}; use aries_vcx::common::ledger::{service_didsov::EndpointDidSov, transactions::write_endpoint}; use aries_vcx_core::{ledger::base_ledger::IndyLedgerWrite, wallet::base_wallet::BaseWallet}; use did_resolver::{ - did_doc::schema::service::typed::ServiceType, did_parser::Did, - traits::resolvable::DidResolvable, + did_doc::schema::service::typed::ServiceType, + did_parser::Did, + traits::resolvable::{resolution_output::DidResolutionOutput, DidResolvable}, }; use did_resolver_sov::resolution::DidSovResolver; use test_utils::devsetup::build_setup_profile; @@ -36,12 +37,12 @@ async fn write_service_on_ledger_and_resolve_did_doc() { let resolver = DidSovResolver::new(profile.ledger_read); let did = format!("did:sov:{}", profile.institution_did); - let did_doc = resolver + let DidResolutionOutput { did_document, .. } = resolver .resolve(&Did::parse(did.clone()).unwrap(), &()) .await .unwrap(); - assert_eq!(did_doc.did_document().id().to_string(), did); + assert_eq!(did_document.id().to_string(), did); } #[tokio::test] diff --git a/did_core/did_methods/did_resolver_web/tests/resolution.rs b/did_core/did_methods/did_resolver_web/tests/resolution.rs index 49ef0adee1..a415ed92f1 100644 --- a/did_core/did_methods/did_resolver_web/tests/resolution.rs +++ b/did_core/did_methods/did_resolver_web/tests/resolution.rs @@ -1,7 +1,9 @@ use std::{convert::Infallible, net::SocketAddr}; use did_resolver::{ - did_doc::schema::did_doc::DidDocument, did_parser::Did, traits::resolvable::DidResolvable, + did_doc::schema::did_doc::DidDocument, + did_parser::Did, + traits::resolvable::{resolution_output::DidResolutionOutput, DidResolvable}, }; use did_resolver_web::resolution::resolver::DidWebResolver; use hyper::{ @@ -111,9 +113,13 @@ async fn test_did_web_resolver() { let did_example_1 = Did::parse(format!("did:web:{}%3A{}", host, port)).unwrap(); let did_example_2 = Did::parse(format!("did:web:{}%3A{}:user:alice", host, port)).unwrap(); - let result_1 = assert_ok!(did_web_resolver.resolve(&did_example_1, &()).await); - verify_did_document(result_1.did_document()); + let DidResolutionOutput { + did_document: ddo1, .. + } = assert_ok!(did_web_resolver.resolve(&did_example_1, &()).await); + verify_did_document(&ddo1); - let result_2 = assert_ok!(did_web_resolver.resolve(&did_example_2, &()).await); - verify_did_document(result_2.did_document()); + let DidResolutionOutput { + did_document: ddo2, .. + } = assert_ok!(did_web_resolver.resolve(&did_example_2, &()).await); + verify_did_document(&ddo2); } diff --git a/did_core/did_resolver/src/traits/resolvable/resolution_output.rs b/did_core/did_resolver/src/traits/resolvable/resolution_output.rs index b73c8f75d9..7c1f96223f 100644 --- a/did_core/did_resolver/src/traits/resolvable/resolution_output.rs +++ b/did_core/did_resolver/src/traits/resolvable/resolution_output.rs @@ -10,9 +10,9 @@ use crate::shared_types::did_document_metadata::DidDocumentMetadata; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub struct DidResolutionOutput { - did_document: DidDocument, - did_resolution_metadata: DidResolutionMetadata, - did_document_metadata: DidDocumentMetadata, + pub did_document: DidDocument, + pub did_resolution_metadata: DidResolutionMetadata, + pub did_document_metadata: DidDocumentMetadata, } impl DidResolutionOutput { @@ -23,18 +23,6 @@ impl DidResolutionOutput { did_document_metadata: None, } } - - pub fn did_document(&self) -> &DidDocument { - &self.did_document - } - - pub fn did_resolution_metadata(&self) -> &DidResolutionMetadata { - &self.did_resolution_metadata - } - - pub fn did_document_metadata(&self) -> &DidDocumentMetadata { - &self.did_document_metadata - } } pub struct DidResolutionOutputBuilder { diff --git a/did_core/did_resolver_registry/src/lib.rs b/did_core/did_resolver_registry/src/lib.rs index 287688778f..20da57f687 100644 --- a/did_core/did_resolver_registry/src/lib.rs +++ b/did_core/did_resolver_registry/src/lib.rs @@ -51,7 +51,7 @@ where }; let result_inner = self.inner.resolve(did, &options).await?; - let did_document_inner_hashmap = serde_json::to_value(result_inner.did_document()) + let did_document_inner_hashmap = serde_json::to_value(result_inner.did_document) .unwrap() .as_object() .unwrap() @@ -61,8 +61,8 @@ where serde_json::from_value(Value::Object(did_document_inner_hashmap))?; Ok(DidResolutionOutput::builder(did_document) - .did_resolution_metadata(result_inner.did_resolution_metadata().clone()) - .did_document_metadata(result_inner.did_document_metadata().clone()) + .did_resolution_metadata(result_inner.did_resolution_metadata) + .did_document_metadata(result_inner.did_document_metadata) .build()) } }