From 1a526993041c18131237e039b27793b5c1ef79a0 Mon Sep 17 00:00:00 2001 From: George Mulhearn <57472912+gmulhearn@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:27:47 +1000 Subject: [PATCH] [Feature] Support DIDExchange 1.1 #1228 (#1230) * move existing into v1_0 section Signed-off-by: George Mulhearn * duplicate types Signed-off-by: George Mulhearn * static generic types for messages created and piped thru all layers Signed-off-by: George Mulhearn * simplify generics Signed-off-by: George Mulhearn * change approach to use runtime versioning rather than generics Signed-off-by: George Mulhearn * v1_1 branch processing, and some clippy Signed-off-by: George Mulhearn * remove old todos Signed-off-by: George Mulhearn * fixes for aath with self for 4/7 performance on RFC0793 & 4/7 on 0023 Signed-off-by: George Mulhearn * smalls patches from acapy testing Signed-off-by: George Mulhearn * fix up mimetype handling as a result of testing acapy (text/string) Signed-off-by: George Mulhearn * handle multikey (acapy uses this) Signed-off-by: George Mulhearn * make invite handshake 1.1 Signed-off-by: George Mulhearn * include invitation id Signed-off-by: George Mulhearn * pthid in response (for acapy) Signed-off-by: George Mulhearn * merge fix and add hack for local aath testing Signed-off-by: George Mulhearn * fixes for didpeer2 Signed-off-by: George Mulhearn * improve VM handling to understand more DIDDoc styles (acapy AATH testing) Signed-off-by: George Mulhearn * fmt Signed-off-by: George Mulhearn * clean switcher Signed-off-by: George Mulhearn * label pass, and some fixes Signed-off-by: George Mulhearn * test fixes Signed-off-by: George Mulhearn * fix did rotate content Signed-off-by: George Mulhearn * pass in handshake ver Signed-off-by: George Mulhearn * any-wrapper approach Signed-off-by: George Mulhearn * lint Signed-off-by: George Mulhearn --------- Signed-off-by: George Mulhearn Co-authored-by: George Mulhearn --- .../src/controllers/did_exchange.rs | 131 +++++++++++------- .../src/controllers/didcomm.rs | 32 ++++- .../src/handlers/did_exchange.rs | 88 ++++++------ .../src/handlers/out_of_band.rs | 2 +- aries/aries_vcx/src/handlers/util.rs | 24 +++- .../did_exchange/state_machine/generic/mod.rs | 24 +++- .../did_exchange/state_machine/helpers.rs | 83 +++++++++-- .../did_exchange/state_machine/mod.rs | 8 +- .../state_machine/requester/helpers.rs | 102 ++++++++++---- .../requester/request_sent/mod.rs | 57 +++++--- .../responder/response_sent/mod.rs | 71 +++++++--- .../states/requester/request_sent.rs | 9 +- aries/aries_vcx/src/utils/didcomm_utils.rs | 76 ++++++++-- .../src/utils/encryption_envelope.rs | 25 +++- aries/aries_vcx/tests/test_did_exchange.rs | 22 +-- aries/messages/src/decorators/attachment.rs | 32 ++++- aries/messages/src/lib.rs | 22 ++- aries/messages/src/misc/mime_type.rs | 2 + aries/messages/src/misc/mod.rs | 26 +++- .../msg_fields/protocols/did_exchange/mod.rs | 86 +----------- .../protocols/did_exchange/problem_report.rs | 112 --------------- .../did_exchange/{ => v1_0}/complete.rs | 52 +++---- .../protocols/did_exchange/v1_0/mod.rs | 77 ++++++++++ .../did_exchange/v1_0/problem_report.rs | 86 ++++++++++++ .../did_exchange/{ => v1_0}/request.rs | 69 ++++----- .../did_exchange/{ => v1_0}/response.rs | 16 +-- .../protocols/did_exchange/v1_1/complete.rs | 75 ++++++++++ .../protocols/did_exchange/v1_1/mod.rs | 77 ++++++++++ .../did_exchange/v1_1/problem_report.rs | 86 ++++++++++++ .../protocols/did_exchange/v1_1/request.rs | 118 ++++++++++++++++ .../protocols/did_exchange/v1_1/response.rs | 106 ++++++++++++++ .../protocols/did_exchange/v1_x/complete.rs | 69 +++++++++ .../protocols/did_exchange/v1_x/mod.rs | 7 + .../did_exchange/v1_x/problem_report.rs | 103 ++++++++++++++ .../protocols/did_exchange/v1_x/request.rs | 85 ++++++++++++ .../protocols/did_exchange/v1_x/response.rs | 72 ++++++++++ aries/messages/src/msg_types/mod.rs | 6 + .../src/msg_types/protocols/did_exchange.rs | 75 +++++++++- aries/messages/src/msg_types/registry.rs | 1 + did_core/did_doc/src/schema/did_doc.rs | 25 +++- .../src/schema/verification_method/mod.rs | 17 ++- .../verification_method_kind.rs | 10 ++ .../verification_method_type.rs | 5 + .../peer_did/numalgos/numalgo2/encoding.rs | 15 +- .../src/peer_did/numalgos/numalgo2/helpers.rs | 28 +++- .../src/peer_did/numalgos/numalgo2/mod.rs | 107 ++++++++++++-- .../numalgos/numalgo2/service_abbreviation.rs | 40 +++--- .../numalgos/numalgo2/verification_method.rs | 40 +++--- .../numalgos/numalgo4/construction_did_doc.rs | 4 +- .../src/peer_did/numalgos/numalgo4/mod.rs | 61 +++++++- .../did_peer/tests/fixtures/basic.rs | 22 ++- .../tests/fixtures/no_routing_keys.rs | 21 ++- .../did_peer/tests/fixtures/no_services.rs | 19 ++- 53 files changed, 1990 insertions(+), 638 deletions(-) delete mode 100644 aries/messages/src/msg_fields/protocols/did_exchange/problem_report.rs rename aries/messages/src/msg_fields/protocols/did_exchange/{ => v1_0}/complete.rs (51%) create mode 100644 aries/messages/src/msg_fields/protocols/did_exchange/v1_0/mod.rs create mode 100644 aries/messages/src/msg_fields/protocols/did_exchange/v1_0/problem_report.rs rename aries/messages/src/msg_fields/protocols/did_exchange/{ => v1_0}/request.rs (65%) rename aries/messages/src/msg_fields/protocols/did_exchange/{ => v1_0}/response.rs (83%) create mode 100644 aries/messages/src/msg_fields/protocols/did_exchange/v1_1/complete.rs create mode 100644 aries/messages/src/msg_fields/protocols/did_exchange/v1_1/mod.rs create mode 100644 aries/messages/src/msg_fields/protocols/did_exchange/v1_1/problem_report.rs create mode 100644 aries/messages/src/msg_fields/protocols/did_exchange/v1_1/request.rs create mode 100644 aries/messages/src/msg_fields/protocols/did_exchange/v1_1/response.rs create mode 100644 aries/messages/src/msg_fields/protocols/did_exchange/v1_x/complete.rs create mode 100644 aries/messages/src/msg_fields/protocols/did_exchange/v1_x/mod.rs create mode 100644 aries/messages/src/msg_fields/protocols/did_exchange/v1_x/problem_report.rs create mode 100644 aries/messages/src/msg_fields/protocols/did_exchange/v1_x/request.rs create mode 100644 aries/messages/src/msg_fields/protocols/did_exchange/v1_x/response.rs diff --git a/aries/agents/aath-backchannel/src/controllers/did_exchange.rs b/aries/agents/aath-backchannel/src/controllers/did_exchange.rs index 2eef94b880..501a40f320 100644 --- a/aries/agents/aath-backchannel/src/controllers/did_exchange.rs +++ b/aries/agents/aath-backchannel/src/controllers/did_exchange.rs @@ -4,11 +4,16 @@ use actix_web::{get, post, web, Responder}; use aries_vcx_agent::aries_vcx::{ did_parser_nom::Did, messages::{ - msg_fields::protocols::did_exchange::{request::Request, DidExchange}, + msg_fields::protocols::did_exchange::{ + v1_0::DidExchangeV1_0, v1_1::DidExchangeV1_1, v1_x::request::AnyRequest, DidExchange, + }, AriesMessage, }, - protocols::did_exchange::state_machine::requester::helpers::invitation_get_first_did_service, + protocols::did_exchange::state_machine::requester::helpers::{ + invitation_get_acceptable_did_exchange_version, invitation_get_first_did_service, + }, }; +use serde_json::Value; use crate::{ controllers::AathRequest, @@ -32,11 +37,13 @@ impl HarnessAgent { .aries_agent .out_of_band() .get_invitation(&invitation_id)?; + + let version = invitation_get_acceptable_did_exchange_version(&invitation)?; let did_inviter: Did = invitation_get_first_did_service(&invitation)?; - let (thid, pthid) = self + let (thid, pthid, my_did) = self .aries_agent .did_exchange() - .handle_msg_invitation(did_inviter.to_string(), Some(invitation_id)) + .handle_msg_invitation(did_inviter.to_string(), Some(invitation_id), version) .await?; if let Some(ref pthid) = pthid { self.store_mapping_pthid_thid(pthid.clone(), thid.clone()); @@ -46,18 +53,23 @@ impl HarnessAgent { ); } let connection_id = pthid.unwrap_or(thid); - Ok(json!({ "connection_id" : connection_id }).to_string()) + Ok(json!({ + "connection_id" : connection_id, + "my_did": my_did + }) + .to_string()) } - pub fn queue_didexchange_request(&self, request: Request) -> HarnessResult<()> { - info!("queue_didexchange_request >> request: {}", request); + pub fn queue_didexchange_request(&self, request: AnyRequest) -> HarnessResult<()> { + info!("queue_didexchange_request >> request: {:?}", request); let mut msg_buffer = self.didx_msg_buffer.write().map_err(|_| { HarnessError::from_msg( HarnessErrorType::InvalidState, "Failed to lock message buffer", ) })?; - msg_buffer.push(request.into()); + let m = AriesMessage::from(request); + msg_buffer.push(m); Ok(()) } @@ -66,13 +78,17 @@ impl HarnessAgent { &self, req: &CreateResolvableDidRequest, ) -> HarnessResult { - let (thid, pthid) = self + let (thid, pthid, my_did) = self .aries_agent .did_exchange() - .handle_msg_invitation(req.their_public_did.clone(), None) // todo: separate the case with/without invitation on did_exchange handler + .handle_msg_invitation(req.their_public_did.clone(), None, Default::default()) // todo: separate the case with/without invitation on did_exchange handler .await?; let connection_id = pthid.unwrap_or(thid); - Ok(json!({ "connection_id": connection_id }).to_string()) + Ok(json!({ + "connection_id": connection_id, + "my_did": my_did + }) + .to_string()) } // Looks up an oldest unprocessed did-exchange request message @@ -98,15 +114,21 @@ impl HarnessAgent { })? .clone() }; - if let AriesMessage::DidExchange(DidExchange::Request(ref request)) = request { - let thid = request.decorators.thread.clone().unwrap().thid; - Ok(json!({ "connection_id": thid }).to_string()) - } else { - Err(HarnessError::from_msg( - HarnessErrorType::InvalidState, - "Message is not a request", - )) - } + let request = match request { + AriesMessage::DidExchange(DidExchange::V1_0(DidExchangeV1_0::Request(request))) + | AriesMessage::DidExchange(DidExchange::V1_1(DidExchangeV1_1::Request(request))) => { + request + } + _ => { + return Err(HarnessError::from_msg( + HarnessErrorType::InvalidState, + "Message is not a request", + )) + } + }; + + let thid = request.decorators.thread.clone().unwrap().thid; + Ok(json!({ "connection_id": thid }).to_string()) } // Note: AVF identifies protocols by thid, but AATH sometimes tracks identifies did-exchange @@ -139,37 +161,52 @@ impl HarnessAgent { ) })? }; - if let AriesMessage::DidExchange(DidExchange::Request(request)) = request { - let opt_invitation = match request.decorators.thread.clone().unwrap().pthid { - None => None, - Some(pthid) => { - let invitation = self.aries_agent.out_of_band().get_invitation(&pthid)?; - Some(invitation) - } - }; - let (thid, pthid) = self - .aries_agent - .did_exchange() - .handle_msg_request(request.clone(), opt_invitation) - .await?; + let request = match request { + AriesMessage::DidExchange(DidExchange::V1_0(DidExchangeV1_0::Request(r))) => { + AnyRequest::V1_0(r) + } + AriesMessage::DidExchange(DidExchange::V1_1(DidExchangeV1_1::Request(r))) => { + AnyRequest::V1_1(r) + } + _ => { + return Err(HarnessError::from_msg( + HarnessErrorType::InvalidState, + "Message is not a request", + )) + } + }; - if let Some(pthid) = pthid { - self.store_mapping_pthid_thid(pthid, thid.clone()); - } else { - warn!("No storing pthid->this mapping; no pthid available"); + let request_thread = &request.inner().decorators.thread; + + let opt_invitation = match request_thread.as_ref().and_then(|th| th.pthid.as_ref()) { + Some(pthid) => { + let invitation = self.aries_agent.out_of_band().get_invitation(pthid)?; + Some(invitation) } + None => None, + }; + let (thid, pthid, my_did, their_did) = self + .aries_agent + .did_exchange() + .handle_msg_request(request, opt_invitation) + .await?; - self.aries_agent - .did_exchange() - .send_response(thid.clone()) - .await?; - Ok(json!({ "connection_id": thid }).to_string()) + if let Some(pthid) = pthid { + self.store_mapping_pthid_thid(pthid, thid.clone()); } else { - Err(HarnessError::from_msg( - HarnessErrorType::InvalidState, - "Message is not a request", - )) + warn!("No storing pthid->this mapping; no pthid available"); } + + self.aries_agent + .did_exchange() + .send_response(thid.clone()) + .await?; + Ok(json!({ + "connection_id": thid, + "my_did": my_did, + "their_did": their_did + }) + .to_string()) } pub async fn didx_get_state(&self, connection_id: &str) -> HarnessResult { @@ -200,7 +237,7 @@ impl HarnessAgent { #[post("/send-request")] async fn send_did_exchange_request( - req: web::Json>, + req: web::Json>, agent: web::Data>, ) -> impl Responder { agent diff --git a/aries/agents/aath-backchannel/src/controllers/didcomm.rs b/aries/agents/aath-backchannel/src/controllers/didcomm.rs index 8fc0b2f6e3..ef8637f928 100644 --- a/aries/agents/aath-backchannel/src/controllers/didcomm.rs +++ b/aries/agents/aath-backchannel/src/controllers/didcomm.rs @@ -6,7 +6,10 @@ use aries_vcx_agent::aries_vcx::{ msg_fields::protocols::{ connection::Connection, cred_issuance::{v1::CredentialIssuanceV1, CredentialIssuance}, - did_exchange::DidExchange, + did_exchange::{ + v1_0::DidExchangeV1_0, v1_1::DidExchangeV1_1, v1_x::request::AnyRequest, + DidExchange, + }, notification::Notification, present_proof::{v1::PresentProofV1, PresentProof}, }, @@ -197,25 +200,40 @@ impl HarnessAgent { async fn handle_did_exchange_msg(&self, msg: DidExchange) -> HarnessResult<()> { match msg { - DidExchange::Request(request) => { - self.queue_didexchange_request(request)?; + DidExchange::V1_0(DidExchangeV1_0::Request(request)) => { + self.queue_didexchange_request(AnyRequest::V1_0(request))?; } - DidExchange::Response(response) => { + DidExchange::V1_1(DidExchangeV1_1::Request(request)) => { + self.queue_didexchange_request(AnyRequest::V1_1(request))?; + } + DidExchange::V1_0(DidExchangeV1_0::Response(response)) => { + let res = self + .aries_agent + .did_exchange() + .handle_msg_response(response.into()) + .await; + if let Err(err) = res { + error!("Error sending complete: {:?}", err); + }; + } + DidExchange::V1_1(DidExchangeV1_1::Response(response)) => { let res = self .aries_agent .did_exchange() - .handle_msg_response(response) + .handle_msg_response(response.into()) .await; if let Err(err) = res { error!("Error sending complete: {:?}", err); }; } - DidExchange::Complete(complete) => { + DidExchange::V1_0(DidExchangeV1_0::Complete(complete)) + | DidExchange::V1_1(DidExchangeV1_1::Complete(complete)) => { self.aries_agent .did_exchange() .handle_msg_complete(complete)?; } - DidExchange::ProblemReport(problem_report) => { + DidExchange::V1_0(DidExchangeV1_0::ProblemReport(problem_report)) + | DidExchange::V1_1(DidExchangeV1_1::ProblemReport(problem_report)) => { self.aries_agent .did_exchange() .receive_problem_report(problem_report)?; diff --git a/aries/agents/aries-vcx-agent/src/handlers/did_exchange.rs b/aries/agents/aries-vcx-agent/src/handlers/did_exchange.rs index ea0edc7455..958f4bdd4c 100644 --- a/aries/agents/aries-vcx-agent/src/handlers/did_exchange.rs +++ b/aries/agents/aries-vcx-agent/src/handlers/did_exchange.rs @@ -5,12 +5,13 @@ use aries_vcx::{ did_parser_nom::Did, messages::{ msg_fields::protocols::{ - did_exchange::{ - complete::Complete, problem_report::ProblemReport, request::Request, - response::Response, + did_exchange::v1_x::{ + complete::Complete, problem_report::ProblemReport, request::AnyRequest, + response::AnyResponse, }, out_of_band::invitation::Invitation as OobInvitation, }, + msg_types::protocols::did_exchange::DidExchangeTypeV1, AriesMessage, }, protocols::did_exchange::{ @@ -63,10 +64,12 @@ impl DidcommHandlerDidExchange { &self, their_did: String, invitation_id: Option, - ) -> AgentResult<(String, Option)> { + version: DidExchangeTypeV1, + ) -> AgentResult<(String, Option, String)> { // todo: type the return type let (our_peer_did, _our_verkey) = create_peer_did_4(self.wallet.as_ref(), self.service_endpoint.clone(), vec![]).await?; + let our_did = our_peer_did.did().to_string(); let their_did: Did = their_did.parse()?; let (requester, request) = GenericDidExchange::construct_request( @@ -74,6 +77,8 @@ impl DidcommHandlerDidExchange { invitation_id, &their_did, &our_peer_did, + "".to_owned(), + version, ) .await?; @@ -82,31 +87,16 @@ impl DidcommHandlerDidExchange { // /agent/command/did-exchange/{id} where {id} is actually {pthid}. // We should have internal strategy to manage threads ourselves, and build necessary // extensions/mappings/accommodations in AATH backchannel - warn!("send_request >>> request: {}", request); - let pthid = request - .clone() - .decorators - .thread - .ok_or_else(|| { - AgentError::from_msg( - AgentErrorKind::InvalidState, - "Request did not contain a thread", - ) - })? - .pthid; - + warn!("send_request >>> request: {:?}", request); + let req_thread = request.inner().decorators.thread.as_ref().ok_or_else(|| { + AgentError::from_msg( + AgentErrorKind::InvalidState, + "Request did not contain a thread", + ) + })?; + let pthid = req_thread.pthid.clone(); // todo: messages must provide easier way to access this without all the shenanigans - let thid = request - .clone() - .decorators - .thread - .ok_or_else(|| { - AgentError::from_msg( - AgentErrorKind::InvalidState, - "Request did not contain a thread id", - ) - })? - .thid; + let thid = req_thread.thid.clone(); let ddo_their = requester.their_did_doc(); let ddo_our = requester.our_did_document(); @@ -127,30 +117,30 @@ impl DidcommHandlerDidExchange { VcxHttpClient .send_message(encryption_envelope.0, service.service_endpoint()) .await?; - Ok((thid, pthid)) + Ok((thid, pthid, our_did)) } // todo: whether invitation exists should handle the framework based on (p)thread matching // rather than being supplied by upper layers pub async fn handle_msg_request( &self, - request: Request, + request: AnyRequest, invitation: Option, - ) -> AgentResult<(String, Option)> { + ) -> AgentResult<(String, Option, String, String)> { // todo: type the return type // Todo: messages should expose fallible API to get thid (for any aries msg). It's common // pattern - let thid = request - .clone() - .decorators - .thread + let thread = request.inner().decorators.thread.as_ref(); + + let thid = thread .ok_or_else(|| { AgentError::from_msg( AgentErrorKind::InvalidState, "Request did not contain a thread id", ) })? - .thid; + .thid + .clone(); // Todo: "invitation_key" should not be None; see the todo inside this scope let invitation_key = match invitation { @@ -165,34 +155,34 @@ impl DidcommHandlerDidExchange { } }; - let (peer_did_4_invitee, _our_verkey) = + let (our_peer_did, _our_verkey) = create_peer_did_4(self.wallet.as_ref(), self.service_endpoint.clone(), vec![]).await?; - let pthid = request - .clone() - .decorators - .thread - .clone() + let pthid = thread .ok_or_else(|| { AgentError::from_msg( AgentErrorKind::InvalidState, "Request did not contain a thread", ) })? - .pthid; + .pthid + .clone(); let (responder, response) = GenericDidExchange::handle_request( self.wallet.as_ref(), self.resolver_registry.clone(), request, - &peer_did_4_invitee, + &our_peer_did, invitation_key, ) .await?; self.did_exchange .insert(&thid, (responder.clone(), Some(response.into())))?; - Ok((thid, pthid)) + let our_did = responder.our_did_document().id().to_string(); + let their_did = responder.their_did_doc().id().to_string(); + + Ok((thid, pthid, our_did, their_did)) } // todo: perhaps injectable transports? Or just return the message let the caller send it? @@ -225,8 +215,12 @@ impl DidcommHandlerDidExchange { } // todo: break down into "process_response" and "send_complete" - pub async fn handle_msg_response(&self, response: Response) -> AgentResult { - let thid = response.decorators.thread.thid.clone(); + pub async fn handle_msg_response(&self, response: AnyResponse) -> AgentResult { + let thread = match response { + AnyResponse::V1_0(ref inner) => &inner.decorators.thread, + AnyResponse::V1_1(ref inner) => &inner.decorators.thread, + }; + let thid = thread.thid.clone(); let (requester, _) = self.did_exchange.get(&thid)?; diff --git a/aries/agents/aries-vcx-agent/src/handlers/out_of_band.rs b/aries/agents/aries-vcx-agent/src/handlers/out_of_band.rs index b955dd49c5..6d01745a55 100644 --- a/aries/agents/aries-vcx-agent/src/handlers/out_of_band.rs +++ b/aries/agents/aries-vcx-agent/src/handlers/out_of_band.rs @@ -44,7 +44,7 @@ impl ServiceOutOfBand { let sender = OutOfBandSender::create() .append_service(&OobService::Did(peer_did.to_string())) .append_handshake_protocol(Protocol::DidExchangeType(DidExchangeType::V1( - DidExchangeTypeV1::new_v1_0(), + DidExchangeTypeV1::new_v1_1(), )))?; self.out_of_band.insert( diff --git a/aries/aries_vcx/src/handlers/util.rs b/aries/aries_vcx/src/handlers/util.rs index b2e0c7ce33..2ed30d0c75 100644 --- a/aries/aries_vcx/src/handlers/util.rs +++ b/aries/aries_vcx/src/handlers/util.rs @@ -4,7 +4,7 @@ use messages::{ connection::{invitation::Invitation, Connection}, coordinate_mediation::CoordinateMediation, cred_issuance::{v1::CredentialIssuanceV1, v2::CredentialIssuanceV2, CredentialIssuance}, - did_exchange::DidExchange, + did_exchange::{v1_0::DidExchangeV1_0, v1_1::DidExchangeV1_1, DidExchange}, discover_features::DiscoverFeatures, notification::Notification, out_of_band::{invitation::Invitation as OobInvitation, OutOfBand}, @@ -75,7 +75,9 @@ macro_rules! make_attach_from_str { .data(attach_data) .build(); attach.id = Some($id); - attach.mime_type = Some(messages::misc::MimeType::Json); + attach.mime_type = Some(shared::maybe_known::MaybeKnown::Known( + messages::misc::MimeType::Json, + )); attach }}; } @@ -241,12 +243,22 @@ pub fn verify_thread_id(thread_id: &str, message: &AriesMessage) -> VcxResult<() AriesMessage::CoordinateMediation(CoordinateMediation::Keylist(msg)) => { matches_opt_thread_id!(msg, thread_id) } - AriesMessage::DidExchange(DidExchange::Request(msg)) => { + AriesMessage::DidExchange(DidExchange::V1_0(DidExchangeV1_0::Request(msg))) + | AriesMessage::DidExchange(DidExchange::V1_1(DidExchangeV1_1::Request(msg))) => { matches_opt_thread_id!(msg, thread_id) } - AriesMessage::DidExchange(DidExchange::Response(msg)) => matches_thread_id!(msg, thread_id), - AriesMessage::DidExchange(DidExchange::Complete(msg)) => matches_thread_id!(msg, thread_id), - AriesMessage::DidExchange(DidExchange::ProblemReport(msg)) => { + AriesMessage::DidExchange(DidExchange::V1_0(DidExchangeV1_0::Response(msg))) => { + matches_thread_id!(msg, thread_id) + } + AriesMessage::DidExchange(DidExchange::V1_0(DidExchangeV1_0::Complete(msg))) + | AriesMessage::DidExchange(DidExchange::V1_1(DidExchangeV1_1::Complete(msg))) => { + matches_thread_id!(msg, thread_id) + } + AriesMessage::DidExchange(DidExchange::V1_0(DidExchangeV1_0::ProblemReport(msg))) + | AriesMessage::DidExchange(DidExchange::V1_1(DidExchangeV1_1::ProblemReport(msg))) => { + matches_thread_id!(msg, thread_id) + } + AriesMessage::DidExchange(DidExchange::V1_1(DidExchangeV1_1::Response(msg))) => { matches_thread_id!(msg, thread_id) } }; diff --git a/aries/aries_vcx/src/protocols/did_exchange/state_machine/generic/mod.rs b/aries/aries_vcx/src/protocols/did_exchange/state_machine/generic/mod.rs index ccc6178a09..08de2843fc 100644 --- a/aries/aries_vcx/src/protocols/did_exchange/state_machine/generic/mod.rs +++ b/aries/aries_vcx/src/protocols/did_exchange/state_machine/generic/mod.rs @@ -5,8 +5,14 @@ use did_doc::schema::did_doc::DidDocument; use did_parser_nom::Did; use did_peer::peer_did::{numalgos::numalgo4::Numalgo4, PeerDid}; use did_resolver_registry::ResolverRegistry; -use messages::msg_fields::protocols::did_exchange::{ - complete::Complete, problem_report::ProblemReport, request::Request, response::Response, +use messages::{ + msg_fields::protocols::did_exchange::v1_x::{ + complete::{AnyComplete, Complete}, + problem_report::ProblemReport, + request::AnyRequest, + response::AnyResponse, + }, + msg_types::protocols::did_exchange::DidExchangeTypeV1, }; use public_key::Key; pub use thin_state::ThinState; @@ -88,13 +94,17 @@ impl GenericDidExchange { invitation_id: Option, their_did: &Did, our_peer_did: &PeerDid, - ) -> Result<(Self, Request), AriesVcxError> { + our_label: String, + version: DidExchangeTypeV1, + ) -> Result<(Self, AnyRequest), AriesVcxError> { let TransitionResult { state, output } = DidExchangeRequester::::construct_request( resolver_registry, invitation_id, their_did, our_peer_did, + our_label, + version, ) .await?; Ok(( @@ -106,10 +116,10 @@ impl GenericDidExchange { pub async fn handle_request( wallet: &impl BaseWallet, resolver_registry: Arc, - request: Request, + request: AnyRequest, our_peer_did: &PeerDid, invitation_key: Option, - ) -> Result<(Self, Response), AriesVcxError> { + ) -> Result<(Self, AnyResponse), AriesVcxError> { let TransitionResult { state, output } = DidExchangeResponder::::receive_request( wallet, @@ -127,9 +137,9 @@ impl GenericDidExchange { pub async fn handle_response( self, - response: Response, + response: AnyResponse, resolver_registry: Arc, - ) -> Result<(Self, Complete), (Self, AriesVcxError)> { + ) -> Result<(Self, AnyComplete), (Self, AriesVcxError)> { match self { GenericDidExchange::Requester(requester_state) => match requester_state { RequesterState::RequestSent(request_sent_state) => { 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 75c4eafc12..91dbf5c83f 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 @@ -9,7 +9,7 @@ use did_doc::schema::{ verification_method::{PublicKeyField, VerificationMethodType}, }; use did_key::DidKey; -use did_parser_nom::DidUrl; +use did_parser_nom::{Did, DidUrl}; use did_peer::peer_did::{ numalgos::numalgo4::{ construction_did_doc::{DidPeer4ConstructionDidDocument, DidPeer4VerificationMethod}, @@ -23,8 +23,10 @@ use messages::{ thread::Thread, timing::Timing, }, - msg_fields::protocols::did_exchange::response::{ - Response, ResponseContent, ResponseDecorators, + msg_fields::protocols::did_exchange::{ + v1_0::response::{Response as ResponseV1_0, ResponseContent as ResponseV1_0Content}, + v1_1::response::{Response as ResponseV1_1, ResponseContent as ResponseV1_1Content}, + v1_x::response::ResponseDecorators, }, }; use public_key::{Key, KeyType}; @@ -41,20 +43,60 @@ use crate::{ utils::base64::URL_SAFE_LENIENT, }; -pub(crate) fn construct_response( +pub(crate) fn construct_response_v1_0( + // pthid inclusion is overkill in practice, but needed. see: https://github.com/hyperledger/aries-rfcs/issues/817 + request_pthid: Option, request_id: String, - our_did_document: &DidDocument, - signed_attach: Attachment, -) -> Response { - let content = ResponseContent::builder() - .did(our_did_document.id().to_string()) - .did_doc(Some(signed_attach)) + did: &Did, + signed_diddoc_attach: Attachment, +) -> ResponseV1_0 { + let thread = match request_pthid { + Some(request_pthid) => Thread::builder() + .thid(request_id) + .pthid(request_pthid) + .build(), + None => Thread::builder().thid(request_id).build(), + }; + + let content = ResponseV1_0Content::builder() + .did(did.to_string()) + .did_doc(Some(signed_diddoc_attach)) + .build(); + let decorators = ResponseDecorators::builder() + .thread(thread) + .timing(Timing::builder().out_time(Utc::now()).build()) + .build(); + ResponseV1_0::builder() + .id(Uuid::new_v4().to_string()) + .content(content) + .decorators(decorators) + .build() +} + +pub(crate) fn construct_response_v1_1( + // pthid inclusion is overkill in practice, but needed. see: https://github.com/hyperledger/aries-rfcs/issues/817 + request_pthid: Option, + request_id: String, + did: &Did, + signed_didrotate_attach: Attachment, +) -> ResponseV1_1 { + let thread = match request_pthid { + Some(request_pthid) => Thread::builder() + .thid(request_id) + .pthid(request_pthid) + .build(), + None => Thread::builder().thid(request_id).build(), + }; + + let content = ResponseV1_1Content::builder() + .did(did.to_string()) + .did_rotate(signed_didrotate_attach) .build(); let decorators = ResponseDecorators::builder() - .thread(Thread::builder().thid(request_id).build()) + .thread(thread) .timing(Timing::builder().out_time(Utc::now()).build()) .build(); - Response::builder() + ResponseV1_1::builder() .id(Uuid::new_v4().to_string()) .content(content) .decorators(decorators) @@ -92,14 +134,16 @@ pub async fn create_peer_did_4( info!("Prepared service for peer:did:4 generation: {} ", service); let vm_ka = DidPeer4VerificationMethod::builder() - .id(vm_ka_id) + .id(vm_ka_id.clone()) .verification_method_type(VerificationMethodType::Ed25519VerificationKey2020) .public_key(PublicKeyField::Multibase { public_key_multibase: key_enc.fingerprint(), }) .build(); let mut construction_did_doc = DidPeer4ConstructionDidDocument::new(); - construction_did_doc.add_key_agreement(vm_ka); + construction_did_doc.add_verification_method(vm_ka); + construction_did_doc.add_key_agreement_ref(vm_ka_id); + construction_did_doc.add_service(service); info!( @@ -125,6 +169,17 @@ pub(crate) fn ddo_to_attach(ddo: DidDocument) -> Result Attachment { + let content_b64 = base64::engine::Engine::encode(&URL_SAFE_LENIENT, did.did()); + Attachment::builder() + .data( + AttachmentData::builder() + .content(AttachmentType::Base64(content_b64)) + .build(), + ) + .build() +} + // TODO: Obviously, extract attachment signing // TODO: JWS verification pub(crate) async fn jws_sign_attach( diff --git a/aries/aries_vcx/src/protocols/did_exchange/state_machine/mod.rs b/aries/aries_vcx/src/protocols/did_exchange/state_machine/mod.rs index 896e0843be..c54f331aff 100644 --- a/aries/aries_vcx/src/protocols/did_exchange/state_machine/mod.rs +++ b/aries/aries_vcx/src/protocols/did_exchange/state_machine/mod.rs @@ -10,8 +10,8 @@ use chrono::Utc; use did_doc::schema::did_doc::DidDocument; use messages::{ decorators::{thread::Thread, timing::Timing}, - msg_fields::protocols::did_exchange::problem_report::{ - ProblemCode, ProblemReport, ProblemReportContent, ProblemReportDecorators, + msg_fields::protocols::did_exchange::v1_x::problem_report::{ + AnyProblemReport, ProblemCode, ProblemReport, ProblemReportContent, ProblemReportDecorators, }, }; use uuid::Uuid; @@ -38,7 +38,7 @@ impl DidExchange { self, reason: String, problem_code: Option, - ) -> TransitionResult, ProblemReport> { + ) -> TransitionResult, AnyProblemReport> { let content = ProblemReportContent::builder() .problem_code(problem_code) .explain(Some(reason.clone())) @@ -67,7 +67,7 @@ impl DidExchange { our_did_document: self.our_did_document, their_did_document: self.their_did_document, }, - output: problem_report, + output: AnyProblemReport::V1_1(problem_report), } } 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 e658130832..cd00d4cf91 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,19 +6,28 @@ use messages::{ timing::Timing, }, msg_fields::protocols::{ - did_exchange::{ - complete::{Complete, CompleteDecorators}, - request::{Request, RequestContent, RequestDecorators}, + did_exchange::v1_x::{ + complete::{AnyComplete, Complete, CompleteDecorators}, + request::{AnyRequest, Request, RequestContent, RequestDecorators}, }, out_of_band::invitation::{Invitation, OobService}, }, + msg_types::{ + protocols::did_exchange::{DidExchangeType, DidExchangeTypeV1}, + Protocol, + }, }; use shared::maybe_known::MaybeKnown; use uuid::Uuid; use crate::errors::error::{AriesVcxError, AriesVcxErrorKind, VcxResult}; -pub fn construct_request(invitation_id: Option, our_did: String) -> Request { +pub fn construct_request( + invitation_id: Option, + our_did: String, + our_label: String, + version: DidExchangeTypeV1, +) -> AnyRequest { let msg_id = Uuid::new_v4().to_string(); let thid = msg_id.clone(); let thread = match invitation_id { @@ -30,43 +39,50 @@ pub fn construct_request(invitation_id: Option, our_did: String) -> Requ .timing(Timing::builder().out_time(Utc::now()).build()) .build(); let content = RequestContent::builder() - .label("".into()) + .label(our_label) .did(our_did) .did_doc(None) .goal(Some("To establish a connection".into())) // Rejected if non-empty by acapy .goal_code(Some(MaybeKnown::Known(ThreadGoalCode::AriesRelBuild))) // Rejected if non-empty by acapy .build(); - Request::builder() + let req = Request::builder() .id(msg_id) .content(content) .decorators(decorators) - .build() + .build(); + + match version { + DidExchangeTypeV1::V1_1(_) => AnyRequest::V1_1(req), + DidExchangeTypeV1::V1_0(_) => AnyRequest::V1_0(req), + } } -pub fn construct_didexchange_complete(request_id: String) -> Complete { - // assuming we'd want to support RFC 100% and include pthread in complete message, we can add - // new function argument: `invitation_id: Option` - // We choose not to do this, as it's rather historic artifact and doesn't have justification in - // practice see https://github.com/hyperledger/aries-rfcs/issues/817 - // We can then build thread decorator conditionally: - // let thread = match invitation_id { - // Some(invitation_id) => Thread::builder() - // .thid(request_id) - // .pthid(invitation_id) - // .build(), - // None => Thread::builder() - // .thid(request_id) - // .build() - // }; - let thread = Thread::builder().thid(request_id).build(); +pub fn construct_didexchange_complete( + // pthid inclusion is overkill in practice, but needed. see: https://github.com/hyperledger/aries-rfcs/issues/817 + invitation_id: Option, + request_id: String, + version: DidExchangeTypeV1, +) -> AnyComplete { + let thread = match invitation_id { + Some(invitation_id) => Thread::builder() + .thid(request_id) + .pthid(invitation_id) + .build(), + None => Thread::builder().thid(request_id).build(), + }; let decorators = CompleteDecorators::builder() .thread(thread) .timing(Timing::builder().out_time(Utc::now()).build()) .build(); - Complete::builder() + let msg = Complete::builder() .id(Uuid::new_v4().to_string()) .decorators(decorators) - .build() + .build(); + + match version { + DidExchangeTypeV1::V1_1(_) => AnyComplete::V1_1(msg), + DidExchangeTypeV1::V1_0(_) => AnyComplete::V1_0(msg), + } } /// We are going to support only DID service values in did-exchange protocol unless there's explicit @@ -89,3 +105,39 @@ pub fn invitation_get_first_did_service(invitation: &Invitation) -> VcxResult VcxResult { + // determine acceptable protocol + let mut did_exch_v1_1_accepted = false; + let mut did_exch_v1_0_accepted = false; + for proto in invitation.content.handshake_protocols.iter().flatten() { + let MaybeKnown::Known(Protocol::DidExchangeType(DidExchangeType::V1(exch_proto))) = proto + else { + continue; + }; + if matches!(exch_proto, DidExchangeTypeV1::V1_1(_)) { + did_exch_v1_1_accepted = true; + continue; + } + if matches!(exch_proto, DidExchangeTypeV1::V1_0(_)) { + did_exch_v1_0_accepted = true; + } + } + + let version = match (did_exch_v1_1_accepted, did_exch_v1_0_accepted) { + (true, _) => DidExchangeTypeV1::new_v1_1(), + (false, true) => DidExchangeTypeV1::new_v1_0(), + _ => { + return Err(AriesVcxError::from_msg( + AriesVcxErrorKind::InvalidInput, + "OOB invitation does not have a suitable handshake protocol for DIDExchange", + )) + } + }; + + Ok(version) +} 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 fd04b83830..30c351239d 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 @@ -4,8 +4,11 @@ use did_parser_nom::Did; use did_peer::peer_did::{numalgos::numalgo4::Numalgo4, PeerDid}; use did_resolver::traits::resolvable::resolution_output::DidResolutionOutput; use did_resolver_registry::ResolverRegistry; -use messages::msg_fields::protocols::did_exchange::{ - complete::Complete as CompleteMessage, request::Request, response::Response, +use messages::{ + msg_fields::protocols::did_exchange::v1_x::{ + complete::AnyComplete, request::AnyRequest, response::AnyResponse, + }, + msg_types::protocols::did_exchange::DidExchangeTypeV1, }; use super::DidExchangeRequester; @@ -27,8 +30,10 @@ impl DidExchangeRequester { invitation_id: Option, their_did: &Did, our_peer_did: &PeerDid, - ) -> Result, AriesVcxError> { - info!( + our_label: String, + version: DidExchangeTypeV1, + ) -> Result, AriesVcxError> { + debug!( "DidExchangeRequester::construct_request >> their_did: {}, our_peer_did: \ {}", their_did, our_peer_did @@ -38,16 +43,22 @@ impl DidExchangeRequester { .await? .did_document; let our_did_document = our_peer_did.resolve_did_doc()?; - let request = construct_request(invitation_id.clone(), our_peer_did.to_string()); + let request = construct_request( + invitation_id.clone(), + our_peer_did.to_string(), + our_label, + version, + ); - info!( - "DidExchangeRequester::construct_request << prepared request: {}", + debug!( + "DidExchangeRequester::construct_request << prepared request: {:?}", request ); Ok(TransitionResult { state: DidExchangeRequester::from_parts( RequestSent { - request_id: request.id.clone(), + request_id: request.inner().id.clone(), + invitation_id, }, their_did_document, our_did_document, @@ -58,16 +69,17 @@ impl DidExchangeRequester { pub async fn receive_response( self, - response: Response, + response: AnyResponse, resolver_registry: Arc, - ) -> Result< - TransitionResult, CompleteMessage>, - TransitionError, - > { - info!( + ) -> Result, AnyComplete>, TransitionError> + { + debug!( "DidExchangeRequester::receive_response >> response: {:?}", response ); + let version = response.get_version(); + let response = response.into_v1_1(); + if response.decorators.thread.thid != self.state.request_id { return Err(TransitionError { error: AriesVcxError::from_msg( @@ -77,17 +89,20 @@ impl DidExchangeRequester { state: self, }); } + // TODO - process differently depending on version let did_document = if let Some(ddo) = response.content.did_doc { - info!( + debug!( "DidExchangeRequester::receive_response >> the Response message \ contained attached ddo" ); + // verify JWS signature on attachment attachment_to_diddoc(ddo).map_err(to_transition_error(self.clone()))? } else { - info!( + debug!( "DidExchangeRequester::receive_response >> the Response message \ contains pairwise DID, resolving to DID Document" ); + // verify JWS signature on attachment IF version == 1.1 let did = &Did::parse(response.content.did).map_err(to_transition_error(self.clone()))?; let DidResolutionOutput { did_document, .. } = resolver_registry @@ -97,9 +112,13 @@ impl DidExchangeRequester { did_document }; - let complete_message = construct_didexchange_complete(self.state.request_id.clone()); - info!( - "DidExchangeRequester::receive_response << complete_message: {}", + let complete_message = construct_didexchange_complete( + self.state.invitation_id, + self.state.request_id.clone(), + version, + ); + debug!( + "DidExchangeRequester::receive_response << complete_message: {:?}", complete_message ); 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 5b1c8afa31..524eee4f77 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 @@ -4,8 +4,13 @@ use aries_vcx_wallet::wallet::base_wallet::BaseWallet; use did_doc::schema::did_doc::DidDocument; use did_peer::peer_did::{numalgos::numalgo4::Numalgo4, PeerDid}; use did_resolver_registry::ResolverRegistry; -use messages::msg_fields::protocols::did_exchange::{ - complete::Complete, request::Request, response::Response, +use messages::{ + msg_fields::protocols::did_exchange::v1_x::{ + complete::Complete, + request::{AnyRequest, Request}, + response::AnyResponse, + }, + msg_types::protocols::did_exchange::DidExchangeTypeV1, }; use public_key::Key; @@ -14,7 +19,8 @@ use crate::{ errors::error::{AriesVcxError, AriesVcxErrorKind}, protocols::did_exchange::{ state_machine::helpers::{ - attachment_to_diddoc, construct_response, ddo_to_attach, jws_sign_attach, + assemble_did_rotate_attachment, attachment_to_diddoc, construct_response_v1_0, + construct_response_v1_1, ddo_to_attach, jws_sign_attach, }, states::{completed::Completed, responder::response_sent::ResponseSent}, transition::{transition_error::TransitionError, transition_result::TransitionResult}, @@ -25,38 +31,61 @@ impl DidExchangeResponder { pub async fn receive_request( wallet: &impl BaseWallet, resolver_registry: Arc, - request: Request, + request: AnyRequest, our_peer_did: &PeerDid, invitation_key: Option, - ) -> Result, Response>, AriesVcxError> { - info!( - "DidExchangeResponder::receive_request >> request: {}, our_peer_did: \ + ) -> Result, AnyResponse>, AriesVcxError> + { + debug!( + "DidExchangeResponder::receive_request >> request: {:?}, our_peer_did: \ {}, invitation_key: {:?}", request, our_peer_did, invitation_key ); + let version = request.get_version(); + let request = request.into_inner(); + let their_ddo = resolve_ddo_from_request(&resolver_registry, &request).await?; let our_did_document = our_peer_did.resolve_did_doc()?; - // 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 ddo_attachment_unsigned = ddo_to_attach(our_did_document.clone())?; - let ddo_attachment = match invitation_key { + + let unsigned_attachment = match version { + DidExchangeTypeV1::V1_1(_) => assemble_did_rotate_attachment(our_peer_did.did()), + DidExchangeTypeV1::V1_0(_) => ddo_to_attach(our_did_document.clone())?, + }; + let attachment = match invitation_key { + Some(invitation_key) => { + // TODO: this must happen only if we rotate DID; We currently do that always + // can skip signing if we don't rotate did document (unique p2p invitations + // with peer DIDs) + jws_sign_attach(unsigned_attachment, invitation_key, wallet).await? + } None => { // TODO: not signing if invitation_key is not provided, that would be case for // implicit invitations. However we should probably sign with // the key the request used as recipient_vk to anoncrypt the request // So argument "invitation_key" should be required - ddo_attachment_unsigned - } - Some(invitation_key) => { - // TODO: this must happen only if we rotate DID; We currently do that always - // can skip signing if we don't rotate did document (unique p2p invitations - // with peer DIDs) - jws_sign_attach(ddo_attachment_unsigned, invitation_key, wallet).await? + unsigned_attachment } }; - let response = construct_response(request.id.clone(), &our_did_document, ddo_attachment); - info!( - "DidExchangeResponder::receive_request << prepared response: {}", + + let request_id = request.id.clone(); + let request_pthid = request.decorators.thread.and_then(|thid| thid.pthid); + + let response = match version { + DidExchangeTypeV1::V1_1(_) => AnyResponse::V1_1(construct_response_v1_1( + request_pthid, + request_id, + our_peer_did.did(), + attachment, + )), + DidExchangeTypeV1::V1_0(_) => AnyResponse::V1_0(construct_response_v1_0( + request_pthid, + request_id, + our_peer_did.did(), + attachment, + )), + }; + debug!( + "DidExchangeResponder::receive_request << prepared response: {:?}", response ); diff --git a/aries/aries_vcx/src/protocols/did_exchange/states/requester/request_sent.rs b/aries/aries_vcx/src/protocols/did_exchange/states/requester/request_sent.rs index 6f844b217a..dcfb89ebfa 100644 --- a/aries/aries_vcx/src/protocols/did_exchange/states/requester/request_sent.rs +++ b/aries/aries_vcx/src/protocols/did_exchange/states/requester/request_sent.rs @@ -2,9 +2,12 @@ use crate::protocols::did_exchange::states::traits::ThreadId; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct RequestSent { - pub request_id: String, /* Note: Historical artifact in Aries RFC, used to fill pthread - * value in Complete message See more info here: https://github.com/hyperledger/aries-rfcs/issues/817 - * pub invitation_id: Option */ + pub request_id: String, + /* Note: Historical artifact in Aries RFC, used to fill pthread + * value in Complete message + * See more info here: https://github.com/hyperledger/aries-rfcs/issues/817 + */ + pub invitation_id: Option, } impl ThreadId for RequestSent { diff --git a/aries/aries_vcx/src/utils/didcomm_utils.rs b/aries/aries_vcx/src/utils/didcomm_utils.rs index b044966659..2a369f26ff 100644 --- a/aries/aries_vcx/src/utils/didcomm_utils.rs +++ b/aries/aries_vcx/src/utils/didcomm_utils.rs @@ -2,7 +2,7 @@ use did_doc::schema::{ did_doc::DidDocument, service::service_key_kind::ServiceKeyKind, types::uri::Uri, verification_method::VerificationMethodType, }; -use public_key::Key; +use public_key::{Key, KeyType}; use crate::errors::error::{AriesVcxError, AriesVcxErrorKind, VcxResult}; @@ -34,28 +34,74 @@ fn resolve_service_key_to_typed_key( } } -pub fn resolve_base58_key_agreement(did_document: &DidDocument) -> VcxResult { - let key_types = [ +/// Resolves the first ed25519 base58 public key (a.k.a. verkey) within the DIDDocuments key +/// agreement keys. Useful for resolving keys that can be used for packing DIDCommV1 messages. +pub fn resolve_ed25519_base58_key_agreement(did_document: &DidDocument) -> VcxResult { + let vm_types = [ VerificationMethodType::Ed25519VerificationKey2018, VerificationMethodType::Ed25519VerificationKey2020, VerificationMethodType::X25519KeyAgreementKey2019, VerificationMethodType::X25519KeyAgreementKey2020, + VerificationMethodType::Multikey, + // would be nice to search for X25519 VM types which could be derived into ed25519 keys + // for the encryption envelope to use. + // would be nice to search for other VM types which _could_ be ed25519 (jwk etc) ]; - let key_base58 = did_document.get_key_agreement_of_type(&key_types)?; - Ok(key_base58.public_key()?.base58()) + let vm = did_document.get_key_agreement_of_type(&vm_types)?; + let key = vm.public_key()?; + + match key.key_type() { + KeyType::Ed25519 => {} + _ => { + return Err(AriesVcxError::from_msg( + AriesVcxErrorKind::InvalidVerkey, + format!("Cannot resolve key agreement as an Ed25519 key: {vm:?}"), + )) + } + } + + Ok(vm.public_key()?.base58()) +} + +pub fn get_ed25519_base58_routing_keys( + their_did_doc: &DidDocument, + service_id: &Uri, +) -> VcxResult> { + let service = their_did_doc.get_service_by_id(service_id)?; + let Ok(routing_keys) = service.extra_field_routing_keys() else { + return Ok(vec![]); + }; + + let mut naked_routing_keys = Vec::new(); + + for key in routing_keys.iter() { + let pub_key = resolve_service_key_to_typed_key(key, their_did_doc)?; + + if pub_key.key_type() == &KeyType::Ed25519 { + naked_routing_keys.push(pub_key.base58()); + } + } + + Ok(naked_routing_keys) } -pub fn get_routing_keys(their_did_doc: &DidDocument, service_id: &Uri) -> VcxResult> { +pub fn get_ed25519_base58_recipient_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, their_did_doc)?.base58()); - } - Ok(naked_routing_keys) + let Ok(recipient_keys) = service.extra_field_recipient_keys() else { + return Ok(vec![]); + }; + + let mut naked_recipient_keys = Vec::new(); + + for key in recipient_keys.iter() { + let pub_key = resolve_service_key_to_typed_key(key, their_did_doc)?; + if pub_key.key_type() == &KeyType::Ed25519 { + naked_recipient_keys.push(pub_key.base58()); } - Err(_err) => Ok(Vec::new()), } + + Ok(naked_recipient_keys) } diff --git a/aries/aries_vcx/src/utils/encryption_envelope.rs b/aries/aries_vcx/src/utils/encryption_envelope.rs index 51ebca1f23..30fc4e75a9 100644 --- a/aries/aries_vcx/src/utils/encryption_envelope.rs +++ b/aries/aries_vcx/src/utils/encryption_envelope.rs @@ -8,9 +8,10 @@ use messages::{ use public_key::{Key, KeyType}; use uuid::Uuid; +use super::didcomm_utils::get_ed25519_base58_recipient_keys; use crate::{ errors::error::prelude::*, - utils::didcomm_utils::{get_routing_keys, resolve_base58_key_agreement}, + utils::didcomm_utils::{get_ed25519_base58_routing_keys, resolve_ed25519_base58_key_agreement}, }; #[derive(Debug)] @@ -62,16 +63,24 @@ impl EncryptionEnvelope { their_did_doc: &DidDocument, their_service_id: &Uri, ) -> VcxResult { - 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, their_service_id)?; + let sender_vk = resolve_ed25519_base58_key_agreement(our_did_doc)?; + + let recipient_key = { + let service_keys = get_ed25519_base58_recipient_keys(their_did_doc, their_service_id)?; + match service_keys.into_iter().next() { + Some(key) => key, + // as a backup, use the first key agreement key, or none + None => resolve_ed25519_base58_key_agreement(their_did_doc)?, + } + }; + let routing_keys = get_ed25519_base58_routing_keys(their_did_doc, their_service_id)?; EncryptionEnvelope::create_from_keys( wallet, data, - Some(&sender_vk.to_string()), - recipient_key.to_string(), - routing_keys.iter().map(|k| k.to_string()).collect(), + Some(&sender_vk), + recipient_key, + routing_keys, ) .await } @@ -80,6 +89,8 @@ impl EncryptionEnvelope { wallet: &impl BaseWallet, data: &[u8], sender_vk: Option<&str>, + // TODO - why not have encryption envelope take typed [Key]s, and enforce they are + // KeyType::Ed25519 recipient_key: String, routing_keys: Vec, ) -> VcxResult { diff --git a/aries/aries_vcx/tests/test_did_exchange.rs b/aries/aries_vcx/tests/test_did_exchange.rs index 6930b47148..ad1b2ae096 100644 --- a/aries/aries_vcx/tests/test_did_exchange.rs +++ b/aries/aries_vcx/tests/test_did_exchange.rs @@ -14,7 +14,10 @@ use aries_vcx::{ states::{requester::request_sent::RequestSent, responder::response_sent::ResponseSent}, transition::transition_result::TransitionResult, }, - utils::{didcomm_utils::resolve_base58_key_agreement, encryption_envelope::EncryptionEnvelope}, + utils::{ + didcomm_utils::resolve_ed25519_base58_key_agreement, + encryption_envelope::EncryptionEnvelope, + }, }; use aries_vcx_ledger::ledger::indy_vdr_ledger::DefaultIndyLedgerRead; use did_doc::schema::{ @@ -27,8 +30,9 @@ use did_peer::resolver::PeerDidResolver; use did_resolver_registry::ResolverRegistry; use did_resolver_sov::resolution::DidSovResolver; use log::info; -use messages::msg_fields::protocols::out_of_band::invitation::{ - Invitation, InvitationContent, OobService, +use messages::{ + msg_fields::protocols::out_of_band::invitation::{Invitation, InvitationContent, OobService}, + msg_types::protocols::did_exchange::DidExchangeTypeV1, }; use pretty_assertions::assert_eq; use test_utils::devsetup::{dev_build_profile_vdr_ledger, SetupPoolDirectory}; @@ -42,8 +46,8 @@ pub mod utils; fn assert_key_agreement(a: DidDocument, b: DidDocument) { log::warn!("comparing did doc a: {}, b: {}", a, b); - let a_key = resolve_base58_key_agreement(&a).unwrap(); - let b_key = resolve_base58_key_agreement(&b).unwrap(); + let a_key = resolve_ed25519_base58_key_agreement(&a).unwrap(); + let b_key = resolve_ed25519_base58_key_agreement(&b).unwrap(); assert_eq!(a_key, b_key); } @@ -125,11 +129,13 @@ async fn did_exchange_test() -> Result<(), Box> { Some(invitation.id), &did_inviter, &requesters_peer_did, + "some-label".to_owned(), + DidExchangeTypeV1::new_v1_1(), ) .await .unwrap(); info!( - "Invitee processes invitation, builds up request {}", + "Invitee processes invitation, builds up request {:?}", &request ); @@ -162,7 +168,7 @@ async fn did_exchange_test() -> Result<(), Box> { .await .unwrap(); - let responder = responder.receive_complete(complete).unwrap(); + let responder = responder.receive_complete(complete.into_inner()).unwrap(); info!("Asserting did document of requester"); assert_key_agreement( @@ -200,7 +206,7 @@ async fn did_exchange_test() -> Result<(), Box> { info!("Encrypted message: {:?}", m); let requesters_peer_did = requesters_peer_did.resolve_did_doc()?; - let expected_sender_vk = resolve_base58_key_agreement(&requesters_peer_did)?; + let expected_sender_vk = resolve_ed25519_base58_key_agreement(&requesters_peer_did)?; let unpacked = EncryptionEnvelope::auth_unpack(&agent_invitee.wallet, m.0, &expected_sender_vk).await?; diff --git a/aries/messages/src/decorators/attachment.rs b/aries/messages/src/decorators/attachment.rs index 8ce8249efa..c3cd17f82d 100644 --- a/aries/messages/src/decorators/attachment.rs +++ b/aries/messages/src/decorators/attachment.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_json::Value; +use shared::maybe_known::MaybeKnown; use typed_builder::TypedBuilder; use url::Url; @@ -13,8 +14,7 @@ use crate::misc::MimeType; #[serde(rename_all = "snake_case")] pub struct Attachment { #[builder(default, setter(strip_option))] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(rename = "@id")] + #[serde(skip_serializing_if = "Option::is_none", rename = "@id")] pub id: Option, #[builder(default, setter(strip_option))] #[serde(skip_serializing_if = "Option::is_none")] @@ -22,10 +22,12 @@ pub struct Attachment { #[builder(default, setter(strip_option))] #[serde(skip_serializing_if = "Option::is_none")] pub filename: Option, - #[builder(default, setter(strip_option))] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(rename = "mime-type")] - pub mime_type: Option, + // mimetype wrapped in MaybeKnown to handle any deserialization from any string. + // other agents may be using mimetypes that this crate is not immediately aware of, but + // we should not fail to deserialize as a result. + #[builder(default, setter(transform = |x: MimeType| Some(MaybeKnown::Known(x))))] + #[serde(skip_serializing_if = "Option::is_none", rename = "mime-type")] + pub mime_type: Option>, #[builder(default, setter(strip_option))] #[serde(skip_serializing_if = "Option::is_none")] pub lastmod_time: Option>, @@ -195,4 +197,22 @@ pub mod tests { test_utils::test_serde(attachment, expected); } + + #[test] + fn test_extended_attachment_with_unknown_mime() { + let mut attachment = make_extended_attachment(); + attachment.mime_type = Some(MaybeKnown::Unknown(String::from("unknown/vcx"))); + + let expected = json!({ + "@id": attachment.id, + "description": attachment.description, + "filename": attachment.filename, + "mime-type": "unknown/vcx", + "lastmod_time": attachment.lastmod_time, + "byte_count": attachment.byte_count, + "data": attachment.data + }); + + test_utils::test_serde(attachment, expected); + } } diff --git a/aries/messages/src/lib.rs b/aries/messages/src/lib.rs index 89c1eb8e74..65f20cc785 100644 --- a/aries/messages/src/lib.rs +++ b/aries/messages/src/lib.rs @@ -17,13 +17,17 @@ use display_as_json::Display; use misc::utils; use msg_fields::protocols::{ cred_issuance::{v1::CredentialIssuanceV1, v2::CredentialIssuanceV2, CredentialIssuance}, - did_exchange::DidExchange, + did_exchange::{v1_0::DidExchangeV1_0, v1_1::DidExchangeV1_1, DidExchange}, pickup::Pickup, present_proof::{v2::PresentProofV2, PresentProof}, }; use msg_types::{ - cred_issuance::CredentialIssuanceType, present_proof::PresentProofType, - report_problem::ReportProblemTypeV1_0, routing::RoutingTypeV1_0, MsgWithType, + cred_issuance::CredentialIssuanceType, + present_proof::PresentProofType, + protocols::did_exchange::{DidExchangeType, DidExchangeTypeV1}, + report_problem::ReportProblemTypeV1_0, + routing::RoutingTypeV1_0, + MsgWithType, }; use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; @@ -188,8 +192,13 @@ impl DelayedSerde for AriesMessage { CoordinateMediation::delayed_deserialize((msg_type, kind_str), deserializer) .map(From::from) } - Protocol::DidExchangeType(msg_type) => { - DidExchange::delayed_deserialize((msg_type, kind_str), deserializer).map(From::from) + Protocol::DidExchangeType(DidExchangeType::V1(DidExchangeTypeV1::V1_0(msg_type))) => { + DidExchangeV1_0::delayed_deserialize((msg_type, kind_str), deserializer) + .map(|x| AriesMessage::from(DidExchange::V1_0(x))) + } + Protocol::DidExchangeType(DidExchangeType::V1(DidExchangeTypeV1::V1_1(msg_type))) => { + DidExchangeV1_1::delayed_deserialize((msg_type, kind_str), deserializer) + .map(|x| AriesMessage::from(DidExchange::V1_1(x))) } } } @@ -214,7 +223,8 @@ impl DelayedSerde for AriesMessage { Self::Notification(v) => v.delayed_serialize(serializer), Self::Pickup(v) => v.delayed_serialize(serializer), Self::CoordinateMediation(v) => v.delayed_serialize(serializer), - Self::DidExchange(v) => v.delayed_serialize(serializer), + Self::DidExchange(DidExchange::V1_0(v)) => v.delayed_serialize(serializer), + Self::DidExchange(DidExchange::V1_1(v)) => v.delayed_serialize(serializer), } } } diff --git a/aries/messages/src/misc/mime_type.rs b/aries/messages/src/misc/mime_type.rs index bd846cfca3..e5eddeb055 100644 --- a/aries/messages/src/misc/mime_type.rs +++ b/aries/messages/src/misc/mime_type.rs @@ -14,6 +14,8 @@ pub enum MimeType { Pdf, #[serde(rename = "text/plain")] Plain, + #[serde(rename = "text/string")] + String, #[serde(rename = "didcomm/aip1")] Aip1, #[serde(rename = "didcomm/aip2;env=rfc19")] diff --git a/aries/messages/src/misc/mod.rs b/aries/messages/src/misc/mod.rs index b264511e6f..ae768dd633 100644 --- a/aries/messages/src/misc/mod.rs +++ b/aries/messages/src/misc/mod.rs @@ -70,25 +70,37 @@ pub mod test_utils { assert_eq!(Protocol::from(protocol_type), deserialized) } - pub fn test_msg(content: T, decorators: U, msg_kind: V, mut expected: Value) + pub fn test_msg(content: T, decorators: U, msg_kind: V, expected: Value) where AriesMessage: From>, V: MessageKind, Protocol: From, { let id = "test".to_owned(); - let msg_type = build_msg_type(msg_kind); - - let obj = expected.as_object_mut().expect("JSON object"); - obj.insert("@id".to_owned(), json!(id)); - obj.insert("@type".to_owned(), json!(msg_type)); let msg = MsgParts::::builder() .id(id) .content(content) .decorators(decorators) .build(); - let msg = AriesMessage::from(msg); + + test_constructed_msg(msg, msg_kind, expected); + } + + pub fn test_constructed_msg(complete: M, msg_kind: V, mut expected: Value) + where + AriesMessage: From, + V: MessageKind, + Protocol: From, + { + let id = "test".to_owned(); + let msg_type = build_msg_type(msg_kind); + + let obj = expected.as_object_mut().expect("JSON object"); + obj.insert("@id".to_owned(), json!(id)); + obj.insert("@type".to_owned(), json!(msg_type)); + + let msg = AriesMessage::from(complete); test_serde(msg, expected); } diff --git a/aries/messages/src/msg_fields/protocols/did_exchange/mod.rs b/aries/messages/src/msg_fields/protocols/did_exchange/mod.rs index c7e676f06d..2424b2c3a4 100644 --- a/aries/messages/src/msg_fields/protocols/did_exchange/mod.rs +++ b/aries/messages/src/msg_fields/protocols/did_exchange/mod.rs @@ -1,85 +1,13 @@ -// TODO: Why are not msg fields and types grouped by protocol??? -pub mod complete; -// TODO: Duplicates connection problem report, deduplicate -pub mod problem_report; -pub mod request; -pub mod response; - use derive_more::From; -use serde::{de::Error, Deserialize, Serialize}; -use shared::misc::serde_ignored::SerdeIgnored as NoContent; +use v1_0::DidExchangeV1_0; +use v1_1::DidExchangeV1_1; -use self::{ - complete::{Complete, CompleteDecorators}, - problem_report::{ProblemReport, ProblemReportContent, ProblemReportDecorators}, - request::{Request, RequestContent, RequestDecorators}, - response::{Response, ResponseContent, ResponseDecorators}, -}; -use crate::{ - misc::utils::{into_msg_with_type, transit_to_aries_msg}, - msg_fields::traits::DelayedSerde, - msg_types::{ - protocols::did_exchange::{ - DidExchangeType as DidExchangeKind, DidExchangeTypeV1, DidExchangeTypeV1_0, - }, - MsgWithType, - }, -}; +pub mod v1_0; +pub mod v1_1; +pub mod v1_x; #[derive(Clone, Debug, From, PartialEq)] pub enum DidExchange { - Request(Request), - Response(Response), - ProblemReport(ProblemReport), - Complete(Complete), + V1_0(DidExchangeV1_0), + V1_1(DidExchangeV1_1), } - -impl DelayedSerde for DidExchange { - type MsgType<'a> = (DidExchangeKind, &'a str); - - fn delayed_deserialize<'de, D>( - msg_type: Self::MsgType<'de>, - deserializer: D, - ) -> Result - where - D: serde::Deserializer<'de>, - { - let (protocol, kind_str) = msg_type; - - let kind = match protocol { - DidExchangeKind::V1(DidExchangeTypeV1::V1_0(kind)) => kind.kind_from_str(kind_str), - }; - - match kind.map_err(D::Error::custom)? { - DidExchangeTypeV1_0::Request => Request::deserialize(deserializer).map(From::from), - DidExchangeTypeV1_0::Response => Response::deserialize(deserializer).map(From::from), - DidExchangeTypeV1_0::ProblemReport => { - ProblemReport::deserialize(deserializer).map(From::from) - } - DidExchangeTypeV1_0::Complete => Complete::deserialize(deserializer).map(From::from), - } - } - - fn delayed_serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match self { - Self::Request(v) => MsgWithType::from(v).serialize(serializer), - Self::Response(v) => MsgWithType::from(v).serialize(serializer), - Self::ProblemReport(v) => MsgWithType::from(v).serialize(serializer), - Self::Complete(v) => MsgWithType::from(v).serialize(serializer), - } - } -} - -// TODO: Seems to be required only for tests? -transit_to_aries_msg!(RequestContent: RequestDecorators, DidExchange); -transit_to_aries_msg!(ResponseContent: ResponseDecorators, DidExchange); -transit_to_aries_msg!(ProblemReportContent: ProblemReportDecorators, DidExchange); -transit_to_aries_msg!(NoContent: CompleteDecorators, DidExchange); - -into_msg_with_type!(Request, DidExchangeTypeV1_0, Request); -into_msg_with_type!(Response, DidExchangeTypeV1_0, Response); -into_msg_with_type!(ProblemReport, DidExchangeTypeV1_0, ProblemReport); -into_msg_with_type!(Complete, DidExchangeTypeV1_0, Complete); diff --git a/aries/messages/src/msg_fields/protocols/did_exchange/problem_report.rs b/aries/messages/src/msg_fields/protocols/did_exchange/problem_report.rs deleted file mode 100644 index 598cfba41a..0000000000 --- a/aries/messages/src/msg_fields/protocols/did_exchange/problem_report.rs +++ /dev/null @@ -1,112 +0,0 @@ -use serde::{Deserialize, Serialize}; -use typed_builder::TypedBuilder; - -use crate::{ - decorators::{localization::MsgLocalization, thread::Thread, timing::Timing}, - msg_parts::MsgParts, -}; - -pub type ProblemReport = MsgParts; - -#[derive(Clone, Debug, Deserialize, Serialize, Default, PartialEq, TypedBuilder)] -pub struct ProblemReportContent { - #[serde(rename = "problem-code")] - #[serde(skip_serializing_if = "Option::is_none")] - pub problem_code: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub explain: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum ProblemCode { - RequestNotAccepted, - RequestProcessingError, - ResponseNotAccepted, - ResponseProcessingError, -} - -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, TypedBuilder)] -pub struct ProblemReportDecorators { - #[serde(rename = "~thread")] - pub thread: Thread, - #[builder(default, setter(strip_option))] - #[serde(rename = "~l10n")] - #[serde(skip_serializing_if = "Option::is_none")] - pub localization: Option, - #[builder(default, setter(strip_option))] - #[serde(rename = "~timing")] - #[serde(skip_serializing_if = "Option::is_none")] - pub timing: Option, -} - -impl ProblemReportDecorators { - pub fn new(thread: Thread) -> Self { - Self { - thread, - localization: None, - timing: None, - } - } -} - -#[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::field_reassign_with_default)] -mod tests { - use serde_json::json; - - use super::*; - use crate::{ - decorators::{ - localization::tests::make_extended_msg_localization, - thread::tests::make_extended_thread, timing::tests::make_extended_timing, - }, - misc::test_utils, - msg_types::protocols::did_exchange::DidExchangeTypeV1_0, - }; - - #[test] - fn test_minimal_conn_problem_report() { - let content = ProblemReportContent::default(); - - let decorators = ProblemReportDecorators::new(make_extended_thread()); - - let expected = json!({ - "~thread": decorators.thread - }); - - test_utils::test_msg( - content, - decorators, - DidExchangeTypeV1_0::ProblemReport, - expected, - ); - } - - #[test] - fn test_extended_conn_problem_report() { - let mut content = ProblemReportContent::default(); - content.problem_code = Some(ProblemCode::RequestNotAccepted); - content.explain = Some("test_conn_problem_report_explain".to_owned()); - - let mut decorators = ProblemReportDecorators::new(make_extended_thread()); - decorators.timing = Some(make_extended_timing()); - decorators.localization = Some(make_extended_msg_localization()); - - let expected = json!({ - "problem-code": content.problem_code, - "explain": content.explain, - "~thread": decorators.thread, - "~timing": decorators.timing, - "~l10n": decorators.localization - }); - - test_utils::test_msg( - content, - decorators, - DidExchangeTypeV1_0::ProblemReport, - expected, - ); - } -} diff --git a/aries/messages/src/msg_fields/protocols/did_exchange/complete.rs b/aries/messages/src/msg_fields/protocols/did_exchange/v1_0/complete.rs similarity index 51% rename from aries/messages/src/msg_fields/protocols/did_exchange/complete.rs rename to aries/messages/src/msg_fields/protocols/did_exchange/v1_0/complete.rs index d93e8086f2..1cab9fbbec 100644 --- a/aries/messages/src/msg_fields/protocols/did_exchange/complete.rs +++ b/aries/messages/src/msg_fields/protocols/did_exchange/v1_0/complete.rs @@ -1,25 +1,15 @@ -use serde::{Deserialize, Serialize}; use shared::misc::serde_ignored::SerdeIgnored as NoContent; -use typed_builder::TypedBuilder; use crate::{ - decorators::{thread::Thread, timing::Timing}, - msg_parts::MsgParts, + msg_fields::protocols::did_exchange::v1_x::complete::CompleteDecorators, msg_parts::MsgParts, }; +/// Alias type for DIDExchange v1.0 Complete message. +/// Note that since this inherits from the V1.X message, the direct serialization +/// of this message type is not recommended, as it will be indistinguisable from V1.1. +/// Instead, this type should be converted to/from an AriesMessage pub type Complete = MsgParts; -// TODO: Pthid is mandatory in this case! -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, TypedBuilder)] -pub struct CompleteDecorators { - #[serde(rename = "~thread")] - pub thread: Thread, - #[builder(default, setter(strip_option))] - #[serde(rename = "~timing")] - #[serde(skip_serializing_if = "Option::is_none")] - pub timing: Option, -} - #[cfg(test)] #[allow(clippy::unwrap_used)] #[allow(clippy::field_reassign_with_default)] @@ -33,6 +23,7 @@ mod tests { timing::tests::make_extended_timing, }, misc::test_utils, + msg_fields::protocols::did_exchange::v1_x::complete::AnyComplete, msg_types::protocols::did_exchange::DidExchangeTypeV1_0, }; @@ -46,17 +37,17 @@ mod tests { } }); - let decorators = CompleteDecorators { - thread, - timing: None, - }; + let decorators = CompleteDecorators::builder().thread(thread).build(); - test_utils::test_msg( - NoContent, - decorators, - DidExchangeTypeV1_0::Complete, - expected, + let msg = AnyComplete::V1_0( + Complete::builder() + .id("test".to_owned()) + .content(NoContent) + .decorators(decorators) + .build(), ); + + test_utils::test_constructed_msg(msg, DidExchangeTypeV1_0::Complete, expected); } #[test] @@ -71,11 +62,14 @@ mod tests { "~timing": serde_json::to_value(make_extended_timing()).unwrap() }); - test_utils::test_msg( - NoContent, - decorators, - DidExchangeTypeV1_0::Complete, - expected, + let msg = AnyComplete::V1_0( + Complete::builder() + .id("test".to_owned()) + .content(NoContent) + .decorators(decorators) + .build(), ); + + test_utils::test_constructed_msg(msg, DidExchangeTypeV1_0::Complete, expected); } } diff --git a/aries/messages/src/msg_fields/protocols/did_exchange/v1_0/mod.rs b/aries/messages/src/msg_fields/protocols/did_exchange/v1_0/mod.rs new file mode 100644 index 0000000000..37a5adde35 --- /dev/null +++ b/aries/messages/src/msg_fields/protocols/did_exchange/v1_0/mod.rs @@ -0,0 +1,77 @@ +pub mod complete; +pub mod problem_report; +pub mod request; +pub mod response; + +use derive_more::From; +use serde::{de::Error, Deserialize, Serialize}; + +use self::{ + complete::Complete, + problem_report::ProblemReport, + request::Request, + response::{Response, ResponseContent}, +}; +use super::{v1_x::response::ResponseDecorators, DidExchange}; +use crate::{ + misc::utils::{into_msg_with_type, transit_to_aries_msg}, + msg_fields::traits::DelayedSerde, + msg_types::{protocols::did_exchange::DidExchangeTypeV1_0, MsgKindType, MsgWithType}, +}; + +#[derive(Clone, Debug, From, PartialEq)] +pub enum DidExchangeV1_0 { + Request(Request), + Response(Response), + ProblemReport(ProblemReport), + Complete(Complete), +} + +impl DelayedSerde for DidExchangeV1_0 { + type MsgType<'a> = (MsgKindType, &'a str); + + fn delayed_deserialize<'de, D>( + msg_type: Self::MsgType<'de>, + deserializer: D, + ) -> Result + where + D: serde::Deserializer<'de>, + { + let (protocol, kind_str) = msg_type; + let kind = protocol.kind_from_str(kind_str); + + match kind.map_err(D::Error::custom)? { + DidExchangeTypeV1_0::Request => Request::deserialize(deserializer).map(From::from), + DidExchangeTypeV1_0::Response => Response::deserialize(deserializer).map(From::from), + DidExchangeTypeV1_0::ProblemReport => { + ProblemReport::deserialize(deserializer).map(From::from) + } + DidExchangeTypeV1_0::Complete => Complete::deserialize(deserializer).map(From::from), + } + } + + fn delayed_serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + Self::Request(v) => { + MsgWithType::<_, DidExchangeTypeV1_0>::from(v).serialize(serializer) + } + Self::Response(v) => MsgWithType::from(v).serialize(serializer), + Self::ProblemReport(v) => { + MsgWithType::<_, DidExchangeTypeV1_0>::from(v).serialize(serializer) + } + Self::Complete(v) => { + MsgWithType::<_, DidExchangeTypeV1_0>::from(v).serialize(serializer) + } + } + } +} + +transit_to_aries_msg!(ResponseContent: ResponseDecorators, DidExchangeV1_0, DidExchange); + +into_msg_with_type!(Request, DidExchangeTypeV1_0, Request); +into_msg_with_type!(Response, DidExchangeTypeV1_0, Response); +into_msg_with_type!(ProblemReport, DidExchangeTypeV1_0, ProblemReport); +into_msg_with_type!(Complete, DidExchangeTypeV1_0, Complete); diff --git a/aries/messages/src/msg_fields/protocols/did_exchange/v1_0/problem_report.rs b/aries/messages/src/msg_fields/protocols/did_exchange/v1_0/problem_report.rs new file mode 100644 index 0000000000..6c8a3352cd --- /dev/null +++ b/aries/messages/src/msg_fields/protocols/did_exchange/v1_0/problem_report.rs @@ -0,0 +1,86 @@ +use crate::{ + msg_fields::protocols::did_exchange::v1_x::problem_report::{ + ProblemReportContent, ProblemReportDecorators, + }, + msg_parts::MsgParts, +}; + +/// Alias type for DIDExchange v1.0 Problem Report message. +/// Note that since this inherits from the V1.X message, the direct serialization +/// of this message type is not recommended, as version metadata will be lost. +/// Instead, this type should be converted to/from an AriesMessage +pub type ProblemReport = MsgParts; + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +#[allow(clippy::field_reassign_with_default)] +mod tests { + use serde_json::json; + + use super::*; + use crate::{ + decorators::{ + localization::tests::make_extended_msg_localization, + thread::tests::make_extended_thread, timing::tests::make_extended_timing, + }, + misc::test_utils, + msg_fields::protocols::did_exchange::v1_x::problem_report::{ + AnyProblemReport, ProblemCode, + }, + msg_types::protocols::did_exchange::DidExchangeTypeV1_0, + }; + + #[test] + fn test_minimal_conn_problem_report() { + let content = ProblemReportContent::builder() + .problem_code(None) + .explain(None) + .build(); + + let decorators = ProblemReportDecorators::new(make_extended_thread()); + + let expected = json!({ + "~thread": decorators.thread + }); + + let msg = AnyProblemReport::V1_0( + ProblemReport::builder() + .id("test".to_owned()) + .content(content) + .decorators(decorators) + .build(), + ); + + test_utils::test_constructed_msg(msg, DidExchangeTypeV1_0::ProblemReport, expected); + } + + #[test] + fn test_extended_conn_problem_report() { + let content = ProblemReportContent::builder() + .problem_code(Some(ProblemCode::RequestNotAccepted)) + .explain(Some("test_conn_problem_report_explain".to_owned())) + .build(); + + let mut decorators = ProblemReportDecorators::new(make_extended_thread()); + decorators.timing = Some(make_extended_timing()); + decorators.localization = Some(make_extended_msg_localization()); + + let expected = json!({ + "problem-code": content.problem_code, + "explain": content.explain, + "~thread": decorators.thread, + "~timing": decorators.timing, + "~l10n": decorators.localization + }); + + let msg = AnyProblemReport::V1_0( + ProblemReport::builder() + .id("test".to_owned()) + .content(content) + .decorators(decorators) + .build(), + ); + + test_utils::test_constructed_msg(msg, DidExchangeTypeV1_0::ProblemReport, expected); + } +} diff --git a/aries/messages/src/msg_fields/protocols/did_exchange/request.rs b/aries/messages/src/msg_fields/protocols/did_exchange/v1_0/request.rs similarity index 65% rename from aries/messages/src/msg_fields/protocols/did_exchange/request.rs rename to aries/messages/src/msg_fields/protocols/did_exchange/v1_0/request.rs index 0cc68ffc63..ed8a1eca0b 100644 --- a/aries/messages/src/msg_fields/protocols/did_exchange/request.rs +++ b/aries/messages/src/msg_fields/protocols/did_exchange/v1_0/request.rs @@ -1,55 +1,34 @@ -use serde::{Deserialize, Serialize}; -use shared::maybe_known::MaybeKnown; -use typed_builder::TypedBuilder; - use crate::{ - decorators::{ - attachment::Attachment, - thread::{Thread, ThreadGoalCode}, - timing::Timing, - }, + msg_fields::protocols::did_exchange::v1_x::request::{RequestContent, RequestDecorators}, msg_parts::MsgParts, }; +/// Alias type for DIDExchange v1.0 Request message. +/// Note that since this inherits from the V1.X message, the direct serialization +/// of this Request is not recommended, as it will be indistinguisable from Request V1.1. +/// Instead, this type should be converted to/from an AriesMessage pub type Request = MsgParts; -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, TypedBuilder)] -pub struct RequestContent { - pub label: String, - pub goal_code: Option>, - pub goal: Option, - pub did: String, // TODO: Use Did - #[serde(rename = "did_doc~attach")] - pub did_doc: Option, -} - -#[derive(Clone, Debug, Deserialize, Serialize, Default, PartialEq, TypedBuilder)] -pub struct RequestDecorators { - #[serde(rename = "~thread")] - #[serde(skip_serializing_if = "Option::is_none")] - pub thread: Option, - #[builder(default, setter(strip_option))] - #[serde(rename = "~timing")] - #[serde(skip_serializing_if = "Option::is_none")] - pub timing: Option, -} - #[cfg(test)] #[allow(clippy::unwrap_used)] #[allow(clippy::field_reassign_with_default)] mod tests { use diddoc_legacy::aries::diddoc::AriesDidDoc; use serde_json::json; + use shared::maybe_known::MaybeKnown; use super::*; use crate::{ decorators::{ - attachment::{AttachmentData, AttachmentType}, - thread::tests::make_extended_thread, + attachment::{Attachment, AttachmentData, AttachmentType}, + thread::{tests::make_extended_thread, ThreadGoalCode}, timing::tests::make_extended_timing, }, misc::test_utils, - msg_fields::protocols::did_exchange::request::{Request, RequestDecorators}, + msg_fields::protocols::did_exchange::{ + v1_0::request::{Request, RequestDecorators}, + v1_x::request::AnyRequest, + }, msg_types::protocols::did_exchange::DidExchangeTypeV1_0, }; @@ -96,12 +75,16 @@ mod tests { "did": content.did, "did_doc~attach": content.did_doc, }); - test_utils::test_msg( - content, - RequestDecorators::default(), - DidExchangeTypeV1_0::Request, - expected, + + let msg = AnyRequest::V1_0( + Request::builder() + .id("test".to_owned()) + .content(content) + .decorators(RequestDecorators::default()) + .build(), ); + + test_utils::test_constructed_msg(msg, DidExchangeTypeV1_0::Request, expected); } #[test] @@ -122,6 +105,14 @@ mod tests { "~timing": decorators.timing }); - test_utils::test_msg(content, decorators, DidExchangeTypeV1_0::Request, expected); + let msg = AnyRequest::V1_0( + Request::builder() + .id("test".to_owned()) + .content(content) + .decorators(decorators) + .build(), + ); + + test_utils::test_constructed_msg(msg, DidExchangeTypeV1_0::Request, expected); } } diff --git a/aries/messages/src/msg_fields/protocols/did_exchange/response.rs b/aries/messages/src/msg_fields/protocols/did_exchange/v1_0/response.rs similarity index 83% rename from aries/messages/src/msg_fields/protocols/did_exchange/response.rs rename to aries/messages/src/msg_fields/protocols/did_exchange/v1_0/response.rs index 0779b114ac..77708ee261 100644 --- a/aries/messages/src/msg_fields/protocols/did_exchange/response.rs +++ b/aries/messages/src/msg_fields/protocols/did_exchange/v1_0/response.rs @@ -2,8 +2,8 @@ use serde::{Deserialize, Serialize}; use typed_builder::TypedBuilder; use crate::{ - decorators::{attachment::Attachment, thread::Thread, timing::Timing}, - msg_parts::MsgParts, + decorators::attachment::Attachment, + msg_fields::protocols::did_exchange::v1_x::response::ResponseDecorators, msg_parts::MsgParts, }; pub type Response = MsgParts; @@ -11,20 +11,10 @@ pub type Response = MsgParts; #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, TypedBuilder)] pub struct ResponseContent { pub did: String, // TODO: Use Did - #[serde(rename = "did_doc~attach")] + #[serde(rename = "did_doc~attach", skip_serializing_if = "Option::is_none")] pub did_doc: Option, } -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, TypedBuilder)] -pub struct ResponseDecorators { - #[serde(rename = "~thread")] - pub thread: Thread, - #[builder(default, setter(strip_option))] - #[serde(rename = "~timing")] - #[serde(skip_serializing_if = "Option::is_none")] - pub timing: Option, -} - #[cfg(test)] #[allow(clippy::unwrap_used)] #[allow(clippy::field_reassign_with_default)] diff --git a/aries/messages/src/msg_fields/protocols/did_exchange/v1_1/complete.rs b/aries/messages/src/msg_fields/protocols/did_exchange/v1_1/complete.rs new file mode 100644 index 0000000000..7b9e3db321 --- /dev/null +++ b/aries/messages/src/msg_fields/protocols/did_exchange/v1_1/complete.rs @@ -0,0 +1,75 @@ +use shared::misc::serde_ignored::SerdeIgnored as NoContent; + +use crate::{ + msg_fields::protocols::did_exchange::v1_x::complete::CompleteDecorators, msg_parts::MsgParts, +}; + +/// Alias type for DIDExchange v1.1 Complete message. +/// Note that since this inherits from the V1.X message, the direct serialization +/// of this message type is not recommended, as it will be indistinguisable from V1.0. +/// Instead, this type should be converted to/from an AriesMessage +pub type Complete = MsgParts; + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +#[allow(clippy::field_reassign_with_default)] +mod tests { + use serde_json::json; + + use super::*; + use crate::{ + decorators::{ + thread::tests::{make_extended_thread, make_minimal_thread}, + timing::tests::make_extended_timing, + }, + misc::test_utils, + msg_fields::protocols::did_exchange::v1_x::complete::AnyComplete, + msg_types::protocols::did_exchange::DidExchangeTypeV1_1, + }; + + #[test] + fn test_minimal_complete_message() { + let thread = make_minimal_thread(); + + let expected = json!({ + "~thread": { + "thid": thread.thid + } + }); + + let decorators = CompleteDecorators::builder().thread(thread).build(); + + let msg = AnyComplete::V1_1( + Complete::builder() + .id("test".to_owned()) + .content(NoContent) + .decorators(decorators) + .build(), + ); + + test_utils::test_constructed_msg(msg, DidExchangeTypeV1_1::Complete, expected); + } + + #[test] + fn test_extended_complete_message() { + let decorators = CompleteDecorators { + thread: make_extended_thread(), + timing: Some(make_extended_timing()), + }; + + let expected = json!({ + "~thread": serde_json::to_value(make_extended_thread()).unwrap(), + "~timing": serde_json::to_value(make_extended_timing()).unwrap() + }); + + let msg = AnyComplete::V1_1( + Complete::builder() + .id("test".to_owned()) + .content(NoContent) + .decorators(decorators) + .build(), + ); + + test_utils::test_constructed_msg(msg, DidExchangeTypeV1_1::Complete, expected); + } +} diff --git a/aries/messages/src/msg_fields/protocols/did_exchange/v1_1/mod.rs b/aries/messages/src/msg_fields/protocols/did_exchange/v1_1/mod.rs new file mode 100644 index 0000000000..698c7a8bba --- /dev/null +++ b/aries/messages/src/msg_fields/protocols/did_exchange/v1_1/mod.rs @@ -0,0 +1,77 @@ +pub mod complete; +pub mod problem_report; +pub mod request; +pub mod response; + +use derive_more::From; +use serde::{de::Error, Deserialize, Serialize}; + +use self::{ + complete::Complete, + problem_report::ProblemReport, + request::Request, + response::{Response, ResponseContent}, +}; +use super::{v1_x::response::ResponseDecorators, DidExchange}; +use crate::{ + misc::utils::{into_msg_with_type, transit_to_aries_msg}, + msg_fields::traits::DelayedSerde, + msg_types::{protocols::did_exchange::DidExchangeTypeV1_1, MsgKindType, MsgWithType}, +}; + +#[derive(Clone, Debug, From, PartialEq)] +pub enum DidExchangeV1_1 { + Request(Request), + Response(Response), + ProblemReport(ProblemReport), + Complete(Complete), +} + +impl DelayedSerde for DidExchangeV1_1 { + type MsgType<'a> = (MsgKindType, &'a str); + + fn delayed_deserialize<'de, D>( + msg_type: Self::MsgType<'de>, + deserializer: D, + ) -> Result + where + D: serde::Deserializer<'de>, + { + let (protocol, kind_str) = msg_type; + let kind = protocol.kind_from_str(kind_str); + + match kind.map_err(D::Error::custom)? { + DidExchangeTypeV1_1::Request => Request::deserialize(deserializer).map(From::from), + DidExchangeTypeV1_1::Response => Response::deserialize(deserializer).map(From::from), + DidExchangeTypeV1_1::ProblemReport => { + ProblemReport::deserialize(deserializer).map(From::from) + } + DidExchangeTypeV1_1::Complete => Complete::deserialize(deserializer).map(From::from), + } + } + + fn delayed_serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + Self::Request(v) => { + MsgWithType::<_, DidExchangeTypeV1_1>::from(v).serialize(serializer) + } + Self::Response(v) => MsgWithType::from(v).serialize(serializer), + Self::ProblemReport(v) => { + MsgWithType::<_, DidExchangeTypeV1_1>::from(v).serialize(serializer) + } + Self::Complete(v) => { + MsgWithType::<_, DidExchangeTypeV1_1>::from(v).serialize(serializer) + } + } + } +} + +transit_to_aries_msg!(ResponseContent: ResponseDecorators, DidExchangeV1_1, DidExchange); + +into_msg_with_type!(Request, DidExchangeTypeV1_1, Request); +into_msg_with_type!(Response, DidExchangeTypeV1_1, Response); +into_msg_with_type!(ProblemReport, DidExchangeTypeV1_1, ProblemReport); +into_msg_with_type!(Complete, DidExchangeTypeV1_1, Complete); diff --git a/aries/messages/src/msg_fields/protocols/did_exchange/v1_1/problem_report.rs b/aries/messages/src/msg_fields/protocols/did_exchange/v1_1/problem_report.rs new file mode 100644 index 0000000000..86a998da92 --- /dev/null +++ b/aries/messages/src/msg_fields/protocols/did_exchange/v1_1/problem_report.rs @@ -0,0 +1,86 @@ +use crate::{ + msg_fields::protocols::did_exchange::v1_x::problem_report::{ + ProblemReportContent, ProblemReportDecorators, + }, + msg_parts::MsgParts, +}; + +/// Alias type for DIDExchange v1.1 problem report message. +/// Note that since this inherits from the V1.X message, the direct serialization +/// of this message type is not recommended, as version metadata will be lost. +/// Instead, this type should be converted to/from an AriesMessage +pub type ProblemReport = MsgParts; + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +#[allow(clippy::field_reassign_with_default)] +mod tests { + use serde_json::json; + + use super::*; + use crate::{ + decorators::{ + localization::tests::make_extended_msg_localization, + thread::tests::make_extended_thread, timing::tests::make_extended_timing, + }, + misc::test_utils, + msg_fields::protocols::did_exchange::v1_x::problem_report::{ + AnyProblemReport, ProblemCode, ProblemReportDecorators, + }, + msg_types::protocols::did_exchange::DidExchangeTypeV1_1, + }; + + #[test] + fn test_minimal_conn_problem_report() { + let content = ProblemReportContent::builder() + .problem_code(None) + .explain(None) + .build(); + + let decorators = ProblemReportDecorators::new(make_extended_thread()); + + let expected = json!({ + "~thread": decorators.thread + }); + + let msg = AnyProblemReport::V1_1( + ProblemReport::builder() + .id("test".to_owned()) + .content(content) + .decorators(decorators) + .build(), + ); + + test_utils::test_constructed_msg(msg, DidExchangeTypeV1_1::ProblemReport, expected); + } + + #[test] + fn test_extended_conn_problem_report() { + let content = ProblemReportContent::builder() + .problem_code(Some(ProblemCode::RequestNotAccepted)) + .explain(Some("test_conn_problem_report_explain".to_owned())) + .build(); + + let mut decorators = ProblemReportDecorators::new(make_extended_thread()); + decorators.timing = Some(make_extended_timing()); + decorators.localization = Some(make_extended_msg_localization()); + + let expected = json!({ + "problem-code": content.problem_code, + "explain": content.explain, + "~thread": decorators.thread, + "~timing": decorators.timing, + "~l10n": decorators.localization + }); + + let msg = AnyProblemReport::V1_1( + ProblemReport::builder() + .id("test".to_owned()) + .content(content) + .decorators(decorators) + .build(), + ); + + test_utils::test_constructed_msg(msg, DidExchangeTypeV1_1::ProblemReport, expected); + } +} diff --git a/aries/messages/src/msg_fields/protocols/did_exchange/v1_1/request.rs b/aries/messages/src/msg_fields/protocols/did_exchange/v1_1/request.rs new file mode 100644 index 0000000000..44456ea24e --- /dev/null +++ b/aries/messages/src/msg_fields/protocols/did_exchange/v1_1/request.rs @@ -0,0 +1,118 @@ +use crate::{ + msg_fields::protocols::did_exchange::v1_x::request::{RequestContent, RequestDecorators}, + msg_parts::MsgParts, +}; + +/// Alias type for DIDExchange v1.1 request message. +/// Note that since this inherits from the V1.X message, the direct serialization +/// of this message type is not recommended, as it will be indistinguisable from Request V1.0. +/// Instead, this type should be converted to/from an AriesMessage +pub type Request = MsgParts; + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +#[allow(clippy::field_reassign_with_default)] +mod tests { + use diddoc_legacy::aries::diddoc::AriesDidDoc; + use serde_json::json; + use shared::maybe_known::MaybeKnown; + + use super::*; + use crate::{ + decorators::{ + attachment::{Attachment, AttachmentData, AttachmentType}, + thread::{tests::make_extended_thread, ThreadGoalCode}, + timing::tests::make_extended_timing, + }, + misc::test_utils, + msg_fields::protocols::did_exchange::{ + v1_1::request::{Request, RequestDecorators}, + v1_x::request::AnyRequest, + }, + msg_types::protocols::did_exchange::DidExchangeTypeV1_1, + }; + + pub fn request_content() -> RequestContent { + let did_doc = AriesDidDoc::default(); + RequestContent { + label: "test_request_label".to_owned(), + goal_code: Some(MaybeKnown::Known(ThreadGoalCode::AriesRelBuild)), + goal: Some("test_goal".to_owned()), + did: did_doc.id.clone(), + did_doc: Some( + Attachment::builder() + .data( + AttachmentData::builder() + .content(AttachmentType::Json( + serde_json::to_value(&did_doc).unwrap(), + )) + .build(), + ) + .build(), + ), + } + } + + #[test] + fn test_print_message() { + let msg: Request = Request::builder() + .id("test_id".to_owned()) + .content(request_content()) + .decorators(RequestDecorators::default()) + .build(); + let printed_json = format!("{}", msg); + let parsed_request: Request = serde_json::from_str(&printed_json).unwrap(); + assert_eq!(msg, parsed_request); + } + + #[test] + fn test_minimal_didexchange_request() { + let content = request_content(); + let expected = json!({ + "label": content.label, + "goal_code": content.goal_code, + "goal": content.goal, + "did": content.did, + "did_doc~attach": content.did_doc, + }); + + let msg = AnyRequest::V1_1( + Request::builder() + .id("test".to_owned()) + .content(content) + .decorators(RequestDecorators::default()) + .build(), + ); + + test_utils::test_constructed_msg(msg, DidExchangeTypeV1_1::Request, expected); + } + + #[test] + fn test_extended_didexchange_request() { + let content = request_content(); + + let mut decorators = RequestDecorators::default(); + decorators.thread = Some(make_extended_thread()); + decorators.timing = Some(make_extended_timing()); + + let expected = json!({ + "label": content.label, + "goal_code": content.goal_code, + "goal": content.goal, + "did": content.did, + "did_doc~attach": content.did_doc, + "~thread": decorators.thread, + "~timing": decorators.timing + }); + + let msg = AnyRequest::V1_1( + Request::builder() + .id("test".to_owned()) + .content(content) + .decorators(decorators) + .build(), + ); + + test_utils::test_constructed_msg(msg, DidExchangeTypeV1_1::Request, expected); + } +} diff --git a/aries/messages/src/msg_fields/protocols/did_exchange/v1_1/response.rs b/aries/messages/src/msg_fields/protocols/did_exchange/v1_1/response.rs new file mode 100644 index 0000000000..3c7f53daaf --- /dev/null +++ b/aries/messages/src/msg_fields/protocols/did_exchange/v1_1/response.rs @@ -0,0 +1,106 @@ +use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; + +use crate::{ + decorators::attachment::Attachment, + msg_fields::protocols::did_exchange::v1_x::response::ResponseDecorators, msg_parts::MsgParts, +}; + +pub type Response = MsgParts; + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, TypedBuilder)] +pub struct ResponseContent { + pub did: String, // TODO: Use Did + #[serde(rename = "did_doc~attach", skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub did_doc: Option, + #[serde(rename = "did_rotate~attach", skip_serializing_if = "Option::is_none")] + #[builder(default, setter(strip_option))] + pub did_rotate: Option, +} + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +#[allow(clippy::field_reassign_with_default)] +mod tests { + use diddoc_legacy::aries::diddoc::AriesDidDoc; + use serde_json::json; + + use super::*; + use crate::{ + decorators::{ + attachment::{AttachmentData, AttachmentType}, + thread::tests::make_extended_thread, + timing::tests::make_extended_timing, + }, + misc::{test_utils, MimeType}, + msg_types::protocols::did_exchange::DidExchangeTypeV1_1, + }; + + fn response_content() -> ResponseContent { + let did_doc = AriesDidDoc::default(); + ResponseContent { + did: did_doc.id.clone(), + did_doc: Some( + Attachment::builder() + .data( + AttachmentData::builder() + .content(AttachmentType::Json( + serde_json::to_value(&did_doc).unwrap(), + )) + .build(), + ) + .build(), + ), + did_rotate: Some( + Attachment::builder() + .data( + AttachmentData::builder() + .content(AttachmentType::Base64(String::from("Qi5kaWRAQjpB"))) + .build(), + ) + .mime_type(MimeType::Plain) + .build(), + ), + } + } + + #[test] + fn test_minimal_conn_response() { + let mut content = response_content(); + content.did_doc = None; + content.did_rotate = None; + + let decorators = ResponseDecorators { + thread: make_extended_thread(), + timing: None, + }; + + let expected = json!({ + "did": content.did, + "~thread": decorators.thread + }); + + test_utils::test_msg(content, decorators, DidExchangeTypeV1_1::Response, expected); + } + + #[test] + fn test_extended_conn_response() { + let content = response_content(); + + let decorators = ResponseDecorators { + thread: make_extended_thread(), + timing: Some(make_extended_timing()), + }; + + let expected = json!({ + "did": content.did, + "did_doc~attach": content.did_doc, + "did_rotate~attach": content.did_rotate, + "~thread": decorators.thread, + "~timing": decorators.timing + }); + + test_utils::test_msg(content, decorators, DidExchangeTypeV1_1::Response, expected); + } +} diff --git a/aries/messages/src/msg_fields/protocols/did_exchange/v1_x/complete.rs b/aries/messages/src/msg_fields/protocols/did_exchange/v1_x/complete.rs new file mode 100644 index 0000000000..f225d817f1 --- /dev/null +++ b/aries/messages/src/msg_fields/protocols/did_exchange/v1_x/complete.rs @@ -0,0 +1,69 @@ +use serde::{Deserialize, Serialize}; +use shared::misc::serde_ignored::SerdeIgnored as NoContent; +use typed_builder::TypedBuilder; + +use crate::{ + decorators::{thread::Thread, timing::Timing}, + msg_fields::protocols::did_exchange::{ + v1_0::DidExchangeV1_0, v1_1::DidExchangeV1_1, DidExchange, + }, + msg_parts::MsgParts, + msg_types::protocols::did_exchange::DidExchangeTypeV1, + AriesMessage, +}; + +/// Alias type for the shared DIDExchange v1.X complete message type. +/// Note the direct serialization of this message type is not recommended, +/// as it will be indistinguisable between V1.1 & V1.0. +/// Instead, this type should be converted to/from an AriesMessage +pub type Complete = MsgParts; + +// TODO: Pthid is mandatory in this case! +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, TypedBuilder)] +pub struct CompleteDecorators { + #[serde(rename = "~thread")] + pub thread: Thread, + #[builder(default, setter(strip_option))] + #[serde(rename = "~timing")] + #[serde(skip_serializing_if = "Option::is_none")] + pub timing: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +#[serde(untagged)] +pub enum AnyComplete { + V1_0(Complete), + V1_1(Complete), +} + +impl AnyComplete { + pub fn get_version(&self) -> DidExchangeTypeV1 { + match self { + AnyComplete::V1_0(_) => DidExchangeTypeV1::new_v1_0(), + AnyComplete::V1_1(_) => DidExchangeTypeV1::new_v1_1(), + } + } +} + +impl AnyComplete { + pub fn into_inner(self) -> Complete { + match self { + AnyComplete::V1_0(r) | AnyComplete::V1_1(r) => r, + } + } + + pub fn inner(&self) -> &Complete { + match self { + AnyComplete::V1_0(r) | AnyComplete::V1_1(r) => r, + } + } +} + +impl From for AriesMessage { + fn from(value: AnyComplete) -> Self { + match value { + AnyComplete::V1_0(inner) => DidExchange::V1_0(DidExchangeV1_0::Complete(inner)).into(), + AnyComplete::V1_1(inner) => DidExchange::V1_1(DidExchangeV1_1::Complete(inner)).into(), + } + } +} diff --git a/aries/messages/src/msg_fields/protocols/did_exchange/v1_x/mod.rs b/aries/messages/src/msg_fields/protocols/did_exchange/v1_x/mod.rs new file mode 100644 index 0000000000..66f957c85a --- /dev/null +++ b/aries/messages/src/msg_fields/protocols/did_exchange/v1_x/mod.rs @@ -0,0 +1,7 @@ +//! Common components for V1.X DIDExchange messages (v1.0 & v1.1). +//! Necessary to prevent duplicated code, since most types between v1.0 & v1.1 are identical + +pub mod complete; +pub mod problem_report; +pub mod request; +pub mod response; diff --git a/aries/messages/src/msg_fields/protocols/did_exchange/v1_x/problem_report.rs b/aries/messages/src/msg_fields/protocols/did_exchange/v1_x/problem_report.rs new file mode 100644 index 0000000000..e8f3d862d3 --- /dev/null +++ b/aries/messages/src/msg_fields/protocols/did_exchange/v1_x/problem_report.rs @@ -0,0 +1,103 @@ +use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; + +use crate::{ + decorators::{localization::MsgLocalization, thread::Thread, timing::Timing}, + msg_fields::protocols::did_exchange::{ + v1_0::DidExchangeV1_0, v1_1::DidExchangeV1_1, DidExchange, + }, + msg_parts::MsgParts, + msg_types::protocols::did_exchange::DidExchangeTypeV1, + AriesMessage, +}; + +/// Alias type for the shared DIDExchange v1.X problem report message type. +/// Note the direct serialization of this message type is not recommended, +/// as version metadata will be lost. +/// Instead, this type should be converted to/from an AriesMessage +pub type ProblemReport = MsgParts; + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, TypedBuilder)] +pub struct ProblemReportContent { + #[serde(rename = "problem-code")] + #[serde(skip_serializing_if = "Option::is_none")] + pub problem_code: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub explain: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum ProblemCode { + RequestNotAccepted, + RequestProcessingError, + ResponseNotAccepted, + ResponseProcessingError, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, TypedBuilder)] +pub struct ProblemReportDecorators { + #[serde(rename = "~thread")] + pub thread: Thread, + #[builder(default, setter(strip_option))] + #[serde(rename = "~l10n")] + #[serde(skip_serializing_if = "Option::is_none")] + pub localization: Option, + #[builder(default, setter(strip_option))] + #[serde(rename = "~timing")] + #[serde(skip_serializing_if = "Option::is_none")] + pub timing: Option, +} + +impl ProblemReportDecorators { + pub fn new(thread: Thread) -> Self { + Self { + thread, + localization: None, + timing: None, + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +#[serde(untagged)] +pub enum AnyProblemReport { + V1_0(ProblemReport), + V1_1(ProblemReport), +} + +impl AnyProblemReport { + pub fn get_version(&self) -> DidExchangeTypeV1 { + match self { + AnyProblemReport::V1_0(_) => DidExchangeTypeV1::new_v1_0(), + AnyProblemReport::V1_1(_) => DidExchangeTypeV1::new_v1_1(), + } + } +} + +impl AnyProblemReport { + pub fn into_inner(self) -> ProblemReport { + match self { + AnyProblemReport::V1_0(r) | AnyProblemReport::V1_1(r) => r, + } + } + + pub fn inner(&self) -> &ProblemReport { + match self { + AnyProblemReport::V1_0(r) | AnyProblemReport::V1_1(r) => r, + } + } +} + +impl From for AriesMessage { + fn from(value: AnyProblemReport) -> Self { + match value { + AnyProblemReport::V1_0(inner) => { + DidExchange::V1_0(DidExchangeV1_0::ProblemReport(inner)).into() + } + AnyProblemReport::V1_1(inner) => { + DidExchange::V1_1(DidExchangeV1_1::ProblemReport(inner)).into() + } + } + } +} diff --git a/aries/messages/src/msg_fields/protocols/did_exchange/v1_x/request.rs b/aries/messages/src/msg_fields/protocols/did_exchange/v1_x/request.rs new file mode 100644 index 0000000000..1d2d8838bc --- /dev/null +++ b/aries/messages/src/msg_fields/protocols/did_exchange/v1_x/request.rs @@ -0,0 +1,85 @@ +use serde::{Deserialize, Serialize}; +use shared::maybe_known::MaybeKnown; +use typed_builder::TypedBuilder; + +use crate::{ + decorators::{ + attachment::Attachment, + thread::{Thread, ThreadGoalCode}, + timing::Timing, + }, + msg_fields::protocols::did_exchange::{ + v1_0::DidExchangeV1_0, v1_1::DidExchangeV1_1, DidExchange, + }, + msg_parts::MsgParts, + msg_types::protocols::did_exchange::DidExchangeTypeV1, + AriesMessage, +}; + +/// Alias type for the shared DIDExchange v1.X request message type. +/// Note the direct serialization of this message type is not recommended, +/// as it will be indistinguisable between V1.1 & V1.0. +/// Instead, this type should be converted to/from an AriesMessage +pub type Request = MsgParts; + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, TypedBuilder)] +pub struct RequestContent { + pub label: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub goal_code: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub goal: Option, + pub did: String, // TODO: Use Did + #[serde(rename = "did_doc~attach", skip_serializing_if = "Option::is_none")] + pub did_doc: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize, Default, PartialEq, TypedBuilder)] +pub struct RequestDecorators { + #[serde(rename = "~thread")] + #[serde(skip_serializing_if = "Option::is_none")] + pub thread: Option, + #[builder(default, setter(strip_option))] + #[serde(rename = "~timing")] + #[serde(skip_serializing_if = "Option::is_none")] + pub timing: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +#[serde(untagged)] +pub enum AnyRequest { + V1_0(Request), + V1_1(Request), +} + +impl AnyRequest { + pub fn get_version(&self) -> DidExchangeTypeV1 { + match self { + AnyRequest::V1_0(_) => DidExchangeTypeV1::new_v1_0(), + AnyRequest::V1_1(_) => DidExchangeTypeV1::new_v1_1(), + } + } +} + +impl AnyRequest { + pub fn into_inner(self) -> Request { + match self { + AnyRequest::V1_0(r) | AnyRequest::V1_1(r) => r, + } + } + + pub fn inner(&self) -> &Request { + match self { + AnyRequest::V1_0(r) | AnyRequest::V1_1(r) => r, + } + } +} + +impl From for AriesMessage { + fn from(value: AnyRequest) -> Self { + match value { + AnyRequest::V1_0(inner) => DidExchange::V1_0(DidExchangeV1_0::Request(inner)).into(), + AnyRequest::V1_1(inner) => DidExchange::V1_1(DidExchangeV1_1::Request(inner)).into(), + } + } +} diff --git a/aries/messages/src/msg_fields/protocols/did_exchange/v1_x/response.rs b/aries/messages/src/msg_fields/protocols/did_exchange/v1_x/response.rs new file mode 100644 index 0000000000..9543d98cf2 --- /dev/null +++ b/aries/messages/src/msg_fields/protocols/did_exchange/v1_x/response.rs @@ -0,0 +1,72 @@ +use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; + +use crate::{ + decorators::{thread::Thread, timing::Timing}, + msg_fields::protocols::did_exchange::{ + v1_0::{response::Response as ResponseV1_0, DidExchangeV1_0}, + v1_1::{ + response::{Response as ResponseV1_1, ResponseContent as ResponseV1_1Content}, + DidExchangeV1_1, + }, + DidExchange, + }, + msg_types::protocols::did_exchange::DidExchangeTypeV1, + AriesMessage, +}; + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, derive_more::From)] +#[serde(untagged)] +pub enum AnyResponse { + V1_0(ResponseV1_0), + V1_1(ResponseV1_1), +} + +impl AnyResponse { + pub fn get_version(&self) -> DidExchangeTypeV1 { + match self { + AnyResponse::V1_0(_) => DidExchangeTypeV1::new_v1_0(), + AnyResponse::V1_1(_) => DidExchangeTypeV1::new_v1_1(), + } + } + + pub fn into_v1_1(self) -> ResponseV1_1 { + match self { + AnyResponse::V1_0(r) => r.into_v1_1(), + AnyResponse::V1_1(r) => r, + } + } +} + +impl ResponseV1_0 { + pub fn into_v1_1(self) -> ResponseV1_1 { + ResponseV1_1 { + id: self.id, + decorators: self.decorators, + content: ResponseV1_1Content { + did: self.content.did, + did_doc: self.content.did_doc, + did_rotate: None, + }, + } + } +} + +impl From for AriesMessage { + fn from(value: AnyResponse) -> Self { + match value { + AnyResponse::V1_0(inner) => DidExchange::V1_0(DidExchangeV1_0::Response(inner)).into(), + AnyResponse::V1_1(inner) => DidExchange::V1_1(DidExchangeV1_1::Response(inner)).into(), + } + } +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, TypedBuilder)] +pub struct ResponseDecorators { + #[serde(rename = "~thread")] + pub thread: Thread, + #[builder(default, setter(strip_option))] + #[serde(rename = "~timing")] + #[serde(skip_serializing_if = "Option::is_none")] + pub timing: Option, +} diff --git a/aries/messages/src/msg_types/mod.rs b/aries/messages/src/msg_types/mod.rs index ad77a324e8..56bf0283c8 100644 --- a/aries/messages/src/msg_types/mod.rs +++ b/aries/messages/src/msg_types/mod.rs @@ -142,3 +142,9 @@ where kind_str.parse() } } + +impl Default for MsgKindType { + fn default() -> Self { + Self(Default::default()) + } +} diff --git a/aries/messages/src/msg_types/protocols/did_exchange.rs b/aries/messages/src/msg_types/protocols/did_exchange.rs index 0c5fb7b3cb..ff9eb294e1 100644 --- a/aries/messages/src/msg_types/protocols/did_exchange.rs +++ b/aries/messages/src/msg_types/protocols/did_exchange.rs @@ -16,6 +16,8 @@ pub enum DidExchangeType { #[transitive(into(DidExchangeType, Protocol))] #[msg_type(major = 1)] pub enum DidExchangeTypeV1 { + #[msg_type(minor = 1, roles = "Role::Requester, Role::Responder")] + V1_1(MsgKindType), #[msg_type(minor = 0, roles = "Role::Requester, Role::Responder")] V1_0(MsgKindType), } @@ -29,6 +31,21 @@ pub enum DidExchangeTypeV1_0 { Complete, } +#[derive(Copy, Clone, Debug, AsRefStr, EnumString, PartialEq)] +#[strum(serialize_all = "snake_case")] +pub enum DidExchangeTypeV1_1 { + Request, + Response, + ProblemReport, + Complete, +} + +impl Default for DidExchangeTypeV1 { + fn default() -> Self { + Self::new_v1_1() + } +} + #[cfg(test)] mod tests { use serde_json::json; @@ -37,18 +54,28 @@ mod tests { use crate::misc::test_utils; #[test] - fn test_protocol_didexchange() { + fn test_protocol_didexchange_v1_0() { test_utils::test_serde( Protocol::from(DidExchangeTypeV1::new_v1_0()), json!("https://didcomm.org/didexchange/1.0"), ) } + #[test] + fn test_protocol_didexchange_v1_1() { + let x = Protocol::from(DidExchangeTypeV1::new_v1_1()); + dbg!(x); + test_utils::test_serde( + Protocol::from(DidExchangeTypeV1::new_v1_1()), + json!("https://didcomm.org/didexchange/1.1"), + ) + } + #[test] fn test_version_resolution_didexchange() { test_utils::test_msg_type_resolution( "https://didcomm.org/didexchange/1.255", - DidExchangeTypeV1::new_v1_0(), + DidExchangeTypeV1::new_v1_1(), ) } @@ -62,7 +89,7 @@ mod tests { } #[test] - fn test_msg_type_request() { + fn test_msg_type_request_v1_0() { test_utils::test_msg_type( "https://didcomm.org/didexchange/1.0", "request", @@ -71,7 +98,7 @@ mod tests { } #[test] - fn test_msg_type_response() { + fn test_msg_type_response_v1_0() { test_utils::test_msg_type( "https://didcomm.org/didexchange/1.0", "response", @@ -80,7 +107,7 @@ mod tests { } #[test] - fn test_msg_type_complete() { + fn test_msg_type_complete_v1_0() { test_utils::test_msg_type( "https://didcomm.org/didexchange/1.0", "complete", @@ -89,11 +116,47 @@ mod tests { } #[test] - fn test_msg_type_problem() { + fn test_msg_type_problem_v1_0() { test_utils::test_msg_type( "https://didcomm.org/didexchange/1.0", "problem_report", DidExchangeTypeV1::new_v1_0(), ) } + + #[test] + fn test_msg_type_request_v1_1() { + test_utils::test_msg_type( + "https://didcomm.org/didexchange/1.1", + "request", + DidExchangeTypeV1::new_v1_1(), + ) + } + + #[test] + fn test_msg_type_response_v1_1() { + test_utils::test_msg_type( + "https://didcomm.org/didexchange/1.1", + "response", + DidExchangeTypeV1::new_v1_1(), + ) + } + + #[test] + fn test_msg_type_complete_v1_1() { + test_utils::test_msg_type( + "https://didcomm.org/didexchange/1.1", + "complete", + DidExchangeTypeV1::new_v1_1(), + ) + } + + #[test] + fn test_msg_type_problem_v1_1() { + test_utils::test_msg_type( + "https://didcomm.org/didexchange/1.1", + "problem_report", + DidExchangeTypeV1::new_v1_1(), + ) + } } diff --git a/aries/messages/src/msg_types/registry.rs b/aries/messages/src/msg_types/registry.rs index ddf5461bec..a6850dd8b2 100644 --- a/aries/messages/src/msg_types/registry.rs +++ b/aries/messages/src/msg_types/registry.rs @@ -97,6 +97,7 @@ lazy_static! { map_insert(&mut m, extract_parts!(PickupTypeV2::new_v2_0())); map_insert(&mut m, extract_parts!(CoordinateMediationTypeV1::new_v1_0())); map_insert(&mut m, extract_parts!(DidExchangeTypeV1::new_v1_0())); + map_insert(&mut m, extract_parts!(DidExchangeTypeV1::new_v1_1())); m }; } diff --git a/did_core/did_doc/src/schema/did_doc.rs b/did_core/did_doc/src/schema/did_doc.rs index e955316031..50423c6eaf 100644 --- a/did_core/did_doc/src/schema/did_doc.rs +++ b/did_core/did_doc/src/schema/did_doc.rs @@ -187,10 +187,31 @@ impl DidDocument { self.extra.get(key) } + /// Scan the DIDDocument for a [VerificationMethod] that matches the given reference. pub fn dereference_key(&self, reference: &DidUrl) -> Option<&VerificationMethod> { - self.verification_method + let vms = self.verification_method.iter(); + + // keys are typically in the VMs ^, but may be embedded in the other fields: + let assertions = self.assertion_method.iter().filter_map(|k| k.resolved()); + let key_agreements = self.key_agreement.iter().filter_map(|k| k.resolved()); + let authentications = self.authentication.iter().filter_map(|k| k.resolved()); + let cap_invocations = self + .capability_invocation + .iter() + .filter_map(|k| k.resolved()); + let cap_delegations = self + .capability_delegation .iter() - .find(|vm| vm.id().fragment() == reference.fragment()) + .filter_map(|k| k.resolved()); + + let mut all_vms = vms + .chain(assertions) + .chain(key_agreements) + .chain(authentications) + .chain(cap_invocations) + .chain(cap_delegations); + + all_vms.find(|vm| vm.id().fragment() == reference.fragment()) } pub fn validate(&self) -> Result<(), DidDocumentBuilderError> { 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 0e046a55e3..0198c2acef 100644 --- a/did_core/did_doc/src/schema/verification_method/mod.rs +++ b/did_core/did_doc/src/schema/verification_method/mod.rs @@ -42,10 +42,19 @@ impl VerificationMethod { } pub fn public_key(&self) -> Result { - Ok(Key::new( - self.public_key.key_decoded()?, - self.verification_method_type.try_into()?, - )?) + let key = match &self.public_key { + PublicKeyField::Multibase { + public_key_multibase, + } => Key::from_fingerprint(public_key_multibase)?, + // TODO - FUTURE - other key types could do with some special handling, i.e. + // those where the key_type is encoded within the key field (multibase, jwk, etc) + _ => Key::new( + self.public_key.key_decoded()?, + self.verification_method_type.try_into()?, + )?, + }; + + Ok(key) } } diff --git a/did_core/did_doc/src/schema/verification_method/verification_method_kind.rs b/did_core/did_doc/src/schema/verification_method/verification_method_kind.rs index 20b11b33f0..3d5aa2916c 100644 --- a/did_core/did_doc/src/schema/verification_method/verification_method_kind.rs +++ b/did_core/did_doc/src/schema/verification_method/verification_method_kind.rs @@ -12,3 +12,13 @@ pub enum VerificationMethodKind { Resolved(VerificationMethod), Resolvable(DidUrl), } + +impl VerificationMethodKind { + /// Convenience function to try get the resolved enum variant (if it is that variant) + pub fn resolved(&self) -> Option<&VerificationMethod> { + match &self { + VerificationMethodKind::Resolved(x) => Some(x), + VerificationMethodKind::Resolvable(_) => None, + } + } +} diff --git a/did_core/did_doc/src/schema/verification_method/verification_method_type.rs b/did_core/did_doc/src/schema/verification_method/verification_method_type.rs index 2921fdb40b..9d41289141 100644 --- a/did_core/did_doc/src/schema/verification_method/verification_method_type.rs +++ b/did_core/did_doc/src/schema/verification_method/verification_method_type.rs @@ -18,6 +18,8 @@ pub enum VerificationMethodType { X25519KeyAgreementKey2019, X25519KeyAgreementKey2020, EcdsaSecp256k1RecoveryMethod2020, + /// https://www.w3.org/TR/vc-data-integrity/#multikey + Multikey, } impl Display for VerificationMethodType { @@ -46,6 +48,9 @@ impl Display for VerificationMethodType { VerificationMethodType::EcdsaSecp256k1RecoveryMethod2020 => { write!(f, "EcdsaSecp256k1RecoveryMethod2020") } + VerificationMethodType::Multikey => { + write!(f, "Multikey") + } } } } 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 f973619e0d..93d798856a 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 @@ -26,14 +26,6 @@ pub(crate) fn append_encoded_key_segments( for ka in did_document.key_agreement() { did = append_encoded_key_segment(did, did_document, ka, ElementPurpose::Encryption)?; } - for vm in did_document.verification_method() { - did = append_encoded_key_segment( - did, - did_document, - &VerificationMethodKind::Resolved(vm.to_owned()), - ElementPurpose::Verification, - )?; - } for a in did_document.authentication() { did = append_encoded_key_segment(did, did_document, a, ElementPurpose::Verification)?; } @@ -181,7 +173,9 @@ mod tests { ); let mut did_document = DidDocument::new(Did::parse(did_full.clone()).unwrap()); - did_document.add_key_agreement_object(vm_0); + did_document.add_key_agreement_ref(vm_0.id().to_owned()); + did_document.add_verification_method(vm_0); + did_document.add_authentication_ref(vm_1.id().to_owned()); did_document.add_verification_method(vm_1); let did = append_encoded_key_segments(did.to_string(), &did_document).unwrap(); @@ -277,7 +271,7 @@ mod tests { let mut did_document = DidDocument::new(did_full.parse().unwrap()); did_document.add_assertion_method_object(vm_0); did_document.add_key_agreement_object(vm_1); - did_document.add_verification_method(vm_2); + did_document.add_authentication_object(vm_2); let did = append_encoded_key_segments(did.to_string(), &did_document).unwrap(); assert_eq!(did, did_full); @@ -302,6 +296,7 @@ mod tests { let mut did_document = DidDocument::new(did_full.parse().unwrap()); did_document.add_verification_method(vm); + did_document.add_authentication_ref(DidUrl::from_fragment(reference.to_string()).unwrap()); did_document.add_key_agreement_ref(DidUrl::from_fragment(reference.to_string()).unwrap()); let did = append_encoded_key_segments(did.to_string(), &did_document).unwrap(); 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 29ea1fbcf7..1e19b3b089 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 @@ -19,6 +19,7 @@ pub fn diddoc_from_peerdid2_elements( public_key_encoding: PublicKeyEncoding, ) -> Result { let mut service_index: usize = 0; + let mut vm_index: usize = 1; // Skipping one here because the first element is empty string for element in did.id()[1..].split('.').skip(1) { @@ -26,6 +27,7 @@ pub fn diddoc_from_peerdid2_elements( element, did_doc, &mut service_index, + &mut vm_index, did, public_key_encoding, )?; @@ -38,6 +40,7 @@ fn add_attributes_from_element( element: &str, mut did_doc: DidDocument, service_index: &mut usize, + vm_index: &mut usize, did: &Did, public_key_encoding: PublicKeyEncoding, ) -> Result { @@ -57,6 +60,7 @@ fn add_attributes_from_element( did_doc = add_key_from_element( purposeless_element, did_doc, + vm_index, did, public_key_encoding, purpose, @@ -83,26 +87,34 @@ fn add_service_from_element( fn add_key_from_element( element: &str, mut did_doc: DidDocument, + vm_index: &mut usize, did: &Did, public_key_encoding: PublicKeyEncoding, purpose: ElementPurpose, ) -> Result { let key = Key::from_fingerprint(element)?; - let vms = get_verification_methods_by_key(&key, did, public_key_encoding)?; + let vms = get_verification_methods_by_key(&key, did, public_key_encoding, vm_index)?; for vm in vms.into_iter() { + let vm_reference = vm.id().to_owned(); + did_doc.add_verification_method(vm); + // https://identity.foundation/peer-did-method-spec/#purpose-codes match purpose { ElementPurpose::Assertion => { - did_doc.add_assertion_method_object(vm); + did_doc.add_assertion_method_ref(vm_reference); } ElementPurpose::Encryption => { - did_doc.add_key_agreement_object(vm); + did_doc.add_key_agreement_ref(vm_reference); } ElementPurpose::Verification => { - did_doc.add_verification_method(vm); + did_doc.add_authentication_ref(vm_reference); + } + ElementPurpose::CapabilityInvocation => { + did_doc.add_capability_invocation_ref(vm_reference) + } + ElementPurpose::CapabilityDelegation => { + did_doc.add_capability_delegation_ref(vm_reference) } - ElementPurpose::CapabilityInvocation => did_doc.add_capability_invocation_object(vm), - ElementPurpose::CapabilityDelegation => did_doc.add_capability_delegation_object(vm), _ => return Err(DidPeerError::UnsupportedPurpose(purpose.into())), } } @@ -204,6 +216,7 @@ mod tests { let did_doc = add_key_from_element( purposeless_key_element, ddo_builder, + &mut 0, &did, public_key_encoding, ElementPurpose::Verification, @@ -212,7 +225,7 @@ mod tests { assert_eq!(did_doc.verification_method().len(), 1); let vm = did_doc.verification_method().first().unwrap(); - assert_eq!(vm.id().to_string(), "#6MkqRYqQ"); + assert_eq!(vm.id().to_string(), "#key-0"); assert_eq!(vm.controller().to_string(), did.to_string()); } @@ -222,6 +235,7 @@ mod tests { assert!(add_key_from_element( "z6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V", DidDocument::new(did.clone()), + &mut 0, &did, PublicKeyEncoding::Multibase, ElementPurpose::Service 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 c561acbc97..49d70f189a 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 @@ -62,7 +62,11 @@ impl Numalgo for Numalgo2 { #[cfg(test)] mod test { - use did_doc::schema::did_doc::DidDocument; + use did_doc::schema::{ + did_doc::DidDocument, service::service_key_kind::ServiceKeyKind, + verification_method::PublicKeyField, + }; + use did_parser_nom::DidUrl; use pretty_assertions::assert_eq; use serde_json::{from_value, json}; @@ -73,25 +77,31 @@ mod test { #[test] fn test_peer_did_2_encode_decode() { + // NOTE 20/6/24: universal resolver resolves an additional "assertionMethod" key for the "V" + // key despite the spec not saying to do this. let expected_did_peer = "did:peer:2.Ez6MkkukgyKAdBN46UAHvia2nxmioo74F6YdvW1nBT1wfKKha.Vz6MkfoapUdLHHgSMq5PYhdHYCoqGuRku2i17cQ9zAoR5cLSm.SeyJpZCI6IiNmb29iYXIiLCJ0IjpbImRpZC1jb21tdW5pY2F0aW9uIl0sInMiOiJodHRwOi8vZHVtbXl1cmwub3JnLyIsInIiOlsiIzZNa2t1a2d5Il0sImEiOlsiZGlkY29tbS9haXAyO2Vudj1yZmMxOSJdfQ"; let value = json!({ "id": expected_did_peer, "verificationMethod": [ { - "id": "#6MkfoapU", + "id": "#key-1", "controller": expected_did_peer, "type": "Ed25519VerificationKey2020", - "publicKeyBase58": "2MKmtP5qx8wtiaYr24KhMiHH5rV3cpkkvPF4LXT4h7fP" - } - ], - "keyAgreement": [ + "publicKeyMultibase": "z6MkkukgyKAdBN46UAHvia2nxmioo74F6YdvW1nBT1wfKKha" + }, { - "id": "#6Mkkukgy", + "id": "#key-2", "controller": expected_did_peer, "type": "Ed25519VerificationKey2020", - "publicKeyBase58": "7TVeP4vBqpZdMfTE314x7gAoyXnPgfPZozsFcjyeQ6vC" + "publicKeyMultibase": "z6MkfoapUdLHHgSMq5PYhdHYCoqGuRku2i17cQ9zAoR5cLSm" } ], + "keyAgreement": [ + "#key-1" + ], + "authentication": [ + "#key-2" + ], "service": [ { "id": "#foobar", @@ -111,8 +121,87 @@ mod test { assert_eq!(did_peer.to_string(), expected_did_peer); let ddo_decoded: DidDocument = did_peer - .to_did_doc_builder(PublicKeyEncoding::Base58) + .to_did_doc_builder(PublicKeyEncoding::Multibase) .unwrap(); + dbg!(&ddo_decoded); assert_eq!(ddo_original, ddo_decoded); } + + #[test] + fn test_acapy_did_peer_2() { + // test vector from AATH testing with acapy 0.12.1 + let did = "did:peer:2.Vz6MkqY3gWxHEp47gCXBmnc5k7sAQChwV76YpZAHZ8erDHatK.SeyJpZCI6IiNkaWRjb21tLTAiLCJ0IjoiZGlkLWNvbW11bmljYXRpb24iLCJwcmlvcml0eSI6MCwicmVjaXBpZW50S2V5cyI6WyIja2V5LTEiXSwiciI6W10sInMiOiJodHRwOi8vaG9zdC5kb2NrZXIuaW50ZXJuYWw6OTAzMSJ9"; + let did = PeerDid::::parse(did).unwrap(); + + let doc = did + .to_did_doc_builder(PublicKeyEncoding::Multibase) + .unwrap(); + assert_eq!(doc.verification_method().len(), 1); + let vm = doc.verification_method_by_id("key-1").unwrap(); + assert_eq!( + vm.public_key().unwrap().fingerprint(), + "z6MkqY3gWxHEp47gCXBmnc5k7sAQChwV76YpZAHZ8erDHatK" + ); + assert_eq!( + vm.public_key_field(), + &PublicKeyField::Multibase { + public_key_multibase: String::from( + "z6MkqY3gWxHEp47gCXBmnc5k7sAQChwV76YpZAHZ8erDHatK" + ) + } + ); + + assert_eq!(doc.service().len(), 1); + let service = doc + .get_service_by_id(&"#didcomm-0".parse().unwrap()) + .unwrap(); + assert_eq!( + service.service_endpoint().to_string(), + "http://host.docker.internal:9031/" + ); + let recips = service.extra_field_recipient_keys().unwrap(); + assert_eq!(recips.len(), 1); + assert_eq!( + recips[0], + ServiceKeyKind::Reference(DidUrl::parse("#key-1".to_string()).unwrap()) + ); + } + + #[test] + fn test_resolving_spec_defined_example() { + // https://identity.foundation/peer-did-method-spec/#example-peer-did-2 + // NOTE: excluding the services, as they use a different type of service to the typical + // service DIDDoc structure + let did = "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.\ + Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR"; + let did = PeerDid::::parse(did).unwrap(); + + let doc = did + .to_did_doc_builder(PublicKeyEncoding::Multibase) + .unwrap(); + let expected_doc: DidDocument = serde_json::from_value(json!({ + "id": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR", + "verificationMethod": [ + { + "id": "#key-1", + "controller": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR", + "type": "Ed25519VerificationKey2020", + "publicKeyMultibase": "z6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc" + }, + { + "id": "#key-2", + "controller": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR", + "type": "X25519KeyAgreementKey2020", + "publicKeyMultibase": "z6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR" + } + ], + "authentication": [ + "#key-1" + ], + "keyAgreement": [ + "#key-2" + ] + })).unwrap(); + assert_eq!(doc, expected_doc); + } } 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 d3810e22e7..739b865072 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 @@ -9,7 +9,7 @@ use did_doc::schema::{ utils::OneOrList, }; use serde::{Deserialize, Serialize}; -use serde_json::from_value; +use serde_json::{from_value, Value}; use url::Url; use crate::error::DidPeerError; @@ -32,6 +32,9 @@ pub struct ServiceAbbreviatedDidPeer2 { #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] accept: Vec, + #[serde(flatten)] + #[serde(skip_serializing_if = "HashMap::is_empty")] + extra: HashMap, } impl ServiceAbbreviatedDidPeer2 { @@ -48,24 +51,9 @@ impl ServiceAbbreviatedDidPeer2 { service_endpoint, routing_keys, accept, + extra: Default::default(), } } - - pub fn service_type(&self) -> &OneOrList { - &self.service_type - } - - pub fn service_endpoint(&self) -> &Url { - &self.service_endpoint - } - - pub fn routing_keys(&self) -> &[ServiceKeyKind] { - &self.routing_keys - } - - pub fn accept(&self) -> &[ServiceAcceptType] { - &self.accept - } } // todo: This is encoding is lossy but shouldn't be. @@ -139,7 +127,7 @@ pub(crate) fn deabbreviate_service( abbreviated: ServiceAbbreviatedDidPeer2, index: usize, ) -> Result { - let service_type = match abbreviated.service_type().clone() { + let service_type = match abbreviated.service_type { OneOrList::One(service_type) => { let typed = match service_type.as_str() { "dm" => ServiceType::DIDCommV2, @@ -167,23 +155,25 @@ pub(crate) fn deabbreviate_service( let mut service = Service::new( id, - abbreviated.service_endpoint().clone(), + abbreviated.service_endpoint, service_type, - HashMap::default(), + abbreviated.extra, ); - let routing_keys = abbreviated.routing_keys(); + let routing_keys = abbreviated.routing_keys; if !routing_keys.is_empty() { - service.add_extra_field_routing_keys(routing_keys.to_vec())?; + service.add_extra_field_routing_keys(routing_keys)?; } - let accept = abbreviated.accept(); + let accept = abbreviated.accept; if !accept.is_empty() { - service.add_extra_field_accept(accept.to_vec())?; + service.add_extra_field_accept(accept)?; } Ok(service) } #[cfg(test)] mod tests { + use std::collections::HashMap; + use did_doc::schema::{ service::{ service_accept_type::ServiceAcceptType, service_key_kind::ServiceKeyKind, @@ -207,6 +197,7 @@ mod tests { service_endpoint: Url::parse("https://example.org").unwrap(), routing_keys: vec![], accept: vec![], + extra: HashMap::new(), }; let index = 0; @@ -230,6 +221,7 @@ mod tests { service_endpoint: service_endpoint.clone(), routing_keys: routing_keys.clone(), accept: accept.clone(), + extra: HashMap::new(), }; let index = 0; diff --git a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/verification_method.rs b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/verification_method.rs index d493b25cde..657becc81e 100644 --- a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/verification_method.rs +++ b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/verification_method.rs @@ -10,8 +10,8 @@ pub fn get_verification_methods_by_key( key: &Key, did: &Did, public_key_encoding: PublicKeyEncoding, + vm_index: &mut usize, ) -> Result, DidPeerError> { - let id = to_did_url_reference(key)?; let vm_type = match key.key_type() { KeyType::Ed25519 => VerificationMethodType::Ed25519VerificationKey2020, KeyType::Bls12381g1 => VerificationMethodType::Bls12381G1Key2020, @@ -26,16 +26,18 @@ pub fn get_verification_methods_by_key( &Key::new(key.key()[48..].to_vec(), KeyType::Bls12381g2)?, did.to_owned(), public_key_encoding, + vm_index, )); } }; - Ok(build_verification_methods_from_type_and_key( + + build_verification_methods_from_type_and_key( vm_type, key, - id, did.to_owned(), public_key_encoding, - )) + vm_index, + ) } pub fn get_key_by_verification_method(vm: &VerificationMethod) -> Result { @@ -58,17 +60,20 @@ pub fn get_key_by_verification_method(vm: &VerificationMethod) -> Result Vec { + vm_index: &mut usize, +) -> Result, DidPeerError> { + let id = nth_key_did_url_reference(*vm_index)?; + *vm_index += 1; + let vm = VerificationMethod::builder() .id(id) - .controller(did.to_owned()) + .controller(did) .verification_method_type(vm_type) .public_key(key_to_key_field(key, public_key_encoding)) .build(); - vec![vm] + Ok(vec![vm]) } fn build_verification_methods_from_bls_multikey( @@ -76,9 +81,12 @@ fn build_verification_methods_from_bls_multikey( g2_key: &Key, did: Did, public_key_encoding: PublicKeyEncoding, + vm_index: &mut usize, ) -> Vec { - let id1 = to_did_url_reference(g1_key).unwrap(); - let id2 = to_did_url_reference(g2_key).unwrap(); + let id1 = nth_key_did_url_reference(*vm_index).unwrap(); + *vm_index += 1; + let id2 = nth_key_did_url_reference(*vm_index).unwrap(); + *vm_index += 1; let vm1 = VerificationMethod::builder() .id(id1) .controller(did.to_owned()) @@ -105,14 +113,8 @@ fn key_to_key_field(key: &Key, public_key_encoding: PublicKeyEncoding) -> Public } } -fn to_did_url_reference(key: &Key) -> Result { - DidUrl::from_fragment( - key.prefixless_fingerprint() - .chars() - .take(8) - .collect::(), - ) - .map_err(Into::into) +fn nth_key_did_url_reference(n: usize) -> Result { + DidUrl::from_fragment(format!("key-{n}")).map_err(Into::into) } #[cfg(test)] @@ -188,6 +190,7 @@ mod tests { key, &did(), PublicKeyEncoding::Multibase, + &mut 0, ) .unwrap(); assert_eq!(vms.len(), 1); @@ -204,6 +207,7 @@ mod tests { key, &did(), PublicKeyEncoding::Base58, + &mut 0, ) .unwrap(); assert_eq!(vms.len(), 1); diff --git a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo4/construction_did_doc.rs b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo4/construction_did_doc.rs index f2c4be1fa0..88a6e32d0e 100644 --- a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo4/construction_did_doc.rs +++ b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo4/construction_did_doc.rs @@ -142,9 +142,9 @@ impl DidPeer4ConstructionDidDocument { .push(DidPeer4VerificationMethodKind::Resolved(method)); } - pub fn add_key_agreement_ref(&mut self, refernece: DidUrl) { + pub fn add_key_agreement_ref(&mut self, reference: DidUrl) { self.key_agreement - .push(DidPeer4VerificationMethodKind::Resolvable(refernece)); + .push(DidPeer4VerificationMethodKind::Resolvable(reference)); } pub fn add_capability_invocation(&mut self, method: DidPeer4VerificationMethod) { diff --git a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo4/mod.rs b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo4/mod.rs index 06084c633d..a726673d6a 100644 --- a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo4/mod.rs +++ b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo4/mod.rs @@ -124,12 +124,13 @@ mod tests { use std::collections::HashMap; use did_doc::schema::{ - service::{typed::ServiceType, Service}, + service::{service_key_kind::ServiceKeyKind, typed::ServiceType, Service}, types::uri::Uri, utils::OneOrList, verification_method::{PublicKeyField, VerificationMethodType}, }; use did_parser_nom::DidUrl; + use public_key::KeyType; use crate::peer_did::{ numalgos::numalgo4::{ @@ -229,4 +230,62 @@ mod tests { let peer_did = PeerDid::::parse(peer_did).unwrap(); peer_did.long_form().unwrap_err(); } + + #[test] + fn test_resolve_acapy_test_vector1() { + let peer_did: &str = "did:peer:4zQmcQCH8nWEBBA6BpSEDxHyhPwHdi5CVGcvsZcjhb618zbA:z5CTtVoAxKjH1V1sKizLy5kLvV6AbmACYfcGmfVUDGn4A7BpnVQEESXEYYUG7W479kDHaqLnk7NJuu4w7ftTd9REipB2CQgW9fjzPvmsXyyHzot9o1tgYHNnqFDXgCXwFYJfjkzz3m6mex1WMN4XHWWNM4NB7exDA2maVGis7gJnVAiNrBExaihyeKJ4nBXrB3ArQ1TyuZ39F9qTeCSrBntTTa85wtUtHz5M1oE7Sj1CZeAEQzDnAMToP9idSrSXUo5z8q9Un325d8MtQgxyKGW2a9VYyW189C722GKQbGQSU3dRSwCanVHJwCh9q2G2eNVPeuydAHXmouCUCq3cVHeUkatv73DSoBV17LEJgq8dAYfvSAutG7LFyvrRW5wNjcQMT7WdFHRCqhtzz18zu6fSTQWM4PQPLMVEaKbs51EeYGiGurhu1ChQMjXqnpcRcpCP7RAEgyWSjMER6e3gdCVsBhQSoqGk1UN8NfVah8pxGg2i5Gd1754Ys6aBEhTashFa47Ke7oPoZ6LZiRMETYhUr1cQY65TQhMzyrR6RzLudeRVgcRdKiTTmP2fFi5H8nCHPSGb4wncUxgn3N5CbFaUC"; + let peer_did = PeerDid::::parse(peer_did).unwrap(); + + let resolved_did_doc = peer_did.resolve_did_doc().unwrap(); + assert_eq!(resolved_did_doc.id().to_string(), "did:peer:4zQmcQCH8nWEBBA6BpSEDxHyhPwHdi5CVGcvsZcjhb618zbA:z5CTtVoAxKjH1V1sKizLy5kLvV6AbmACYfcGmfVUDGn4A7BpnVQEESXEYYUG7W479kDHaqLnk7NJuu4w7ftTd9REipB2CQgW9fjzPvmsXyyHzot9o1tgYHNnqFDXgCXwFYJfjkzz3m6mex1WMN4XHWWNM4NB7exDA2maVGis7gJnVAiNrBExaihyeKJ4nBXrB3ArQ1TyuZ39F9qTeCSrBntTTa85wtUtHz5M1oE7Sj1CZeAEQzDnAMToP9idSrSXUo5z8q9Un325d8MtQgxyKGW2a9VYyW189C722GKQbGQSU3dRSwCanVHJwCh9q2G2eNVPeuydAHXmouCUCq3cVHeUkatv73DSoBV17LEJgq8dAYfvSAutG7LFyvrRW5wNjcQMT7WdFHRCqhtzz18zu6fSTQWM4PQPLMVEaKbs51EeYGiGurhu1ChQMjXqnpcRcpCP7RAEgyWSjMER6e3gdCVsBhQSoqGk1UN8NfVah8pxGg2i5Gd1754Ys6aBEhTashFa47Ke7oPoZ6LZiRMETYhUr1cQY65TQhMzyrR6RzLudeRVgcRdKiTTmP2fFi5H8nCHPSGb4wncUxgn3N5CbFaUC"); + assert_eq!( + resolved_did_doc.also_known_as()[0].to_string(), + "did:peer:4zQmcQCH8nWEBBA6BpSEDxHyhPwHdi5CVGcvsZcjhb618zbA" + ); + + // vm/key + assert_eq!(resolved_did_doc.verification_method().len(), 1); + let vm = resolved_did_doc.verification_method_by_id("key-0").unwrap(); + assert_eq!( + vm.verification_method_type(), + &VerificationMethodType::Multikey + ); + assert_eq!( + vm.public_key_field(), + &PublicKeyField::Multibase { + public_key_multibase: String::from( + "z6MkuNenWjqDeZ4DjkHoqX6WdDYTfUUqcR7ASezo846GHe74" + ) + } + ); + let key = vm.public_key().unwrap(); + assert_eq!( + key.fingerprint(), + "z6MkuNenWjqDeZ4DjkHoqX6WdDYTfUUqcR7ASezo846GHe74" + ); + assert_eq!(key.key_type(), &KeyType::Ed25519); + + // servie + assert_eq!(resolved_did_doc.service().len(), 1); + let service = resolved_did_doc + .get_service_by_id(&"#didcomm-0".parse().unwrap()) + .unwrap(); + assert_eq!( + service.service_type(), + &OneOrList::One(ServiceType::DIDCommV1) + ); + assert_eq!( + service.service_endpoint().to_string(), + "http://host.docker.internal:9031/" + ); + let service_recip = service.extra_field_recipient_keys().unwrap(); + assert_eq!( + service_recip, + vec![ServiceKeyKind::Reference("#key-0".parse().unwrap())] + ); + log::info!( + "resolved document: {}", + serde_json::to_string_pretty(&resolved_did_doc).unwrap() + ); + } } diff --git a/did_core/did_methods/did_peer/tests/fixtures/basic.rs b/did_core/did_methods/did_peer/tests/fixtures/basic.rs index 618c00fece..9edf68ec9e 100644 --- a/did_core/did_methods/did_peer/tests/fixtures/basic.rs +++ b/did_core/did_methods/did_peer/tests/fixtures/basic.rs @@ -13,29 +13,27 @@ pub static DID_DOC_BASIC: &str = r##" "alsoKnownAs": ["did:peer:3zQmaTbkb2T8CbKPzXSCgsdWHxgX3qvjmpmTQwfATu3crFCv"], "verificationMethod": [ { - "id": "#6MkqRYqQ", + "id": "#key-1", + "type": "X25519KeyAgreementKey2020", + "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0", + "publicKeyBase58": "JhNWeSVLMYccCk7iopQW4guaSJTojqpMEELgSLhKwRr" + }, + { + "id": "#key-2", "type": "Ed25519VerificationKey2020", "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0", "publicKeyBase58": "ByHnpUCFb1vAfh9CFZ8ZkmUZguURW8nSw889hy6rD8L7" }, { - "id": "#6MkgoLTn", + "id": "#key-3", "type": "Ed25519VerificationKey2020", "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0", "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J" } ], - "authentication": [], + "authentication": ["#key-2", "#key-3"], "assertionMethod": [], - "keyAgreement": [ - { - "id": "#6LSbysY2", - "type": "X25519KeyAgreementKey2020", - "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0", - "publicKeyBase58": "JhNWeSVLMYccCk7iopQW4guaSJTojqpMEELgSLhKwRr" - } - - ], + "keyAgreement": ["#key-1"], "capabilityInvocation": [], "capabilityDelegation": [], "service": [ diff --git a/did_core/did_methods/did_peer/tests/fixtures/no_routing_keys.rs b/did_core/did_methods/did_peer/tests/fixtures/no_routing_keys.rs index 24ed44e722..feeb8b1ec6 100644 --- a/did_core/did_methods/did_peer/tests/fixtures/no_routing_keys.rs +++ b/did_core/did_methods/did_peer/tests/fixtures/no_routing_keys.rs @@ -13,28 +13,27 @@ pub static DID_DOC_NO_ROUTING_KEYS: &str = r##" "alsoKnownAs": ["did:peer:3zQmVKBM36ZvoTCEecoNWAmLvDcePbksQ2Ag2pxyGa24eUp8"], "verificationMethod": [ { - "id": "#6MkqRYqQ", + "id": "#key-1", + "type": "X25519KeyAgreementKey2020", + "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9", + "publicKeyMultibase": "z6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc" + }, + { + "id": "#key-2", "type": "Ed25519VerificationKey2020", "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9", "publicKeyMultibase": "z6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V" }, { - "id": "#6MkgoLTn", + "id": "#key-3", "type": "Ed25519VerificationKey2020", "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9", "publicKeyMultibase": "z6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg" } ], - "authentication": [], + "authentication": ["#key-2", "#key-3"], "assertionMethod": [], - "keyAgreement": [ - { - "id": "#6LSbysY2", - "type": "X25519KeyAgreementKey2020", - "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9", - "publicKeyMultibase": "z6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc" - } - ], + "keyAgreement": ["#key-1"], "capabilityInvocation": [], "capabilityDelegation": [], "service": [ diff --git a/did_core/did_methods/did_peer/tests/fixtures/no_services.rs b/did_core/did_methods/did_peer/tests/fixtures/no_services.rs index 58d345e3b7..d6cd3762bd 100644 --- a/did_core/did_methods/did_peer/tests/fixtures/no_services.rs +++ b/did_core/did_methods/did_peer/tests/fixtures/no_services.rs @@ -11,22 +11,21 @@ pub static DID_DOC_NO_SERVICES: &str = r##" "alsoKnownAs": ["did:peer:3zQmdysQimott3jS93beGPVX8sTRSRFJWt1FsihPcSy9kZfB"], "verificationMethod": [ { - "id": "#6MkqRYqQ", + "id": "#key-1", + "type": "X25519KeyAgreementKey2020", + "controller": "did:peer:2.Ez6LSpSrLxbAhg2SHwKk7kwpsH7DM7QjFS5iK6qP87eViohud.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V", + "publicKeyMultibase": "z6LSpSrLxbAhg2SHwKk7kwpsH7DM7QjFS5iK6qP87eViohud" + }, + { + "id": "#key-2", "type": "Ed25519VerificationKey2020", "controller": "did:peer:2.Ez6LSpSrLxbAhg2SHwKk7kwpsH7DM7QjFS5iK6qP87eViohud.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V", "publicKeyMultibase": "z6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V" } ], - "authentication": [], + "authentication": ["#key-2"], "assertionMethod": [], - "keyAgreement": [ - { - "id": "#6LSpSrLx", - "type": "X25519KeyAgreementKey2020", - "controller": "did:peer:2.Ez6LSpSrLxbAhg2SHwKk7kwpsH7DM7QjFS5iK6qP87eViohud.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V", - "publicKeyMultibase": "z6LSpSrLxbAhg2SHwKk7kwpsH7DM7QjFS5iK6qP87eViohud" - } - ], + "keyAgreement": ["#key-1"], "capabilityInvocation": [], "capabilityDelegation": [] }