diff --git a/.gitignore b/.gitignore index 45f9270f02..cf26819427 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ *.code-workspace **/tails.txt .session.vim +**/rust/aath-backchannel diff --git a/Cargo.lock b/Cargo.lock index 831305dc5b..c935083aa3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -471,12 +471,12 @@ dependencies = [ "chrono", "derive_builder", "did_doc", - "did_doc_sov", "did_key", "did_parser", "did_peer", "did_resolver", "did_resolver_registry", + "did_resolver_sov", "diddoc_legacy", "env_logger 0.10.0", "futures", @@ -1768,31 +1768,21 @@ version = "0.1.0" dependencies = [ "base64", "bs58 0.5.0", + "did_key", "did_parser", + "display_as_json", "hex", "multibase", "pem", "public_key", "serde", "serde_json", + "thiserror", + "typed-builder 0.16.2", "uniresid", "url", ] -[[package]] -name = "did_doc_sov" -version = "0.1.0" -dependencies = [ - "base64", - "did_doc", - "did_key", - "display_as_json", - "public_key", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "did_key" version = "0.1.0" @@ -1829,12 +1819,12 @@ dependencies = [ "base64", "bs58 0.5.0", "did_doc", - "did_doc_sov", "did_parser", "did_resolver", "display_as_json", "multibase", "once_cell", + "pretty_assertions", "public_key", "regex", "serde", @@ -1843,6 +1833,7 @@ dependencies = [ "thiserror", "tokio", "unsigned-varint", + "url", ] [[package]] @@ -1854,6 +1845,7 @@ dependencies = [ "did_doc", "did_parser", "serde", + "serde_json", ] [[package]] @@ -1876,8 +1868,8 @@ dependencies = [ "aries_vcx_core", "async-trait", "chrono", - "did_doc_sov", "did_resolver", + "log", "mockall", "serde", "serde_json", @@ -1915,6 +1907,12 @@ dependencies = [ "url", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "difflib" version = "0.4.0" @@ -3389,7 +3387,6 @@ version = "0.62.0" dependencies = [ "chrono", "derive_more", - "did_doc_sov", "did_parser", "diddoc_legacy", "display_as_json", @@ -4034,6 +4031,16 @@ dependencies = [ "termtree", ] +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -6106,6 +6113,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "zerocopy" version = "0.7.32" diff --git a/Cargo.toml b/Cargo.toml index 45515038cb..133ece0f45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,6 @@ members = [ "did_core/did_doc", "did_core/did_methods/did_peer", "did_core/did_methods/did_key", - "did_core/did_doc_sov", "did_core/did_parser", "did_core/did_parser_nom", "did_core/did_resolver", diff --git a/aries/agents/aries-vcx-agent/src/agent/init.rs b/aries/agents/aries-vcx-agent/src/agent/init.rs index 430fb3729d..c1c57107d9 100644 --- a/aries/agents/aries-vcx-agent/src/agent/init.rs +++ b/aries/agents/aries-vcx-agent/src/agent/init.rs @@ -2,9 +2,10 @@ use std::sync::Arc; use aries_vcx::{ common::ledger::{ - service_didsov::{DidSovServiceType, EndpointDidSov}, + service_didsov::EndpointDidSov, transactions::{add_new_did, write_endpoint}, }, + did_doc::schema::service::typed::ServiceType, global::settings::DEFAULT_LINK_SECRET_ALIAS, }; use aries_vcx_core::{ @@ -110,7 +111,7 @@ impl Agent { .await?; let endpoint = EndpointDidSov::create() .set_service_endpoint(init_config.service_endpoint.clone()) - .set_types(Some(vec![DidSovServiceType::DidCommunication])); + .set_types(Some(vec![ServiceType::DIDCommV1.to_string()])); write_endpoint( wallet.as_ref(), ledger_write.as_ref(), @@ -134,7 +135,6 @@ impl Agent { init_config.service_endpoint.clone(), )); let did_exchange = Arc::new(ServiceDidExchange::new( - ledger_read.clone(), wallet.clone(), did_resolver_registry, init_config.service_endpoint.clone(), diff --git a/aries/agents/aries-vcx-agent/src/error/convertors.rs b/aries/agents/aries-vcx-agent/src/error/convertors.rs index 54852e6ba6..195acb16db 100644 --- a/aries/agents/aries-vcx-agent/src/error/convertors.rs +++ b/aries/agents/aries-vcx-agent/src/error/convertors.rs @@ -2,11 +2,11 @@ use std::{convert::From, num::ParseIntError}; use aries_vcx::{ did_doc::error::DidDocumentBuilderError, - did_doc_sov::error::DidDocumentSovError, errors::error::{AriesVcxError, AriesVcxErrorKind}, protocols::did_exchange::state_machine::generic::GenericDidExchange, }; use aries_vcx_core::errors::error::AriesVcxCoreError; +use did_resolver_sov::did_resolver::did_doc::schema::utils::error::DidDocumentLookupError; use crate::error::*; @@ -30,7 +30,6 @@ impl From for AgentError { } } -// TODO impl From for AgentError { fn from(err: AriesVcxCoreError) -> Self { let kind = AgentErrorKind::GenericAriesVcxError; @@ -39,14 +38,6 @@ impl From for AgentError { } } -impl From for AgentError { - fn from(err: DidDocumentSovError) -> Self { - let kind = AgentErrorKind::GenericAriesVcxError; - let message = format!("DidDocumentSovError; err: {:?}", err.to_string()); - AgentError { message, kind } - } -} - impl From for AgentError { fn from(err: DidDocumentBuilderError) -> Self { let kind = AgentErrorKind::GenericAriesVcxError; @@ -63,6 +54,14 @@ impl From for AgentError { } } +impl From for AgentError { + fn from(err: did_peer::error::DidPeerError) -> Self { + let kind = AgentErrorKind::GenericAriesVcxError; + let message = format!("DidPeerError; err: {:?}", err.to_string()); + AgentError { message, kind } + } +} + impl From for AgentError { fn from(err: public_key::PublicKeyError) -> Self { let kind = AgentErrorKind::GenericAriesVcxError; @@ -86,6 +85,13 @@ impl From<(GenericDidExchange, AriesVcxError)> for AgentError { AgentError { message, kind } } } +impl From for AgentError { + fn from(err: DidDocumentLookupError) -> Self { + let kind = AgentErrorKind::GenericAriesVcxError; + let message = format!("DidDocumentLookupError; err: {:?}", err.to_string()); + AgentError { message, kind } + } +} impl From for AgentError { fn from(err: anoncreds_types::Error) -> Self { diff --git a/aries/agents/aries-vcx-agent/src/helper.rs b/aries/agents/aries-vcx-agent/src/helper.rs deleted file mode 100644 index c0c36bb368..0000000000 --- a/aries/agents/aries-vcx-agent/src/helper.rs +++ /dev/null @@ -1,57 +0,0 @@ -use aries_vcx::{ - did_doc_sov::{service::ServiceSov, DidDocumentSov}, - messages::AriesMessage, - utils::{encryption_envelope::EncryptionEnvelope, from_did_doc_sov_to_legacy}, -}; -use aries_vcx_core::wallet::base_wallet::BaseWallet; -use serde_json::json; -use url::Url; - -use crate::{AgentError, AgentErrorKind, AgentResult}; - -pub fn get_their_endpoint(did_document: &DidDocumentSov) -> AgentResult { - let service = did_document.service().first().ok_or(AgentError::from_msg( - AgentErrorKind::InvalidState, - "No service found", - ))?; - // todo: will get cleaned up after service is de-generified - let url: String = match service { - ServiceSov::Legacy(d) => d.service_endpoint().to_string(), - ServiceSov::AIP1(d) => d.service_endpoint().to_string(), - ServiceSov::DIDCommV1(d) => d.service_endpoint().to_string(), - ServiceSov::DIDCommV2(d) => d.service_endpoint().to_string(), - }; - Url::parse(&url).map_err(|err| { - AgentError::from_msg( - AgentErrorKind::InvalidState, - &format!("Failed to parse url found in did document due: {:?}", err), - ) - }) -} - -pub async fn pairwise_encrypt( - our_did_doc: &DidDocumentSov, - their_did_doc: &DidDocumentSov, - wallet: &impl BaseWallet, - message: &AriesMessage, -) -> AgentResult { - let sender_verkey = our_did_doc - .resolved_key_agreement() - .next() - .ok_or_else(|| { - AgentError::from_msg( - AgentErrorKind::InvalidState, - "No key agreement method found in our did document", - ) - })? - .public_key()? - .base58(); - EncryptionEnvelope::create( - wallet, - json!(message).to_string().as_bytes(), - Some(&sender_verkey), - &from_did_doc_sov_to_legacy(their_did_doc.clone())?, - ) - .await - .map_err(|err| err.into()) -} diff --git a/aries/agents/aries-vcx-agent/src/http.rs b/aries/agents/aries-vcx-agent/src/http.rs index 41371446fd..e141a865d1 100644 --- a/aries/agents/aries-vcx-agent/src/http.rs +++ b/aries/agents/aries-vcx-agent/src/http.rs @@ -6,7 +6,7 @@ pub struct VcxHttpClient; #[async_trait] impl Transport for VcxHttpClient { - async fn send_message(&self, msg: Vec, service_endpoint: Url) -> VcxResult<()> { + async fn send_message(&self, msg: Vec, service_endpoint: &Url) -> VcxResult<()> { shared::http_client::post_message(msg, service_endpoint).await?; Ok(()) } diff --git a/aries/agents/aries-vcx-agent/src/lib.rs b/aries/agents/aries-vcx-agent/src/lib.rs index 558dc5c769..a9cb543d33 100644 --- a/aries/agents/aries-vcx-agent/src/lib.rs +++ b/aries/agents/aries-vcx-agent/src/lib.rs @@ -9,7 +9,6 @@ extern crate uuid; mod agent; mod error; -pub mod helper; mod http; mod services; mod storage; diff --git a/aries/agents/aries-vcx-agent/src/services/connection.rs b/aries/agents/aries-vcx-agent/src/services/connection.rs index c608c2a3b8..7f1c7c3b67 100644 --- a/aries/agents/aries-vcx-agent/src/services/connection.rs +++ b/aries/agents/aries-vcx-agent/src/services/connection.rs @@ -2,9 +2,12 @@ use std::sync::{Arc, Mutex}; use aries_vcx::{ handlers::util::AnyInvitation, - messages::msg_fields::protocols::{ - connection::{request::Request, response::Response}, - notification::ack::Ack, + messages::{ + msg_fields::protocols::{ + connection::{request::Request, response::Response}, + notification::ack::Ack, + }, + AriesMessage, }, protocols::connection::{ pairwise_info::PairwiseInfo, Connection, GenericConnection, State, ThinState, @@ -44,6 +47,23 @@ impl ServiceConnections { } } + pub async fn send_message( + &self, + connection_id: &str, + message: &AriesMessage, + ) -> AgentResult<()> { + let connection = self.get_by_id(connection_id)?; + let wallet = self.wallet.as_ref(); + info!( + "Sending message to connection identified by id {}. Plaintext message payload: {}", + connection_id, message + ); + connection + .send_message(wallet, message, &VcxHttpClient) + .await?; + Ok(()) + } + pub async fn create_invitation( &self, pw_info: Option, diff --git a/aries/agents/aries-vcx-agent/src/services/did_exchange.rs b/aries/agents/aries-vcx-agent/src/services/did_exchange.rs index cddb304895..e5b663aec4 100644 --- a/aries/agents/aries-vcx-agent/src/services/did_exchange.rs +++ b/aries/agents/aries-vcx-agent/src/services/did_exchange.rs @@ -1,50 +1,57 @@ use std::sync::Arc; use aries_vcx::{ - messages::msg_fields::protocols::{ - did_exchange::{ - complete::Complete, problem_report::ProblemReport, request::Request, response::Response, + did_doc::schema::{service::typed::ServiceType, types::uri::Uri}, + did_parser::Did, + messages::{ + msg_fields::protocols::{ + did_exchange::{ + complete::Complete, problem_report::ProblemReport, request::Request, + response::Response, + }, + out_of_band::invitation::Invitation as OobInvitation, }, - out_of_band::invitation::Invitation as OobInvitation, + AriesMessage, }, protocols::did_exchange::{ - resolve_key_from_invitation, - state_machine::generic::{GenericDidExchange, ThinState}, + resolve_enc_key_from_invitation, + state_machine::{ + create_our_did_document, + generic::{GenericDidExchange, ThinState}, + }, }, transport::Transport, + utils::encryption_envelope::EncryptionEnvelope, }; -use aries_vcx_core::{ - ledger::indy_vdr_ledger::DefaultIndyLedgerRead, wallet::base_wallet::BaseWallet, -}; +use aries_vcx_core::wallet::base_wallet::BaseWallet; +use did_peer::peer_did::{numalgos::numalgo2::Numalgo2, PeerDid}; use did_resolver_registry::ResolverRegistry; +use did_resolver_sov::did_resolver::did_doc::schema::did_doc::DidDocument; use super::connection::ServiceEndpoint; use crate::{ - helper::{get_their_endpoint, pairwise_encrypt}, http::VcxHttpClient, storage::{object_cache::ObjectCache, Storage}, AgentError, AgentErrorKind, AgentResult, }; +// todo: break down into requester and responder services? pub struct ServiceDidExchange { - ledger_read: Arc, wallet: Arc, resolver_registry: Arc, service_endpoint: ServiceEndpoint, - did_exchange: Arc>, + did_exchange: Arc)>>, public_did: String, } impl ServiceDidExchange { pub fn new( - ledger_read: Arc, wallet: Arc, resolver_registry: Arc, service_endpoint: ServiceEndpoint, public_did: String, ) -> Self { Self { - ledger_read, wallet, service_endpoint, resolver_registry, @@ -53,14 +60,46 @@ impl ServiceDidExchange { } } - pub async fn send_request_public(&self, their_did: String) -> AgentResult { - let (requester, request) = GenericDidExchange::construct_request_public( - self.ledger_read.as_ref(), - format!("did:sov:{}", their_did).parse()?, - format!("did:sov:{}", self.public_did).parse()?, + pub async fn send_request( + &self, + their_did: String, + invitation_id: Option, + ) -> AgentResult<(String, Option)> { + // todo: type the return type + let (our_did_document, _our_verkey) = + create_our_did_document(self.wallet.as_ref(), self.service_endpoint.clone(), vec![]) + .await?; + + let their_did: Did = their_did.parse()?; + let our_peer_did = PeerDid::::from_did_doc(our_did_document.clone())?; + let (requester, request) = GenericDidExchange::construct_request( + self.resolver_registry.clone(), + invitation_id, + &their_did, + &our_peer_did, ) .await?; - let request_id = request + + // TODO: decouple this from AATH. The reason why we identify the requester's did-exchange + // protocol with pthid is because that's what AATH expects when calling GET + // /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; + + // todo: messages must provide easier way to access this without all the shenanigans + let thid = request .clone() .decorators .thread @@ -71,25 +110,40 @@ impl ServiceDidExchange { ) })? .thid; + let ddo_their = requester.their_did_doc(); let ddo_our = requester.our_did_document(); - let encryption_envelope = - pairwise_encrypt(ddo_our, ddo_their, self.wallet.as_ref(), &request.into()).await?; + let service = ddo_their.get_service_of_type(&ServiceType::DIDCommV1)?; + let encryption_envelope = pairwise_encrypt( + ddo_our, + ddo_their, + self.wallet.as_ref(), + &request.into(), + service.id(), + ) + .await?; + // todo: hack; There's issue on AATH level https://github.com/hyperledger/aries-agent-test-harness/issues/784 + // but if AATH can not be changed and both thid and pthid are used to track instance + // of protocol then we need to update storage to enable identification by + // multiple IDs (both thid, pthid (or arbitrary other)) + self.did_exchange.insert(&thid, (requester.clone(), None))?; VcxHttpClient - .send_message(encryption_envelope.0, get_their_endpoint(ddo_their)?) + .send_message(encryption_envelope.0, service.service_endpoint()) .await?; - self.did_exchange.insert(&request_id, requester.clone()) + Ok((thid, pthid)) } - pub async fn send_response( + // todo: whether invitation exists should handle the framework based on (p)thread matching + // rather than being supplied by upper layers + pub async fn process_request( &self, request: Request, - invitation: OobInvitation, - ) -> AgentResult { - // TODO: We should fetch the out of band invite associated with the request. - // We don't want to be sending response if we don't know if there is any invitation - // associated with the request. - let request_id = request + invitation: Option, + ) -> AgentResult<(String, Option)> { + // 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 @@ -100,80 +154,160 @@ impl ServiceDidExchange { ) })? .thid; - let invitation_key = - resolve_key_from_invitation(&invitation, &self.resolver_registry).await?; + + // Todo: "invitation_key" should not be None; see the todo inside this scope + let invitation_key = match invitation { + None => { + // TODO: Case for "implicit invitations", where request is sent on basis of + // knowledge of public DID However in that cases we should + // probably use the Recipient Verkey which was used to anoncrypt the Request msg + None + } + Some(invitation) => { + Some(resolve_enc_key_from_invitation(&invitation, &self.resolver_registry).await?) + } + }; + + let (our_did_document, _our_verkey) = + create_our_did_document(self.wallet.as_ref(), self.service_endpoint.clone(), vec![]) + .await?; + let peer_did_invitee = PeerDid::::from_did_doc(our_did_document.clone())?; + + let pthid = request + .clone() + .decorators + .thread + .clone() + .ok_or_else(|| { + AgentError::from_msg( + AgentErrorKind::InvalidState, + "Request did not contain a thread", + ) + })? + .pthid; + let (responder, response) = GenericDidExchange::handle_request( self.wallet.as_ref(), self.resolver_registry.clone(), request, - self.service_endpoint.clone(), - vec![], - invitation.id.clone(), + &peer_did_invitee, invitation_key, ) .await?; + self.did_exchange + .insert(&thid, (responder.clone(), Some(response.into())))?; + + Ok((thid, pthid)) + } + + // todo: perhaps injectable transports? Or just return the message let the caller send it? + // The transports abstraction could understand https, wss, didcomm etc. + pub async fn send_response(&self, thid: String) -> AgentResult { + info!("ServiceDidExchange::send_response >>> thid: {}", thid); + let (responder, aries_msg) = self.did_exchange.get(&thid)?; + let aries_msg: AriesMessage = aries_msg.unwrap(); + debug!( + "ServiceDidExchange::send_response >>> successfully found state machine and a message \ + to be send" + ); + let ddo_their = responder.their_did_doc(); let ddo_our = responder.our_did_document(); - let encryption_envelope = - pairwise_encrypt(ddo_our, ddo_their, self.wallet.as_ref(), &response.into()).await?; + let service = ddo_their.get_service_of_type(&ServiceType::DIDCommV1)?; + let encryption_envelope = pairwise_encrypt( + ddo_our, + ddo_their, + self.wallet.as_ref(), + &aries_msg, + service.id(), + ) + .await?; VcxHttpClient - .send_message(encryption_envelope.0, get_their_endpoint(ddo_their)?) + .send_message(encryption_envelope.0, service.service_endpoint()) .await?; - self.did_exchange.insert(&request_id, responder.clone()) + info!("ServiceDidExchange::send_response <<< successfully sent response"); + Ok(thid) } + // todo: break down into "process_response" and "send_complete" pub async fn send_complete(&self, response: Response) -> AgentResult { - let thread_id = response.decorators.thread.thid.clone(); - let (requester, complete) = self - .did_exchange - .get(&thread_id)? - .handle_response(response) - .await?; + let thid = response.decorators.thread.thid.clone(); + + let (requester, _) = self.did_exchange.get(&thid)?; + + let (requester, complete) = requester.handle_response(response).await?; let ddo_their = requester.their_did_doc(); let ddo_our = requester.our_did_document(); - let encryption_envelope = - pairwise_encrypt(ddo_our, ddo_their, self.wallet.as_ref(), &complete.into()).await?; + let service = ddo_their.get_service_of_type(&ServiceType::DIDCommV1)?; + let encryption_envelope = pairwise_encrypt( + ddo_our, + ddo_their, + self.wallet.as_ref(), + &complete.into(), + service.id(), + ) + .await?; + self.did_exchange.insert(&thid, (requester.clone(), None))?; VcxHttpClient - .send_message(encryption_envelope.0, get_their_endpoint(ddo_their)?) + .send_message(encryption_envelope.0, service.service_endpoint()) .await?; - self.did_exchange.insert(&thread_id, requester.clone()) + Ok(thid) } pub fn receive_complete(&self, complete: Complete) -> AgentResult { let thread_id = complete.decorators.thread.thid.clone(); - let requester = self - .did_exchange - .get(&thread_id)? - .handle_complete(complete)?; - self.did_exchange.insert(&thread_id, requester) + let (requester, _) = self.did_exchange.get(&thread_id)?; + let requester = requester.handle_complete(complete)?; + self.did_exchange.insert(&thread_id, (requester, None)) } pub fn receive_problem_report(&self, problem_report: ProblemReport) -> AgentResult { let thread_id = problem_report.decorators.thread.thid.clone(); - let requester = self - .did_exchange - .get(&thread_id)? - .handle_problem_report(problem_report)?; - self.did_exchange.insert(&thread_id, requester) + let (requester, _) = self.did_exchange.get(&thread_id)?; + let requester = requester.handle_problem_report(problem_report)?; + self.did_exchange.insert(&thread_id, (requester, None)) } pub fn exists_by_id(&self, thread_id: &str) -> bool { self.did_exchange.contains_key(thread_id) } - pub fn invitation_id(&self, thread_id: &str) -> AgentResult { - Ok(self - .did_exchange - .get(thread_id)? - .invitation_id() - .to_string()) + pub fn invitation_id(&self, _thread_id: &str) -> AgentResult { + unimplemented!() } pub fn public_did(&self) -> &str { self.public_did.as_ref() } - pub fn get_state(&self, thread_id: &str) -> AgentResult { - Ok(self.did_exchange.get(thread_id)?.get_state()) + // TODO: this 'thread_or_pthread' ugliness exists because in case of inviter, AATH identifies + // the instance of protocol by thid, but for invitee, it expects identification by pthid + // Created issue https://github.com/hyperledger/aries-agent-test-harness/issues/784 + pub fn get_state(&self, thid: &str) -> AgentResult { + let (protocol, _) = self.did_exchange.get(thid)?; + Ok(protocol.get_state()) } } + +pub async fn pairwise_encrypt( + our_did_doc: &DidDocument, + their_did_doc: &DidDocument, + wallet: &impl BaseWallet, + message: &AriesMessage, + their_service_id: &Uri, +) -> AgentResult { + EncryptionEnvelope::create( + wallet, + serde_json::json!(message).to_string().as_bytes(), + our_did_doc, + their_did_doc, + their_service_id, + ) + .await + .map_err(|err| { + AgentError::from_msg( + AgentErrorKind::InvalidState, + &format!("Failed to pairwise encrypt message due err: {}", err), + ) + }) +} diff --git a/aries/agents/aries-vcx-agent/src/services/holder.rs b/aries/agents/aries-vcx-agent/src/services/holder.rs index ae4ff6ee36..127b471539 100644 --- a/aries/agents/aries-vcx-agent/src/services/holder.rs +++ b/aries/agents/aries-vcx-agent/src/services/holder.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use aries_vcx::{ + did_parser::Did, handlers::issuance::holder::Holder, messages::{ msg_fields::protocols::cred_issuance::v1::{ @@ -77,16 +78,11 @@ impl ServiceCredentialsHolder { connection_id: &str, propose_credential: ProposeCredentialV1, ) -> AgentResult { - let connection = self.service_connections.get_by_id(connection_id)?; + let holder = Holder::create_with_proposal("foobar", propose_credential)?; - let mut holder = Holder::create("")?; - holder.set_proposal(propose_credential.clone())?; - connection - .send_message( - self.wallet.as_ref(), - &propose_credential.into(), - &VcxHttpClient, - ) + let aries_msg: AriesMessage = holder.get_proposal()?.into(); + self.service_connections + .send_message(connection_id, &aries_msg) .await?; self.creds_holder.insert( @@ -101,44 +97,34 @@ impl ServiceCredentialsHolder { offer: OfferCredentialV1, ) -> AgentResult { self.service_connections.get_by_id(connection_id)?; - let holder = Holder::create_from_offer("", offer)?; + let holder = Holder::create_from_offer("foobar", offer)?; self.creds_holder.insert( &holder.get_thread_id()?, HolderWrapper::new(holder, connection_id), ) } - pub async fn send_credential_request( - &self, - thread_id: Option<&str>, - connection_id: Option<&str>, - ) -> AgentResult { - let (mut holder, connection_id) = match (thread_id, connection_id) { - (Some(id), Some(connection_id)) => (self.get_holder(id)?, connection_id.to_string()), - (Some(id), None) => (self.get_holder(id)?, self.get_connection_id(id)?), - (None, Some(connection_id)) => (Holder::create("")?, connection_id.to_string()), - (None, None) => return Err(AgentError::from_kind(AgentErrorKind::InvalidArguments)), - }; + pub async fn send_credential_request(&self, thread_id: &str) -> AgentResult { + let connection_id = self.get_connection_id(thread_id)?; let connection = self.service_connections.get_by_id(&connection_id)?; + // todo: technically doesn't need to be DID at all, and definitely need not to be pairwise + // DID + let pw_did_as_entropy = connection.pairwise_info().pw_did.to_string(); - let pw_did = connection.pairwise_info().pw_did.to_string(); - - let send_closure: SendClosure = Box::new(|msg: AriesMessage| { - Box::pin(async move { - connection - .send_message(self.wallet.as_ref(), &msg, &VcxHttpClient) - .await - }) - }); - let msg_response = holder + let mut holder = self.get_holder(thread_id)?; + let message = holder .prepare_credential_request( self.wallet.as_ref(), self.ledger_read.as_ref(), &self.anoncreds, - pw_did.parse()?, + Did::parse(pw_did_as_entropy)?, ) .await?; - send_closure(msg_response).await?; + + self.service_connections + .send_message(&connection_id, &message) + .await?; + self.creds_holder.insert( &holder.get_thread_id()?, HolderWrapper::new(holder, &connection_id), diff --git a/aries/agents/aries-vcx-agent/src/services/issuer.rs b/aries/agents/aries-vcx-agent/src/services/issuer.rs index ba1f9aa769..3ce4468254 100644 --- a/aries/agents/aries-vcx-agent/src/services/issuer.rs +++ b/aries/agents/aries-vcx-agent/src/services/issuer.rs @@ -74,10 +74,14 @@ impl ServiceCredentialsIssuer { proposal: &ProposeCredentialV1, ) -> AgentResult { let issuer = Issuer::create_from_proposal("", proposal)?; - self.creds_issuer.insert( - &issuer.get_thread_id()?, - IssuerWrapper::new(issuer, connection_id), - ) + let thread_id = issuer.get_thread_id()?; + self.creds_issuer + .insert(&thread_id, IssuerWrapper::new(issuer, connection_id))?; + info!( + "Created new IssuerCredential with resource id: {}", + thread_id + ); + Ok(thread_id) } pub async fn send_credential_offer( diff --git a/aries/agents/aries-vcx-agent/src/services/out_of_band.rs b/aries/agents/aries-vcx-agent/src/services/out_of_band.rs index 202b55c3b2..e18e110501 100644 --- a/aries/agents/aries-vcx-agent/src/services/out_of_band.rs +++ b/aries/agents/aries-vcx-agent/src/services/out_of_band.rs @@ -1,10 +1,6 @@ use std::sync::Arc; use aries_vcx::{ - did_doc_sov::{ - extra_fields::{didcommv1::ExtraFieldsDidCommV1, KeyKind}, - service::{didcommv1::ServiceDidCommV1, ServiceSov}, - }, handlers::out_of_band::{ receiver::OutOfBandReceiver, sender::OutOfBandSender, GenericOutOfBand, }, @@ -16,11 +12,10 @@ use aries_vcx::{ }, AriesMessage, }, - protocols::did_exchange::state_machine::generate_keypair, + protocols::did_exchange::state_machine::create_our_did_document, }; use aries_vcx_core::wallet::base_wallet::BaseWallet; -use public_key::KeyType; -use uuid::Uuid; +use did_peer::peer_did::{numalgos::numalgo2::Numalgo2, PeerDid}; use super::connection::ServiceEndpoint; use crate::{ @@ -44,19 +39,13 @@ impl ServiceOutOfBand { } pub async fn create_invitation(&self) -> AgentResult { - let public_key = generate_keypair(self.wallet.as_ref(), KeyType::Ed25519).await?; - let service = { - let service_id = Uuid::new_v4().to_string(); - ServiceSov::DIDCommV1(ServiceDidCommV1::new( - service_id.parse()?, - self.service_endpoint.to_owned().into(), - ExtraFieldsDidCommV1::builder() - .set_recipient_keys(vec![KeyKind::DidKey(public_key.try_into()?)]) - .build(), - )?) - }; + let (our_did_document, _our_verkey) = + create_our_did_document(self.wallet.as_ref(), self.service_endpoint.clone(), vec![]) + .await?; + let peer_did = PeerDid::::from_did_doc(our_did_document)?; + let sender = OutOfBandSender::create() - .append_service(&OobService::SovService(service)) + .append_service(&OobService::Did(peer_did.to_string())) .append_handshake_protocol(Protocol::DidExchangeType(DidExchangeType::V1( DidExchangeTypeV1::new_v1_0(), )))?; diff --git a/aries/agents/aries-vcx-agent/src/storage/object_cache.rs b/aries/agents/aries-vcx-agent/src/storage/object_cache.rs index 8f34dfd37e..dd2aa02557 100644 --- a/aries/agents/aries-vcx-agent/src/storage/object_cache.rs +++ b/aries/agents/aries-vcx-agent/src/storage/object_cache.rs @@ -89,6 +89,10 @@ where } fn insert(&self, id: &str, obj: T) -> AgentResult { + info!( + "Inserting object {} into in-mem storage: {}", + id, self.cache_name + ); let mut store = self._lock_store_write()?; match store.insert(id.to_string(), Mutex::new(obj)) { diff --git a/aries/agents/mediator/src/aries_agent/mod.rs b/aries/agents/mediator/src/aries_agent/mod.rs index 4bdc05fef4..de1e052539 100644 --- a/aries/agents/mediator/src/aries_agent/mod.rs +++ b/aries/agents/mediator/src/aries_agent/mod.rs @@ -137,9 +137,14 @@ impl Agent { our_vk: &VerKey, their_diddoc: &AriesDidDoc, ) -> Result { - EncryptionEnvelope::create(self.wallet.as_ref(), message, Some(our_vk), their_diddoc) - .await - .map_err(string_from_std_error) + EncryptionEnvelope::create_from_legacy( + self.wallet.as_ref(), + message, + Some(our_vk), + their_diddoc, + ) + .await + .map_err(string_from_std_error) } pub async fn auth_and_get_details( @@ -197,7 +202,7 @@ impl Agent { .map_err(|e| e.to_string())?; let aries_response = AriesMessage::Connection(Connection::Response(response)); let their_diddoc = request.content.connection.did_doc; - let packed_response_envelope = EncryptionEnvelope::create( + let packed_response_envelope = EncryptionEnvelope::create_from_legacy( self.wallet.as_ref(), json!(aries_response).to_string().as_bytes(), Some(&old_vk), diff --git a/aries/agents/mediator/src/aries_agent/utils.rs b/aries/agents/mediator/src/aries_agent/utils.rs index fbefebdd12..f074c078e4 100644 --- a/aries/agents/mediator/src/aries_agent/utils.rs +++ b/aries/agents/mediator/src/aries_agent/utils.rs @@ -55,7 +55,7 @@ pub struct MockTransport; #[async_trait] impl Transport for MockTransport { - async fn send_message(&self, _msg: Vec, _service_endpoint: url::Url) -> VcxResult<()> { + async fn send_message(&self, _msg: Vec, _service_endpoint: &url::Url) -> VcxResult<()> { Ok(()) } } diff --git a/aries/agents/mediator/tests/mediator-protocol-pickup.rs b/aries/agents/mediator/tests/mediator-protocol-pickup.rs index a8416fd563..2fb8c4db80 100644 --- a/aries/agents/mediator/tests/mediator-protocol-pickup.rs +++ b/aries/agents/mediator/tests/mediator-protocol-pickup.rs @@ -47,7 +47,7 @@ async fn forward_basic_anoncrypt_message( .id("JustHello".to_string()) .build(); - let EncryptionEnvelope(packed_message) = EncryptionEnvelope::create( + let EncryptionEnvelope(packed_message) = EncryptionEnvelope::create_from_legacy( agent_f.get_wallet_ref().as_ref(), &serde_json::to_vec(&message)?, None, diff --git a/aries/agents/mediator/tests/mediator-routing-forward.rs b/aries/agents/mediator/tests/mediator-routing-forward.rs index 663206ed9e..17d017ecf8 100644 --- a/aries/agents/mediator/tests/mediator-routing-forward.rs +++ b/aries/agents/mediator/tests/mediator-routing-forward.rs @@ -58,7 +58,7 @@ async fn test_forward_flow() -> Result<()> { "Prepared message {:?}, proceeding to anoncrypt wrap", serde_json::to_string(&message).unwrap() ); - let EncryptionEnvelope(packed_message) = EncryptionEnvelope::create( + let EncryptionEnvelope(packed_message) = EncryptionEnvelope::create_from_legacy( agent.get_wallet_ref().as_ref(), &serde_json::to_vec(&message)?, None, diff --git a/aries/aries_vcx/Cargo.toml b/aries/aries_vcx/Cargo.toml index fe9b317514..6a1228a607 100644 --- a/aries/aries_vcx/Cargo.toml +++ b/aries/aries_vcx/Cargo.toml @@ -48,8 +48,8 @@ did_resolver = { path = "../../did_core/did_resolver" } did_doc = { path = "../../did_core/did_doc" } did_key = { path = "../../did_core/did_methods/did_key" } public_key = { path = "../../did_core/public_key" } -did_doc_sov = { path = "../../did_core/did_doc_sov" } did_peer = { path = "../../did_core/did_methods/did_peer" } +did_resolver_sov = { path = "../../did_core/did_methods/did_resolver_sov" } did_resolver_registry = { path = "../../did_core/did_resolver_registry" } bs58 = "0.5.0" async-trait = "0.1.53" diff --git a/aries/aries_vcx/src/common/ledger/service_didsov.rs b/aries/aries_vcx/src/common/ledger/service_didsov.rs index 6c7499c9cb..d36868675b 100644 --- a/aries/aries_vcx/src/common/ledger/service_didsov.rs +++ b/aries/aries_vcx/src/common/ledger/service_didsov.rs @@ -13,20 +13,7 @@ pub struct EndpointDidSov { #[serde(default)] pub routing_keys: Option>, #[serde(default)] - pub types: Option>, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -#[cfg_attr(test, derive(PartialEq, Eq))] -pub enum DidSovServiceType { - #[serde(rename = "endpoint")] // AIP 1.0 - Endpoint, - #[serde(rename = "did-communication")] // AIP 2.0 - DidCommunication, - #[serde(rename = "DIDComm")] // DIDComm V2 - DIDComm, - #[serde(other)] - Unknown, + pub types: Option>, } impl EndpointDidSov { @@ -44,7 +31,7 @@ impl EndpointDidSov { self } - pub fn set_types(mut self, types: Option>) -> Self { + pub fn set_types(mut self, types: Option>) -> Self { self.types = types; self } @@ -62,10 +49,11 @@ impl Default for EndpointDidSov { #[cfg(test)] mod unit_tests { + use did_doc::schema::service::typed::ServiceType; use diddoc_legacy::aries::diddoc::test_utils::{_routing_keys, _service_endpoint}; use test_utils::devsetup::SetupMocks; - use crate::common::ledger::service_didsov::{DidSovServiceType, EndpointDidSov}; + use crate::common::ledger::service_didsov::EndpointDidSov; #[test] fn test_service_comparison() { @@ -97,9 +85,10 @@ mod unit_tests { .set_service_endpoint(_service_endpoint()) .set_routing_keys(Some(_routing_keys())) .set_types(Some(vec![ - DidSovServiceType::Endpoint, - DidSovServiceType::DidCommunication, - DidSovServiceType::DIDComm, + ServiceType::AIP1.to_string(), + ServiceType::DIDCommV1.to_string(), + ServiceType::DIDCommV2.to_string(), + "foobar".to_string(), ])); let expected = json!({ @@ -108,7 +97,7 @@ mod unit_tests { "Hezce2UWMZ3wUhVkh2LfKSs8nDzWwzs2Win7EzNN3YaR", "3LYuxJBJkngDbvJj4zjx13DBUdZ2P96eNybwd2n9L9AU" ], - "types": ["endpoint", "did-communication", "DIDComm"] + "types": ["endpoint", "did-communication", "DIDCommMessaging", "foobar"] }); assert_eq!(expected, json!(&service1)); } @@ -116,14 +105,13 @@ mod unit_tests { #[test] fn test_didsov_service_deserialization() { SetupMocks::init(); - let data = json!({ "endpoint": "http://localhost:8080", "routingKeys": [ "Hezce2UWMZ3wUhVkh2LfKSs8nDzWwzs2Win7EzNN3YaR", "3LYuxJBJkngDbvJj4zjx13DBUdZ2P96eNybwd2n9L9AU" ], - "types": ["endpoint", "did-communication", "DIDComm", "foobar"] + "types": ["endpoint", "did-communication", "DIDCommMessaging", "foobar"] }) .to_string(); @@ -133,10 +121,10 @@ mod unit_tests { assert_eq!( deserialized.types, Some(vec![ - DidSovServiceType::Endpoint, - DidSovServiceType::DidCommunication, - DidSovServiceType::DIDComm, - DidSovServiceType::Unknown + ServiceType::AIP1.to_string(), + ServiceType::DIDCommV1.to_string(), + ServiceType::DIDCommV2.to_string(), + "foobar".to_string() ]) ); } diff --git a/aries/aries_vcx/src/common/ledger/transactions.rs b/aries/aries_vcx/src/common/ledger/transactions.rs index cca17089c1..a3798ce5dd 100644 --- a/aries/aries_vcx/src/common/ledger/transactions.rs +++ b/aries/aries_vcx/src/common/ledger/transactions.rs @@ -7,6 +7,7 @@ use aries_vcx_core::{ }, wallet::base_wallet::BaseWallet, }; +use did_doc::schema::service::Service; use did_parser::Did; use diddoc_legacy::aries::service::AriesService; use messages::msg_fields::protocols::out_of_band::invitation::OobService; @@ -16,7 +17,6 @@ use serde_json::Value; use crate::{ common::{keys::get_verkey_from_ledger, ledger::service_didsov::EndpointDidSov}, errors::error::{AriesVcxError, AriesVcxErrorKind, VcxResult}, - utils::from_service_sov_to_legacy, }; #[derive(Deserialize, Debug)] @@ -74,7 +74,6 @@ pub async fn resolve_service( ) -> VcxResult { match service { OobService::AriesService(service) => Ok(service.clone()), - OobService::SovService(service) => Ok(from_service_sov_to_legacy(service.to_owned())), OobService::Did(did) => get_service(indy_ledger, &did.clone().parse()?).await, } } @@ -196,6 +195,31 @@ pub async fn write_endpoint( Ok(res) } +fn _service_to_didsov_endpoint_attribute(service: &Service) -> EndpointDidSov { + let routing_keys: Option> = service + .extra_field_routing_keys() + .ok() + .map(|keys| keys.iter().map(|key| key.to_string()).collect()); + + let service_types = service.service_types(); + let types_str: Vec = service_types.iter().map(|t| t.to_string()).collect(); + EndpointDidSov::create() + .set_routing_keys(routing_keys) + .set_types(Some(types_str)) + .set_service_endpoint(service.service_endpoint().clone()) +} + +pub async fn write_endpoint_from_service( + wallet: &impl BaseWallet, + indy_ledger_write: &impl IndyLedgerWrite, + did: &Did, + service: &Service, +) -> VcxResult<(String, EndpointDidSov)> { + let attribute = _service_to_didsov_endpoint_attribute(service); + let res = write_endpoint(wallet, indy_ledger_write, did, &attribute).await?; + Ok((res, attribute)) +} + pub async fn add_attr( wallet: &impl BaseWallet, indy_ledger_write: &impl IndyLedgerWrite, diff --git a/aries/aries_vcx/src/errors/mapping_others.rs b/aries/aries_vcx/src/errors/mapping_others.rs index 8219c40ccf..6be3052e92 100644 --- a/aries/aries_vcx/src/errors/mapping_others.rs +++ b/aries/aries_vcx/src/errors/mapping_others.rs @@ -1,6 +1,7 @@ use std::{num::ParseIntError, sync::PoisonError}; use aries_vcx_core::errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind}; +use did_doc::schema::{types::uri::UriWrapperError, utils::error::DidDocumentLookupError}; use shared::errors::http_error::HttpError; use crate::{ @@ -45,8 +46,8 @@ impl From for AriesVcxError { } } -impl From for AriesVcxError { - fn from(err: did_doc_sov::error::DidDocumentSovError) -> Self { +impl From for AriesVcxError { + fn from(err: DidDocumentLookupError) -> Self { AriesVcxError::from_msg(AriesVcxErrorKind::InvalidState, err.to_string()) } } @@ -81,6 +82,12 @@ impl From for AriesVcxError { } } +impl From for AriesVcxError { + fn from(err: UriWrapperError) -> Self { + AriesVcxError::from_msg(AriesVcxErrorKind::InvalidInput, err.to_string()) + } +} + impl From for AriesVcxError { fn from(err: ParseIntError) -> Self { AriesVcxError::from_msg(AriesVcxErrorKind::InvalidInput, err.to_string()) diff --git a/aries/aries_vcx/src/handlers/issuance/holder.rs b/aries/aries_vcx/src/handlers/issuance/holder.rs index 7a4eed17e8..d1fd135f3c 100644 --- a/aries/aries_vcx/src/handlers/issuance/holder.rs +++ b/aries/aries_vcx/src/handlers/issuance/holder.rs @@ -61,6 +61,10 @@ impl Holder { Ok(Holder { holder_sm }) } + pub fn get_proposal(&self) -> VcxResult { + self.holder_sm.get_proposal() + } + pub fn create_with_proposal( source_id: &str, propose_credential: ProposeCredentialV1, diff --git a/aries/aries_vcx/src/handlers/issuance/issuer.rs b/aries/aries_vcx/src/handlers/issuance/issuer.rs index db82e3e6ad..a7e3622bcf 100644 --- a/aries/aries_vcx/src/handlers/issuance/issuer.rs +++ b/aries/aries_vcx/src/handlers/issuance/issuer.rs @@ -43,7 +43,6 @@ fn _build_credential_preview(credential_json: &str) -> VcxResult>> credential_json: {:?}", secret!(credential_json) ); - let cred_values: serde_json::Value = serde_json::from_str(credential_json).map_err(|err| { AriesVcxError::from_msg( AriesVcxErrorKind::InvalidJson, diff --git a/aries/aries_vcx/src/lib.rs b/aries/aries_vcx/src/lib.rs index ed7cf1da3a..a5e03f47c5 100644 --- a/aries/aries_vcx/src/lib.rs +++ b/aries/aries_vcx/src/lib.rs @@ -21,7 +21,6 @@ extern crate derive_builder; pub extern crate aries_vcx_core; pub extern crate did_doc; -pub extern crate did_doc_sov; pub extern crate did_parser; pub extern crate did_peer; pub extern crate messages; diff --git a/aries/aries_vcx/src/protocols/connection/generic/mod.rs b/aries/aries_vcx/src/protocols/connection/generic/mod.rs index bcdd0f7a14..120804c382 100644 --- a/aries/aries_vcx/src/protocols/connection/generic/mod.rs +++ b/aries/aries_vcx/src/protocols/connection/generic/mod.rs @@ -180,7 +180,7 @@ impl GenericConnection { AriesVcxErrorKind::NotReady, "No DidDoc present", ))?; - EncryptionEnvelope::create( + EncryptionEnvelope::create_from_legacy( wallet, json!(message).to_string().as_bytes(), Some(sender_verkey), @@ -207,7 +207,7 @@ impl GenericConnection { let service_endpoint = did_doc.get_endpoint().ok_or_else(|| { AriesVcxError::from_msg(AriesVcxErrorKind::InvalidUrl, "No URL in DID Doc") })?; - transport.send_message(msg, service_endpoint).await + transport.send_message(msg, &service_endpoint).await } } @@ -340,7 +340,7 @@ mod connection_serde_tests { #[async_trait] impl Transport for MockTransport { - async fn send_message(&self, _msg: Vec, _service_endpoint: Url) -> VcxResult<()> { + async fn send_message(&self, _msg: Vec, _service_endpoint: &Url) -> VcxResult<()> { Ok(()) } } diff --git a/aries/aries_vcx/src/protocols/connection/mod.rs b/aries/aries_vcx/src/protocols/connection/mod.rs index 5368355451..863b25dcf0 100644 --- a/aries/aries_vcx/src/protocols/connection/mod.rs +++ b/aries/aries_vcx/src/protocols/connection/mod.rs @@ -100,7 +100,7 @@ where message: &AriesMessage, ) -> VcxResult { let sender_verkey = &self.pairwise_info().pw_vk; - EncryptionEnvelope::create( + EncryptionEnvelope::create_from_legacy( wallet, json!(message).to_string().as_bytes(), Some(sender_verkey), @@ -137,7 +137,7 @@ where let service_endpoint = self.their_did_doc().get_endpoint().ok_or_else(|| { AriesVcxError::from_msg(AriesVcxErrorKind::InvalidUrl, "No URL in DID Doc") })?; - transport.send_message(msg, service_endpoint).await + transport.send_message(msg, &service_endpoint).await } } diff --git a/aries/aries_vcx/src/protocols/did_exchange/mod.rs b/aries/aries_vcx/src/protocols/did_exchange/mod.rs index b1d9cfb573..71769b1408 100644 --- a/aries/aries_vcx/src/protocols/did_exchange/mod.rs +++ b/aries/aries_vcx/src/protocols/did_exchange/mod.rs @@ -1,20 +1,84 @@ use std::sync::Arc; -use did_doc_sov::extra_fields::KeyKind; -use did_resolver::traits::resolvable::resolution_output::DidResolutionOutput; +use did_doc::schema::{ + did_doc::DidDocument, + verification_method::{VerificationMethod, VerificationMethodKind}, +}; +use did_parser::DidUrl; use did_resolver_registry::ResolverRegistry; use messages::msg_fields::protocols::out_of_band::invitation::{ Invitation as OobInvitation, OobService, }; -use public_key::{Key, KeyType}; +use public_key::Key; -use crate::errors::error::{AriesVcxError, AriesVcxErrorKind}; +use crate::errors::error::{AriesVcxError, AriesVcxErrorKind, VcxResult}; pub mod state_machine; pub mod states; pub mod transition; -pub async fn resolve_key_from_invitation( +fn resolve_verification_method( + did_doc: &DidDocument, + verification_method_ref: &DidUrl, +) -> Result { + let key = did_doc.verification_method().iter().find(|key_agreement| { + let reference_fragment = match verification_method_ref.fragment() { + None => { + warn!( + "Fragment not found in verification method reference {}", + verification_method_ref + ); + return false; + } + Some(fragment) => fragment, + }; + let key_agreement_fragment = match key_agreement.id().fragment() { + None => { + warn!( + "Fragment not found in verification method {}", + key_agreement.id() + ); + return false; + } + Some(fragment) => fragment, + }; + reference_fragment == key_agreement_fragment + }); + match key { + None => Err(AriesVcxError::from_msg( + AriesVcxErrorKind::InvalidState, + format!( + "Verification method not found in resolved did document {}", + did_doc + ), + )), + Some(verification_method) => Ok(verification_method.clone()), + } +} + +fn resolve_first_key_agreement(did_document: &DidDocument) -> VcxResult { + // todo: did_document needs robust way to resolve this, I shouldn't care if there's reference or + // actual key in the key_agreement Abstract the user from format/structure of the did + // document + let verification_method_kind = did_document.key_agreement().first().ok_or_else(|| { + AriesVcxError::from_msg( + AriesVcxErrorKind::InvalidState, + format!( + "No verification method found in resolved did document {}", + did_document + ), + ) + })?; + let verification_method = match verification_method_kind { + VerificationMethodKind::Resolved(verification_method) => verification_method.clone(), + VerificationMethodKind::Resolvable(verification_method_ref) => { + resolve_verification_method(did_document, verification_method_ref)? + } + }; + Ok(verification_method) +} + +pub async fn resolve_enc_key_from_invitation( invitation: &OobInvitation, resolver_registry: &Arc, ) -> Result { @@ -24,16 +88,9 @@ pub async fn resolve_key_from_invitation( "Invitation does not contain any services", ) })? { - OobService::SovService(service) => match service.extra().first_recipient_key()? { - KeyKind::DidKey(did_key) => Ok(did_key.key().to_owned()), - KeyKind::Value(value) => Ok(Key::from_base58(value, KeyType::Ed25519)?), - KeyKind::Reference(reference) => Err(AriesVcxError::from_msg( - AriesVcxErrorKind::InvalidInput, - format!("Cannot resolve the reference {reference} without a did document"), - )), - }, OobService::Did(did) => { - let DidResolutionOutput { did_document, .. } = resolver_registry + info!("Invitation contains service (DID format): {}", did); + let output = resolver_registry .resolve(&did.clone().try_into()?, &Default::default()) .await .map_err(|err| { @@ -42,25 +99,15 @@ pub async fn resolve_key_from_invitation( format!("DID resolution failed: {err}"), ) })?; - Ok(did_document - .verification_method() - .first() - .ok_or_else(|| { - AriesVcxError::from_msg( - AriesVcxErrorKind::InvalidState, - "No verification method found in resolved did document", - ) - })? - .public_key()?) + info!( + "resolve_enc_key_from_invitation >> Resolved did document {}", + output.did_document + ); + let key = resolve_first_key_agreement(&output.did_document)?; + Ok(key.public_key()?) + } + OobService::AriesService(_service) => { + unimplemented!("Embedded Aries Service not yet supported by did-exchange") } - OobService::AriesService(service) => Ok(Key::from_base58( - service.recipient_keys.first().ok_or_else(|| { - AriesVcxError::from_msg( - AriesVcxErrorKind::InvalidState, - "No recipient key found in aries service", - ) - })?, - KeyType::Ed25519, - )?), } } 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 da40c87678..d680262937 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 @@ -1,18 +1,15 @@ use std::sync::Arc; -use aries_vcx_core::{ledger::base_ledger::IndyLedgerRead, wallet::base_wallet::BaseWallet}; -use did_doc_sov::DidDocumentSov; +use aries_vcx_core::wallet::base_wallet::BaseWallet; +use did_doc::schema::did_doc::DidDocument; use did_parser::Did; +use did_peer::peer_did::{numalgos::numalgo2::Numalgo2, PeerDid}; use did_resolver_registry::ResolverRegistry; -use messages::msg_fields::protocols::{ - did_exchange::{ - complete::Complete, problem_report::ProblemReport, request::Request, response::Response, - }, - out_of_band::invitation::Invitation, +use messages::msg_fields::protocols::did_exchange::{ + complete::Complete, problem_report::ProblemReport, request::Request, response::Response, }; use public_key::Key; pub use thin_state::ThinState; -use url::Url; use super::{requester::DidExchangeRequester, responder::DidExchangeResponder}; use crate::{ @@ -50,7 +47,7 @@ pub enum ResponderState { } impl GenericDidExchange { - pub fn our_did_document(&self) -> &DidDocumentSov { + pub fn our_did_document(&self) -> &DidDocument { match self { GenericDidExchange::Requester(requester_state) => match requester_state { RequesterState::RequestSent(request_sent_state) => request_sent_state.our_did_doc(), @@ -67,7 +64,7 @@ impl GenericDidExchange { } } - pub fn their_did_doc(&self) -> &DidDocumentSov { + pub fn their_did_doc(&self) -> &DidDocument { match self { GenericDidExchange::Requester(requester_state) => match requester_state { RequesterState::RequestSent(request_sent_state) => { @@ -86,55 +83,18 @@ impl GenericDidExchange { } } - pub fn invitation_id(&self) -> &str { - match self { - GenericDidExchange::Requester(requester_state) => match requester_state { - RequesterState::RequestSent(request_sent_state) => { - request_sent_state.get_invitation_id() - } - RequesterState::Completed(completed_state) => completed_state.get_invitation_id(), - RequesterState::Abandoned(_) => todo!(), - }, - GenericDidExchange::Responder(responder_state) => match responder_state { - ResponderState::ResponseSent(response_sent_state) => { - response_sent_state.get_invitation_id() - } - ResponderState::Completed(completed_state) => completed_state.get_invitation_id(), - ResponderState::Abandoned(_abandoned_state) => todo!(), - }, - } - } - - pub async fn construct_request_public( - ledger: &impl IndyLedgerRead, - their_did: Did, - our_did: Did, - ) -> Result<(Self, Request), AriesVcxError> { - let TransitionResult { state, output } = - DidExchangeRequester::::construct_request_public( - ledger, their_did, our_did, - ) - .await?; - Ok(( - GenericDidExchange::Requester(RequesterState::RequestSent(state)), - output, - )) - } - - pub async fn construct_request_pairwise( - wallet: &impl BaseWallet, - invitation: Invitation, + pub async fn construct_request( resolver_registry: Arc, - service_endpoint: Url, - routing_keys: Vec, + invitation_id: Option, + their_did: &Did, + our_peer_did: &PeerDid, ) -> Result<(Self, Request), AriesVcxError> { let TransitionResult { state, output } = - DidExchangeRequester::::construct_request_pairwise( - wallet, - invitation, + DidExchangeRequester::::construct_request( resolver_registry, - service_endpoint, - routing_keys, + invitation_id, + their_did, + our_peer_did, ) .await?; Ok(( @@ -147,19 +107,15 @@ impl GenericDidExchange { wallet: &impl BaseWallet, resolver_registry: Arc, request: Request, - service_endpoint: Url, - routing_keys: Vec, - invitation_id: String, - invitation_key: Key, + our_peer_did: &PeerDid, + invitation_key: Option, ) -> Result<(Self, Response), AriesVcxError> { let TransitionResult { state, output } = DidExchangeResponder::::receive_request( wallet, resolver_registry, request, - service_endpoint, - routing_keys, - invitation_id, + our_peer_did, invitation_key, ) .await?; diff --git a/aries/aries_vcx/src/protocols/did_exchange/state_machine/generic/thin_state.rs b/aries/aries_vcx/src/protocols/did_exchange/state_machine/generic/thin_state.rs index fef38af111..03fbc633b9 100644 --- a/aries/aries_vcx/src/protocols/did_exchange/state_machine/generic/thin_state.rs +++ b/aries/aries_vcx/src/protocols/did_exchange/state_machine/generic/thin_state.rs @@ -1,11 +1,12 @@ +// todo: remove text representations, and should definitely not be driven by AATH expectations #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ThinState { - #[serde(rename = "RequestSent")] + #[serde(rename = "request-sent")] RequestSent, - #[serde(rename = "ResponseSent")] + #[serde(rename = "response-sent")] ResponseSent, - #[serde(rename = "Completed")] + #[serde(rename = "completed")] Completed, - #[serde(rename = "Abandoned")] + #[serde(rename = "abandoned")] Abandoned, } 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 1cefcb1f8d..2b054d4e37 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 @@ -2,22 +2,30 @@ use std::collections::HashMap; use aries_vcx_core::wallet::base_wallet::BaseWallet; use base64::engine::general_purpose::URL_SAFE_NO_PAD; +use chrono::Utc; use did_doc::schema::{ + did_doc::DidDocument, + service::{service_key_kind::ServiceKeyKind, typed::didcommv1::ServiceDidCommV1, Service}, types::uri::Uri, - verification_method::{VerificationMethod, VerificationMethodKind, VerificationMethodType}, -}; -use did_doc_sov::{ - extra_fields::{didcommv1::ExtraFieldsDidCommV1, KeyKind}, - service::{didcommv1::ServiceDidCommV1, ServiceSov}, - DidDocumentSov, + verification_method::{VerificationMethod, VerificationMethodType}, }; use did_key::DidKey; use did_parser::{Did, DidUrl}; use did_peer::peer_did::{numalgos::numalgo2::Numalgo2, PeerDid}; -use messages::decorators::attachment::{Attachment, AttachmentData, AttachmentType}; +use messages::{ + decorators::{ + attachment::{Attachment, AttachmentData, AttachmentType}, + thread::Thread, + timing::Timing, + }, + msg_fields::protocols::did_exchange::response::{ + Response, ResponseContent, ResponseDecorators, + }, +}; use public_key::{Key, KeyType}; use serde_json::Value; use url::Url; +use uuid::Uuid; use crate::{ errors::error::{AriesVcxError, AriesVcxErrorKind}, @@ -27,6 +35,26 @@ use crate::{ }, }; +pub fn construct_response( + 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)) + .build(); + let decorators = ResponseDecorators::builder() + .thread(Thread::builder().thid(request_id).build()) + .timing(Timing::builder().out_time(Utc::now()).build()) + .build(); + Response::builder() + .id(Uuid::new_v4().to_string()) + .content(content) + .decorators(decorators) + .build() +} + pub async fn generate_keypair( wallet: &impl BaseWallet, key_type: KeyType, @@ -35,82 +63,56 @@ pub async fn generate_keypair( Ok(Key::from_base58(&pairwise_info.pw_vk, key_type)?) } -pub fn construct_service( - routing_keys: Vec, - recipient_keys: Vec, - service_endpoint: Url, -) -> Result { - let extra = ExtraFieldsDidCommV1::builder() - .set_routing_keys(routing_keys) - .set_recipient_keys(recipient_keys) - .build(); - let service = ServiceSov::DIDCommV1(ServiceDidCommV1::new( - Uri::new("#0")?, - service_endpoint.into(), - extra, - )?); - Ok(service) -} - pub async fn create_our_did_document( wallet: &impl BaseWallet, service_endpoint: Url, routing_keys: Vec, -) -> Result<(DidDocumentSov, Key), AriesVcxError> { - let key_ver = generate_keypair(wallet, KeyType::Ed25519).await?; - let key_enc = generate_keypair(wallet, KeyType::X25519).await?; - let service = construct_service( - routing_keys.into_iter().map(KeyKind::Value).collect(), - vec![KeyKind::DidKey(key_enc.clone().try_into()?)], +) -> Result<(DidDocument, Key), AriesVcxError> { + let key_enc = generate_keypair(wallet, KeyType::Ed25519).await?; + + let service: Service = ServiceDidCommV1::new( + Uri::new("#0")?, service_endpoint, - )?; - - // TODO: Make it easier to generate peer did from keys and service, and generate DDO from it - let did_document_temp = did_doc_from_keys( - Default::default(), - key_ver.clone(), - key_enc.clone(), - service.clone(), - )?; - let peer_did = PeerDid::::from_did_doc(did_document_temp.into())?; - - Ok(( - did_doc_from_keys(peer_did.into(), key_ver, key_enc.clone(), service)?, - key_enc, - )) + 0, + vec![], + routing_keys + .into_iter() + .map(ServiceKeyKind::Value) + .collect(), + ) + .try_into()?; + + info!("Prepared service for peer:did:2 generation: {} ", service); + let mut did_document = did_doc_from_keys(Default::default(), key_enc.clone(), service)?; + info!( + "Created did document for peer:did:2 generation: {} ", + did_document + ); + let peer_did = PeerDid::::from_did_doc(did_document.clone())?; + did_document.set_id(peer_did.did().clone()); + Ok((did_document, key_enc)) } fn did_doc_from_keys( did: Did, - key_ver: Key, key_enc: Key, - service: ServiceSov, -) -> Result { - let vm_ver_id = DidUrl::from_fragment(key_ver.short_prefixless_fingerprint())?; + service: Service, +) -> Result { let vm_ka_id = DidUrl::from_fragment(key_enc.short_prefixless_fingerprint())?; - let vm_ver = VerificationMethod::builder( - vm_ver_id, - did.clone(), - VerificationMethodType::Ed25519VerificationKey2020, - ) - .add_public_key_base58(key_ver.base58()) - .build(); let vm_ka = VerificationMethod::builder( vm_ka_id, did.clone(), - VerificationMethodType::X25519KeyAgreementKey2020, + VerificationMethodType::Ed25519VerificationKey2020, ) .add_public_key_base58(key_enc.base58()) .build(); - Ok(DidDocumentSov::builder(did) + Ok(DidDocument::builder(did) .add_service(service) - .add_verification_method(vm_ver) - // TODO: Include just reference - .add_key_agreement(VerificationMethodKind::Resolved(vm_ka)) + .add_key_agreement(vm_ka) .build()) } -pub fn ddo_sov_to_attach(ddo: DidDocumentSov) -> Result { +pub fn ddo_to_attach(ddo: DidDocument) -> Result { // Interop note: acapy accepts unsigned when using peer dids? let content_b64 = base64::engine::Engine::encode(&URL_SAFE_NO_PAD, serde_json::to_string(&ddo)?); @@ -170,7 +172,7 @@ pub async fn jws_sign_attach( } } -pub fn attach_to_ddo_sov(attachment: Attachment) -> Result { +pub fn attachment_to_diddoc(attachment: Attachment) -> Result { match attachment.data.content { AttachmentType::Json(value) => serde_json::from_value(value).map_err(Into::into), AttachmentType::Base64(ref value) => { @@ -182,7 +184,7 @@ pub fn attach_to_ddo_sov(attachment: Attachment) -> Result(&bytes).map_err(Into::into) + serde_json::from_slice::(&bytes).map_err(Into::into) } _ => Err(AriesVcxError::from_msg( AriesVcxErrorKind::InvalidJson, @@ -200,3 +202,69 @@ where state, } } + +#[cfg(test)] +mod tests { + use did_doc::schema::{ + service::typed::ServiceType, utils::OneOrList, verification_method::VerificationMethodKind, + }; + + use super::*; + + #[tokio::test] + async fn test_did_doc_from_keys() { + let key_enc = Key::new( + "tyntrez7bCthPqvZUDGwhYB1bSe9HzpLdSeHFpuSwst".into(), + KeyType::Ed25519, + ) + .unwrap(); + + let service_endpoint = Url::parse("http://example.com").unwrap(); + let routing_keys = vec![ + ServiceKeyKind::Value("routing_key1".into()), + ServiceKeyKind::Value("routing_key2".into()), + ]; + let service: Service = ServiceDidCommV1::new( + Uri::new("#service-0").unwrap(), + service_endpoint.clone(), + 0, + vec![], + routing_keys, + ) + .try_into() + .unwrap(); + + let did = Did::default(); + + let result = did_doc_from_keys(did, key_enc.clone(), service); + + assert!(result.is_ok()); + let did_doc = result.unwrap(); + + assert_eq!(did_doc.service().len(), 1); + let ddo_service = did_doc.service().first().unwrap(); + assert_eq!(&ddo_service.id().to_string(), "#service-0"); + assert_eq!( + ddo_service.service_type(), + &OneOrList::One(ServiceType::DIDCommV1) + ); + assert_eq!(ddo_service.service_endpoint(), &service_endpoint); + assert_eq!( + ddo_service.extra_field_routing_keys().unwrap(), + vec![ + ServiceKeyKind::Value("routing_key1".into()), + ServiceKeyKind::Value("routing_key2".into()) + ] + ); + + assert_eq!(did_doc.key_agreement().len(), 1); + match did_doc.key_agreement().first().unwrap() { + VerificationMethodKind::Resolved(key_agreement) => { + assert_eq!(key_agreement.public_key().unwrap(), key_enc); + } + VerificationMethodKind::Resolvable(_) => { + panic!("Key agreement was expected to have embedded key"); + } + } + } +} 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 74003b34a2..efc6f26322 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 @@ -7,8 +7,8 @@ pub mod responder; use std::marker::PhantomData; use chrono::Utc; -use did_doc_sov::DidDocumentSov; -pub use helpers::generate_keypair; +use did_doc::schema::did_doc::DidDocument; +pub use helpers::{create_our_did_document, generate_keypair}; use messages::{ decorators::{thread::Thread, timing::Timing}, msg_fields::protocols::did_exchange::problem_report::{ @@ -18,10 +18,7 @@ use messages::{ use uuid::Uuid; use super::{ - states::{ - abandoned::Abandoned, - traits::{InvitationId, ThreadId}, - }, + states::{abandoned::Abandoned, traits::ThreadId}, transition::transition_result::TransitionResult, }; @@ -29,8 +26,8 @@ use super::{ pub struct DidExchange { state: S, initiation_type: PhantomData, - our_did_document: DidDocumentSov, - their_did_document: DidDocumentSov, + our_did_document: DidDocument, + their_did_document: DidDocument, } impl DidExchange { @@ -91,17 +88,11 @@ impl DidExchange { } } -impl DidExchange { - pub fn get_invitation_id(&self) -> &str { - self.state.invitation_id() - } -} - impl DidExchange { pub fn from_parts( state: S, - their_did_document: DidDocumentSov, - our_did_document: DidDocumentSov, + their_did_document: DidDocument, + our_did_document: DidDocument, ) -> Self { Self { state, @@ -113,11 +104,11 @@ impl DidExchange { } impl DidExchange { - pub fn our_did_doc(&self) -> &DidDocumentSov { + pub fn our_did_doc(&self) -> &DidDocument { &self.our_did_document } - pub fn their_did_doc(&self) -> &DidDocumentSov { + pub fn their_did_doc(&self) -> &DidDocument { &self.their_did_document } } 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 new file mode 100644 index 0000000000..b6e75c416e --- /dev/null +++ b/aries/aries_vcx/src/protocols/did_exchange/state_machine/requester/helpers.rs @@ -0,0 +1,91 @@ +use chrono::Utc; +use did_parser::Did; +use messages::{ + decorators::{ + thread::{Thread, ThreadGoalCode}, + timing::Timing, + }, + msg_fields::protocols::{ + did_exchange::{ + complete::{Complete, CompleteDecorators}, + request::{Request, RequestContent, RequestDecorators}, + }, + out_of_band::invitation::{Invitation, OobService}, + }, +}; +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 { + let msg_id = Uuid::new_v4().to_string(); + let thid = msg_id.clone(); + let thread = match invitation_id { + Some(invitation_id) => Thread::builder().thid(thid).pthid(invitation_id).build(), + None => Thread::builder().thid(thid).build(), + }; + let decorators = RequestDecorators::builder() + .thread(Some(thread)) + .timing(Timing::builder().out_time(Utc::now()).build()) + .build(); + let content = RequestContent::builder() + .label("".into()) + .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() + .id(msg_id) + .content(content) + .decorators(decorators) + .build() +} + +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(); + let decorators = CompleteDecorators::builder() + .thread(thread) + .timing(Timing::builder().out_time(Utc::now()).build()) + .build(); + Complete::builder() + .id(Uuid::new_v4().to_string()) + .decorators(decorators) + .build() +} + +/// We are going to support only DID service values in did-exchange protocol unless there's explicit +/// good reason to keep support for "embedded" type of service value. +/// This function returns first found DID based service value from invitation. +/// TODO: also used by harness, move this to messages crate +pub fn invitation_get_first_did_service(invitation: &Invitation) -> VcxResult { + for service in invitation.content.services.iter() { + if let OobService::Did(did_string) = service { + return Did::parse(did_string.clone()).map_err(|err| { + AriesVcxError::from_msg( + AriesVcxErrorKind::InvalidState, + format!("Invalid DID in invitation: {}", err), + ) + }); + } + } + Err(AriesVcxError::from_msg( + AriesVcxErrorKind::InvalidState, + "Invitation does not contain did service", + )) +} diff --git a/aries/aries_vcx/src/protocols/did_exchange/state_machine/requester/mod.rs b/aries/aries_vcx/src/protocols/did_exchange/state_machine/requester/mod.rs index 076f744208..bf7f68a700 100644 --- a/aries/aries_vcx/src/protocols/did_exchange/state_machine/requester/mod.rs +++ b/aries/aries_vcx/src/protocols/did_exchange/state_machine/requester/mod.rs @@ -1,3 +1,4 @@ +pub mod helpers; mod request_sent; use super::DidExchange; diff --git a/aries/aries_vcx/src/protocols/did_exchange/state_machine/requester/request_sent/helpers.rs b/aries/aries_vcx/src/protocols/did_exchange/state_machine/requester/request_sent/helpers.rs deleted file mode 100644 index 2a467cfb6f..0000000000 --- a/aries/aries_vcx/src/protocols/did_exchange/state_machine/requester/request_sent/helpers.rs +++ /dev/null @@ -1,167 +0,0 @@ -use std::sync::Arc; - -use aries_vcx_core::ledger::base_ledger::IndyLedgerRead; -use chrono::Utc; -use did_doc::schema::verification_method::{VerificationMethod, VerificationMethodType}; -use did_doc_sov::{service::ServiceSov, DidDocumentSov}; -use did_parser::{Did, DidUrl}; -use did_resolver::traits::resolvable::resolution_output::DidResolutionOutput; -use did_resolver_registry::ResolverRegistry; -use messages::{ - decorators::{ - thread::{Thread, ThreadGoalCode}, - timing::Timing, - }, - msg_fields::protocols::{ - did_exchange::request::{Request, RequestContent, RequestDecorators}, - out_of_band::invitation::{Invitation as OobInvitation, Invitation, OobService}, - }, -}; -use shared::maybe_known::MaybeKnown; -use uuid::Uuid; - -use crate::{ - common::ledger::transactions::resolve_service, - errors::error::{AriesVcxError, AriesVcxErrorKind, VcxResult}, - utils::from_legacy_service_to_service_sov, -}; - -pub fn verify_handshake_protocol(invitation: OobInvitation) -> Result<(), AriesVcxError> { - invitation - .content - .handshake_protocols - .ok_or_else(|| { - AriesVcxError::from_msg( - AriesVcxErrorKind::InvalidState, - "Invitation does not contain handshake protpcols", - ) - })? - .iter() - .find(|protocol| match protocol { - // TODO: Improve this check - MaybeKnown::Known(protocol) if protocol.to_string().contains("didexchange") => true, - _ => false, - }) - .ok_or_else(|| { - AriesVcxError::from_msg( - AriesVcxErrorKind::InvalidState, - "Invitation does not contain didexchange handshake protocol", - ) - })?; - Ok(()) -} - -pub async fn did_doc_from_did( - ledger: &impl IndyLedgerRead, - did: Did, -) -> Result<(DidDocumentSov, ServiceSov), AriesVcxError> { - let service = resolve_service(ledger, &OobService::Did(did.id().to_string())).await?; - let did_url: DidUrl = format!("{}#vm-0", did).try_into()?; - let vm = VerificationMethod::builder( - did_url, - did.clone(), - VerificationMethodType::Ed25519VerificationKey2020, - ) - .add_public_key_base58( - service - .recipient_keys - .first() - .ok_or_else(|| { - AriesVcxError::from_msg( - AriesVcxErrorKind::InvalidState, - "No recipient keys found in resolved service", - ) - })? - .clone(), - ) - .build(); - let sov_service = from_legacy_service_to_service_sov(service)?; - let did_document = DidDocumentSov::builder(did.clone()) - .add_service(sov_service.clone()) - .add_controller(did) - .add_verification_method(vm) - .build(); - Ok((did_document, sov_service)) -} - -pub fn construct_request(invitation_id: String, our_did: String) -> Request { - let request_id = Uuid::new_v4().to_string(); - let decorators = RequestDecorators::builder() - .thread(Some( - Thread::builder() - .thid(request_id.clone()) - .pthid(invitation_id) - .build(), - )) - .timing(Timing::builder().out_time(Utc::now()).build()) - .build(); - let content = RequestContent::builder() - .label("".into()) - .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() - .id(request_id) - .content(content) - .decorators(decorators) - .build() -} - -pub async fn oob_invitation_to_diddoc( - resolver_registry: &Arc, - invitation: Invitation, -) -> VcxResult { - let mut builder = DidDocumentSov::builder(Default::default()); - - let mut resolved_services = vec![]; - let mut resolved_vms = vec![]; - let mut resolved_kas = vec![]; - let mut resolved_dids = vec![]; - - for service in invitation.content.services { - match service { - OobService::SovService(service) => { - builder = builder.add_service(service.clone()); - } - OobService::Did(did) => { - let parsed_did = Did::parse(did)?; - let DidResolutionOutput { did_document, .. } = resolver_registry - .resolve(&parsed_did, &Default::default()) - .await?; - resolved_services.extend( - did_document - .service() - .iter() - .map(|s| ServiceSov::try_from(s.clone())) - .collect::, _>>()?, - ); - resolved_vms.extend_from_slice(did_document.verification_method()); - resolved_kas.extend_from_slice(did_document.key_agreement()); - resolved_dids.push(parsed_did); - } - OobService::AriesService(service) => { - resolved_services.push(from_legacy_service_to_service_sov(service.clone())?) - } - } - } - - for service in resolved_services { - builder = builder.add_service(service); - } - - for vm in resolved_vms { - builder = builder.add_verification_method(vm.clone()); - } - - for ka in resolved_kas { - builder = builder.add_key_agreement(ka.clone()); - } - - for did in resolved_dids { - builder = builder.add_controller(did); - } - - Ok(builder.build()) -} 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 9422cfe72a..51a049ca8d 100644 --- a/aries/aries_vcx/src/protocols/did_exchange/state_machine/requester/request_sent/mod.rs +++ b/aries/aries_vcx/src/protocols/did_exchange/state_machine/requester/request_sent/mod.rs @@ -1,85 +1,57 @@ use std::sync::Arc; -use aries_vcx_core::{ledger::base_ledger::IndyLedgerRead, wallet::base_wallet::BaseWallet}; -use chrono::Utc; use did_parser::Did; -use did_peer::resolver::PeerDidResolver; -use did_resolver::traits::resolvable::DidResolvable; -use did_resolver_registry::ResolverRegistry; -use helpers::{ - construct_request, did_doc_from_did, oob_invitation_to_diddoc, verify_handshake_protocol, +use did_peer::{ + peer_did::{numalgos::numalgo2::Numalgo2, PeerDid}, + resolver::options::PublicKeyEncoding, }; -use messages::{ - decorators::{thread::Thread, timing::Timing}, - msg_fields::protocols::{ - did_exchange::{ - complete::{Complete as CompleteMessage, Complete, CompleteDecorators}, - request::Request, - response::Response, - }, - out_of_band::invitation::Invitation, - }, +use did_resolver_registry::ResolverRegistry; +use messages::msg_fields::protocols::did_exchange::{ + complete::Complete as CompleteMessage, request::Request, response::Response, }; -use url::Url; -use uuid::Uuid; use super::DidExchangeRequester; use crate::{ errors::error::{AriesVcxError, AriesVcxErrorKind}, protocols::did_exchange::{ - state_machine::helpers::{attach_to_ddo_sov, create_our_did_document, to_transition_error}, + state_machine::{ + helpers::{attachment_to_diddoc, to_transition_error}, + requester::helpers::{construct_didexchange_complete, construct_request}, + }, states::{completed::Completed, requester::request_sent::RequestSent}, transition::{transition_error::TransitionError, transition_result::TransitionResult}, }, + utils::didcomm_utils::resolve_didpeer2, }; -mod helpers; - impl DidExchangeRequester { - pub async fn construct_request_pairwise( - wallet: &impl BaseWallet, - invitation: Invitation, + pub async fn construct_request( resolver_registry: Arc, - service_endpoint: Url, - routing_keys: Vec, + invitation_id: Option, + their_did: &Did, + our_peer_did: &PeerDid, ) -> Result, AriesVcxError> { - verify_handshake_protocol(invitation.clone())?; - let (our_did_document, _our_verkey) = - create_our_did_document(wallet, service_endpoint, routing_keys).await?; - let their_did_document = - oob_invitation_to_diddoc(&resolver_registry, invitation.clone()).await?; - - let request = construct_request(invitation.id.clone(), our_did_document.id().to_string()); + info!( + "DidExchangeRequester::construct_request >> their_did: {}, our_peer_did: \ + {}", + their_did, our_peer_did + ); + let their_did_document = resolver_registry + .resolve(their_did, &Default::default()) + .await? + .did_document; + let our_did_document = resolve_didpeer2(our_peer_did, PublicKeyEncoding::Base58).await?; + let request = construct_request(invitation_id.clone(), our_peer_did.to_string()); + info!( + "DidExchangeRequester::construct_request << prepared request: {}", + request + ); Ok(TransitionResult { state: DidExchangeRequester::from_parts( RequestSent { - invitation_id: invitation.id.clone(), request_id: request.id.clone(), - }, - their_did_document, - our_did_document, - ), - output: request, - }) - } - - pub async fn construct_request_public( - ledger: &impl IndyLedgerRead, - their_did: Did, - our_did: Did, - ) -> Result, AriesVcxError> { - let (their_did_document, service) = did_doc_from_did(ledger, their_did.clone()).await?; - let (our_did_document, _) = did_doc_from_did(ledger, our_did.clone()).await?; - let invitation_id = format!("{}#{}", their_did, service.id()); - - let request = construct_request(invitation_id.clone(), our_did.to_string()); - - Ok(TransitionResult { - state: DidExchangeRequester::from_parts( - RequestSent { - request_id: request.id.clone(), - invitation_id, + // invitation_id, }, their_did_document, our_did_document, @@ -95,6 +67,10 @@ impl DidExchangeRequester { TransitionResult, CompleteMessage>, TransitionError, > { + info!( + "DidExchangeRequester::receive_response >> response: {:?}", + response + ); if response.decorators.thread.thid != self.state.request_id { return Err(TransitionError { error: AriesVcxError::from_msg( @@ -105,41 +81,34 @@ impl DidExchangeRequester { }); } let did_document = if let Some(ddo) = response.content.did_doc { - attach_to_ddo_sov(ddo).map_err(to_transition_error(self.clone()))? + info!( + "DidExchangeRequester::receive_response >> the Response message \ + contained attached ddo" + ); + attachment_to_diddoc(ddo).map_err(to_transition_error(self.clone()))? } else { - PeerDidResolver::new() - .resolve( - &response - .content - .did - .parse() - .map_err(to_transition_error(self.clone()))?, - &Default::default(), - ) + info!( + "DidExchangeRequester::receive_response >> the Response message \ + contains pairwise DID, resolving to DID Document" + ); + let peer_did = PeerDid::::parse(response.content.did) + .map_err(to_transition_error(self.clone()))?; + resolve_didpeer2(&peer_did, PublicKeyEncoding::Base58) .await .map_err(to_transition_error(self.clone()))? - .did_document() - .to_owned() - .into() }; - let decorators = CompleteDecorators::builder() - .thread( - Thread::builder() - .thid(self.state.request_id.clone()) - .pthid(self.state.invitation_id.clone()) - .build(), - ) - .timing(Timing::builder().out_time(Utc::now()).build()) - .build(); - let complete_message = Complete::builder() - .id(Uuid::new_v4().to_string()) - .decorators(decorators) - .build(); + + let complete_message = construct_didexchange_complete( + self.state.request_id.clone(), // self.state.invitation_id.clone(), + ); + info!( + "DidExchangeRequester::receive_response << complete_message: {}", + complete_message + ); Ok(TransitionResult { state: DidExchangeRequester::from_parts( Completed { - invitation_id: self.state.invitation_id, request_id: self.state.request_id, }, // TODO: Make sure to make the DDO identifier did:peer:3 for both 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 d5034f79c4..81edfb0f03 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 @@ -1,31 +1,28 @@ use std::sync::Arc; use aries_vcx_core::wallet::base_wallet::BaseWallet; -use chrono::Utc; -use did_doc_sov::DidDocumentSov; +use did_doc::schema::did_doc::DidDocument; +use did_peer::{ + peer_did::{numalgos::numalgo2::Numalgo2, PeerDid}, + resolver::options::PublicKeyEncoding, +}; use did_resolver_registry::ResolverRegistry; -use messages::{ - decorators::{thread::Thread, timing::Timing}, - msg_fields::protocols::did_exchange::{ - complete::Complete, - request::Request, - response::{Response, ResponseContent, ResponseDecorators}, - }, +use messages::msg_fields::protocols::did_exchange::{ + complete::Complete, request::Request, response::Response, }; use public_key::Key; -use url::Url; -use uuid::Uuid; use super::DidExchangeResponder; use crate::{ errors::error::{AriesVcxError, AriesVcxErrorKind}, protocols::did_exchange::{ state_machine::helpers::{ - attach_to_ddo_sov, create_our_did_document, ddo_sov_to_attach, jws_sign_attach, + attachment_to_diddoc, construct_response, ddo_to_attach, jws_sign_attach, }, states::{completed::Completed, responder::response_sent::ResponseSent}, transition::{transition_error::TransitionError, transition_result::TransitionResult}, }, + utils::didcomm_utils::resolve_didpeer2, }; impl DidExchangeResponder { @@ -33,54 +30,44 @@ impl DidExchangeResponder { wallet: &impl BaseWallet, resolver_registry: Arc, request: Request, - service_endpoint: Url, - routing_keys: Vec, - invitation_id: String, - invitation_key: Key, + our_peer_did: &PeerDid, + invitation_key: Option, ) -> Result, Response>, AriesVcxError> { - let their_ddo = resolve_their_ddo(&resolver_registry, &request).await?; - let (our_did_document, _enc_key) = - create_our_did_document(wallet, service_endpoint, routing_keys).await?; - - if request.decorators.thread.and_then(|t| t.pthid) != Some(invitation_id.clone()) { - return Err(AriesVcxError::from_msg( - AriesVcxErrorKind::InvalidState, - "Parent thread ID of the request does not match the id of the invite", - )); - } - - // TODO: Response should sign the new *did* with invitation_key only if key was rotated - let signed_attach = jws_sign_attach( - ddo_sov_to_attach(our_did_document.clone())?, - invitation_key, - wallet, - ) - .await?; - - let content = ResponseContent::builder() - .did(our_did_document.id().to_string()) - .did_doc(Some(signed_attach)) - .build(); - let decorators = ResponseDecorators::builder() - .thread( - Thread::builder() - .thid(request.id.clone()) - .pthid(invitation_id.clone()) // todo: do we need to set this in Response? - .build(), - ) - .timing(Timing::builder().out_time(Utc::now()).build()) - .build(); - let response = Response::builder() - .id(Uuid::new_v4().to_string()) - .content(content) - .decorators(decorators) - .build(); + info!( + "DidExchangeResponder::receive_request >> request: {}, our_peer_did: \ + {}, invitation_key: {:?}", + request, our_peer_did, invitation_key + ); + let their_ddo = resolve_ddo_from_request(&resolver_registry, &request).await?; + let our_did_document = resolve_didpeer2(our_peer_did, PublicKeyEncoding::Base58).await?; + // 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 { + 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? + } + }; + let response = construct_response(request.id.clone(), &our_did_document, ddo_attachment); + info!( + "DidExchangeResponder::receive_request << prepared response: {}", + response + ); Ok(TransitionResult { state: DidExchangeResponder::from_parts( ResponseSent { request_id: request.id, - invitation_id, }, their_ddo, our_did_document, @@ -102,18 +89,8 @@ impl DidExchangeResponder { state: self, }); } - if complete.decorators.thread.pthid != Some(self.state.invitation_id.to_string()) { - return Err(TransitionError { - error: AriesVcxError::from_msg( - AriesVcxErrorKind::InvalidState, - "Parent thread ID of the complete message does not match the id of the invite", - ), - state: self, - }); - } Ok(DidExchangeResponder::from_parts( Completed { - invitation_id: self.state.invitation_id, request_id: self.state.request_id, }, self.their_did_document, @@ -122,22 +99,20 @@ impl DidExchangeResponder { } } -async fn resolve_their_ddo( +async fn resolve_ddo_from_request( resolver_registry: &Arc, request: &Request, -) -> Result { +) -> Result { Ok(request .content .did_doc .clone() - .map(attach_to_ddo_sov) + .map(attachment_to_diddoc) .transpose()? .unwrap_or( resolver_registry .resolve(&request.content.did.parse()?, &Default::default()) .await? - .did_document() - .to_owned() - .into(), + .did_document, )) } diff --git a/aries/aries_vcx/src/protocols/did_exchange/states/completed.rs b/aries/aries_vcx/src/protocols/did_exchange/states/completed.rs index faa54b9f86..94bd84bbfb 100644 --- a/aries/aries_vcx/src/protocols/did_exchange/states/completed.rs +++ b/aries/aries_vcx/src/protocols/did_exchange/states/completed.rs @@ -1,10 +1,9 @@ use std::clone::Clone; -use super::traits::{InvitationId, ThreadId}; +use super::traits::ThreadId; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Completed { - pub invitation_id: String, pub request_id: String, } @@ -13,9 +12,3 @@ impl ThreadId for Completed { self.request_id.as_str() } } - -impl InvitationId for Completed { - fn invitation_id(&self) -> &str { - self.invitation_id.as_str() - } -} 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 7b2e3c3561..6f844b217a 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 @@ -1,9 +1,10 @@ -use crate::protocols::did_exchange::states::traits::{InvitationId, ThreadId}; +use crate::protocols::did_exchange::states::traits::ThreadId; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct RequestSent { - pub invitation_id: String, - pub request_id: String, + 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 { @@ -11,9 +12,3 @@ impl ThreadId for RequestSent { self.request_id.as_str() } } - -impl InvitationId for RequestSent { - fn invitation_id(&self) -> &str { - self.invitation_id.as_str() - } -} diff --git a/aries/aries_vcx/src/protocols/did_exchange/states/responder/response_sent.rs b/aries/aries_vcx/src/protocols/did_exchange/states/responder/response_sent.rs index ec1f0e5161..339eae5942 100644 --- a/aries/aries_vcx/src/protocols/did_exchange/states/responder/response_sent.rs +++ b/aries/aries_vcx/src/protocols/did_exchange/states/responder/response_sent.rs @@ -1,8 +1,7 @@ -use crate::protocols::did_exchange::states::traits::{InvitationId, ThreadId}; +use crate::protocols::did_exchange::states::traits::ThreadId; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct ResponseSent { - pub invitation_id: String, pub request_id: String, } @@ -11,9 +10,3 @@ impl ThreadId for ResponseSent { self.request_id.as_str() } } - -impl InvitationId for ResponseSent { - fn invitation_id(&self) -> &str { - self.invitation_id.as_str() - } -} diff --git a/aries/aries_vcx/src/protocols/did_exchange/states/traits.rs b/aries/aries_vcx/src/protocols/did_exchange/states/traits.rs index 63c585eb74..b2614192f5 100644 --- a/aries/aries_vcx/src/protocols/did_exchange/states/traits.rs +++ b/aries/aries_vcx/src/protocols/did_exchange/states/traits.rs @@ -1,7 +1,3 @@ pub trait ThreadId { fn thread_id(&self) -> &str; } - -pub trait InvitationId { - fn invitation_id(&self) -> &str; -} diff --git a/aries/aries_vcx/src/protocols/issuance/issuer/state_machine.rs b/aries/aries_vcx/src/protocols/issuance/issuer/state_machine.rs index 3037d5d814..d84ccb9030 100644 --- a/aries/aries_vcx/src/protocols/issuance/issuer/state_machine.rs +++ b/aries/aries_vcx/src/protocols/issuance/issuer/state_machine.rs @@ -331,7 +331,6 @@ impl IssuerSM { source_id, thread_id, } = self; - warn!("IssuerSM::build_credential_offer_msg >>> thread_id: {thread_id}"); let state = match state { IssuerFullState::Initial(_) | IssuerFullState::OfferSet(_) diff --git a/aries/aries_vcx/src/transport.rs b/aries/aries_vcx/src/transport.rs index 016c9945b2..8d14bb0bae 100644 --- a/aries/aries_vcx/src/transport.rs +++ b/aries/aries_vcx/src/transport.rs @@ -7,7 +7,7 @@ use crate::errors::error::VcxResult; /// [`crate::protocols::connection::Connection`]. #[async_trait] pub trait Transport: Send + Sync { - async fn send_message(&self, msg: Vec, service_endpoint: Url) -> VcxResult<()>; + async fn send_message(&self, msg: Vec, service_endpoint: &Url) -> VcxResult<()>; } // While in many cases the auto-dereferencing does the trick, @@ -18,7 +18,7 @@ impl Transport for &T where T: Transport + ?Sized, { - async fn send_message(&self, msg: Vec, service_endpoint: Url) -> VcxResult<()> { + async fn send_message(&self, msg: Vec, service_endpoint: &Url) -> VcxResult<()> { self.send_message(msg, service_endpoint).await } } diff --git a/aries/aries_vcx/src/utils/didcomm_utils.rs b/aries/aries_vcx/src/utils/didcomm_utils.rs new file mode 100644 index 0000000000..c6a9fb0fcd --- /dev/null +++ b/aries/aries_vcx/src/utils/didcomm_utils.rs @@ -0,0 +1,84 @@ +use did_doc::schema::{ + did_doc::DidDocument, service::service_key_kind::ServiceKeyKind, types::uri::Uri, + verification_method::VerificationMethodType, +}; +use did_peer::{ + peer_did::{numalgos::numalgo2::Numalgo2, PeerDid}, + resolver::{options::PublicKeyEncoding, PeerDidResolutionOptions, PeerDidResolver}, +}; +use did_resolver::{ + error::GenericError, + traits::resolvable::{resolution_output::DidResolutionOutput, DidResolvable}, +}; +use public_key::Key; + +use crate::errors::error::{AriesVcxError, AriesVcxErrorKind, VcxResult}; + +pub(crate) async fn resolve_didpeer2( + did_peer: &PeerDid, + encoding: PublicKeyEncoding, +) -> Result { + let DidResolutionOutput { did_document, .. } = PeerDidResolver::new() + .resolve( + did_peer.did(), + &PeerDidResolutionOptions { + encoding: Some(encoding), + }, + ) + .await?; + Ok(did_document) +} + +fn resolve_service_key_to_typed_key( + key: &ServiceKeyKind, + did_document: &DidDocument, +) -> VcxResult { + match key { + ServiceKeyKind::DidKey(did_key) => Ok(did_key.key().clone()), + ServiceKeyKind::Reference(reference) => { + let verification_method = did_document.dereference_key(reference).ok_or_else(|| { + AriesVcxError::from_msg( + AriesVcxErrorKind::InvalidState, + format!("Unable to dereference key: {}", reference), + ) + })?; + let key = verification_method.public_key().map_err(|err| { + AriesVcxError::from_msg( + AriesVcxErrorKind::InvalidState, + format!("Unable to get public key from verification method: {}", err), + ) + })?; + Ok(key) + } + ServiceKeyKind::Value(value) => Ok(Key::new( + value.as_bytes().to_vec(), + public_key::KeyType::Ed25519, + )?), + } +} + +pub fn resolve_base58_key_agreement(did_document: &DidDocument) -> VcxResult { + let key_types = [ + VerificationMethodType::Ed25519VerificationKey2018, + VerificationMethodType::Ed25519VerificationKey2020, + VerificationMethodType::X25519KeyAgreementKey2019, + VerificationMethodType::X25519KeyAgreementKey2020, + ]; + let key_base58 = did_document.get_key_agreement_of_type(&key_types)?; + Ok(key_base58.public_key()?.base58()) +} + +pub fn get_routing_keys(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) + } + Err(_err) => Ok(Vec::new()), + } +} diff --git a/aries/aries_vcx/src/utils/encryption_envelope.rs b/aries/aries_vcx/src/utils/encryption_envelope.rs index cca14f4724..c323298f39 100644 --- a/aries/aries_vcx/src/utils/encryption_envelope.rs +++ b/aries/aries_vcx/src/utils/encryption_envelope.rs @@ -1,4 +1,5 @@ use aries_vcx_core::wallet::base_wallet::BaseWallet; +use did_doc::schema::{did_doc::DidDocument, types::uri::Uri}; use diddoc_legacy::aries::diddoc::AriesDidDoc; use messages::{ msg_fields::protocols::routing::{Forward, ForwardContent}, @@ -7,15 +8,16 @@ use messages::{ use public_key::{Key, KeyType}; use uuid::Uuid; -use crate::errors::error::prelude::*; +use crate::{ + errors::error::prelude::*, + utils::didcomm_utils::{get_routing_keys, resolve_base58_key_agreement}, +}; #[derive(Debug)] pub struct EncryptionEnvelope(pub Vec); impl EncryptionEnvelope { - /// Create an Encryption Envelope from a plaintext AriesMessage encoded as sequence of bytes. - /// If did_doc includes routing_keys, then also wrap in appropriate layers of forward message. - pub async fn create( + pub async fn create_from_legacy( wallet: &impl BaseWallet, data: &[u8], sender_vk: Option<&str>, @@ -38,10 +40,43 @@ impl EncryptionEnvelope { format!("No recipient key found in DIDDoc: {:?}", did_doc), ))?; let routing_keys = did_doc.routing_keys(); - Self::create2(wallet, data, sender_vk, recipient_key, routing_keys).await + Self::create_from_keys(wallet, data, sender_vk, recipient_key, routing_keys).await + } + + /// Create encrypted message based on key agreement keys of our did document, counterparties + /// did document and their specific service, identified by id, which must be part of their + /// did document + /// + /// # Arguments + /// + /// * `our_did_doc` - Our did_document, which the counterparty should already be in possession + /// of + /// * `their_did_doc` - The did document of the counterparty, the recipient of the encrypted + /// message + /// * `their_service_id` - Id of service where message will be sent. The counterparty did + /// document must contain Service object identified with such value. + pub async fn create( + wallet: &impl BaseWallet, + data: &[u8], + our_did_doc: &DidDocument, + their_did_doc: &DidDocument, + their_service_id: &Uri, + ) -> VcxResult { + 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)?; + + EncryptionEnvelope::create_from_keys( + wallet, + data, + Some(&sender_vk.to_string()), + recipient_key.to_string(), + routing_keys.iter().map(|k| k.to_string()).collect(), + ) + .await } - pub async fn create2( + pub async fn create_from_keys( wallet: &impl BaseWallet, data: &[u8], sender_vk: Option<&str>, @@ -253,7 +288,7 @@ pub mod unit_tests { let data_original = "foobar"; - let envelope = EncryptionEnvelope::create2( + let envelope = EncryptionEnvelope::create_from_keys( &setup.wallet, data_original.as_bytes(), None, @@ -288,7 +323,7 @@ pub mod unit_tests { let data_original = "foobar"; - let envelope = EncryptionEnvelope::create2( + let envelope = EncryptionEnvelope::create_from_keys( &setup.wallet, data_original.as_bytes(), Some(&sender_data.verkey().base58()), @@ -330,7 +365,7 @@ pub mod unit_tests { let data_original = "foobar"; - let envelope = EncryptionEnvelope::create2( + let envelope = EncryptionEnvelope::create_from_keys( &setup.wallet, data_original.as_bytes(), Some(&sender_data.verkey().base58()), @@ -376,7 +411,7 @@ pub mod unit_tests { let data_original = "foobar"; - let envelope = EncryptionEnvelope::create2( + let envelope = EncryptionEnvelope::create_from_keys( &setup.wallet, data_original.as_bytes(), Some(&bob_data.verkey().base58()), // bob trying to impersonate alice diff --git a/aries/aries_vcx/src/utils/mod.rs b/aries/aries_vcx/src/utils/mod.rs index 7fb33cb1eb..c396924759 100644 --- a/aries/aries_vcx/src/utils/mod.rs +++ b/aries/aries_vcx/src/utils/mod.rs @@ -1,15 +1,3 @@ -use did_doc::schema::verification_method::{VerificationMethod, VerificationMethodType}; -use did_doc_sov::{ - extra_fields::{didcommv1::ExtraFieldsDidCommV1, KeyKind}, - service::{didcommv1::ServiceDidCommV1, ServiceSov}, - DidDocumentSov, -}; -use did_key::DidKey; -use did_parser::Did; -use diddoc_legacy::aries::{diddoc::AriesDidDoc, service::AriesService}; - -use crate::errors::error::{AriesVcxError, AriesVcxErrorKind, VcxResult}; - #[cfg(debug_assertions)] #[macro_export] macro_rules! secret { @@ -32,185 +20,6 @@ pub mod qualifier; #[macro_use] pub mod encryption_envelope; +pub mod didcomm_utils; pub mod serialization; pub mod validation; - -// TODO: Get rid of this, migrate off the legacy diddoc -pub fn from_did_doc_sov_to_legacy(ddo: DidDocumentSov) -> VcxResult { - let mut new_ddo = AriesDidDoc { - id: ddo.id().to_string(), - ..Default::default() - }; - new_ddo.set_service_endpoint( - ddo.service() - .first() - .ok_or_else(|| { - AriesVcxError::from_msg( - AriesVcxErrorKind::InvalidState, - "No service present in DDO", - ) - })? - .service_endpoint() - .into(), - ); - let mut recipient_keys = vec![]; - for ka in ddo.resolved_key_agreement() { - recipient_keys.push(ka.public_key()?.base58()); - } - for service in ddo.service() { - if let Ok(key_kinds) = service.extra().recipient_keys() { - for key_kind in key_kinds { - match key_kind { - KeyKind::DidKey(key) => { - recipient_keys.push(key.key().base58()); - } - KeyKind::Reference(_) => {} - KeyKind::Value(_) => {} - } - } - } - } - new_ddo.set_recipient_keys(recipient_keys); - Ok(new_ddo) -} - -pub fn from_legacy_did_doc_to_sov(ddo: AriesDidDoc) -> VcxResult { - let did: Did = ddo.id.parse().unwrap_or_default(); - let vm = VerificationMethod::builder( - did.clone().into(), - did.clone(), - VerificationMethodType::Ed25519VerificationKey2020, - ) - .add_public_key_base58( - ddo.recipient_keys()? - .first() - .ok_or_else(|| { - AriesVcxError::from_msg( - AriesVcxErrorKind::InvalidState, - "No recipient in the DDO being converted", - ) - })? - .to_string(), - ) - .build(); - let new_ddo = DidDocumentSov::builder(did.clone()) - .add_service(from_legacy_service_to_service_sov( - ddo.service - .first() - .ok_or_else(|| { - AriesVcxError::from_msg( - AriesVcxErrorKind::InvalidState, - "No service in the DDO being converted", - ) - })? - .clone(), - )?) - .add_controller(did) - .add_verification_method(vm) - .build(); - Ok(new_ddo) -} - -pub fn from_legacy_service_to_service_sov(service: AriesService) -> VcxResult { - let extra = ExtraFieldsDidCommV1::builder() - .set_recipient_keys( - service - .recipient_keys - .iter() - .map(String::to_owned) - .map(|s| -> VcxResult { - if s.starts_with("did:key:") { - Ok(KeyKind::DidKey(DidKey::parse(s)?)) - } else { - Ok(KeyKind::Value(s)) - } - }) - .collect::>>()?, - ) - .set_routing_keys( - service - .routing_keys - .iter() - .map(String::to_owned) - .map(|s| -> VcxResult { - if s.starts_with("did:key:") { - Ok(KeyKind::DidKey(DidKey::parse(s)?)) - } else { - Ok(KeyKind::Value(s)) - } - }) - .collect::>>()?, - ) - .build(); - Ok(ServiceSov::DIDCommV1(ServiceDidCommV1::new( - // TODO: Why was this necessary? Double-check - service.id.parse().unwrap_or_default(), - service.service_endpoint.into(), - extra, - )?)) -} - -pub fn from_service_sov_to_legacy(service: ServiceSov) -> AriesService { - info!( - "Converting AnyService to expanded AriesService: {:?}", - service - ); - match service { - ServiceSov::AIP1(service) => AriesService { - id: service.id().to_string(), - service_endpoint: service.service_endpoint().into(), - ..Default::default() - }, - ServiceSov::DIDCommV1(service) => { - let extra = service.extra(); - let recipient_keys = extra - .recipient_keys() - .iter() - .map(|key| key.to_string()) - .collect(); - let routing_keys = extra - .routing_keys() - .iter() - .map(|key| key.to_string()) - .collect(); - AriesService { - id: service.id().to_string(), - recipient_keys, - routing_keys, - service_endpoint: service.service_endpoint().into(), - ..Default::default() - } - } - ServiceSov::DIDCommV2(service) => { - let extra = service.extra(); - let routing_keys = extra - .routing_keys() - .iter() - .map(|key| key.to_string()) - .collect(); - AriesService { - id: service.id().to_string(), - routing_keys, - service_endpoint: service.service_endpoint().into(), - ..Default::default() - } - } - ServiceSov::Legacy(service) => AriesService { - id: service.id().to_string(), - recipient_keys: service - .extra() - .recipient_keys() - .iter() - .map(|key| key.to_string()) - .collect(), - routing_keys: service - .extra() - .routing_keys() - .iter() - .map(|key| key.to_string()) - .collect(), - service_endpoint: service.service_endpoint().into(), - ..Default::default() - }, - } -} diff --git a/aries/aries_vcx/tests/test_did_exchange.rs b/aries/aries_vcx/tests/test_did_exchange.rs index 0703f744df..79dc1a323d 100644 --- a/aries/aries_vcx/tests/test_did_exchange.rs +++ b/aries/aries_vcx/tests/test_did_exchange.rs @@ -1,109 +1,177 @@ extern crate log; -use std::sync::Arc; +use std::{error::Error, sync::Arc, thread, time::Duration}; use aries_vcx::{ - handlers::out_of_band::sender::OutOfBandSender, + common::ledger::transactions::write_endpoint_from_service, protocols::did_exchange::{ - resolve_key_from_invitation, + resolve_enc_key_from_invitation, state_machine::{ - generate_keypair, requester::DidExchangeRequester, responder::DidExchangeResponder, + create_our_did_document, + requester::{helpers::invitation_get_first_did_service, DidExchangeRequester}, + responder::DidExchangeResponder, }, states::{requester::request_sent::RequestSent, responder::response_sent::ResponseSent}, transition::transition_result::TransitionResult, }, + utils::{didcomm_utils::resolve_base58_key_agreement, encryption_envelope::EncryptionEnvelope}, }; -use did_doc_sov::{ - extra_fields::{didcommv1::ExtraFieldsDidCommV1, KeyKind}, - service::{didcommv1::ServiceDidCommV1, ServiceSov}, +use aries_vcx_core::ledger::indy_vdr_ledger::DefaultIndyLedgerRead; +use did_doc::schema::{ + did_doc::DidDocument, + service::typed::{didcommv1::ServiceDidCommV1, ServiceType}, + types::uri::Uri, }; -use did_peer::resolver::PeerDidResolver; +use did_parser::Did; +use did_peer::{ + peer_did::{numalgos::numalgo2::Numalgo2, PeerDid}, + resolver::{options::PublicKeyEncoding, PeerDidResolutionOptions, PeerDidResolver}, +}; +use did_resolver::traits::resolvable::{resolution_output::DidResolutionOutput, DidResolvable}; use did_resolver_registry::ResolverRegistry; -use messages::{ - msg_fields::protocols::out_of_band::invitation::OobService, - msg_types::{ - protocols::did_exchange::{DidExchangeType, DidExchangeTypeV1}, - Protocol, - }, +use did_resolver_sov::resolution::DidSovResolver; +use log::info; +use messages::msg_fields::protocols::out_of_band::invitation::{ + Invitation, InvitationContent, OobService, }; -use public_key::KeyType; -use test_utils::devsetup::SetupPoolDirectory; +use test_utils::devsetup::{dev_build_profile_vdr_ledger, SetupPoolDirectory}; use url::Url; -use uuid::Uuid; -use crate::utils::test_agent::{create_test_agent, create_test_agent_trustee}; +use crate::utils::test_agent::{ + create_test_agent, create_test_agent_endorser_2, create_test_agent_trustee, +}; pub mod utils; +pub(crate) async fn resolve_didpeer2( + did_peer: &PeerDid, + encoding: PublicKeyEncoding, +) -> DidDocument { + let DidResolutionOutput { did_document, .. } = PeerDidResolver::new() + .resolve( + did_peer.did(), + &PeerDidResolutionOptions { + encoding: Some(encoding), + }, + ) + .await + .unwrap(); + did_document +} + +fn assert_key_agreement(a: DidDocument, b: DidDocument) { + let a_key = resolve_base58_key_agreement(&a).unwrap(); + let b_key = resolve_base58_key_agreement(&b).unwrap(); + assert_eq!(a_key, b_key); +} + #[tokio::test] #[ignore] -async fn did_exchange_test() { +async fn did_exchange_test() -> Result<(), Box> { let setup = SetupPoolDirectory::init().await; - let institution = create_test_agent_trustee(setup.genesis_file_path.clone()).await; - let consumer = create_test_agent(setup.genesis_file_path).await; + let dummy_url: Url = "http://dummyurl.org".parse().unwrap(); + let agent_trustee = create_test_agent_trustee(setup.genesis_file_path.clone()).await; + // todo: patrik: update create_test_agent_endorser_2 to not consume trustee agent + let agent_inviter = + create_test_agent_endorser_2(&setup.genesis_file_path, agent_trustee).await?; + let create_service = ServiceDidCommV1::new( + Uri::new("#service-0").unwrap(), + dummy_url.clone(), + 0, + vec![], + vec![], + ); + write_endpoint_from_service( + &agent_inviter.wallet, + &agent_inviter.ledger_write, + &agent_inviter.institution_did, + &create_service.try_into()?, + ) + .await?; + thread::sleep(Duration::from_millis(100)); + + let agent_invitee = create_test_agent(setup.genesis_file_path.clone()).await; + + let (ledger_read_2, _) = dev_build_profile_vdr_ledger(setup.genesis_file_path); + let ledger_read_2_arc = Arc::new(ledger_read_2); + + // if we were to use, more generally, the `dev_build_featured_indy_ledger`, we would need to + // here the type based on the feature flag (indy vs proxy vdr client) which is pain + // we need to improve DidSovResolver such that Rust compiler can fully infer the return type + let did_sov_resolver: DidSovResolver, DefaultIndyLedgerRead> = + DidSovResolver::new(ledger_read_2_arc); - let did_peer_resolver = PeerDidResolver::new(); let resolver_registry = Arc::new( ResolverRegistry::new() - .register_resolver::("peer".into(), did_peer_resolver), + .register_resolver::("peer".into(), PeerDidResolver::new()) + .register_resolver("sov".into(), did_sov_resolver), ); - let url: Url = "http://dummyurl.org".parse().unwrap(); - - let public_key = generate_keypair(&institution.wallet, KeyType::Ed25519) - .await - .unwrap(); - let service = { - let service_id = Uuid::new_v4().to_string(); - ServiceSov::DIDCommV1( - ServiceDidCommV1::new( - service_id.parse().unwrap(), - url.clone().into(), - ExtraFieldsDidCommV1::builder() - .set_recipient_keys(vec![KeyKind::DidKey(public_key.try_into().unwrap())]) - .build(), - ) - .unwrap(), + let invitation = Invitation::builder() + .id("test_invite_id".to_owned()) + .content( + InvitationContent::builder() + .services(vec![OobService::Did(format!( + "did:sov:{}", + agent_inviter.institution_did + ))]) + .build(), ) - }; - let invitation = OutOfBandSender::create() - .append_service(&OobService::SovService(service)) - .append_handshake_protocol(Protocol::DidExchangeType(DidExchangeType::V1( - DidExchangeTypeV1::new_v1_0(), - ))) - .unwrap() - .oob - .clone(); - - let invitation_id = invitation.id.clone(); - let invitation_key = resolve_key_from_invitation(&invitation, &resolver_registry) + .build(); + let invitation_key = resolve_enc_key_from_invitation(&invitation, &resolver_registry) .await .unwrap(); + info!( + "Inviter prepares invitation and passes to invitee {}", + invitation + ); + + let (requesters_did_document, _our_verkey) = + create_our_did_document(&agent_invitee.wallet, dummy_url.clone(), vec![]).await?; + info!("Requester prepares did document: {requesters_did_document}"); + let requesters_peer_did = PeerDid::::from_did_doc(requesters_did_document.clone())?; + info!("Requester prepares their peer:did: {requesters_peer_did}"); + let did_inviter: Did = invitation_get_first_did_service(&invitation)?; + info!( + "Invitee resolves Inviter's DID from invitation {} (as a first DID service found in the \ + invitation)", + did_inviter + ); let TransitionResult { state: requester, output: request, - } = DidExchangeRequester::::construct_request_pairwise( - &consumer.wallet, - invitation, + } = DidExchangeRequester::::construct_request( resolver_registry.clone(), - url.clone(), - vec![], + Some(invitation.id), + &did_inviter, + &requesters_peer_did, ) .await .unwrap(); + info!( + "Invitee processes invitation, builds up request {}", + &request + ); + + let (responders_did_document, _our_verkey) = + create_our_did_document(&agent_invitee.wallet, dummy_url.clone(), vec![]).await?; + info!("Responder prepares did document: {responders_did_document}"); + let responders_peer_did = PeerDid::::from_did_doc(responders_did_document.clone())?; + info!("Responder prepares their peer:did: {responders_peer_did}"); + + let check_diddoc = resolve_didpeer2(&responders_peer_did, PublicKeyEncoding::Base58).await; + info!("Responder decodes constructed peer:did as did document: {check_diddoc}"); let TransitionResult { - state: responder, output: response, + state: responder, } = DidExchangeResponder::::receive_request( - &institution.wallet, + &agent_inviter.wallet, resolver_registry, request, - url.clone(), - vec![], - invitation_id, - invitation_key, + &responders_peer_did, + Some(invitation_key), ) .await .unwrap(); @@ -115,43 +183,46 @@ async fn did_exchange_test() { let responder = responder.receive_complete(complete).unwrap(); - let responder_key = responder - .our_did_doc() - .verification_method() - .first() - .unwrap() - .public_key() - .unwrap() - .base58(); - assert_eq!( - requester - .their_did_doc() - .verification_method() - .first() - .unwrap() - .public_key() - .unwrap() - .base58(), - responder_key + info!("Asserting did document of requester"); + assert_key_agreement( + requester.our_did_doc().clone(), + responder.their_did_doc().clone(), + ); + info!("Asserting did document of responder"); + assert_key_agreement( + responder.our_did_doc().clone(), + requester.their_did_doc().clone(), ); - let requester_key = requester - .our_did_doc() - .verification_method() - .first() - .unwrap() - .public_key() - .unwrap() - .base58(); - assert_eq!( - responder - .their_did_doc() - .verification_method() - .first() - .unwrap() - .public_key() - .unwrap() - .base58(), - requester_key + info!( + "Requesters did document (requesters view): {}", + requester.our_did_doc() + ); + info!( + "Responders did document (requesters view): {}", + requester.their_did_doc() ); + + let data = "Hello world"; + let service = requester + .their_did_doc() + .get_service_of_type(&ServiceType::DIDCommV1)?; + let m = EncryptionEnvelope::create( + &agent_invitee.wallet, + data.as_bytes(), + requester.our_did_doc(), + requester.their_did_doc(), + service.id(), + ) + .await?; + + info!("Encrypted message: {:?}", m); + + let expected_sender_vk = resolve_base58_key_agreement(&requesters_did_document)?; + let unpacked = + EncryptionEnvelope::auth_unpack(&agent_invitee.wallet, m.0, &expected_sender_vk).await?; + + info!("Unpacked message: {:?}", unpacked); + + Ok(()) } diff --git a/aries/aries_vcx/tests/utils/scenarios/connection.rs b/aries/aries_vcx/tests/utils/scenarios/connection.rs index 92457d4246..216cb0004c 100644 --- a/aries/aries_vcx/tests/utils/scenarios/connection.rs +++ b/aries/aries_vcx/tests/utils/scenarios/connection.rs @@ -51,7 +51,7 @@ async fn establish_connection_from_invite( #[async_trait] impl Transport for DummyHttpClient { - async fn send_message(&self, _msg: Vec, _service_endpoint: Url) -> VcxResult<()> { + async fn send_message(&self, _msg: Vec, _service_endpoint: &Url) -> VcxResult<()> { Ok(()) } } diff --git a/aries/aries_vcx/tests/utils/scenarios/proof_presentation.rs b/aries/aries_vcx/tests/utils/scenarios/proof_presentation.rs index 5e2768b8c3..187f42d127 100644 --- a/aries/aries_vcx/tests/utils/scenarios/proof_presentation.rs +++ b/aries/aries_vcx/tests/utils/scenarios/proof_presentation.rs @@ -159,7 +159,7 @@ pub async fn create_proof_request_data( .name(request_name.unwrap_or("name").to_string()) .requested_attributes(requested_attrs) .requested_predicates(requested_preds) - .non_revoked(revocation_interval) + .non_revoked(Some(revocation_interval)) .build() .into() } diff --git a/aries/aries_vcx/tests/utils/test_agent.rs b/aries/aries_vcx/tests/utils/test_agent.rs index b6f6f0e9b2..684d82a0aa 100644 --- a/aries/aries_vcx/tests/utils/test_agent.rs +++ b/aries/aries_vcx/tests/utils/test_agent.rs @@ -85,6 +85,33 @@ pub async fn create_test_agent( create_test_agent_from_seed(&generate_random_seed(), genesis_file_path).await } +pub async fn create_test_agent_endorser_2( + genesis_file_path: &str, + test_agent_trustee: TestAgent< + impl IndyLedgerRead + AnoncredsLedgerRead, + impl IndyLedgerWrite + AnoncredsLedgerWrite, + impl BaseAnonCreds, + impl BaseWallet, + >, +) -> Result< + TestAgent< + impl IndyLedgerRead + AnoncredsLedgerRead, + impl IndyLedgerWrite + AnoncredsLedgerWrite, + impl BaseAnonCreds, + impl BaseWallet, + >, + Box, +> { + let agent_endorser = create_test_agent_endorser( + test_agent_trustee.ledger_write, + test_agent_trustee.wallet, + genesis_file_path, + &test_agent_trustee.institution_did, + ) + .await?; + Ok(agent_endorser) +} + pub async fn create_test_agent_endorser( ledger_write: LW, trustee_wallet: W, diff --git a/aries/messages/Cargo.toml b/aries/messages/Cargo.toml index b73573117e..3674b26aaf 100644 --- a/aries/messages/Cargo.toml +++ b/aries/messages/Cargo.toml @@ -25,5 +25,4 @@ messages_macros = { path = "../messages_macros" } diddoc_legacy = { path = "../misc/legacy/diddoc_legacy" } shared = { path = "../misc/shared" } did_parser = { path = "../../did_core/did_parser" } -did_doc_sov = { path = "../../did_core/did_doc_sov" } display_as_json = { path = "../../misc/display_as_json" } diff --git a/aries/messages/src/msg_fields/protocols/out_of_band/invitation.rs b/aries/messages/src/msg_fields/protocols/out_of_band/invitation.rs index 651c032cdc..2ca5b9aa44 100644 --- a/aries/messages/src/msg_fields/protocols/out_of_band/invitation.rs +++ b/aries/messages/src/msg_fields/protocols/out_of_band/invitation.rs @@ -1,4 +1,3 @@ -use did_doc_sov::service::ServiceSov; use diddoc_legacy::aries::service::AriesService; use serde::{Deserialize, Serialize}; use shared::maybe_known::MaybeKnown; @@ -53,7 +52,7 @@ pub enum OobService { // and if service id is not a resolvable did (it must be just a URI) // then there is no way to resolve the recipient keys // must be missing something - SovService(ServiceSov), + // SovService(ServiceSov), AriesService(AriesService), Did(String), } diff --git a/aries/misc/anoncreds_types/src/data_types/messages/pres_request.rs b/aries/misc/anoncreds_types/src/data_types/messages/pres_request.rs index 3ae92ccc6f..bc3704e44c 100644 --- a/aries/misc/anoncreds_types/src/data_types/messages/pres_request.rs +++ b/aries/misc/anoncreds_types/src/data_types/messages/pres_request.rs @@ -29,7 +29,6 @@ pub struct PresentationRequestPayload { #[serde(default)] #[builder(default)] pub requested_predicates: HashMap, - #[builder(setter(strip_option))] #[serde(skip_serializing_if = "Option::is_none")] #[builder(default)] pub non_revoked: Option, diff --git a/aries/misc/shared/src/http_client.rs b/aries/misc/shared/src/http_client.rs index 855ea1fb62..8b37051ed1 100644 --- a/aries/misc/shared/src/http_client.rs +++ b/aries/misc/shared/src/http_client.rs @@ -21,10 +21,10 @@ lazy_static! { }; } -pub async fn post_message(body_content: Vec, url: Url) -> HttpResult> { +pub async fn post_message(body_content: Vec, url: &Url) -> HttpResult> { debug!("post_message >> http client sending request POST {}", &url); - let response = send_post_request(&url, body_content).await?; + let response = send_post_request(url, body_content).await?; process_response(response).await } diff --git a/aries/wrappers/uniffi-aries-vcx/core/src/core/http_client.rs b/aries/wrappers/uniffi-aries-vcx/core/src/core/http_client.rs index 99c6fb4f28..0e29279c83 100644 --- a/aries/wrappers/uniffi-aries-vcx/core/src/core/http_client.rs +++ b/aries/wrappers/uniffi-aries-vcx/core/src/core/http_client.rs @@ -6,7 +6,7 @@ pub struct HttpClient; #[async_trait] impl Transport for HttpClient { - async fn send_message(&self, msg: Vec, service_endpoint: Url) -> VcxResult<()> { + async fn send_message(&self, msg: Vec, service_endpoint: &Url) -> VcxResult<()> { post_message(msg, service_endpoint).await?; Ok(()) } diff --git a/did_core/did_doc/Cargo.toml b/did_core/did_doc/Cargo.toml index 106b002025..5743f444a7 100644 --- a/did_core/did_doc/Cargo.toml +++ b/did_core/did_doc/Cargo.toml @@ -15,3 +15,7 @@ serde = { version = "1.0.159", default-features = false, features = ["derive"] } serde_json = "1.0.95" uniresid = { version = "0.1.4", default-features = false, features = ["serde"] } url = { version = "2.3.1", features = ["serde"] } +display_as_json = { path = "../../misc/display_as_json" } +did_key = { path = "../did_methods/did_key" } +thiserror = "1.0.40" +typed-builder = "0.16.0" diff --git a/did_core/did_doc/src/error.rs b/did_core/did_doc/src/error.rs index 16b42c0d4a..26e5f9e720 100644 --- a/did_core/did_doc/src/error.rs +++ b/did_core/did_doc/src/error.rs @@ -1,17 +1,11 @@ -use url::ParseError; - -use crate::schema::verification_method::VerificationMethodType; +use crate::schema::verification_method::{error::KeyDecodingError, VerificationMethodType}; #[derive(Debug)] pub enum DidDocumentBuilderError { - InvalidInput(String), + CustomError(String), MissingField(&'static str), - UnsupportedPublicKeyField(&'static str), JsonError(serde_json::Error), - PemError(pem::PemError), - Base58DecodeError(bs58::decode::Error), - Base64DecodeError(base64::DecodeError), - HexDecodeError(hex::FromHexError), + KeyDecodingError(KeyDecodingError), UnsupportedVerificationMethodType(VerificationMethodType), PublicKeyError(public_key::PublicKeyError), } @@ -19,36 +13,24 @@ pub enum DidDocumentBuilderError { impl std::fmt::Display for DidDocumentBuilderError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - DidDocumentBuilderError::InvalidInput(input) => { - write!(f, "Invalid input: {}", input) - } DidDocumentBuilderError::MissingField(field) => { write!(f, "Missing field: {}", field) } - DidDocumentBuilderError::UnsupportedPublicKeyField(field) => { - write!(f, "Unsupported public key field: {}", field) - } DidDocumentBuilderError::JsonError(error) => { write!(f, "(De)serialization error: {}", error) } - DidDocumentBuilderError::PemError(error) => { - write!(f, "PEM error: {}", error) - } - DidDocumentBuilderError::Base58DecodeError(error) => { - write!(f, "Base58 decode error: {}", error) - } - DidDocumentBuilderError::Base64DecodeError(error) => { - write!(f, "Base64 decode error: {}", error) - } - DidDocumentBuilderError::HexDecodeError(error) => { - write!(f, "Hex decode error: {}", error) - } DidDocumentBuilderError::UnsupportedVerificationMethodType(vm_type) => { write!(f, "Unsupported verification method type: {}", vm_type) } DidDocumentBuilderError::PublicKeyError(error) => { write!(f, "Public key error: {}", error) } + DidDocumentBuilderError::CustomError(string) => { + write!(f, "Custom DidDocumentBuilderError: {}", string) + } + DidDocumentBuilderError::KeyDecodingError(error) => { + write!(f, "Key decoding error: {}", error) + } } } } @@ -57,10 +39,6 @@ impl std::error::Error for DidDocumentBuilderError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { DidDocumentBuilderError::JsonError(error) => Some(error), - DidDocumentBuilderError::PemError(error) => Some(error), - DidDocumentBuilderError::Base58DecodeError(error) => Some(error), - DidDocumentBuilderError::Base64DecodeError(error) => Some(error), - DidDocumentBuilderError::HexDecodeError(error) => Some(error), DidDocumentBuilderError::PublicKeyError(error) => Some(error), _ => None, } @@ -73,38 +51,14 @@ impl From for DidDocumentBuilderError { } } -impl From for DidDocumentBuilderError { - fn from(error: pem::PemError) -> Self { - DidDocumentBuilderError::PemError(error) - } -} - -impl From for DidDocumentBuilderError { - fn from(error: bs58::decode::Error) -> Self { - DidDocumentBuilderError::Base58DecodeError(error) - } -} - -impl From for DidDocumentBuilderError { - fn from(error: base64::DecodeError) -> Self { - DidDocumentBuilderError::Base64DecodeError(error) - } -} - -impl From for DidDocumentBuilderError { - fn from(error: hex::FromHexError) -> Self { - DidDocumentBuilderError::HexDecodeError(error) - } -} - -impl From for DidDocumentBuilderError { - fn from(error: ParseError) -> Self { - DidDocumentBuilderError::InvalidInput(error.to_string()) - } -} - impl From for DidDocumentBuilderError { fn from(error: public_key::PublicKeyError) -> Self { DidDocumentBuilderError::PublicKeyError(error) } } + +impl From for DidDocumentBuilderError { + fn from(error: KeyDecodingError) -> Self { + DidDocumentBuilderError::KeyDecodingError(error) + } +} diff --git a/did_core/did_doc/src/lib.rs b/did_core/did_doc/src/lib.rs index 6253bcfe61..72b4934403 100644 --- a/did_core/did_doc/src/lib.rs +++ b/did_core/did_doc/src/lib.rs @@ -1,3 +1,4 @@ +extern crate display_as_json; extern crate serde; extern crate serde_json; diff --git a/did_core/did_doc/src/schema/did_doc.rs b/did_core/did_doc/src/schema/did_doc.rs index d5ea72df97..4b150d41e8 100644 --- a/did_core/did_doc/src/schema/did_doc.rs +++ b/did_core/did_doc/src/schema/did_doc.rs @@ -1,28 +1,26 @@ -use std::{collections::HashMap, fmt::Display}; +use std::collections::HashMap; use did_parser::{Did, DidUrl}; +use display_as_json::Display; use serde::{Deserialize, Serialize}; use serde_json::Value; use super::{ - service::Service, types::uri::Uri, utils::OneOrList, verification_method::{VerificationMethod, VerificationMethodKind}, }; -use crate::error::DidDocumentBuilderError; +use crate::{error::DidDocumentBuilderError, schema::service::Service}; -pub type ControllerAlias = OneOrList; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Display)] #[serde(default)] #[serde(rename_all = "camelCase")] -pub struct DidDocument { +pub struct DidDocument { id: Did, #[serde(skip_serializing_if = "Vec::is_empty")] also_known_as: Vec, #[serde(skip_serializing_if = "Option::is_none")] - controller: Option, + controller: Option>, #[serde(skip_serializing_if = "Vec::is_empty")] verification_method: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] @@ -36,24 +34,14 @@ pub struct DidDocument { #[serde(skip_serializing_if = "Vec::is_empty")] capability_delegation: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] - service: Vec>, + service: Vec, #[serde(skip_serializing_if = "HashMap::is_empty")] #[serde(flatten)] extra: HashMap, } -impl Display for DidDocument -where - E: Display + Serialize, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let json = serde_json::to_string(self).unwrap(); - write!(f, "{}", json) - } -} - -impl DidDocument { - pub fn builder(id: Did) -> DidDocumentBuilder { +impl DidDocument { + pub fn builder(id: Did) -> DidDocumentBuilder { DidDocumentBuilder::new(id) } @@ -61,6 +49,10 @@ impl DidDocument { &self.id } + pub fn set_id(&mut self, id: Did) { + self.id = id; + } + pub fn also_known_as(&self) -> &[Uri] { self.also_known_as.as_ref() } @@ -93,7 +85,7 @@ impl DidDocument { self.capability_delegation.as_ref() } - pub fn service(&self) -> &[Service] { + pub fn service(&self) -> &[Service] { self.service.as_ref() } @@ -113,8 +105,8 @@ impl DidDocument { } } -#[derive(Debug)] -pub struct DidDocumentBuilder { +#[derive(Default, Debug)] +pub struct DidDocumentBuilder { id: Did, also_known_as: Vec, controller: Vec, @@ -124,29 +116,11 @@ pub struct DidDocumentBuilder { key_agreement: Vec, capability_invocation: Vec, capability_delegation: Vec, - service: Vec>, + service: Vec, extra: HashMap, } -impl Default for DidDocumentBuilder { - fn default() -> Self { - Self { - id: Default::default(), - also_known_as: Default::default(), - controller: Default::default(), - verification_method: Default::default(), - authentication: Default::default(), - assertion_method: Default::default(), - key_agreement: Default::default(), - capability_invocation: Default::default(), - capability_delegation: Default::default(), - service: Default::default(), - extra: Default::default(), - } - } -} - -impl DidDocumentBuilder { +impl DidDocumentBuilder { pub fn new(id: Did) -> Self { Self { id, @@ -229,7 +203,7 @@ impl DidDocumentBuilder { self } - pub fn add_service(mut self, service: Service) -> Self { + pub fn add_service(mut self, service: Service) -> Self { self.service.push(service); self } @@ -239,7 +213,7 @@ impl DidDocumentBuilder { self } - pub fn build(self) -> DidDocument { + pub fn build(self) -> DidDocument { let controller = if self.controller.is_empty() { None } else { @@ -261,8 +235,8 @@ impl DidDocumentBuilder { } } -impl From> for DidDocumentBuilder { - fn from(did_document: DidDocument) -> Self { +impl From for DidDocumentBuilder { + fn from(did_document: DidDocument) -> Self { let controller = match did_document.controller { Some(OneOrList::List(list)) => list, _ => Vec::new(), @@ -287,7 +261,7 @@ impl From> for DidDocumentBuilder { #[cfg(test)] mod tests { use super::*; - use crate::schema::{service::ServiceBuilder, verification_method::VerificationMethodType}; + use crate::schema::verification_method::{PublicKeyField, VerificationMethodType}; #[test] fn test_did_document_builder() { @@ -313,13 +287,13 @@ mod tests { .build(); let service_id = Uri::new("did:example:123456789abcdefghi;service-1").unwrap(); - let service_type = "test-service".to_string(); let service_endpoint = "https://example.com/service"; - let service = - ServiceBuilder::<()>::new(service_id, service_endpoint.try_into().unwrap(), ()) - .add_service_type(service_type) - .unwrap() - .build(); + let service = Service::new( + service_id, + service_endpoint.try_into().unwrap(), + OneOrList::One(ServiceType::Other("test-service".to_string())), + HashMap::default(), + ); let document = DidDocumentBuilder::new(id.clone()) .add_also_known_as(also_known_as.clone()) @@ -392,4 +366,275 @@ mod tests { panic!("Verification method not found") }; } + + use std::str::FromStr; + + use did_parser::{Did, DidUrl}; + use serde_json::Value; + + use crate::schema::{ + did_doc::DidDocument, + service::typed::ServiceType, + types::{jsonwebkey::JsonWebKey, uri::Uri}, + verification_method::{VerificationMethod, VerificationMethodKind}, + }; + + const VALID_DID_DOC_JSON: &str = r##" + { + "@context": [ + "https://w3.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1" + ], + "id": "did:web:did-actor-alice", + "alsoKnownAs": [ + "https://example.com/user-profile/123" + ], + "publicKey": [ + { + "id": "did:web:did-actor-alice#z6MkrmNwty5ajKtFqc1U48oL2MMLjWjartwc5sf2AihZwXDN", + "controller": "did:web:did-actor-alice", + "type": "Ed25519VerificationKey2018", + "publicKeyBase58": "DK7uJiq9PnPnj7AmNZqVBFoLuwTjT1hFPrk6LSjZ2JRz" + } + ], + "authentication": [ + "did:web:did-actor-alice#z6MkrmNwty5ajKtFqc1U48oL2MMLjWjartwc5sf2AihZwXDN" + ], + "assertionMethod": [ + "did:web:did-actor-alice#z6MkrmNwty5ajKtFqc1U48oL2MMLjWjartwc5sf2AihZwXDN" + ], + "capabilityDelegation": [ + "did:web:did-actor-alice#z6MkrmNwty5ajKtFqc1U48oL2MMLjWjartwc5sf2AihZwXDN" + ], + "capabilityInvocation": [ + "did:web:did-actor-alice#z6MkrmNwty5ajKtFqc1U48oL2MMLjWjartwc5sf2AihZwXDN" + ], + "verificationMethod": [ + { + "id": "#g1", + "controller": "did:web:did-actor-alice", + "type": "JsonWebKey2020", + "publicKeyJwk": { + "kty": "EC", + "crv": "BLS12381_G1", + "x": "hxF12gtsn9ju4-kJq2-nUjZQKVVWpcBAYX5VHnUZMDilClZsGuOaDjlXS8pFE1GG" + } + }, + { + "id": "#g2", + "controller": "did:web:did-actor-alice", + "type": "JsonWebKey2020", + "publicKeyJwk": { + "kty": "EC", + "crv": "BLS12381_G2", + "x": "l4MeBsn_OGa2OEDtHeHdq0TBC8sYh6QwoI7QsNtZk9oAru1OnGClaAPlMbvvs73EABDB6GjjzybbOHarkBmP6pon8H1VuMna0nkEYihZi8OodgdbwReDiDvWzZuXXMl-" + } + } + ], + "keyAgreement": [ + { + "id": "did:web:did-actor-alice#zC8GybikEfyNaausDA4mkT4egP7SNLx2T1d1kujLQbcP6h", + "type": "X25519KeyAgreementKey2019", + "controller": "did:web:did-actor-alice", + "publicKeyBase58": "CaSHXEvLKS6SfN9aBfkVGBpp15jSnaHazqHgLHp8KZ3Y" + } + ] + } + "##; + + #[test] + fn test_deserialization() { + let did_doc: DidDocument = serde_json::from_str(VALID_DID_DOC_JSON).unwrap(); + + assert_eq!( + did_doc.id(), + &"did:web:did-actor-alice".to_string().try_into().unwrap() + ); + assert_eq!( + did_doc.also_known_as(), + vec![Uri::from_str("https://example.com/user-profile/123").unwrap()] + ); + + let controller: Did = "did:web:did-actor-alice".to_string().try_into().unwrap(); + + let pk_id = DidUrl::parse( + "did:web:did-actor-alice#z6MkrmNwty5ajKtFqc1U48oL2MMLjWjartwc5sf2AihZwXDN".to_string(), + ) + .unwrap(); + + let vm1_id = DidUrl::parse("#g1".to_string()).unwrap(); + let vm1 = VerificationMethod::builder( + vm1_id, + controller.clone(), + VerificationMethodType::JsonWebKey2020, + ) + .add_public_key_jwk( + JsonWebKey::from_str( + r#"{ + "kty": "EC", + "crv": "BLS12381_G1", + "x": "hxF12gtsn9ju4-kJq2-nUjZQKVVWpcBAYX5VHnUZMDilClZsGuOaDjlXS8pFE1GG" + }"#, + ) + .unwrap(), + ) + .build(); + + let vm2_id = DidUrl::parse("#g2".to_string()).unwrap(); + let vm2 = VerificationMethod::builder( + vm2_id, + controller.clone(), + VerificationMethodType::JsonWebKey2020, + ) + .add_public_key_jwk( + JsonWebKey::from_str( + r#"{ + "kty": "EC", + "crv": "BLS12381_G2", + "x": "l4MeBsn_OGa2OEDtHeHdq0TBC8sYh6QwoI7QsNtZk9oAru1OnGClaAPlMbvvs73EABDB6GjjzybbOHarkBmP6pon8H1VuMna0nkEYihZi8OodgdbwReDiDvWzZuXXMl-" + }"#, + ) + .unwrap(), + ) + .build(); + + assert_eq!(did_doc.verification_method().get(0).unwrap().clone(), vm1); + assert_eq!(did_doc.verification_method().get(1).unwrap().clone(), vm2); + + assert_eq!( + did_doc.authentication(), + &[VerificationMethodKind::Resolvable(pk_id.clone())] + ); + + assert_eq!( + did_doc.assertion_method(), + &[VerificationMethodKind::Resolvable(pk_id.clone())] + ); + + assert_eq!( + did_doc.capability_delegation(), + &[VerificationMethodKind::Resolvable(pk_id.clone())] + ); + + assert_eq!( + did_doc.capability_invocation(), + &[VerificationMethodKind::Resolvable(pk_id)] + ); + + assert_eq!( + did_doc.extra_field("publicKey").unwrap().clone(), + Value::Array(vec![Value::Object( + serde_json::from_str( + r#"{ + "id": "did:web:did-actor-alice#z6MkrmNwty5ajKtFqc1U48oL2MMLjWjartwc5sf2AihZwXDN", + "type": "Ed25519VerificationKey2018", + "controller": "did:web:did-actor-alice", + "publicKeyBase58": "DK7uJiq9PnPnj7AmNZqVBFoLuwTjT1hFPrk6LSjZ2JRz" + }"# + ) + .unwrap() + )]) + ); + + let ka1_id = DidUrl::parse( + "did:web:did-actor-alice#zC8GybikEfyNaausDA4mkT4egP7SNLx2T1d1kujLQbcP6h".to_string(), + ) + .unwrap(); + let ka1 = VerificationMethod::builder( + ka1_id, + controller, + VerificationMethodType::X25519KeyAgreementKey2019, + ) + .add_public_key_base58("CaSHXEvLKS6SfN9aBfkVGBpp15jSnaHazqHgLHp8KZ3Y".to_string()) + .build(); + + assert_eq!( + did_doc.key_agreement(), + &[VerificationMethodKind::Resolved(ka1)] + ); + } + + #[test] + fn test_serialization() { + let did_doc: DidDocument = serde_json::from_str(VALID_DID_DOC_JSON).unwrap(); + + let serialized_json = serde_json::to_string(&did_doc).unwrap(); + + let original_json_value: DidDocument = serde_json::from_str(VALID_DID_DOC_JSON).unwrap(); + let serialized_json_value: DidDocument = serde_json::from_str(&serialized_json).unwrap(); + assert_eq!(serialized_json_value, original_json_value); + } + + #[test] + fn did_doc_dereferencing() { + let did_doc: DidDocument = serde_json::from_value(serde_json::json!({ + "@context": [ + "https://w3.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1" + ], + "id": "did:web:did-actor-alice", + "alsoKnownAs": [ + "https://example.com/user-profile/123" + ], + "verificationMethod": [ + { + "id": "did:example:123456789abcdefghi#keys-2", + "type": "Ed25519VerificationKey2020", + "controller": "did:example:123456789abcdefghi", + "publicKeyMultibase": "zH3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV" + }, + { + "id": "#keys-3", + "type": "Ed25519VerificationKey2020", + "controller": "did:example:123456789abcdefghi", + "publicKeyMultibase": "zH3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV" + } + ] + })) + .unwrap(); + { + let vm = did_doc + .dereference_key( + &DidUrl::parse("did:example:123456789abcdefghi#keys-2".to_string()).unwrap(), + ) + .unwrap(); + + assert_eq!(vm.id().to_string(), "did:example:123456789abcdefghi#keys-2"); + assert_eq!( + vm.controller().to_string(), + "did:example:123456789abcdefghi" + ); + assert_eq!( + vm.verification_method_type(), + &VerificationMethodType::Ed25519VerificationKey2020 + ); + assert_eq!( + vm.public_key_field(), + &PublicKeyField::Multibase { + public_key_multibase: "zH3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV" + .to_string() + } + ); + } + { + let vm = did_doc + .dereference_key(&DidUrl::parse("#keys-2".to_string()).unwrap()) + .unwrap(); + assert_eq!(vm.id().to_string(), "did:example:123456789abcdefghi#keys-2"); + } + { + let vm = did_doc + .dereference_key( + &DidUrl::parse("did:example:123456789abcdefghi#keys-3".to_string()).unwrap(), + ) + .unwrap(); + assert_eq!(vm.id().to_string(), "#keys-3"); + } + { + let vm = did_doc + .dereference_key(&DidUrl::parse("#keys-3".to_string()).unwrap()) + .unwrap(); + assert_eq!(vm.id().to_string(), "#keys-3"); + } + } } diff --git a/did_core/did_doc/src/schema/legacy.rs b/did_core/did_doc/src/schema/legacy.rs new file mode 100644 index 0000000000..4bc71bf0da --- /dev/null +++ b/did_core/did_doc/src/schema/legacy.rs @@ -0,0 +1,299 @@ +use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine}; +use did_parser::{Did, DidUrl}; +use public_key::{Key, KeyType}; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; + +use crate::schema::{ + did_doc::DidDocument, + service::Service, + verification_method::{VerificationMethod, VerificationMethodType}, +}; + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, display_as_json::Display)] +#[serde(deny_unknown_fields)] +pub struct LegacyDidDoc { + id: Did, + #[serde(default)] + #[serde(rename = "publicKey")] + public_key: Vec, + #[serde(default)] + authentication: Vec, + service: Vec, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, display_as_json::Display)] +pub struct LegacyKeyAgreement { + id: String, + #[serde(rename = "type")] + verification_method_type: String, + controller: String, + #[serde(rename = "publicKeyBase58")] + public_key_base_58: String, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, display_as_json::Display)] +pub struct LegacyAuthentication { + #[serde(rename = "type")] + verification_method_type: String, + #[serde(rename = "publicKey")] + public_key: String, +} + +fn resolve_legacy_authentication_key( + legacy_authentication: &LegacyAuthentication, + legacy_public_keys: &[LegacyKeyAgreement], +) -> Result { + if let Some(fragment) = legacy_authentication.public_key.split('#').last() { + Ok(legacy_public_keys + .iter() + .find(|pk| pk.id.ends_with(fragment)) + .ok_or_else(|| format!("Public key with id {} not found", fragment))? + .public_key_base_58 + .clone()) + } else { + Ok(legacy_authentication.public_key.clone()) + } +} + +fn collect_authentication_fingerprints(legacy_ddo: &LegacyDidDoc) -> Result, String> { + let mut authentication_fingerprints = vec![]; + + for auth in &legacy_ddo.authentication { + let resolved_legacy_authentication_key = match auth.verification_method_type.as_str() { + "Ed25519SignatureAuthentication2018" => { + resolve_legacy_authentication_key(auth, &legacy_ddo.public_key)? + } + "Ed25519Signature2018" => auth.public_key.clone(), + _ => { + continue; + } + }; + + let fingerprint = Key::from_base58(&resolved_legacy_authentication_key, KeyType::Ed25519) + .map_err(|err| { + format!( + "Error converting legacy authentication key to new key: {:?}, error: {:?}", + auth, err + ) + })? + .fingerprint(); + + authentication_fingerprints.push(fingerprint); + } + + for vm in &legacy_ddo.public_key { + // Ed25519VerificationKey2018 check is used due to aries-vcx using this as key type in + // the legacy did doc + if !&["Ed25519Signature2018", "Ed25519VerificationKey2018"] + .contains(&vm.verification_method_type.as_str()) + { + continue; + } + + let fingerprint = Key::from_base58(vm.public_key_base_58.as_str(), KeyType::Ed25519) + .map_err(|err| { + format!( + "Error converting legacy public key to new key: {:?}, error: {:?}", + vm, err + ) + })? + .fingerprint(); + + if !authentication_fingerprints.contains(&fingerprint) { + authentication_fingerprints.push(fingerprint); + } + } + + Ok(authentication_fingerprints) +} + +fn collect_encoded_services(legacy_ddo: &LegacyDidDoc) -> Vec { + let mut encoded_services = vec![]; + for service in &legacy_ddo.service { + let priority = service.extra_field_priority().unwrap_or(0); + let routing_keys = service.extra_field_routing_keys().unwrap_or(vec![]); + let recipient_keys = service.extra_field_recipient_keys().unwrap_or(vec![]); + let service_endpoint = service.service_endpoint().to_string(); + let service_type = service.service_types().first().unwrap().to_string(); + let service = json!({ + "priority": priority, + "r": routing_keys, + "recipientKeys": recipient_keys, + "s": service_endpoint, + "t": service_type, + }); + let service_encoded = STANDARD_NO_PAD.encode(service.to_string().as_bytes()); + encoded_services.push(service_encoded); + } + encoded_services +} + +fn construct_peer_did( + authentication_fingerprints: &[String], + encoded_services: &[String], +) -> Result { + // TODO: Perhaps proper ID is did:peer:3 with alsoKnowAs set to did:peer:2 (or vice versa?) + let mut did = "did:peer:2".to_string(); + + for fingerprint in authentication_fingerprints { + did.push_str(&format!(".V{}", fingerprint)); + } + + for service in encoded_services { + did.push_str(&format!(".S{}", service)); + } + + Did::parse(did).map_err(|err| format!("Error parsing peer did, error: {:?}", err)) +} + +fn construct_new_did_document( + legacy_ddo: &LegacyDidDoc, + authentication_fingerprints: &[String], + did: Did, +) -> Result { + let mut builder = DidDocument::builder(did.clone()); + + for (i, fingerprint) in authentication_fingerprints.iter().enumerate() { + let id = DidUrl::from_fragment((i + 1).to_string()) + .map_err(|err| format!("Error constructing did url from fragment, error: {:?}", err))?; + builder = builder.add_verification_method( + VerificationMethod::builder( + id, + did.clone(), + VerificationMethodType::Ed25519VerificationKey2018, + ) + .add_public_key_multibase(fingerprint.clone()) + .build(), + ); + } + + for service in &legacy_ddo.service { + builder = builder.add_service(service.clone()); + } + + Ok(builder.build()) +} + +// https://github.com/TimoGlastra/legacy-did-transformation +fn convert_legacy_ddo_to_new(legacy_ddo: LegacyDidDoc) -> Result { + let authentication_fingerprints = collect_authentication_fingerprints(&legacy_ddo)?; + let encoded_services = collect_encoded_services(&legacy_ddo); + let did = construct_peer_did(&authentication_fingerprints, &encoded_services)?; + construct_new_did_document(&legacy_ddo, &authentication_fingerprints, did) +} + +pub fn deserialize_legacy_or_new_diddoc_value(val: Value) -> Result { + match serde_json::from_value::(val.clone()) { + Ok(legacy_doc) => convert_legacy_ddo_to_new(legacy_doc), + Err(_err) => Ok(serde_json::from_value::(val).map_err(|err| { + format!( + "Error deserializing did document from value, error: {:?}", + err + ) + })?), + } +} + +pub fn deserialize_legacy_or_new_diddoc_str(val: String) -> Result { + let value = serde_json::from_str::(&val) + .map_err(|err| format!("Error deserializing did doc value, error: {:?}", err))?; + deserialize_legacy_or_new_diddoc_value(value) +} + +#[cfg(test)] +mod tests { + use crate::schema::{ + did_doc::DidDocument, legacy::deserialize_legacy_or_new_diddoc_str, + service::service_key_kind::ServiceKeyKind, + }; + + const LEGACY_DID_DOC_JSON: &str = r#" + { + "id": "2ZHFFhzA2XtTD6hJqzL7ux", + "publicKey": [ + { + "id": "1", + "type": "Ed25519VerificationKey2018", + "controller": "2ZHFFhzA2XtTD6hJqzL7ux", + "publicKeyBase58": "rCw3x5h1jS6gPo7rRrt3EYbXXe5nNjnGbdf1jAwUxuj" + } + ], + "authentication": [ + { + "type": "Ed25519SignatureAuthentication2018", + "publicKey": "2ZHFFhzA2XtTD6hJqzL7ux#1" + } + ], + "service": [ + { + "id": "did:example:123456789abcdefghi;indy", + "type": "IndyAgent", + "priority": 0, + "recipientKeys": [ + "2ZHFFhzA2XtTD6hJqzL7ux#1" + ], + "routingKeys": [ + "8Ps2WosJ9AV1eXPoJKsEJdM3NchPhSyS8qFt6LQUTKv2", + "Hezce2UWMZ3wUhVkh2LfKSs8nDzWwzs2Win7EzNN3YaR" + ], + "serviceEndpoint": "http://localhost:8080/agency/msg" + } + ] + }"#; + + const VERKEY_BASE58: &str = "6MkfJTyeCL8MGvZntdpXzpitL6bM6uwCFz8xcYar18xQBh7"; + + const DID_PEER: &str = "did:peer:2.Vz6MkfJTyeCL8MGvZntdpXzpitL6bM6uwCFz8xcYar18xQBh7.SeyJwcmlvcml0eSI6MCwiciI6WyI4UHMyV29zSjlBVjFlWFBvSktzRUpkTTNOY2hQaFN5UzhxRnQ2TFFVVEt2MiIsIkhlemNlMlVXTVozd1VoVmtoMkxmS1NzOG5Eeld3enMyV2luN0V6Tk4zWWFSIl0sInJlY2lwaWVudEtleXMiOlsiMlpIRkZoekEyWHRURDZoSnF6TDd1eCMxIl0sInMiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYWdlbmN5L21zZyIsInQiOiJJbmR5QWdlbnQifQ"; + + #[test] + fn test_deserialization_legacy() { + let did_doc: DidDocument = + deserialize_legacy_or_new_diddoc_str(LEGACY_DID_DOC_JSON.into()).unwrap(); + assert_eq!(did_doc.id().to_string(), DID_PEER); + assert_eq!(did_doc.verification_method().len(), 1); + assert_eq!(did_doc.authentication().len(), 0); + assert_eq!(did_doc.assertion_method().len(), 0); + assert_eq!(did_doc.key_agreement().len(), 0); + assert_eq!(did_doc.service().len(), 1); + + let verification_method = did_doc.verification_method().first().unwrap(); + assert_eq!(verification_method.id().to_string(), "#1"); + assert_eq!(verification_method.controller().to_string(), DID_PEER); + assert_eq!( + verification_method + .public_key() + .unwrap() + .prefixless_fingerprint(), + VERKEY_BASE58 + ); + + let service = did_doc.service().first().unwrap(); + assert_eq!( + service.id().to_string(), + "did:example:123456789abcdefghi;indy" + ); + assert_eq!( + service.service_endpoint().to_string().as_str(), + "http://localhost:8080/agency/msg" + ); + + let recipient_key = match service + .extra_field_recipient_keys() + .unwrap() + .first() + .unwrap() + { + ServiceKeyKind::Reference(did_url) => did_doc + .dereference_key(did_url) + .unwrap() + .public_key() + .unwrap() + .prefixless_fingerprint(), + _ => panic!("Expected reference"), + }; + assert_eq!(recipient_key, VERKEY_BASE58); + assert_eq!(service.extra_field_priority().unwrap(), 0); + assert_eq!(service.extra_field_routing_keys().unwrap().len(), 2); + } +} diff --git a/did_core/did_doc/src/schema/mod.rs b/did_core/did_doc/src/schema/mod.rs index bca3088b80..b9d1f4635d 100644 --- a/did_core/did_doc/src/schema/mod.rs +++ b/did_core/did_doc/src/schema/mod.rs @@ -1,4 +1,5 @@ pub mod did_doc; +pub mod legacy; pub mod service; pub mod types; pub mod utils; diff --git a/did_core/did_doc/src/schema/service.rs b/did_core/did_doc/src/schema/service.rs deleted file mode 100644 index 92fe73b62e..0000000000 --- a/did_core/did_doc/src/schema/service.rs +++ /dev/null @@ -1,225 +0,0 @@ -use std::collections::HashSet; - -use serde::{Deserialize, Serialize}; - -use super::{ - types::{uri::Uri, url::Url}, - utils::OneOrList, -}; -use crate::error::DidDocumentBuilderError; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct Service { - id: Uri, - #[serde(rename = "type")] - service_type: OneOrList, - service_endpoint: Url, - #[serde(flatten)] - extra: E, -} - -impl Service { - pub fn builder(id: Uri, service_endpoint: Url, extra: E) -> ServiceBuilder { - ServiceBuilder::new(id, service_endpoint, extra) - } - - pub fn id(&self) -> &Uri { - &self.id - } - - pub fn service_type(&self) -> &OneOrList { - &self.service_type - } - - pub fn service_endpoint(&self) -> &Url { - &self.service_endpoint - } - - pub fn extra(&self) -> &E { - &self.extra - } -} - -#[derive(Debug)] -pub struct ServiceBuilder { - id: Uri, - service_type: HashSet, - service_endpoint: Url, - extra: E, -} - -impl ServiceBuilder { - pub fn new(id: Uri, service_endpoint: Url, extra: E) -> Self { - Self { - id, - service_type: Default::default(), - service_endpoint, - extra, - } - } - - pub fn add_service_type( - self, - service_type: String, - ) -> Result, DidDocumentBuilderError> { - if service_type.is_empty() { - return Err(DidDocumentBuilderError::InvalidInput( - "Invalid service type: empty string".into(), - )); - } - if self.service_type.contains(&service_type) { - return Err(DidDocumentBuilderError::InvalidInput( - "Service type was already included".into(), - )); - } - let mut service_types = self.service_type.clone(); - service_types.insert(service_type); - Ok(ServiceBuilder { - id: self.id, - service_type: service_types, - service_endpoint: self.service_endpoint, - extra: self.extra, - }) - } - - pub fn build(self) -> Service { - let service_type = match self.service_type.len() { - 1 => OneOrList::One(self.service_type.into_iter().next().unwrap()), - _ => OneOrList::List(self.service_type.into_iter().collect()), - }; - Service { - id: self.id, - service_type, - service_endpoint: self.service_endpoint, - extra: self.extra, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn create_valid_uri() -> Uri { - Uri::new("http://example.com").unwrap() - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] - #[serde(rename_all = "camelCase")] - pub struct ExtraSov { - pub priority: u32, - pub recipient_keys: Vec, - pub routing_keys: Vec, - } - - #[test] - fn test_service_builder_basic() { - let id = create_valid_uri(); - let service_endpoint = "http://example.com/endpoint"; - let service_type = "DIDCommMessaging".to_string(); - - let service = ServiceBuilder::::new( - id.clone(), - service_endpoint.try_into().unwrap(), - Default::default(), - ) - .add_service_type(service_type.clone()) - .unwrap() - .build(); - - assert_eq!(service.id(), &id); - assert_eq!(service.service_endpoint().as_ref(), service_endpoint); - assert_eq!(service.service_type(), &OneOrList::One(service_type)); - } - - #[test] - fn test_service_builder_add_extra() { - let id = create_valid_uri(); - let service_endpoint = "http://example.com/endpoint"; - let service_type = "DIDCommMessaging".to_string(); - let recipient_keys = vec!["foo".to_string()]; - let routing_keys = vec!["bar".to_string()]; - let extra = ExtraSov { - priority: 0, - recipient_keys: recipient_keys.clone(), - routing_keys: routing_keys.clone(), - }; - - let service = - ServiceBuilder::::new(id, service_endpoint.try_into().unwrap(), extra) - .add_service_type(service_type) - .unwrap() - .build(); - - assert_eq!(service.extra().recipient_keys, recipient_keys); - assert_eq!(service.extra().routing_keys, routing_keys); - } - - #[test] - fn test_service_builder_add_duplicate_types() { - let id = create_valid_uri(); - let service_endpoint = "http://example.com/endpoint"; - let service_type = "DIDCommMessaging".to_string(); - - ServiceBuilder::::new( - id, - service_endpoint.try_into().unwrap(), - Default::default(), - ) - .add_service_type(service_type.clone()) - .unwrap() - .add_service_type(service_type) - .unwrap_err(); - } - - #[test] - fn test_service_builder_add_type_missing_type() { - let id = create_valid_uri(); - let service_endpoint = "http://example.com/endpoint"; - - let res = ServiceBuilder::::new( - id, - service_endpoint.try_into().unwrap(), - Default::default(), - ) - .add_service_type("".to_string()); - assert!(res.is_err()); - } - - #[test] - fn test_service_serde() { - let service_serialized = r#"{ - "id": "did:sov:HR6vs6GEZ8rHaVgjg2WodM#did-communication", - "type": "did-communication", - "priority": 0, - "recipientKeys": [ - "did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-agreement-1" - ], - "routingKeys": [], - "accept": [ - "didcomm/aip2;env=rfc19" - ], - "serviceEndpoint": "https://example.com/endpoint" - }"#; - - let service: Service = serde_json::from_str(service_serialized).unwrap(); - assert_eq!( - service.id(), - &Uri::new("did:sov:HR6vs6GEZ8rHaVgjg2WodM#did-communication").unwrap() - ); - assert_eq!( - service.service_type(), - &OneOrList::One("did-communication".to_string()) - ); - assert_eq!( - service.service_endpoint().as_ref(), - "https://example.com/endpoint" - ); - assert_eq!(service.extra().priority, 0); - assert_eq!( - service.extra().recipient_keys, - vec!["did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-agreement-1".to_string()] - ); - } -} diff --git a/did_core/did_doc/src/schema/service/mod.rs b/did_core/did_doc/src/schema/service/mod.rs new file mode 100644 index 0000000000..f1bab97f70 --- /dev/null +++ b/did_core/did_doc/src/schema/service/mod.rs @@ -0,0 +1,296 @@ +use std::collections::HashMap; + +use display_as_json::Display; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use service_accept_type::ServiceAcceptType; +use service_key_kind::ServiceKeyKind; +use url::Url; + +use crate::{ + error::DidDocumentBuilderError, + schema::{service::typed::ServiceType, types::uri::Uri, utils::OneOrList}, +}; + +pub mod service_accept_type; +pub mod service_key_kind; +pub mod typed; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Display)] +#[serde(rename_all = "camelCase")] +pub struct Service { + id: Uri, + #[serde(rename = "type")] + service_type: OneOrList, + service_endpoint: Url, + #[serde(flatten)] + #[serde(skip_serializing_if = "HashMap::is_empty")] + extra: HashMap, +} + +impl Service { + pub fn new( + id: Uri, + service_endpoint: Url, + service_type: OneOrList, + extra: HashMap, + ) -> Service { + Service { + id, + service_endpoint, + service_type, + extra, + } + } + + pub fn id(&self) -> &Uri { + &self.id + } + + pub fn service_type(&self) -> &OneOrList { + &self.service_type + } + + pub fn service_types(&self) -> &[ServiceType] { + match &self.service_type { + OneOrList::One(service_type) => std::slice::from_ref(service_type), + OneOrList::List(service_types) => service_types.as_slice(), + } + } + + pub fn service_endpoint(&self) -> &Url { + &self.service_endpoint + } + + pub fn extra(&self) -> &HashMap { + &self.extra + } + + pub fn extra_field_priority(&self) -> Result { + self._expected_extra_field_type::("priority") + } + + pub fn extra_field_routing_keys(&self) -> Result, DidDocumentBuilderError> { + self._expected_extra_field_type::>("routingKeys") + } + + pub fn extra_field_recipient_keys( + &self, + ) -> Result, DidDocumentBuilderError> { + self._expected_extra_field_type::>("recipientKeys") + } + + pub fn extra_field_accept(&self) -> Result, DidDocumentBuilderError> { + self._expected_extra_field_type::>("accept") + } + + fn _expected_extra_field_type serde::Deserialize<'de>>( + &self, + key: &'static str, + ) -> Result { + match self.extra_field_as_as::(key) { + None => Err(DidDocumentBuilderError::MissingField(key)), + Some(value) => value, + } + } + + pub fn extra_field_as_as serde::Deserialize<'de>>( + &self, + key: &str, + ) -> Option> { + match self.extra.get(key) { + Some(value) => { + let result = serde_json::from_value::(value.clone()).map_err(|_err| { + DidDocumentBuilderError::CustomError(format!( + "Extra field {} is not of type {}", + key, + std::any::type_name::() + )) + }); + Some(result) + } + None => None, + } + } + + pub fn add_extra_field_as( + &mut self, + key: &str, + value: T, + ) -> Result<(), DidDocumentBuilderError> { + let value = serde_json::to_value(value).map_err(|_err| { + DidDocumentBuilderError::CustomError(format!( + "Failed to serialize extra field {} as {}", + key, + std::any::type_name::() + )) + })?; + self.extra.insert(key.to_string(), value); + Ok(()) + } + + pub fn add_extra_field_routing_keys( + &mut self, + routing_keys: Vec, + ) -> Result<(), DidDocumentBuilderError> { + self.add_extra_field_as("routingKeys", routing_keys) + } + + pub fn add_extra_field_recipient_keys( + &mut self, + recipient_keys: Vec, + ) -> Result<(), DidDocumentBuilderError> { + self.add_extra_field_as("recipientKeys", recipient_keys) + } + + pub fn add_extra_field_accept( + &mut self, + accept: Vec, + ) -> Result<(), DidDocumentBuilderError> { + self.add_extra_field_as("accept", accept) + } + + pub fn add_extra_field_priority( + &mut self, + priority: u32, + ) -> Result<(), DidDocumentBuilderError> { + self.add_extra_field_as("priority", priority) + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use did_parser::DidUrl; + use serde_json::json; + + use crate::schema::{ + service::{ + service_accept_type::ServiceAcceptType, service_key_kind::ServiceKeyKind, + typed::ServiceType, Service, + }, + types::uri::Uri, + utils::OneOrList, + }; + + #[test] + fn test_service_builder() { + let uri_id = Uri::new("http://example.com").unwrap(); + let service_endpoint = "http://example.com/endpoint"; + let service_type = ServiceType::DIDCommV2; + + let service = Service::new( + uri_id.clone(), + service_endpoint.try_into().unwrap(), + OneOrList::One(service_type.clone()), + HashMap::default(), + ); + + assert_eq!(service.id(), &uri_id); + assert_eq!(service.service_endpoint().as_ref(), service_endpoint); + assert_eq!(service.service_types(), vec!(service_type.clone())); + assert_eq!(service.service_type(), &OneOrList::One(service_type)); + } + + #[test] + fn test_serde_service_aip1() { + let service_aip1 = json!({ + "id": "service-0", + "type": "endpoint", + "serviceEndpoint": "https://example.com/endpoint" + }) + .to_string(); + let service = serde_json::from_str::(&service_aip1).unwrap(); + + assert_eq!(service.id().to_string(), "service-0"); + assert_eq!( + service.service_endpoint().to_string(), + "https://example.com/endpoint" + ); + assert_eq!(service.service_types().first().unwrap(), &ServiceType::AIP1); + } + + #[test] + fn test_serde_service_didcomm1() { + let service_didcomm1 = json!({ + "id": "service-0", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-agreement-1"], + "routingKeys": [ "did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-agreement-2"], + "accept": ["didcomm/aip2;env=rfc19"], + "serviceEndpoint": "https://example.com/endpoint" + }) + .to_string(); + let service = serde_json::from_str::(&service_didcomm1).unwrap(); + + assert_eq!(service.id().to_string(), "service-0"); + assert_eq!( + service.service_types().first().unwrap(), + &ServiceType::DIDCommV1 + ); + assert_eq!( + service.service_endpoint().to_string(), + "https://example.com/endpoint" + ); + + let recipient_keys = service.extra_field_recipient_keys().unwrap(); + assert_eq!(recipient_keys.len(), 1); + assert_eq!( + recipient_keys.first().unwrap(), + &ServiceKeyKind::Reference( + DidUrl::parse(String::from( + "did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-agreement-1" + )) + .unwrap() + ) + ); + + let routing_keys = service.extra_field_routing_keys().unwrap(); + assert_eq!(routing_keys.len(), 1); + assert_eq!( + routing_keys.first().unwrap(), + &ServiceKeyKind::Reference( + DidUrl::parse(String::from( + "did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-agreement-2" + )) + .unwrap() + ) + ); + + let accept = service.extra_field_accept().unwrap(); + assert_eq!(accept.len(), 1); + assert_eq!(accept.first().unwrap(), &ServiceAcceptType::DIDCommV1); + + let priority = service.extra_field_priority().unwrap(); + assert_eq!(priority, 0); + } + + #[test] + fn test_serde_service_didcomm2() { + let service_didcomm2 = json!({ + "id": "service-0", + "type": "DIDCommMessaging", + "accept": [ "didcomm/v2"], + "routingKeys": [], + "serviceEndpoint": "https://example.com/endpoint" + }) + .to_string(); + let service = serde_json::from_str::(&service_didcomm2).unwrap(); + + assert_eq!(service.id().to_string(), "service-0"); + assert_eq!( + service.service_types().first().unwrap(), + &ServiceType::DIDCommV2 + ); + assert_eq!( + service.service_endpoint().to_string(), + "https://example.com/endpoint" + ); + + let accept = service.extra_field_accept().unwrap(); + assert_eq!(accept.len(), 1); + assert_eq!(accept.first().unwrap(), &ServiceAcceptType::DIDCommV2); + } +} diff --git a/did_core/did_doc/src/schema/service/service_accept_type.rs b/did_core/did_doc/src/schema/service/service_accept_type.rs new file mode 100644 index 0000000000..a92c7ec067 --- /dev/null +++ b/did_core/did_doc/src/schema/service/service_accept_type.rs @@ -0,0 +1,57 @@ +use std::fmt::Display; + +use serde::{Deserialize, Deserializer, Serialize}; + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub enum ServiceAcceptType { + DIDCommV1, + DIDCommV2, + Other(String), +} + +impl From<&str> for ServiceAcceptType { + fn from(s: &str) -> Self { + match s { + "didcomm/aip2;env=rfc19" => ServiceAcceptType::DIDCommV1, + "didcomm/v2" => ServiceAcceptType::DIDCommV2, + _ => ServiceAcceptType::Other(s.to_string()), + } + } +} + +impl Display for ServiceAcceptType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ServiceAcceptType::DIDCommV1 => write!(f, "didcomm/aip2;env=rfc19"), + ServiceAcceptType::DIDCommV2 => write!(f, "didcomm/v2"), + ServiceAcceptType::Other(other) => write!(f, "{}", other), + } + } +} + +impl<'de> Deserialize<'de> for ServiceAcceptType { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + match s.as_str() { + "didcomm/aip2;env=rfc19" => Ok(ServiceAcceptType::DIDCommV1), + "didcomm/v2" => Ok(ServiceAcceptType::DIDCommV2), + _ => Ok(ServiceAcceptType::Other(s)), + } + } +} + +impl Serialize for ServiceAcceptType { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + ServiceAcceptType::DIDCommV1 => serializer.serialize_str("didcomm/aip2;env=rfc19"), + ServiceAcceptType::DIDCommV2 => serializer.serialize_str("didcomm/v2"), + ServiceAcceptType::Other(other) => serializer.serialize_str(other), + } + } +} diff --git a/did_core/did_doc/src/schema/service/service_key_kind.rs b/did_core/did_doc/src/schema/service/service_key_kind.rs new file mode 100644 index 0000000000..9f16100a89 --- /dev/null +++ b/did_core/did_doc/src/schema/service/service_key_kind.rs @@ -0,0 +1,23 @@ +use std::fmt::Display; + +use did_key::DidKey; +use did_parser::DidUrl; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(untagged)] +pub enum ServiceKeyKind { + DidKey(DidKey), + Reference(DidUrl), + Value(String), +} + +impl Display for ServiceKeyKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ServiceKeyKind::Reference(did_url) => write!(f, "{}", did_url), + ServiceKeyKind::Value(value) => write!(f, "{}", value), + ServiceKeyKind::DidKey(did_key) => write!(f, "{}", did_key), + } + } +} diff --git a/did_core/did_doc_sov/src/extra_fields/aip1.rs b/did_core/did_doc/src/schema/service/typed/aip1.rs similarity index 76% rename from did_core/did_doc_sov/src/extra_fields/aip1.rs rename to did_core/did_doc/src/schema/service/typed/aip1.rs index 4f1d13ee57..d2ec34348f 100644 --- a/did_core/did_doc_sov/src/extra_fields/aip1.rs +++ b/did_core/did_doc/src/schema/service/typed/aip1.rs @@ -1,6 +1,7 @@ use display_as_json::Display; use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Display)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Display, TypedBuilder)] #[serde(deny_unknown_fields)] pub struct ExtraFieldsAIP1 {} diff --git a/did_core/did_doc/src/schema/service/typed/didcommv1.rs b/did_core/did_doc/src/schema/service/typed/didcommv1.rs new file mode 100644 index 0000000000..5408c88a4d --- /dev/null +++ b/did_core/did_doc/src/schema/service/typed/didcommv1.rs @@ -0,0 +1,123 @@ +use std::collections::HashMap; + +use display_as_json::Display; +use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; +use url::Url; + +use crate::{ + error::DidDocumentBuilderError, + schema::{ + service::{ + service_accept_type::ServiceAcceptType, + service_key_kind::ServiceKeyKind, + typed::{ServiceType, TypedService}, + Service, + }, + types::uri::Uri, + utils::OneOrList, + }, +}; + +#[derive(Serialize, Clone, Debug, PartialEq)] +pub struct ServiceDidCommV1 { + #[serde(flatten)] + service: TypedService, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Display, TypedBuilder)] +#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] +pub struct ExtraFieldsDidCommV1 { + priority: u32, + recipient_keys: Vec, + routing_keys: Vec, + #[serde(default)] + accept: Vec, +} + +impl ServiceDidCommV1 { + pub fn new( + id: Uri, + service_endpoint: Url, + priority: u32, + recipient_keys: Vec, + routing_keys: Vec, + ) -> Self { + let extra = ExtraFieldsDidCommV1::builder() + .priority(priority) + .recipient_keys(recipient_keys) + .routing_keys(routing_keys) + .accept(vec![ServiceAcceptType::DIDCommV1]) + .build(); + Self { + service: TypedService:: { + id, + service_type: ServiceType::DIDCommV1, + service_endpoint, + extra, + }, + } + } + + pub fn id(&self) -> &Uri { + self.service.id() + } + + pub fn service_endpoint(&self) -> Url { + self.service.service_endpoint().clone() + } + + pub fn extra(&self) -> &ExtraFieldsDidCommV1 { + self.service.extra() + } +} + +impl TryFrom for Service { + type Error = DidDocumentBuilderError; + + fn try_from(did_comm_service: ServiceDidCommV1) -> Result { + let mut extra_fields = HashMap::new(); + extra_fields.insert( + "priority".to_string(), + serde_json::Value::from(did_comm_service.extra().priority()), + ); + extra_fields.insert( + "recipientKeys".to_string(), + serde_json::to_value(did_comm_service.extra().recipient_keys())?, + ); + extra_fields.insert( + "routingKeys".to_string(), + serde_json::to_value(did_comm_service.extra().routing_keys())?, + ); + extra_fields.insert( + "accept".to_string(), + serde_json::to_value(did_comm_service.extra().accept())?, + ); + + Ok(Service::new( + did_comm_service.id().clone(), + did_comm_service.service_endpoint(), + OneOrList::One(ServiceType::DIDCommV1), + extra_fields, + )) + } +} + +impl ExtraFieldsDidCommV1 { + pub fn priority(&self) -> u32 { + self.priority + } + + pub fn recipient_keys(&self) -> &[ServiceKeyKind] { + self.recipient_keys.as_ref() + } + + pub fn routing_keys(&self) -> &[ServiceKeyKind] { + self.routing_keys.as_ref() + } + + pub fn accept(&self) -> &[ServiceAcceptType] { + self.accept.as_ref() + } +} diff --git a/did_core/did_doc/src/schema/service/typed/didcommv2.rs b/did_core/did_doc/src/schema/service/typed/didcommv2.rs new file mode 100644 index 0000000000..1f0bcdbbd1 --- /dev/null +++ b/did_core/did_doc/src/schema/service/typed/didcommv2.rs @@ -0,0 +1,73 @@ +use display_as_json::Display; +use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; +use url::Url; + +use crate::schema::{ + service::{ + service_accept_type::ServiceAcceptType, + service_key_kind::ServiceKeyKind, + typed::{ServiceType, TypedService}, + }, + types::uri::Uri, +}; + +#[derive(Serialize, Clone, Debug, PartialEq)] +pub struct ServiceDidCommV2 { + #[serde(flatten)] + service: TypedService, +} + +impl ServiceDidCommV2 { + pub fn new( + id: Uri, + service_endpoint: Url, + routing_keys: Vec, + accept: Vec, + ) -> Self { + let extra: ExtraFieldsDidCommV2 = ExtraFieldsDidCommV2::builder() + .routing_keys(routing_keys) + .accept(accept) + .build(); + Self { + service: TypedService:: { + id, + service_type: ServiceType::DIDCommV2, + service_endpoint, + extra, + }, + } + } + + pub fn id(&self) -> &Uri { + self.service.id() + } + + pub fn service_endpoint(&self) -> Url { + self.service.service_endpoint().clone() + } + + pub fn extra(&self) -> &ExtraFieldsDidCommV2 { + self.service.extra() + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Display, TypedBuilder)] +#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] +pub struct ExtraFieldsDidCommV2 { + #[serde(skip_serializing_if = "Vec::is_empty")] + routing_keys: Vec, + #[serde(default)] + accept: Vec, +} + +impl ExtraFieldsDidCommV2 { + pub fn routing_keys(&self) -> &[ServiceKeyKind] { + self.routing_keys.as_ref() + } + + pub fn accept(&self) -> &[ServiceAcceptType] { + self.accept.as_ref() + } +} diff --git a/did_core/did_doc_sov/src/extra_fields/legacy.rs b/did_core/did_doc/src/schema/service/typed/legacy.rs similarity index 61% rename from did_core/did_doc_sov/src/extra_fields/legacy.rs rename to did_core/did_doc/src/schema/service/typed/legacy.rs index 6947989070..af97658857 100644 --- a/did_core/did_doc_sov/src/extra_fields/legacy.rs +++ b/did_core/did_doc/src/schema/service/typed/legacy.rs @@ -1,26 +1,27 @@ use display_as_json::Display; use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; -use super::KeyKind; +use crate::schema::service::service_key_kind::ServiceKeyKind; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Display)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Display, TypedBuilder)] #[serde(rename_all = "camelCase")] #[serde(deny_unknown_fields)] pub struct ExtraFieldsLegacy { #[serde(default)] priority: u32, #[serde(default)] - recipient_keys: Vec, + recipient_keys: Vec, #[serde(default)] - routing_keys: Vec, + routing_keys: Vec, } impl ExtraFieldsLegacy { - pub fn recipient_keys(&self) -> &[KeyKind] { + pub fn recipient_keys(&self) -> &[ServiceKeyKind] { self.recipient_keys.as_ref() } - pub fn routing_keys(&self) -> &[KeyKind] { + pub fn routing_keys(&self) -> &[ServiceKeyKind] { self.routing_keys.as_ref() } diff --git a/did_core/did_doc/src/schema/service/typed/mod.rs b/did_core/did_doc/src/schema/service/typed/mod.rs new file mode 100644 index 0000000000..795a820cfe --- /dev/null +++ b/did_core/did_doc/src/schema/service/typed/mod.rs @@ -0,0 +1,171 @@ +pub mod aip1; +pub mod didcommv1; +pub mod didcommv2; +pub mod legacy; + +use std::{fmt::Display, str::FromStr}; + +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use url::Url; + +use crate::{error::DidDocumentBuilderError, schema::types::uri::Uri}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub(crate) struct TypedService { + id: Uri, + #[serde(rename = "type")] + service_type: ServiceType, + service_endpoint: Url, + #[serde(flatten)] + extra: E, +} + +impl TypedService { + pub fn id(&self) -> &Uri { + &self.id + } + + pub fn service_endpoint(&self) -> &Url { + &self.service_endpoint + } + + pub fn extra(&self) -> &E { + &self.extra + } +} + +const SERVICE_TYPE_AIP1: &str = "endpoint"; +const SERVICE_TYPE_DIDCOMMV1: &str = "did-communication"; +const SERVICE_TYPE_DIDCOMMV2: &str = "DIDCommMessaging"; +const SERVICE_TYPE_LEGACY: &str = "IndyAgent"; + +#[derive(Clone, Debug, PartialEq)] +pub enum ServiceType { + AIP1, + DIDCommV1, + DIDCommV2, + Legacy, + Other(String), +} + +impl FromStr for ServiceType { + type Err = DidDocumentBuilderError; + + fn from_str(s: &str) -> Result { + match s { + SERVICE_TYPE_AIP1 => Ok(ServiceType::AIP1), + SERVICE_TYPE_DIDCOMMV1 => Ok(ServiceType::DIDCommV1), + SERVICE_TYPE_DIDCOMMV2 => Ok(ServiceType::DIDCommV2), + SERVICE_TYPE_LEGACY => Ok(ServiceType::Legacy), + _ => Ok(ServiceType::Other(s.to_owned())), + } + } +} + +impl<'de> Deserialize<'de> for ServiceType { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + s.parse().map_err(de::Error::custom) + } +} + +impl Display for ServiceType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ServiceType::AIP1 => write!(f, "endpoint"), + ServiceType::DIDCommV1 => write!(f, "did-communication"), + // Interop note: AFJ useses DIDComm, Acapy uses DIDCommMessaging + // Not matching spec: + // * did:sov method - https://sovrin-foundation.github.io/sovrin/spec/did-method-spec-template.html#crud-operation-definitions + // Matching spec: + // * did:peer method - https://identity.foundation/peer-did-method-spec/#multi-key-creation + // * did core - https://www.w3.org/TR/did-spec-registries/#didcommmessaging + // * didcommv2 - https://identity.foundation/didcomm-messaging/spec/#service-endpoint + ServiceType::DIDCommV2 => write!(f, "DIDCommMessaging"), + ServiceType::Legacy => write!(f, "IndyAgent"), + ServiceType::Other(other) => write!(f, "{}", other), + } + } +} + +impl Serialize for ServiceType { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + ServiceType::AIP1 => serializer.serialize_str(SERVICE_TYPE_AIP1), + ServiceType::DIDCommV1 => serializer.serialize_str(SERVICE_TYPE_DIDCOMMV1), + ServiceType::DIDCommV2 => serializer.serialize_str(SERVICE_TYPE_DIDCOMMV2), + ServiceType::Legacy => serializer.serialize_str(SERVICE_TYPE_LEGACY), + ServiceType::Other(ref value) => serializer.serialize_str(value), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_service_type_serialize() { + let service_type = ServiceType::AIP1; + let serialized = serde_json::to_string(&service_type).unwrap(); + assert_eq!(serialized, "\"endpoint\""); + + let service_type = ServiceType::DIDCommV1; + let serialized = serde_json::to_string(&service_type).unwrap(); + assert_eq!(serialized, "\"did-communication\""); + + let service_type = ServiceType::DIDCommV2; + let serialized = serde_json::to_string(&service_type).unwrap(); + assert_eq!(serialized, "\"DIDCommMessaging\""); + + let service_type = ServiceType::Legacy; + let serialized = serde_json::to_string(&service_type).unwrap(); + assert_eq!(serialized, "\"IndyAgent\""); + + let service_type = ServiceType::Other("foobar".to_string()); + let serialized = serde_json::to_string(&service_type).unwrap(); + assert_eq!(serialized, "\"foobar\""); + } + + #[test] + fn test_service_type_deserialize() { + let deserialized: ServiceType = serde_json::from_str("\"endpoint\"").unwrap(); + assert_eq!(deserialized, ServiceType::AIP1); + + let deserialized: ServiceType = serde_json::from_str("\"did-communication\"").unwrap(); + assert_eq!(deserialized, ServiceType::DIDCommV1); + + let deserialized: ServiceType = serde_json::from_str("\"DIDCommMessaging\"").unwrap(); + assert_eq!(deserialized, ServiceType::DIDCommV2); + + let deserialized: ServiceType = serde_json::from_str("\"IndyAgent\"").unwrap(); + assert_eq!(deserialized, ServiceType::Legacy); + + let deserialized: ServiceType = serde_json::from_str("\"foobar\"").unwrap(); + assert_eq!(deserialized, ServiceType::Other("foobar".to_string())); + } + + #[test] + fn test_service_from_unquoted_string() { + let service = ServiceType::from_str("endpoint").unwrap(); + assert_eq!(service, ServiceType::AIP1); + + let service = ServiceType::from_str("did-communication").unwrap(); + assert_eq!(service, ServiceType::DIDCommV1); + + let service = ServiceType::from_str("DIDCommMessaging").unwrap(); + assert_eq!(service, ServiceType::DIDCommV2); + + let service = ServiceType::from_str("IndyAgent").unwrap(); + assert_eq!(service, ServiceType::Legacy); + + let service = ServiceType::from_str("foobar").unwrap(); + assert_eq!(service, ServiceType::Other("foobar".to_string())); + } +} diff --git a/did_core/did_doc/src/schema/types/jsonwebkey.rs b/did_core/did_doc/src/schema/types/jsonwebkey.rs index ce33744901..34bc41a4a2 100644 --- a/did_core/did_doc/src/schema/types/jsonwebkey.rs +++ b/did_core/did_doc/src/schema/types/jsonwebkey.rs @@ -1,13 +1,30 @@ use std::{ collections::HashMap, + error::Error, fmt::{self, Display, Formatter}, str::FromStr, }; use serde::{Deserialize, Serialize}; use serde_json::Value; +use thiserror::Error; -use crate::error::DidDocumentBuilderError; +#[derive(Debug, Error)] +pub struct JsonWebKeyError { + reason: &'static str, + #[source] + source: Box, +} + +impl Display for JsonWebKeyError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "JsonWebKeyError, reason: {}, source: {}", + self.reason, self.source + ) + } +} #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] // TODO: Introduce proper custom type @@ -24,17 +41,24 @@ pub struct JsonWebKey { } impl JsonWebKey { - pub fn new(jwk: &str) -> Result { - Ok(serde_json::from_str(jwk)?) + // todo: More future-proof way would be creating custom error type, but seems as overkill atm? + pub fn new(jwk: &str) -> Result { + serde_json::from_str(jwk).map_err(|err| JsonWebKeyError { + reason: "Parsing JWK failed", + source: Box::new(err), + }) } - pub fn to_vec(&self) -> Result, DidDocumentBuilderError> { - serde_json::to_vec(self).map_err(|e| e.into()) + pub fn to_vec(&self) -> Result, JsonWebKeyError> { + serde_json::to_vec(self).map_err(|err| JsonWebKeyError { + reason: "Serializing JWK to vector failed", + source: Box::new(err), + }) } } impl FromStr for JsonWebKey { - type Err = DidDocumentBuilderError; + type Err = JsonWebKeyError; fn from_str(s: &str) -> Result { Self::new(s) diff --git a/did_core/did_doc/src/schema/types/mod.rs b/did_core/did_doc/src/schema/types/mod.rs index cb528f76f1..c93d485d7c 100644 --- a/did_core/did_doc/src/schema/types/mod.rs +++ b/did_core/did_doc/src/schema/types/mod.rs @@ -1,4 +1,3 @@ pub mod jsonwebkey; pub mod multibase; pub mod uri; -pub mod url; diff --git a/did_core/did_doc/src/schema/types/multibase.rs b/did_core/did_doc/src/schema/types/multibase.rs index fdc5d642e5..2509411d68 100644 --- a/did_core/did_doc/src/schema/types/multibase.rs +++ b/did_core/did_doc/src/schema/types/multibase.rs @@ -1,12 +1,29 @@ use std::{ + error::Error, fmt::{self, Display, Formatter}, str::FromStr, }; use multibase::{decode, Base}; use serde::{Deserialize, Serialize}; +use thiserror::Error; -use crate::error::DidDocumentBuilderError; +#[derive(Debug, Error)] +pub struct MultibaseWrapperError { + reason: &'static str, + #[source] + source: Box, +} + +impl Display for MultibaseWrapperError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "MultibaseWrapperError, reason: {}, source: {}", + self.reason, self.source + ) + } +} // https://datatracker.ietf.org/doc/html/draft-multiformats-multibase-07 #[derive(Clone, Debug, PartialEq)] @@ -16,9 +33,10 @@ pub struct Multibase { } impl Multibase { - pub fn new(multibase: String) -> Result { - let (base, bytes) = decode(multibase).map_err(|err| { - DidDocumentBuilderError::InvalidInput(format!("Invalid multibase key: {}", err)) + pub fn new(multibase: String) -> Result { + let (base, bytes) = decode(multibase).map_err(|err| MultibaseWrapperError { + reason: "Decoding multibase value failed", + source: Box::new(err), })?; Ok(Self { base, bytes }) } @@ -52,7 +70,7 @@ impl<'de> Deserialize<'de> for Multibase { } impl FromStr for Multibase { - type Err = DidDocumentBuilderError; + type Err = MultibaseWrapperError; fn from_str(s: &str) -> Result { Self::new(s.to_string()) @@ -118,7 +136,14 @@ mod tests { #[test] fn test_multibase_from_str_invalid() { let multibase = "invalidmultibasekey".parse::(); - assert!(multibase.is_err()); + let err = multibase.expect_err("Error was expected."); + assert!(err + .source() + .expect("Error was expected to has source set up.") + .is::()); + assert!(err + .to_string() + .contains("Decoding multibase value failed, source: ")); } #[test] diff --git a/did_core/did_doc/src/schema/types/uri.rs b/did_core/did_doc/src/schema/types/uri.rs index ef1d0b518e..494ee6c60f 100644 --- a/did_core/did_doc/src/schema/types/uri.rs +++ b/did_core/did_doc/src/schema/types/uri.rs @@ -4,22 +4,32 @@ use std::{ }; use serde::{Deserialize, Serialize}; - -use crate::error::DidDocumentBuilderError; +use thiserror::Error; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] pub struct Uri(uniresid::Uri); +#[derive(Debug, Error)] +pub struct UriWrapperError { + reason: uniresid::Error, +} + +impl Display for UriWrapperError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "UriWrapperError: {}", self.reason) + } +} + impl Uri { - pub fn new(uri: &str) -> Result { - Ok(Self(uniresid::Uri::try_from(uri).map_err(|e| { - DidDocumentBuilderError::InvalidInput(format!("Invalid URI: {}", e)) - })?)) + pub fn new(uri: &str) -> Result { + Ok(Self( + uniresid::Uri::try_from(uri).map_err(|e| UriWrapperError { reason: e })?, + )) } } impl FromStr for Uri { - type Err = DidDocumentBuilderError; + type Err = UriWrapperError; fn from_str(s: &str) -> Result { Self::new(s) diff --git a/did_core/did_doc/src/schema/types/url.rs b/did_core/did_doc/src/schema/types/url.rs deleted file mode 100644 index f6578fec59..0000000000 --- a/did_core/did_doc/src/schema/types/url.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::{fmt::Display, str::FromStr}; - -use serde::{Deserialize, Serialize}; -use url::Url as UrlDep; - -use crate::error::DidDocumentBuilderError; - -// TODO: This was bad idea, get rid of it -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct Url(UrlDep); - -impl Url { - pub fn new(url: &str) -> Result { - Ok(Self(UrlDep::parse(url)?)) - } -} - -impl TryFrom<&str> for Url { - type Error = DidDocumentBuilderError; - - fn try_from(value: &str) -> Result { - Ok(Self(UrlDep::parse(value)?)) - } -} - -impl FromStr for Url { - type Err = DidDocumentBuilderError; - - fn from_str(s: &str) -> Result { - Ok(Self(UrlDep::parse(s)?)) - } -} - -impl From for Url { - fn from(url: UrlDep) -> Self { - Self(url) - } -} - -impl From for UrlDep { - fn from(url: Url) -> Self { - url.0 - } -} - -impl AsRef for Url { - fn as_ref(&self) -> &str { - self.0.as_str() - } -} - -impl Display for Url { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.as_str().fmt(f) - } -} diff --git a/did_core/did_doc/src/schema/utils/error.rs b/did_core/did_doc/src/schema/utils/error.rs new file mode 100644 index 0000000000..a8d7640eba --- /dev/null +++ b/did_core/did_doc/src/schema/utils/error.rs @@ -0,0 +1,20 @@ +use std::fmt::{self, Display, Formatter}; + +use thiserror::Error; + +#[derive(Debug, Error)] +pub struct DidDocumentLookupError { + reason: String, +} + +impl DidDocumentLookupError { + pub fn new(reason: String) -> Self { + Self { reason } + } +} + +impl Display for DidDocumentLookupError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "DiddocLookupError: {}", self.reason) + } +} diff --git a/did_core/did_doc/src/schema/utils/mod.rs b/did_core/did_doc/src/schema/utils/mod.rs index 088ea8bda2..99c2120fc6 100644 --- a/did_core/did_doc/src/schema/utils/mod.rs +++ b/did_core/did_doc/src/schema/utils/mod.rs @@ -1,7 +1,17 @@ +pub mod error; + use std::fmt::{Debug, Display}; use serde::{Deserialize, Serialize}; +use crate::schema::{ + did_doc::DidDocument, + service::{typed::ServiceType, Service}, + types::uri::Uri, + utils::error::DidDocumentLookupError, + verification_method::{VerificationMethod, VerificationMethodKind, VerificationMethodType}, +}; + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(untagged)] pub enum OneOrList { @@ -9,6 +19,15 @@ pub enum OneOrList { List(Vec), } +impl OneOrList { + pub fn first(&self) -> Option { + match self { + OneOrList::One(s) => Some(s.clone()), + OneOrList::List(s) => s.first().cloned(), + } + } +} + impl Display for OneOrList { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -17,3 +36,116 @@ impl Display for OneOrList { } } } + +impl DidDocument { + pub fn get_key_agreement_of_type( + &self, + key_types: &[VerificationMethodType], + ) -> Result { + for verification_method_kind in self.key_agreement() { + let verification_method = match verification_method_kind { + VerificationMethodKind::Resolved(verification_method) => verification_method, + VerificationMethodKind::Resolvable(reference) => { + match self.dereference_key(reference) { + None => { + return Err(DidDocumentLookupError::new(format!( + "Unable to resolve key agreement key by reference: {}", + reference + ))) + } + Some(verification_method) => verification_method, + } + } + }; + for key_type in key_types { + if verification_method.verification_method_type() == key_type { + return Ok(verification_method.clone()); + } + } + } + Err(DidDocumentLookupError::new( + "No supported key_agreement keys have been found".to_string(), + )) + } + + pub fn get_service_of_type( + &self, + service_type: &ServiceType, + ) -> Result { + self.service() + .iter() + .find(|service| service.service_types().contains(service_type)) + .cloned() + .ok_or(DidDocumentLookupError::new(format!( + "Failed to look up service object by type {}", + service_type + ))) + } + + pub fn get_service_by_id(&self, id: &Uri) -> Result { + self.service() + .iter() + .find(|service| service.id() == id) + .cloned() + .ok_or(DidDocumentLookupError::new(format!( + "Failed to look up service object by id {}", + id + ))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::schema::verification_method::VerificationMethodType; + + const DID_DOC: &str = r##" + { + "@context": [ + "https://w3.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1" + ], + "id": "did:web:did-actor-alice", + "alsoKnownAs": [ + "https://example.com/user-profile/123" + ], + "keyAgreement": [ + { + "id": "#foo", + "type": "Bls12381G2Key2020", + "controller": "did:web:did-actor-alice", + "publicKeyBase58": "CaSHXEvLKS6SfN9aBfkVGBpp15jSnaHazqHgLHp8KZ3Y" + }, + { + "id": "#bar", + "type": "X25519KeyAgreementKey2020", + "controller": "did:web:did-actor-alice", + "publicKeyBase58": "CaSHXEvLKS6SfN9aBfkVGBpp15jSnaHazqHgLHp8KZ3Y" + } + ] + } + "##; + + #[test] + fn should_resolve_key_agreement() { + let did_document: DidDocument = serde_json::from_str(DID_DOC).unwrap(); + let methods = &vec![ + VerificationMethodType::Ed25519VerificationKey2020, + VerificationMethodType::X25519KeyAgreementKey2020, + ]; + let key = did_document.get_key_agreement_of_type(methods).unwrap(); + assert_eq!(key.id().to_string(), "#bar") + } + + #[test] + fn should_not_resolve_key_agreement() { + let did_document: DidDocument = serde_json::from_str(DID_DOC).unwrap(); + let methods = &vec![VerificationMethodType::Bls12381G1Key2020]; + let err = did_document + .get_key_agreement_of_type(methods) + .expect_err("expected error"); + assert!(err + .to_string() + .contains("No supported key_agreement keys have been found")) + } +} diff --git a/did_core/did_doc/src/schema/verification_method/error.rs b/did_core/did_doc/src/schema/verification_method/error.rs new file mode 100644 index 0000000000..235d175e09 --- /dev/null +++ b/did_core/did_doc/src/schema/verification_method/error.rs @@ -0,0 +1,92 @@ +use std::{ + error::Error, + fmt, + fmt::{Display, Formatter}, +}; + +use thiserror::Error; + +use crate::schema::types::{jsonwebkey::JsonWebKeyError, multibase::MultibaseWrapperError}; + +#[derive(Debug, Error)] +pub struct KeyDecodingError { + reason: &'static str, + #[source] + source: Option>, +} + +impl KeyDecodingError { + pub fn new(reason: &'static str) -> Self { + KeyDecodingError { + reason, + source: None, + } + } +} + +impl Display for KeyDecodingError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match &self.source { + Some(source) => write!( + f, + "KeyDecodingError, reason: {}, source: {}", + self.reason, source + ), + None => write!(f, "KeyDecodingError, reason: {}", self.reason), + } + } +} + +impl From for KeyDecodingError { + fn from(error: pem::PemError) -> Self { + KeyDecodingError { + reason: "Failed to decode PEM", + source: Some(Box::new(error)), + } + } +} + +impl From for KeyDecodingError { + fn from(error: bs58::decode::Error) -> Self { + KeyDecodingError { + reason: "Failed to decode base58", + source: Some(Box::new(error)), + } + } +} + +impl From for KeyDecodingError { + fn from(error: base64::DecodeError) -> Self { + KeyDecodingError { + reason: "Failed to decode base64", + source: Some(Box::new(error)), + } + } +} + +impl From for KeyDecodingError { + fn from(error: hex::FromHexError) -> Self { + KeyDecodingError { + reason: "Failed to decode hex value", + source: Some(Box::new(error)), + } + } +} + +impl From for KeyDecodingError { + fn from(error: MultibaseWrapperError) -> Self { + KeyDecodingError { + reason: "Failed to decode multibase value", + source: Some(Box::new(error)), + } + } +} + +impl From for KeyDecodingError { + fn from(error: JsonWebKeyError) -> Self { + KeyDecodingError { + reason: "Failed to decode JWK", + source: Some(Box::new(error)), + } + } +} diff --git a/did_core/did_doc/src/schema/verification_method/mod.rs b/did_core/did_doc/src/schema/verification_method/mod.rs index df5ab7c02f..013e1078fc 100644 --- a/did_core/did_doc/src/schema/verification_method/mod.rs +++ b/did_core/did_doc/src/schema/verification_method/mod.rs @@ -1,4 +1,5 @@ -mod public_key; +pub mod error; +pub mod public_key; mod verification_method_kind; mod verification_method_type; diff --git a/did_core/did_doc/src/schema/verification_method/public_key.rs b/did_core/did_doc/src/schema/verification_method/public_key.rs index 96238b7f10..cc05514fb9 100644 --- a/did_core/did_doc/src/schema/verification_method/public_key.rs +++ b/did_core/did_doc/src/schema/verification_method/public_key.rs @@ -3,9 +3,9 @@ use std::str::FromStr; use base64::{engine::general_purpose, Engine}; use serde::{Deserialize, Serialize}; -use crate::{ - error::DidDocumentBuilderError, - schema::types::{jsonwebkey::JsonWebKey, multibase::Multibase}, +use crate::schema::{ + types::{jsonwebkey::JsonWebKey, multibase::Multibase}, + verification_method::error::KeyDecodingError, }; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] @@ -29,7 +29,7 @@ pub enum PublicKeyField { } impl PublicKeyField { - pub fn key_decoded(&self) -> Result, DidDocumentBuilderError> { + pub fn key_decoded(&self) -> Result, KeyDecodingError> { match self { PublicKeyField::Multibase { public_key_multibase, @@ -37,7 +37,7 @@ impl PublicKeyField { let multibase = Multibase::from_str(public_key_multibase)?; Ok(multibase.as_ref().to_vec()) } - PublicKeyField::Jwk { public_key_jwk } => public_key_jwk.to_vec(), + PublicKeyField::Jwk { public_key_jwk } => Ok(public_key_jwk.to_vec()?), PublicKeyField::Base58 { public_key_base58 } => { Ok(bs58::decode(public_key_base58).into_vec()?) } @@ -48,20 +48,22 @@ impl PublicKeyField { PublicKeyField::Pem { public_key_pem } => { Ok(pem::parse(public_key_pem.as_bytes())?.contents().to_vec()) } - PublicKeyField::Pgp { public_key_pgp: _ } => Err( - DidDocumentBuilderError::UnsupportedPublicKeyField("publicKeyPgp"), - ), + PublicKeyField::Pgp { public_key_pgp: _ } => Err(KeyDecodingError::new( + "PGP public key decoding not supported", + )), } } // TODO: Other formats - pub fn base58(&self) -> Result { + pub fn base58(&self) -> Result { Ok(bs58::encode(self.key_decoded()?).into_string()) } } #[cfg(test)] mod tests { + use std::error::Error; + use super::*; static PUBLIC_KEY_MULTIBASE: &str = "z6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc"; @@ -115,4 +117,32 @@ mod tests { PUBLIC_KEY_BYTES.to_vec() ); } + + #[test] + fn test_b58_fails() { + let public_key_field = PublicKeyField::Base58 { + public_key_base58: "abcdefghijkl".to_string(), + }; + let err = public_key_field.key_decoded().expect_err("Expected error"); + println!("Error: {}", err); + assert!(err + .source() + .expect("Error was expected to has source set up.") + .is::()); + assert!(err.to_string().contains("Failed to decode base58")); + } + + #[test] + fn test_pem_fails() { + let public_key_field = PublicKeyField::Pem { + public_key_pem: "abcdefghijkl".to_string(), + }; + let err = public_key_field.key_decoded().unwrap_err(); + println!("Error: {}", err); + assert!(err + .source() + .expect("Error was expected to has source set up.") + .is::()); + assert!(err.to_string().contains("Failed to decode PEM")); + } } diff --git a/did_core/did_doc/tests/serde.rs b/did_core/did_doc/tests/serde.rs deleted file mode 100644 index d7c6aeb3c9..0000000000 --- a/did_core/did_doc/tests/serde.rs +++ /dev/null @@ -1,195 +0,0 @@ -use std::str::FromStr; - -use did_doc::schema::{ - did_doc::DidDocument, - types::{jsonwebkey::JsonWebKey, uri::Uri}, - verification_method::{VerificationMethod, VerificationMethodKind, VerificationMethodType}, -}; -use did_parser::{Did, DidUrl}; -use serde_json::Value; - -const VALID_DID_DOC_JSON: &str = r##" -{ - "@context": [ - "https://w3.org/ns/did/v1", - "https://w3id.org/security/suites/ed25519-2018/v1" - ], - "id": "did:web:did-actor-alice", - "alsoKnownAs": [ - "https://example.com/user-profile/123" - ], - "publicKey": [ - { - "id": "did:web:did-actor-alice#z6MkrmNwty5ajKtFqc1U48oL2MMLjWjartwc5sf2AihZwXDN", - "controller": "did:web:did-actor-alice", - "type": "Ed25519VerificationKey2018", - "publicKeyBase58": "DK7uJiq9PnPnj7AmNZqVBFoLuwTjT1hFPrk6LSjZ2JRz" - } - ], - "authentication": [ - "did:web:did-actor-alice#z6MkrmNwty5ajKtFqc1U48oL2MMLjWjartwc5sf2AihZwXDN" - ], - "assertionMethod": [ - "did:web:did-actor-alice#z6MkrmNwty5ajKtFqc1U48oL2MMLjWjartwc5sf2AihZwXDN" - ], - "capabilityDelegation": [ - "did:web:did-actor-alice#z6MkrmNwty5ajKtFqc1U48oL2MMLjWjartwc5sf2AihZwXDN" - ], - "capabilityInvocation": [ - "did:web:did-actor-alice#z6MkrmNwty5ajKtFqc1U48oL2MMLjWjartwc5sf2AihZwXDN" - ], - "verificationMethod": [ - { - "id": "#g1", - "controller": "did:web:did-actor-alice", - "type": "JsonWebKey2020", - "publicKeyJwk": { - "kty": "EC", - "crv": "BLS12381_G1", - "x": "hxF12gtsn9ju4-kJq2-nUjZQKVVWpcBAYX5VHnUZMDilClZsGuOaDjlXS8pFE1GG" - } - }, - { - "id": "#g2", - "controller": "did:web:did-actor-alice", - "type": "JsonWebKey2020", - "publicKeyJwk": { - "kty": "EC", - "crv": "BLS12381_G2", - "x": "l4MeBsn_OGa2OEDtHeHdq0TBC8sYh6QwoI7QsNtZk9oAru1OnGClaAPlMbvvs73EABDB6GjjzybbOHarkBmP6pon8H1VuMna0nkEYihZi8OodgdbwReDiDvWzZuXXMl-" - } - } - ], - "keyAgreement": [ - { - "id": "did:web:did-actor-alice#zC8GybikEfyNaausDA4mkT4egP7SNLx2T1d1kujLQbcP6h", - "type": "X25519KeyAgreementKey2019", - "controller": "did:web:did-actor-alice", - "publicKeyBase58": "CaSHXEvLKS6SfN9aBfkVGBpp15jSnaHazqHgLHp8KZ3Y" - } - ] -} -"##; - -#[test] -fn test_deserialization() { - let did_doc: DidDocument<()> = serde_json::from_str(VALID_DID_DOC_JSON).unwrap(); - - assert_eq!( - did_doc.id(), - &"did:web:did-actor-alice".to_string().try_into().unwrap() - ); - assert_eq!( - did_doc.also_known_as(), - vec![Uri::from_str("https://example.com/user-profile/123").unwrap()] - ); - - let controller: Did = "did:web:did-actor-alice".to_string().try_into().unwrap(); - - let pk_id = DidUrl::parse( - "did:web:did-actor-alice#z6MkrmNwty5ajKtFqc1U48oL2MMLjWjartwc5sf2AihZwXDN".to_string(), - ) - .unwrap(); - - let vm1_id = DidUrl::parse("#g1".to_string()).unwrap(); - let vm1 = VerificationMethod::builder( - vm1_id, - controller.clone(), - VerificationMethodType::JsonWebKey2020, - ) - .add_public_key_jwk( - JsonWebKey::from_str( - r#"{ - "kty": "EC", - "crv": "BLS12381_G1", - "x": "hxF12gtsn9ju4-kJq2-nUjZQKVVWpcBAYX5VHnUZMDilClZsGuOaDjlXS8pFE1GG" - }"#, - ) - .unwrap(), - ) - .build(); - - let vm2_id = DidUrl::parse("#g2".to_string()).unwrap(); - let vm2 = VerificationMethod::builder( - vm2_id, - controller.clone(), - VerificationMethodType::JsonWebKey2020, - ) - .add_public_key_jwk( - JsonWebKey::from_str( - r#"{ - "kty": "EC", - "crv": "BLS12381_G2", - "x": "l4MeBsn_OGa2OEDtHeHdq0TBC8sYh6QwoI7QsNtZk9oAru1OnGClaAPlMbvvs73EABDB6GjjzybbOHarkBmP6pon8H1VuMna0nkEYihZi8OodgdbwReDiDvWzZuXXMl-" - }"#, - ) - .unwrap(), - ) - .build(); - - assert_eq!(did_doc.verification_method().get(0).unwrap().clone(), vm1); - assert_eq!(did_doc.verification_method().get(1).unwrap().clone(), vm2); - - assert_eq!( - did_doc.authentication(), - &[VerificationMethodKind::Resolvable(pk_id.clone())] - ); - - assert_eq!( - did_doc.assertion_method(), - &[VerificationMethodKind::Resolvable(pk_id.clone())] - ); - - assert_eq!( - did_doc.capability_delegation(), - &[VerificationMethodKind::Resolvable(pk_id.clone())] - ); - - assert_eq!( - did_doc.capability_invocation(), - &[VerificationMethodKind::Resolvable(pk_id)] - ); - - assert_eq!( - did_doc.extra_field("publicKey").unwrap().clone(), - Value::Array(vec![Value::Object( - serde_json::from_str( - r#"{ - "id": "did:web:did-actor-alice#z6MkrmNwty5ajKtFqc1U48oL2MMLjWjartwc5sf2AihZwXDN", - "type": "Ed25519VerificationKey2018", - "controller": "did:web:did-actor-alice", - "publicKeyBase58": "DK7uJiq9PnPnj7AmNZqVBFoLuwTjT1hFPrk6LSjZ2JRz" - }"# - ) - .unwrap() - )]) - ); - - let ka1_id = DidUrl::parse( - "did:web:did-actor-alice#zC8GybikEfyNaausDA4mkT4egP7SNLx2T1d1kujLQbcP6h".to_string(), - ) - .unwrap(); - let ka1 = VerificationMethod::builder( - ka1_id, - controller, - VerificationMethodType::X25519KeyAgreementKey2019, - ) - .add_public_key_base58("CaSHXEvLKS6SfN9aBfkVGBpp15jSnaHazqHgLHp8KZ3Y".to_string()) - .build(); - - assert_eq!( - did_doc.key_agreement(), - &[VerificationMethodKind::Resolved(ka1)] - ); -} - -#[test] -fn test_serialization() { - let did_doc: DidDocument<()> = serde_json::from_str(VALID_DID_DOC_JSON).unwrap(); - - let serialized_json = serde_json::to_string(&did_doc).unwrap(); - - let original_json_value: DidDocument<()> = serde_json::from_str(VALID_DID_DOC_JSON).unwrap(); - let serialized_json_value: DidDocument<()> = serde_json::from_str(&serialized_json).unwrap(); - assert_eq!(serialized_json_value, original_json_value); -} diff --git a/did_core/did_doc_sov/Cargo.toml b/did_core/did_doc_sov/Cargo.toml deleted file mode 100644 index 4e52f772e0..0000000000 --- a/did_core/did_doc_sov/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "did_doc_sov" -version = "0.1.0" -edition = "2021" - -[dependencies] -base64 = "0.21.2" # TODO: Remove after transition to new DDO is done -did_doc = { path = "../did_doc" } -did_key = { path = "../did_methods/did_key" } -public_key = { path = "../public_key" } # TODO: Remove after transition to new DDO is done -serde = { version = "1.0.159", default-features = false, features = ["derive"] } -serde_json = "1.0.95" -thiserror = "1.0.40" -display_as_json = { path = "../../misc/display_as_json" } diff --git a/did_core/did_doc_sov/src/error.rs b/did_core/did_doc_sov/src/error.rs deleted file mode 100644 index 022dae7032..0000000000 --- a/did_core/did_doc_sov/src/error.rs +++ /dev/null @@ -1,15 +0,0 @@ -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum DidDocumentSovError { - #[error("Attempted to access empty collection: {0}")] - EmptyCollection(&'static str), - #[error("DID document builder error: {0}")] - DidDocumentBuilderError(#[from] did_doc::error::DidDocumentBuilderError), - #[error("Unexpected service type: {0}")] - UnexpectedServiceType(String), - #[error("Index out of bounds: {0}")] - IndexOutOfBounds(usize), - #[error("JSON error")] - JsonError(#[from] serde_json::Error), -} diff --git a/did_core/did_doc_sov/src/extra_fields/didcommv1.rs b/did_core/did_doc_sov/src/extra_fields/didcommv1.rs deleted file mode 100644 index ca2a5c0b32..0000000000 --- a/did_core/did_doc_sov/src/extra_fields/didcommv1.rs +++ /dev/null @@ -1,91 +0,0 @@ -use display_as_json::Display; -use serde::{Deserialize, Serialize}; - -use super::{AcceptType, KeyKind}; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Display)] -#[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] -pub struct ExtraFieldsDidCommV1 { - priority: u32, - recipient_keys: Vec, - routing_keys: Vec, - #[serde(default)] - accept: Vec, -} - -impl ExtraFieldsDidCommV1 { - pub fn builder() -> ExtraFieldsDidCommV1Builder { - ExtraFieldsDidCommV1Builder::default() - } - - pub fn priority(&self) -> u32 { - self.priority - } - - pub fn recipient_keys(&self) -> &[KeyKind] { - self.recipient_keys.as_ref() - } - - pub fn routing_keys(&self) -> &[KeyKind] { - self.routing_keys.as_ref() - } - - pub fn accept(&self) -> &[AcceptType] { - self.accept.as_ref() - } -} - -pub struct ExtraFieldsDidCommV1Builder { - priority: u32, - recipient_keys: Vec, - routing_keys: Vec, - accept: Vec, -} - -impl Default for ExtraFieldsDidCommV1Builder { - fn default() -> Self { - Self { - priority: 0, - recipient_keys: Vec::new(), - routing_keys: Vec::new(), - accept: vec![AcceptType::DIDCommV1], - } - } -} - -impl ExtraFieldsDidCommV1Builder { - pub fn set_priority(mut self, priority: u32) -> Self { - self.priority = priority; - self - } - - pub fn set_recipient_keys(mut self, recipient_keys: Vec) -> Self { - self.recipient_keys = recipient_keys; - self - } - - pub fn set_routing_keys(mut self, routing_keys: Vec) -> Self { - self.routing_keys = routing_keys; - self - } - - pub fn set_accept(mut self, accept: Vec) -> Self { - self.accept = accept; - self - } - - pub fn add_accept(mut self, accept: AcceptType) -> Self { - self.accept.push(accept); - self - } - - pub fn build(self) -> ExtraFieldsDidCommV1 { - ExtraFieldsDidCommV1 { - priority: self.priority, - recipient_keys: self.recipient_keys, - routing_keys: self.routing_keys, - accept: self.accept, - } - } -} diff --git a/did_core/did_doc_sov/src/extra_fields/didcommv2.rs b/did_core/did_doc_sov/src/extra_fields/didcommv2.rs deleted file mode 100644 index bef7cef365..0000000000 --- a/did_core/did_doc_sov/src/extra_fields/didcommv2.rs +++ /dev/null @@ -1,65 +0,0 @@ -use display_as_json::Display; -use serde::{Deserialize, Serialize}; - -use super::{AcceptType, KeyKind}; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, Display)] -#[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] -pub struct ExtraFieldsDidCommV2 { - routing_keys: Vec, - #[serde(default)] - accept: Vec, -} - -impl ExtraFieldsDidCommV2 { - pub fn builder() -> ExtraFieldsDidCommV2Builder { - ExtraFieldsDidCommV2Builder::default() - } - - pub fn accept(&self) -> &[AcceptType] { - self.accept.as_ref() - } - - pub fn routing_keys(&self) -> &[KeyKind] { - self.routing_keys.as_ref() - } -} - -pub struct ExtraFieldsDidCommV2Builder { - routing_keys: Vec, - accept: Vec, -} - -impl Default for ExtraFieldsDidCommV2Builder { - fn default() -> Self { - Self { - routing_keys: Vec::new(), - accept: vec![AcceptType::DIDCommV2], - } - } -} - -impl ExtraFieldsDidCommV2Builder { - pub fn set_routing_keys(mut self, routing_keys: Vec) -> Self { - self.routing_keys = routing_keys; - self - } - - pub fn set_accept(mut self, accept: Vec) -> Self { - self.accept = accept; - self - } - - pub fn add_accept(mut self, accept: AcceptType) -> Self { - self.accept.push(accept); - self - } - - pub fn build(self) -> ExtraFieldsDidCommV2 { - ExtraFieldsDidCommV2 { - routing_keys: self.routing_keys, - accept: self.accept, - } - } -} diff --git a/did_core/did_doc_sov/src/extra_fields/mod.rs b/did_core/did_doc_sov/src/extra_fields/mod.rs deleted file mode 100644 index 2ae0aaa7d7..0000000000 --- a/did_core/did_doc_sov/src/extra_fields/mod.rs +++ /dev/null @@ -1,150 +0,0 @@ -use std::fmt::Display; - -use did_doc::did_parser::DidUrl; -use did_key::DidKey; -use serde::{Deserialize, Deserializer, Serialize}; - -use crate::error::DidDocumentSovError; - -pub mod aip1; -pub mod didcommv1; -pub mod didcommv2; -pub mod legacy; - -#[derive(Clone, Debug, Eq, PartialEq, Hash)] -pub enum AcceptType { - DIDCommV1, - DIDCommV2, - Other(String), -} - -impl From<&str> for AcceptType { - fn from(s: &str) -> Self { - match s { - "didcomm/aip2;env=rfc19" => AcceptType::DIDCommV1, - "didcomm/v2" => AcceptType::DIDCommV2, - _ => AcceptType::Other(s.to_string()), - } - } -} - -impl Display for AcceptType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - AcceptType::DIDCommV1 => write!(f, "didcomm/aip2;env=rfc19"), - AcceptType::DIDCommV2 => write!(f, "didcomm/v2"), - AcceptType::Other(other) => write!(f, "{}", other), - } - } -} - -impl<'de> Deserialize<'de> for AcceptType { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - match s.as_str() { - "didcomm/aip2;env=rfc19" => Ok(AcceptType::DIDCommV1), - "didcomm/v2" => Ok(AcceptType::DIDCommV2), - _ => Ok(AcceptType::Other(s)), - } - } -} - -impl Serialize for AcceptType { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match self { - AcceptType::DIDCommV1 => serializer.serialize_str("didcomm/aip2;env=rfc19"), - AcceptType::DIDCommV2 => serializer.serialize_str("didcomm/v2"), - AcceptType::Other(other) => serializer.serialize_str(other), - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(untagged)] -pub enum KeyKind { - DidKey(DidKey), - Reference(DidUrl), - Value(String), -} - -impl Display for KeyKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - KeyKind::Reference(did_url) => write!(f, "{}", did_url), - KeyKind::Value(value) => write!(f, "{}", value), - KeyKind::DidKey(did_key) => write!(f, "{}", did_key), - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, display_as_json::Display)] -#[serde(untagged)] -pub enum ExtraFieldsSov { - DIDCommV1(didcommv1::ExtraFieldsDidCommV1), - DIDCommV2(didcommv2::ExtraFieldsDidCommV2), - AIP1(aip1::ExtraFieldsAIP1), - Legacy(legacy::ExtraFieldsLegacy), -} - -impl Default for ExtraFieldsSov { - fn default() -> Self { - ExtraFieldsSov::AIP1(aip1::ExtraFieldsAIP1::default()) - } -} - -impl ExtraFieldsSov { - pub fn recipient_keys(&self) -> Result<&[KeyKind], DidDocumentSovError> { - match self { - ExtraFieldsSov::DIDCommV1(extra) => Ok(extra.recipient_keys()), - ExtraFieldsSov::Legacy(extra) => Ok(extra.recipient_keys()), - ExtraFieldsSov::AIP1(_) | ExtraFieldsSov::DIDCommV2(_) => { - Err(DidDocumentSovError::EmptyCollection("recipient_keys")) - } - } - } - - pub fn routing_keys(&self) -> Result<&[KeyKind], DidDocumentSovError> { - match self { - ExtraFieldsSov::DIDCommV1(extra) => Ok(extra.routing_keys()), - ExtraFieldsSov::DIDCommV2(extra) => Ok(extra.routing_keys()), - ExtraFieldsSov::Legacy(extra) => Ok(extra.routing_keys()), - ExtraFieldsSov::AIP1(_) => Err(DidDocumentSovError::EmptyCollection("routing_keys")), - } - } - - pub fn first_recipient_key(&self) -> Result<&KeyKind, DidDocumentSovError> { - self.recipient_keys()? - .first() - .ok_or(DidDocumentSovError::EmptyCollection("recipient_keys")) - } - - pub fn first_routing_key(&self) -> Result<&KeyKind, DidDocumentSovError> { - self.routing_keys()? - .first() - .ok_or(DidDocumentSovError::EmptyCollection("routing_keys")) - } - - pub fn accept(&self) -> Result<&[AcceptType], DidDocumentSovError> { - match self { - ExtraFieldsSov::DIDCommV1(extra) => Ok(extra.accept()), - ExtraFieldsSov::DIDCommV2(extra) => Ok(extra.accept()), - ExtraFieldsSov::AIP1(_) | ExtraFieldsSov::Legacy(_) => { - Err(DidDocumentSovError::EmptyCollection("accept")) - } - } - } - - pub fn priority(&self) -> Result { - match self { - ExtraFieldsSov::DIDCommV1(extra) => Ok(extra.priority()), - ExtraFieldsSov::Legacy(extra) => Ok(extra.priority()), - _ => Err(DidDocumentSovError::EmptyCollection("priority")), - } - } -} diff --git a/did_core/did_doc_sov/src/legacy/mod.rs b/did_core/did_doc_sov/src/legacy/mod.rs deleted file mode 100644 index 96a4a742e3..0000000000 --- a/did_core/did_doc_sov/src/legacy/mod.rs +++ /dev/null @@ -1,211 +0,0 @@ -use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine}; -use did_doc::{ - did_parser::{Did, DidUrl}, - schema::{ - did_doc::DidDocument, - service::Service, - verification_method::{VerificationMethod, VerificationMethodType}, - }, -}; -use public_key::{Key, KeyType}; -use serde::{Deserialize, Deserializer, Serialize}; -use serde_json::{json, Value}; - -use crate::{extra_fields::ExtraFieldsSov, service::legacy::ServiceLegacy}; - -// TODO: Remove defaults if it turns out they are not needed. Preserved based on the original -// legacy DDO implementation. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] -#[serde(deny_unknown_fields)] -pub struct LegacyDidDoc { - id: Did, - #[serde(default)] - #[serde(rename = "publicKey")] - public_key: Vec, - #[serde(default)] - authentication: Vec, - service: Vec, -} - -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] -pub struct LegacyKeyAgreement { - id: String, - #[serde(rename = "type")] - verification_method_type: String, - controller: String, - #[serde(rename = "publicKeyBase58")] - public_key_base_58: String, -} - -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] -pub struct LegacyAuthentication { - #[serde(rename = "type")] - verification_method_type: String, - #[serde(rename = "publicKey")] - public_key: String, -} - -fn resolve_legacy_authentication_key( - legacy_authentication: &LegacyAuthentication, - legacy_public_keys: &[LegacyKeyAgreement], -) -> Result { - if let Some(fragment) = legacy_authentication.public_key.split('#').last() { - Ok(legacy_public_keys - .iter() - .find(|pk| pk.id.ends_with(fragment)) - .ok_or_else(|| format!("Public key with id {} not found", fragment))? - .public_key_base_58 - .clone()) - } else { - Ok(legacy_authentication.public_key.clone()) - } -} - -fn collect_authentication_fingerprints(legacy_ddo: &LegacyDidDoc) -> Result, String> { - let mut authentication_fingerprints = vec![]; - - for auth in &legacy_ddo.authentication { - let resolved_legacy_authentication_key = match auth.verification_method_type.as_str() { - "Ed25519SignatureAuthentication2018" => { - resolve_legacy_authentication_key(auth, &legacy_ddo.public_key)? - } - "Ed25519Signature2018" => auth.public_key.clone(), - _ => { - continue; - } - }; - - let fingerprint = Key::from_base58(&resolved_legacy_authentication_key, KeyType::Ed25519) - .map_err(|err| { - format!( - "Error converting legacy authentication key to new key: {:?}, error: {:?}", - auth, err - ) - })? - .fingerprint(); - - authentication_fingerprints.push(fingerprint); - } - - for vm in &legacy_ddo.public_key { - // Ed25519VerificationKey2018 check is used due to aries-vcx using this as key type in the - // legacy did doc - if !&["Ed25519Signature2018", "Ed25519VerificationKey2018"] - .contains(&vm.verification_method_type.as_str()) - { - continue; - } - - let fingerprint = Key::from_base58(vm.public_key_base_58.as_str(), KeyType::Ed25519) - .map_err(|err| { - format!( - "Error converting legacy public key to new key: {:?}, error: {:?}", - vm, err - ) - })? - .fingerprint(); - - if !authentication_fingerprints.contains(&fingerprint) { - authentication_fingerprints.push(fingerprint); - } - } - - Ok(authentication_fingerprints) -} - -fn collect_encoded_services(legacy_ddo: &LegacyDidDoc) -> Vec { - let mut encoded_services = vec![]; - for service in &legacy_ddo.service { - let service = json!({ - "priority": service.extra().priority(), - "r": service.extra().routing_keys(), - "recipientKeys": service.extra().recipient_keys(), - "s": service.service_endpoint(), - "t": service.service_type(), - }); - let service_encoded = STANDARD_NO_PAD.encode(service.to_string().as_bytes()); - encoded_services.push(service_encoded); - } - encoded_services -} - -fn construct_peer_did( - authentication_fingerprints: &[String], - encoded_services: &[String], -) -> Result { - // TODO: Perhaps proper ID is did:peer:3 with alsoKnowAs set to did:peer:2 (or vice versa?) - let mut did = "did:peer:2".to_string(); - - for fingerprint in authentication_fingerprints { - did.push_str(&format!(".V{}", fingerprint)); - } - - for service in encoded_services { - did.push_str(&format!(".S{}", service)); - } - - Did::parse(did).map_err(|err| format!("Error parsing peer did, error: {:?}", err)) -} - -fn construct_new_did_document( - legacy_ddo: &LegacyDidDoc, - authentication_fingerprints: &[String], - did: Did, -) -> Result, String> { - let mut builder = DidDocument::builder(did.clone()); - - for (i, fingerprint) in authentication_fingerprints.iter().enumerate() { - let id = DidUrl::from_fragment((i + 1).to_string()) - .map_err(|err| format!("Error constructing did url from fragment, error: {:?}", err))?; - builder = builder.add_verification_method( - VerificationMethod::builder( - id, - did.clone(), - VerificationMethodType::Ed25519VerificationKey2018, - ) - .add_public_key_multibase(fingerprint.clone()) - .build(), - ); - } - - for service in &legacy_ddo.service { - builder = builder.add_service( - TryInto::>::try_into(service.clone()).map_err(|err| { - format!( - "Error converting legacy service to new service: {:?}, error: {:?}", - service, err - ) - })?, - ); - } - - Ok(builder.build()) -} - -// https://github.com/TimoGlastra/legacy-did-transformation -fn convert_legacy_ddo_to_new( - legacy_ddo: LegacyDidDoc, -) -> Result, String> { - let authentication_fingerprints = collect_authentication_fingerprints(&legacy_ddo)?; - let encoded_services = collect_encoded_services(&legacy_ddo); - - let did = construct_peer_did(&authentication_fingerprints, &encoded_services)?; - - construct_new_did_document(&legacy_ddo, &authentication_fingerprints, did) -} - -pub fn deserialize_legacy_or_new<'de, D>( - deserializer: D, -) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let val = Value::deserialize(deserializer)?; - match serde_json::from_value::(val.clone()) { - Ok(legacy_doc) => { - Ok(convert_legacy_ddo_to_new(legacy_doc).map_err(serde::de::Error::custom)?) - } - Err(_err) => serde_json::from_value::>(val) - .map_err(serde::de::Error::custom), - } -} diff --git a/did_core/did_doc_sov/src/lib.rs b/did_core/did_doc_sov/src/lib.rs deleted file mode 100644 index ed9db72ba6..0000000000 --- a/did_core/did_doc_sov/src/lib.rs +++ /dev/null @@ -1,266 +0,0 @@ -extern crate display_as_json; - -pub mod error; -pub mod extra_fields; -// TODO: Remove once migration is done -mod legacy; -pub mod service; - -use std::collections::HashMap; - -use did_doc::{ - did_parser::{Did, DidUrl}, - schema::{ - did_doc::{ControllerAlias, DidDocument, DidDocumentBuilder}, - service::Service, - utils::OneOrList, - verification_method::{VerificationMethod, VerificationMethodKind}, - }, -}; -use extra_fields::ExtraFieldsSov; -use serde::{de, Deserialize, Deserializer, Serialize}; -use serde_json::Value; -use service::ServiceSov; - -#[derive(Clone, Debug, PartialEq)] -pub struct DidDocumentSov { - did_doc: DidDocument, - services: Vec, -} - -impl DidDocumentSov { - pub fn builder(id: Did) -> DidDocumentSovBuilder { - DidDocumentSovBuilder::new(id) - } - - pub fn id(&self) -> &Did { - self.did_doc.id() - } - - pub fn controller(&self) -> Option<&ControllerAlias> { - self.did_doc.controller() - } - - pub fn verification_method(&self) -> &[VerificationMethod] { - self.did_doc.verification_method() - } - - pub fn authentication(&self) -> &[VerificationMethodKind] { - self.did_doc.authentication() - } - - pub fn service(&self) -> &[ServiceSov] { - self.services.as_ref() - } - - pub fn assertion_method(&self) -> &[VerificationMethodKind] { - self.did_doc.assertion_method() - } - - pub fn key_agreement(&self) -> &[VerificationMethodKind] { - self.did_doc.key_agreement() - } - - pub fn resolved_key_agreement(&self) -> impl Iterator { - self.did_doc - .key_agreement() - .iter() - .filter_map(|vm| match vm { - VerificationMethodKind::Resolved(resolved) => Some(resolved), - VerificationMethodKind::Resolvable(reference) => self.dereference_key(reference), - }) - } - - pub fn capability_invocation(&self) -> &[VerificationMethodKind] { - self.did_doc.capability_invocation() - } - - pub fn capability_delegation(&self) -> &[VerificationMethodKind] { - self.did_doc.capability_delegation() - } - - pub fn extra_field(&self, key: &str) -> Option<&Value> { - self.did_doc.extra_field(key) - } - - pub fn dereference_key(&self, reference: &DidUrl) -> Option<&VerificationMethod> { - self.did_doc.dereference_key(reference) - } -} - -pub struct DidDocumentSovBuilder { - ddo_builder: DidDocumentBuilder, - services: Vec, -} - -impl DidDocumentSovBuilder { - pub fn new(id: Did) -> Self { - Self { - ddo_builder: DidDocumentBuilder::new(id), - services: Vec::new(), - } - } - - pub fn add_controller(mut self, controller: Did) -> Self { - self.ddo_builder = self.ddo_builder.add_controller(controller); - self - } - - pub fn add_verification_method(mut self, verification_method: VerificationMethod) -> Self { - self.ddo_builder = self - .ddo_builder - .add_verification_method(verification_method); - self - } - - pub fn add_key_agreement(mut self, key_agreement: VerificationMethodKind) -> Self { - match key_agreement { - VerificationMethodKind::Resolved(ka) => { - self.ddo_builder = self.ddo_builder.add_key_agreement(ka); - } - VerificationMethodKind::Resolvable(ka_ref) => { - self.ddo_builder = self.ddo_builder.add_key_agreement_reference(ka_ref); - } - } - self - } - - pub fn add_service(mut self, service: ServiceSov) -> Self { - self.services.push(service); - self - } - - pub fn build(self) -> DidDocumentSov { - DidDocumentSov { - did_doc: self.ddo_builder.build(), - services: self.services, - } - } -} - -impl<'de> Deserialize<'de> for DidDocumentSov { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize, Clone, Debug, PartialEq)] - struct TempDidDocumentSov { - #[serde(flatten)] - // TODO: Remove once the transition is done - #[serde(deserialize_with = "legacy::deserialize_legacy_or_new")] - did_doc: DidDocument, - } - - let temp = TempDidDocumentSov::deserialize(deserializer)?; - - let services = temp - .did_doc - .service() - .iter() - .map(|s| ServiceSov::try_from(s.clone())) - .collect::, _>>() - .map_err(|_| de::Error::custom("Failed to convert service"))?; - - Ok(DidDocumentSov { - did_doc: temp.did_doc, - services, - }) - } -} - -impl Serialize for DidDocumentSov { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut builder: DidDocumentBuilder = self.did_doc.clone().into(); - - for service_sov in &self.services { - let service: Service = service_sov - .clone() - .try_into() - .map_err(serde::ser::Error::custom)?; - // Not very efficient, but - // * we don't expect many services - // * does not require allowing to remove services from existing DDO or builder - if !self - .did_doc - .service() - .iter() - .any(|s| s.id() == service.id()) - { - builder = builder.add_service(service); - } - } - - builder.build().serialize(serializer) - } -} - -impl From for DidDocument { - fn from(ddo: DidDocumentSov) -> Self { - let mut ddo_builder = DidDocument::::builder(ddo.did_doc.id().clone()); - for service in ddo.service() { - ddo_builder = ddo_builder.add_service(service.clone().try_into().unwrap()); - } - if let Some(controller) = ddo.did_doc.controller() { - match controller { - OneOrList::One(controller) => { - ddo_builder = ddo_builder.add_controller(controller.clone()); - } - OneOrList::List(list) => { - for controller in list { - ddo_builder = ddo_builder.add_controller(controller.clone()); - } - } - } - } - for vm in ddo.verification_method() { - ddo_builder = ddo_builder.add_verification_method(vm.clone()); - } - for ka in ddo.key_agreement() { - match ka { - VerificationMethodKind::Resolved(ka) => { - ddo_builder = ddo_builder.add_key_agreement(ka.clone()); - } - VerificationMethodKind::Resolvable(ka_ref) => { - ddo_builder = ddo_builder.add_key_agreement_reference(ka_ref.clone()); - } - } - } - ddo_builder.build() - } -} - -impl From> for DidDocumentSov { - fn from(ddo: DidDocument) -> Self { - let mut builder = DidDocumentSov::builder(ddo.id().clone()); - for service in ddo.service() { - builder = builder.add_service(service.clone().try_into().unwrap()); - } - for vm in ddo.verification_method() { - builder = builder.add_verification_method(vm.clone()); - } - for ka in ddo.key_agreement() { - builder = builder.add_key_agreement(ka.clone()); - } - // TODO: Controller - builder.build() - } -} - -impl From>> for DidDocumentSov { - fn from(ddo: DidDocument>) -> Self { - let mut builder = DidDocumentSov::builder(ddo.id().clone()); - for service in ddo.service() { - builder = builder.add_service(service.clone().try_into().unwrap()); - } - for vm in ddo.verification_method() { - builder = builder.add_verification_method(vm.clone()); - } - for ka in ddo.key_agreement() { - builder = builder.add_key_agreement(ka.clone()); - } - builder.build() - } -} diff --git a/did_core/did_doc_sov/src/service/aip1.rs b/did_core/did_doc_sov/src/service/aip1.rs deleted file mode 100644 index c980382ba9..0000000000 --- a/did_core/did_doc_sov/src/service/aip1.rs +++ /dev/null @@ -1,116 +0,0 @@ -use std::collections::HashMap; - -use did_doc::schema::{ - service::Service, - types::{uri::Uri, url::Url}, - utils::OneOrList, -}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -use super::ServiceType; -use crate::{ - error::DidDocumentSovError, - extra_fields::{aip1::ExtraFieldsAIP1, ExtraFieldsSov}, -}; - -#[derive(Serialize, Clone, Debug, PartialEq)] -pub struct ServiceAIP1 { - #[serde(flatten)] - service: Service, -} - -impl ServiceAIP1 { - pub fn new( - id: Uri, - service_endpoint: Url, - extra: ExtraFieldsAIP1, - ) -> Result { - Ok(Self { - service: Service::builder(id, service_endpoint, extra) - .add_service_type(ServiceType::AIP1.to_string())? - .build(), - }) - } - - pub fn id(&self) -> &Uri { - self.service.id() - } - - pub fn service_type(&self) -> ServiceType { - ServiceType::AIP1 - } - - pub fn service_endpoint(&self) -> Url { - self.service.service_endpoint().clone() - } - - pub fn extra(&self) -> &ExtraFieldsAIP1 { - self.service.extra() - } -} - -impl TryFrom> for ServiceAIP1 { - type Error = DidDocumentSovError; - - fn try_from(service: Service) -> Result { - match service.extra() { - ExtraFieldsSov::AIP1(extra) => Self::new( - service.id().clone(), - service.service_endpoint().clone(), - extra.clone(), - ), - _ => Err(DidDocumentSovError::UnexpectedServiceType( - service.service_type().to_string(), - )), - } - } -} - -impl TryFrom>> for ServiceAIP1 { - type Error = DidDocumentSovError; - - fn try_from(service: Service>) -> Result { - let extra = - serde_json::from_value::(serde_json::to_value(service.extra())?)?; - Self::new( - service.id().clone(), - service.service_endpoint().clone(), - extra, - ) - } -} - -impl<'de> Deserialize<'de> for ServiceAIP1 { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let service = Service::::deserialize(deserializer)?; - match service.service_type() { - OneOrList::One(service_type) if *service_type == ServiceType::AIP1.to_string() => {} - OneOrList::List(service_types) - if service_types.contains(&ServiceType::AIP1.to_string()) => {} - _ => { - return Err(serde::de::Error::custom( - "Extra fields don't match service type", - )) - } - }; - match service.extra() { - ExtraFieldsSov::AIP1(extra) => Ok(Self { - service: Service::builder( - service.id().clone(), - service.service_endpoint().clone(), - extra.clone(), - ) - .add_service_type(ServiceType::AIP1.to_string()) - .map_err(serde::de::Error::custom)? - .build(), - }), - _ => Err(serde::de::Error::custom( - "Extra fields don't match service type", - )), - } - } -} diff --git a/did_core/did_doc_sov/src/service/didcommv1.rs b/did_core/did_doc_sov/src/service/didcommv1.rs deleted file mode 100644 index 735f0c743e..0000000000 --- a/did_core/did_doc_sov/src/service/didcommv1.rs +++ /dev/null @@ -1,117 +0,0 @@ -use std::collections::HashMap; - -use did_doc::schema::{ - service::Service, - types::{uri::Uri, url::Url}, - utils::OneOrList, -}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -use super::ServiceType; -use crate::{ - error::DidDocumentSovError, - extra_fields::{didcommv1::ExtraFieldsDidCommV1, ExtraFieldsSov}, -}; - -#[derive(Serialize, Clone, Debug, PartialEq)] -pub struct ServiceDidCommV1 { - #[serde(flatten)] - service: Service, -} - -impl ServiceDidCommV1 { - pub fn new( - id: Uri, - service_endpoint: Url, - extra: ExtraFieldsDidCommV1, - ) -> Result { - Ok(Self { - service: Service::builder(id, service_endpoint, extra) - .add_service_type(ServiceType::DIDCommV1.to_string())? - .build(), - }) - } - - pub fn id(&self) -> &Uri { - self.service.id() - } - - pub fn service_type(&self) -> ServiceType { - ServiceType::DIDCommV1 - } - - pub fn service_endpoint(&self) -> Url { - self.service.service_endpoint().clone() - } - - pub fn extra(&self) -> &ExtraFieldsDidCommV1 { - self.service.extra() - } -} - -impl TryFrom> for ServiceDidCommV1 { - type Error = DidDocumentSovError; - - fn try_from(service: Service) -> Result { - match service.extra() { - ExtraFieldsSov::DIDCommV1(extra) => Self::new( - service.id().clone(), - service.service_endpoint().clone(), - extra.clone(), - ), - _ => Err(DidDocumentSovError::UnexpectedServiceType( - service.service_type().to_string(), - )), - } - } -} - -impl TryFrom>> for ServiceDidCommV1 { - type Error = DidDocumentSovError; - - fn try_from(service: Service>) -> Result { - let extra = - serde_json::from_value::(serde_json::to_value(service.extra())?)?; - Self::new( - service.id().clone(), - service.service_endpoint().clone(), - extra, - ) - } -} - -impl<'de> Deserialize<'de> for ServiceDidCommV1 { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let service = Service::::deserialize(deserializer)?; - match service.service_type() { - OneOrList::One(service_type) if *service_type == ServiceType::DIDCommV1.to_string() => { - } - OneOrList::List(service_types) - if service_types.contains(&ServiceType::DIDCommV1.to_string()) => {} - _ => { - return Err(serde::de::Error::custom( - "Extra fields don't match service type", - )) - } - }; - match service.extra() { - ExtraFieldsSov::DIDCommV1(extra) => Ok(Self { - service: Service::builder( - service.id().clone(), - service.service_endpoint().clone(), - extra.clone(), - ) - .add_service_type(ServiceType::DIDCommV1.to_string()) - .map_err(serde::de::Error::custom)? - .build(), - }), - _ => Err(serde::de::Error::custom( - "Extra fields don't match service type", - )), - } - } -} diff --git a/did_core/did_doc_sov/src/service/didcommv2.rs b/did_core/did_doc_sov/src/service/didcommv2.rs deleted file mode 100644 index 06c62f94d6..0000000000 --- a/did_core/did_doc_sov/src/service/didcommv2.rs +++ /dev/null @@ -1,117 +0,0 @@ -use std::collections::HashMap; - -use did_doc::schema::{ - service::Service, - types::{uri::Uri, url::Url}, - utils::OneOrList, -}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -use super::ServiceType; -use crate::{ - error::DidDocumentSovError, - extra_fields::{didcommv2::ExtraFieldsDidCommV2, ExtraFieldsSov}, -}; - -#[derive(Serialize, Clone, Debug, PartialEq)] -pub struct ServiceDidCommV2 { - #[serde(flatten)] - service: Service, -} - -impl ServiceDidCommV2 { - pub fn new( - id: Uri, - service_endpoint: Url, - extra: ExtraFieldsDidCommV2, - ) -> Result { - Ok(Self { - service: Service::builder(id, service_endpoint, extra) - .add_service_type(ServiceType::DIDCommV2.to_string())? - .build(), - }) - } - - pub fn id(&self) -> &Uri { - self.service.id() - } - - pub fn service_type(&self) -> ServiceType { - ServiceType::DIDCommV2 - } - - pub fn service_endpoint(&self) -> Url { - self.service.service_endpoint().clone() - } - - pub fn extra(&self) -> &ExtraFieldsDidCommV2 { - self.service.extra() - } -} - -impl TryFrom> for ServiceDidCommV2 { - type Error = DidDocumentSovError; - - fn try_from(service: Service) -> Result { - match service.extra() { - ExtraFieldsSov::DIDCommV2(extra) => Self::new( - service.id().clone(), - service.service_endpoint().clone(), - extra.clone(), - ), - _ => Err(DidDocumentSovError::UnexpectedServiceType( - service.service_type().to_string(), - )), - } - } -} - -impl TryFrom>> for ServiceDidCommV2 { - type Error = DidDocumentSovError; - - fn try_from(service: Service>) -> Result { - let extra = - serde_json::from_value::(serde_json::to_value(service.extra())?)?; - Self::new( - service.id().clone(), - service.service_endpoint().clone(), - extra, - ) - } -} - -impl<'de> Deserialize<'de> for ServiceDidCommV2 { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let service = Service::::deserialize(deserializer)?; - match service.service_type() { - OneOrList::One(service_type) if *service_type == ServiceType::DIDCommV2.to_string() => { - } - OneOrList::List(service_types) - if service_types.contains(&ServiceType::DIDCommV2.to_string()) => {} - _ => { - return Err(serde::de::Error::custom( - "Extra fields don't match service type", - )) - } - }; - match service.extra() { - ExtraFieldsSov::DIDCommV2(extra) => Ok(Self { - service: Service::builder( - service.id().clone(), - service.service_endpoint().clone(), - extra.clone(), - ) - .add_service_type(ServiceType::DIDCommV2.to_string()) - .map_err(serde::de::Error::custom)? - .build(), - }), - _ => Err(serde::de::Error::custom( - "Extra fields don't match service type", - )), - } - } -} diff --git a/did_core/did_doc_sov/src/service/legacy.rs b/did_core/did_doc_sov/src/service/legacy.rs deleted file mode 100644 index f0a989a5e7..0000000000 --- a/did_core/did_doc_sov/src/service/legacy.rs +++ /dev/null @@ -1,84 +0,0 @@ -use did_doc::schema::{ - service::Service, - types::{uri::Uri, url::Url}, -}; -use serde::{Deserialize, Serialize}; - -use super::ServiceType; -use crate::{ - error::DidDocumentSovError, - extra_fields::{legacy::ExtraFieldsLegacy, ExtraFieldsSov}, -}; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct ServiceLegacy { - #[serde(default)] - id: Uri, - #[serde(rename = "type")] - service_type: ServiceType, - service_endpoint: Url, - #[serde(flatten)] - extra: ExtraFieldsLegacy, -} - -impl ServiceLegacy { - pub fn new( - id: Uri, - service_endpoint: Url, - extra: ExtraFieldsLegacy, - ) -> Result { - Ok(Self { - id, - service_type: ServiceType::Legacy, - service_endpoint, - extra, - }) - } - - pub fn id(&self) -> &Uri { - &self.id - } - - pub fn service_type(&self) -> ServiceType { - ServiceType::Legacy - } - - pub fn service_endpoint(&self) -> Url { - self.service_endpoint.clone() - } - - pub fn extra(&self) -> &ExtraFieldsLegacy { - &self.extra - } -} - -impl TryFrom> for ServiceLegacy { - type Error = DidDocumentSovError; - - fn try_from(service: Service) -> Result { - match service.extra() { - ExtraFieldsSov::Legacy(extra) => Self::new( - service.id().clone(), - service.service_endpoint().clone(), - extra.clone(), - ), - _ => Err(DidDocumentSovError::UnexpectedServiceType( - service.service_type().to_string(), - )), - } - } -} - -impl TryFrom for Service { - type Error = DidDocumentSovError; - - fn try_from(service: ServiceLegacy) -> Result { - let extra = ExtraFieldsSov::Legacy(service.extra); - Ok( - Service::builder(service.id, service.service_endpoint, extra) - .add_service_type(ServiceType::Legacy.to_string())? - .build(), - ) - } -} diff --git a/did_core/did_doc_sov/src/service/mod.rs b/did_core/did_doc_sov/src/service/mod.rs deleted file mode 100644 index a6d25349bd..0000000000 --- a/did_core/did_doc_sov/src/service/mod.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::{collections::HashMap, fmt::Display}; - -use did_doc::schema::{ - service::Service, - types::{uri::Uri, url::Url}, -}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -use crate::{error::DidDocumentSovError, extra_fields::ExtraFieldsSov}; - -pub mod aip1; -pub mod didcommv1; -pub mod didcommv2; -pub mod legacy; - -#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] -pub enum ServiceType { - #[serde(rename = "endpoint")] - AIP1, - #[serde(rename = "did-communication")] - DIDCommV1, - #[serde(rename = "DIDCommMessaging")] - DIDCommV2, - #[serde(rename = "IndyAgent")] - Legacy, -} - -impl Display for ServiceType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ServiceType::AIP1 => write!(f, "endpoint"), - ServiceType::DIDCommV1 => write!(f, "did-communication"), - // Interop note: AFJ useses DIDComm, Acapy uses DIDCommMessaging - // Not matching spec: - // * did:sov method - https://sovrin-foundation.github.io/sovrin/spec/did-method-spec-template.html#crud-operation-definitions - // Matching spec: - // * did:peer method - https://identity.foundation/peer-did-method-spec/#multi-key-creation - // * did core - https://www.w3.org/TR/did-spec-registries/#didcommmessaging - // * didcommv2 - https://identity.foundation/didcomm-messaging/spec/#service-endpoint - ServiceType::DIDCommV2 => write!(f, "DIDCommMessaging"), - ServiceType::Legacy => write!(f, "IndyAgent"), - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(untagged)] -pub enum ServiceSov { - Legacy(legacy::ServiceLegacy), - AIP1(aip1::ServiceAIP1), - DIDCommV1(didcommv1::ServiceDidCommV1), - DIDCommV2(didcommv2::ServiceDidCommV2), -} - -impl ServiceSov { - pub fn id(&self) -> &Uri { - match self { - ServiceSov::AIP1(service) => service.id(), - ServiceSov::DIDCommV1(service) => service.id(), - ServiceSov::DIDCommV2(service) => service.id(), - ServiceSov::Legacy(service) => service.id(), - } - } - - pub fn service_type(&self) -> ServiceType { - match self { - ServiceSov::AIP1(service) => service.service_type(), - ServiceSov::DIDCommV1(service) => service.service_type(), - ServiceSov::DIDCommV2(service) => service.service_type(), - ServiceSov::Legacy(service) => service.service_type(), - } - } - - pub fn service_endpoint(&self) -> Url { - match self { - ServiceSov::AIP1(service) => service.service_endpoint(), - ServiceSov::DIDCommV1(service) => service.service_endpoint(), - ServiceSov::DIDCommV2(service) => service.service_endpoint(), - ServiceSov::Legacy(service) => service.service_endpoint(), - } - } - - pub fn extra(&self) -> ExtraFieldsSov { - match self { - ServiceSov::AIP1(service) => ExtraFieldsSov::AIP1(service.extra().to_owned()), - ServiceSov::DIDCommV1(service) => ExtraFieldsSov::DIDCommV1(service.extra().to_owned()), - ServiceSov::DIDCommV2(service) => ExtraFieldsSov::DIDCommV2(service.extra().to_owned()), - ServiceSov::Legacy(service) => ExtraFieldsSov::Legacy(service.extra().to_owned()), - } - } -} - -impl TryFrom> for ServiceSov { - type Error = DidDocumentSovError; - - fn try_from(service: Service) -> Result { - match service.extra() { - ExtraFieldsSov::AIP1(_extra) => Ok(ServiceSov::AIP1(service.try_into()?)), - ExtraFieldsSov::DIDCommV1(_extra) => Ok(ServiceSov::DIDCommV1(service.try_into()?)), - ExtraFieldsSov::DIDCommV2(_extra) => Ok(ServiceSov::DIDCommV2(service.try_into()?)), - ExtraFieldsSov::Legacy(_extra) => Ok(ServiceSov::Legacy(service.try_into()?)), - } - } -} - -impl TryFrom>> for ServiceSov { - type Error = DidDocumentSovError; - - fn try_from(service: Service>) -> Result { - match service.extra().get("type") { - Some(service_type) => match service_type.as_str() { - Some("AIP1") => Ok(ServiceSov::AIP1(service.try_into()?)), - Some("DIDCommV1") => Ok(ServiceSov::DIDCommV1(service.try_into()?)), - Some("DIDCommV2") => Ok(ServiceSov::DIDCommV2(service.try_into()?)), - _ => Err(DidDocumentSovError::UnexpectedServiceType( - service_type.to_string(), - )), - }, - None => Ok(ServiceSov::AIP1(service.try_into()?)), - } - } -} - -impl TryFrom for Service { - type Error = DidDocumentSovError; - - fn try_from(service: ServiceSov) -> Result { - match service { - ServiceSov::AIP1(service) => Ok(Service::builder( - service.id().clone(), - service.service_endpoint(), - ExtraFieldsSov::AIP1(service.extra().to_owned()), - ) - .add_service_type(service.service_type().to_string())? - .build()), - ServiceSov::DIDCommV1(service) => Ok(Service::builder( - service.id().clone(), - service.service_endpoint(), - ExtraFieldsSov::DIDCommV1(service.extra().to_owned()), - ) - .add_service_type(service.service_type().to_string())? - .build()), - ServiceSov::DIDCommV2(service) => Ok(Service::builder( - service.id().clone(), - service.service_endpoint(), - ExtraFieldsSov::DIDCommV2(service.extra().to_owned()), - ) - .add_service_type(service.service_type().to_string())? - .build()), - ServiceSov::Legacy(service) => Ok(Service::builder( - service.id().clone(), - service.service_endpoint(), - ExtraFieldsSov::Legacy(service.extra().to_owned()), - ) - .add_service_type(service.service_type().to_string())? - .build()), - } - } -} diff --git a/did_core/did_doc_sov/tests/builder.rs b/did_core/did_doc_sov/tests/builder.rs deleted file mode 100644 index 57ec1e86e0..0000000000 --- a/did_core/did_doc_sov/tests/builder.rs +++ /dev/null @@ -1,106 +0,0 @@ -use did_doc::schema::types::{uri::Uri, url::Url}; -use did_doc_sov::{ - extra_fields::{ - aip1::ExtraFieldsAIP1, didcommv1::ExtraFieldsDidCommV1, didcommv2::ExtraFieldsDidCommV2, - KeyKind, - }, - service::{ - aip1::ServiceAIP1, didcommv1::ServiceDidCommV1, didcommv2::ServiceDidCommV2, ServiceSov, - }, - DidDocumentSov, -}; - -const ID: &str = "did:sov:WRfXPg8dantKVubE3HX8pw"; -const SERVICE_ENDPOINT: &str = "https://example.com"; - -#[test] -fn test_service_build_aip1() { - let service = ServiceAIP1::new( - ID.parse().unwrap(), - SERVICE_ENDPOINT.parse().unwrap(), - ExtraFieldsAIP1::default(), - ) - .unwrap(); - let did_doc = DidDocumentSov::builder(Default::default()) - .add_service(ServiceSov::AIP1(service)) - .build(); - let services = did_doc.service(); - assert_eq!(services.len(), 1); - let first_service = services.get(0).unwrap(); - assert_eq!(first_service.id().clone(), ID.parse::().unwrap()); - assert_eq!( - first_service.service_endpoint(), - SERVICE_ENDPOINT.parse::().unwrap() - ); - let first_extra = first_service.extra(); - assert!(first_extra.priority().is_err()); - assert!(first_extra.recipient_keys().is_err()); - assert!(first_extra.routing_keys().is_err()); -} - -#[test] -fn test_service_build_didcommv1() { - let extra_fields_didcommv1 = ExtraFieldsDidCommV1::builder() - .set_priority(1) - .set_routing_keys(vec![KeyKind::Value("foo".to_owned())]) - .set_recipient_keys(vec![KeyKind::Value("bar".to_owned())]) - .build(); - let service = ServiceDidCommV1::new( - ID.parse().unwrap(), - SERVICE_ENDPOINT.parse().unwrap(), - extra_fields_didcommv1, - ) - .unwrap(); - let did_doc = DidDocumentSov::builder(Default::default()) - .add_service(ServiceSov::DIDCommV1(service)) - .build(); - let services = did_doc.service(); - assert_eq!(services.len(), 1); - let first_service = services.get(0).unwrap(); - assert_eq!(first_service.id().clone(), ID.parse::().unwrap()); - assert_eq!( - first_service.service_endpoint(), - SERVICE_ENDPOINT.parse::().unwrap() - ); - let first_extra = first_service.extra(); - assert_eq!(first_extra.priority().unwrap(), 1); - assert_eq!( - first_extra.recipient_keys().unwrap(), - &[KeyKind::Value("bar".to_owned())] - ); - assert_eq!( - first_extra.routing_keys().unwrap(), - &[KeyKind::Value("foo".to_owned())] - ); -} - -#[test] -fn test_service_build_didcommv2() { - let extra_fields_didcommv2 = ExtraFieldsDidCommV2::builder() - .set_routing_keys(vec![KeyKind::Value("foo".to_owned())]) - .build(); - let service = ServiceDidCommV2::new( - ID.parse().unwrap(), - SERVICE_ENDPOINT.parse().unwrap(), - extra_fields_didcommv2, - ) - .unwrap(); - let did_doc = DidDocumentSov::builder(Default::default()) - .add_service(ServiceSov::DIDCommV2(service)) - .build(); - let services = did_doc.service(); - assert_eq!(services.len(), 1); - let first_service = services.get(0).unwrap(); - assert_eq!(first_service.id().clone(), ID.parse::().unwrap()); - assert_eq!( - first_service.service_endpoint(), - SERVICE_ENDPOINT.parse::().unwrap() - ); - let first_extra = first_service.extra(); - assert!(first_extra.priority().is_err()); - assert!(first_extra.recipient_keys().is_err()); - assert_eq!( - first_extra.routing_keys().unwrap(), - &[KeyKind::Value("foo".to_owned())] - ); -} diff --git a/did_core/did_doc_sov/tests/serde.rs b/did_core/did_doc_sov/tests/serde.rs deleted file mode 100644 index 3ebf9c8026..0000000000 --- a/did_core/did_doc_sov/tests/serde.rs +++ /dev/null @@ -1,149 +0,0 @@ -use did_doc_sov::{ - extra_fields::{AcceptType, KeyKind}, - service::ServiceType, - DidDocumentSov, -}; - -const DID_DOC_DATA: &str = r#" -{ - "@context": [ - "https://www.w3.org/ns/did/v1", - "https://w3id.org/security/suites/ed25519-2018/v1", - "https://w3id.org/security/suites/x25519-2019/v1" - ], - "id": "did:sov:HR6vs6GEZ8rHaVgjg2WodM", - "verificationMethod": [ - { - "type": "Ed25519VerificationKey2018", - "id": "did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-1", - "controller": "did:sov:HR6vs6GEZ8rHaVgjg2WodM", - "publicKeyBase58": "9wvq2i4xUa5umXoThe83CDgx1e5bsjZKJL4DEWvTP9qe" - }, - { - "type": "X25519KeyAgreementKey2019", - "id": "did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-agreement-1", - "controller": "did:sov:HR6vs6GEZ8rHaVgjg2WodM", - "publicKeyBase58": "3mHtKcQFEzqeUcnce5BAuzAgLEbqKaV542pUf9xQ5Pf8" - } - ], - "authentication": [ - "did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-1" - ], - "assertionMethod": [ - "did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-1" - ], - "keyAgreement": [ - "did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-agreement-1" - ], - "service": [ - { - "id": "did:sov:HR6vs6GEZ8rHaVgjg2WodM#endpoint", - "type": "endpoint", - "serviceEndpoint": "https://example.com/endpoint" - }, - { - "id": "did:sov:HR6vs6GEZ8rHaVgjg2WodM#did-communication", - "type": "did-communication", - "priority": 0, - "recipientKeys": [ - "did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-agreement-1" - ], - "routingKeys": [], - "accept": [ - "didcomm/aip2;env=rfc19" - ], - "serviceEndpoint": "https://example.com/endpoint" - }, - { - "id": "did:sov:HR6vs6GEZ8rHaVgjg2WodM#didcomm-1", - "type": "DIDComm", - "accept": [ - "didcomm/v2" - ], - "routingKeys": [], - "serviceEndpoint": "https://example.com/endpoint" - } - ] -} -"#; - -#[test] -fn test_deserialization() { - let did_doc = serde_json::from_str::(DID_DOC_DATA).unwrap(); - assert_eq!(did_doc.id().to_string(), "did:sov:HR6vs6GEZ8rHaVgjg2WodM"); - assert_eq!(did_doc.verification_method().len(), 2); - assert_eq!(did_doc.authentication().len(), 1); - assert_eq!(did_doc.assertion_method().len(), 1); - assert_eq!(did_doc.key_agreement().len(), 1); - assert_eq!(did_doc.service().len(), 3); - - let services = did_doc.service(); - let first_service = services.get(0).unwrap(); - assert_eq!( - first_service.service_endpoint().to_string(), - "https://example.com/endpoint" - ); - assert_eq!(first_service.service_type(), ServiceType::AIP1); - - let second_service = services.get(1).unwrap(); - assert_eq!( - second_service.id().to_string(), - "did:sov:HR6vs6GEZ8rHaVgjg2WodM#did-communication" - ); - assert_eq!(second_service.service_type(), ServiceType::DIDCommV1); - assert_eq!( - second_service.service_endpoint().to_string(), - "https://example.com/endpoint" - ); - - let third_service = services.get(2).unwrap(); - assert_eq!( - third_service.id().to_string(), - "did:sov:HR6vs6GEZ8rHaVgjg2WodM#didcomm-1" - ); - assert_eq!(third_service.service_type(), ServiceType::DIDCommV2); - assert_eq!( - third_service.service_endpoint().to_string(), - "https://example.com/endpoint" - ); - - let second_extra = second_service.extra(); - assert!(!second_extra.recipient_keys().unwrap().is_empty()); - assert_eq!(second_extra.routing_keys().unwrap().len(), 0); - assert!(second_extra.first_recipient_key().is_ok()); - assert!(second_extra.first_routing_key().is_err()); - assert_eq!( - second_extra.accept().unwrap().get(0).unwrap().clone(), - AcceptType::DIDCommV1 - ); - assert_eq!(second_extra.priority().unwrap(), 0); - - let third_extra = third_service.extra(); - assert!(third_extra.recipient_keys().is_err()); - assert_eq!(third_extra.routing_keys().unwrap().len(), 0); - assert!(third_extra.first_recipient_key().is_err()); - assert!(third_extra.first_routing_key().is_err()); - assert_eq!( - third_extra.accept().unwrap().get(0).unwrap().clone(), - AcceptType::DIDCommV2 - ); - assert!(third_extra.priority().is_err()); - - if let KeyKind::Reference(reference) = second_extra.first_recipient_key().unwrap() { - let vm = did_doc.dereference_key(reference).unwrap(); - assert_eq!( - vm.id().to_string(), - "did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-agreement-1" - ); - } else { - panic!("Expected reference key kind"); - } -} - -#[test] -fn test_deserialization_and_serialization() { - let did_doc_1 = serde_json::from_str::(DID_DOC_DATA).unwrap(); - let serialized = serde_json::to_string_pretty(&did_doc_1).unwrap(); - let did_doc_2 = serde_json::from_str::(&serialized).unwrap(); - assert_eq!(did_doc_1, did_doc_2); -} diff --git a/did_core/did_doc_sov/tests/serde_legacy.rs b/did_core/did_doc_sov/tests/serde_legacy.rs deleted file mode 100644 index e3bcdca794..0000000000 --- a/did_core/did_doc_sov/tests/serde_legacy.rs +++ /dev/null @@ -1,86 +0,0 @@ -use did_doc_sov::{extra_fields::KeyKind, DidDocumentSov}; - -const LEGACY_DID_DOC_JSON: &str = r#" -{ - "id": "2ZHFFhzA2XtTD6hJqzL7ux", - "publicKey": [ - { - "id": "1", - "type": "Ed25519VerificationKey2018", - "controller": "2ZHFFhzA2XtTD6hJqzL7ux", - "publicKeyBase58": "rCw3x5h1jS6gPo7rRrt3EYbXXe5nNjnGbdf1jAwUxuj" - } - ], - "authentication": [ - { - "type": "Ed25519SignatureAuthentication2018", - "publicKey": "2ZHFFhzA2XtTD6hJqzL7ux#1" - } - ], - "service": [ - { - "id": "did:example:123456789abcdefghi;indy", - "type": "IndyAgent", - "priority": 0, - "recipientKeys": [ - "2ZHFFhzA2XtTD6hJqzL7ux#1" - ], - "routingKeys": [ - "8Ps2WosJ9AV1eXPoJKsEJdM3NchPhSyS8qFt6LQUTKv2", - "Hezce2UWMZ3wUhVkh2LfKSs8nDzWwzs2Win7EzNN3YaR" - ], - "serviceEndpoint": "http://localhost:8080/agency/msg" - } - ] -} -"#; - -const VERKEY_BASE58: &str = "6MkfJTyeCL8MGvZntdpXzpitL6bM6uwCFz8xcYar18xQBh7"; - -const DID_PEER: &str = "did:peer:2.Vz6MkfJTyeCL8MGvZntdpXzpitL6bM6uwCFz8xcYar18xQBh7.SeyJwcmlvcml0eSI6MCwiciI6WyI4UHMyV29zSjlBVjFlWFBvSktzRUpkTTNOY2hQaFN5UzhxRnQ2TFFVVEt2MiIsIkhlemNlMlVXTVozd1VoVmtoMkxmS1NzOG5Eeld3enMyV2luN0V6Tk4zWWFSIl0sInJlY2lwaWVudEtleXMiOlsiMlpIRkZoekEyWHRURDZoSnF6TDd1eCMxIl0sInMiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYWdlbmN5L21zZyIsInQiOiJJbmR5QWdlbnQifQ"; - -#[test] -fn test_deserialization_legacy() { - let did_doc: DidDocumentSov = serde_json::from_str(LEGACY_DID_DOC_JSON).unwrap(); - println!("{:#?}", serde_json::to_string_pretty(&did_doc).unwrap()); - assert_eq!(did_doc.id().to_string(), DID_PEER); - assert_eq!(did_doc.verification_method().len(), 1); - assert_eq!(did_doc.authentication().len(), 0); - assert_eq!(did_doc.assertion_method().len(), 0); - assert_eq!(did_doc.key_agreement().len(), 0); - assert_eq!(did_doc.service().len(), 1); - - let verification_method = did_doc.verification_method().first().unwrap(); - assert_eq!(verification_method.id().to_string(), "#1"); - assert_eq!(verification_method.controller().to_string(), DID_PEER); - assert_eq!( - verification_method - .public_key() - .unwrap() - .prefixless_fingerprint(), - VERKEY_BASE58 - ); - - let service = did_doc.service().first().unwrap(); - assert_eq!( - service.id().to_string(), - "did:example:123456789abcdefghi;indy" - ); - assert_eq!( - service.service_endpoint().to_string().as_str(), - "http://localhost:8080/agency/msg" - ); - - let recipient_key = match service.extra().first_recipient_key().unwrap() { - KeyKind::Reference(did_url) => did_doc - .dereference_key(did_url) - .unwrap() - .public_key() - .unwrap() - .prefixless_fingerprint(), - _ => panic!("Expected reference"), - }; - assert_eq!(recipient_key, VERKEY_BASE58); - assert_eq!(service.extra().priority().unwrap(), 0); - assert_eq!(service.extra().routing_keys().unwrap().len(), 2); -} diff --git a/did_core/did_methods/did_key/src/lib.rs b/did_core/did_methods/did_key/src/lib.rs index 5e555e1423..65973a3c35 100644 --- a/did_core/did_methods/did_key/src/lib.rs +++ b/did_core/did_methods/did_key/src/lib.rs @@ -8,6 +8,8 @@ use error::DidKeyError; use public_key::Key; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +/// Represents did:key where the DID ID is public key itself +/// See the spec: https://w3c-ccg.github.io/did-method-key/ #[derive(Clone, Debug, PartialEq)] pub struct DidKey { key: Key, diff --git a/did_core/did_methods/did_peer/Cargo.toml b/did_core/did_methods/did_peer/Cargo.toml index 19a275fa73..3edc2e69c2 100644 --- a/did_core/did_methods/did_peer/Cargo.toml +++ b/did_core/did_methods/did_peer/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" [dependencies] did_parser = { path = "../../did_parser" } did_doc = { path = "../../did_doc" } -did_doc_sov = { path = "../../did_doc_sov" } did_resolver = { path = "../../did_resolver" } public_key = { path = "../../public_key" } thiserror = "1.0.40" @@ -22,7 +21,9 @@ multibase = "0.9.1" unsigned-varint = "0.7.1" once_cell = "1.18.0" sha256 = "1.1.4" +url = { version = "2.3.1", features = ["serde"] } display_as_json = { path = "../../../misc/display_as_json" } [dev-dependencies] tokio = { version = "1.27.0", default-features = false, features = ["macros", "rt"] } +pretty_assertions = "1.4.0" diff --git a/did_core/did_methods/did_peer/examples/demo.rs b/did_core/did_methods/did_peer/examples/demo.rs index 2a41cca295..a02f0cf842 100644 --- a/did_core/did_methods/did_peer/examples/demo.rs +++ b/did_core/did_methods/did_peer/examples/demo.rs @@ -2,36 +2,24 @@ use std::error::Error; use did_doc::schema::{ did_doc::DidDocument, - service::ServiceBuilder, - types::{uri::Uri, url::Url}, verification_method::{VerificationMethod, VerificationMethodType}, }; -use did_doc_sov::extra_fields::{didcommv1::ExtraFieldsDidCommV1, ExtraFieldsSov, KeyKind}; use did_parser::{Did, DidUrl}; -use did_peer::peer_did::{ - numalgos::{numalgo2::Numalgo2, numalgo3::Numalgo3}, - PeerDid, +use did_peer::{ + peer_did::{ + numalgos::{numalgo2::Numalgo2, numalgo3::Numalgo3}, + PeerDid, + }, + resolver::{options::PublicKeyEncoding, PeerDidResolutionOptions, PeerDidResolver}, }; +use did_resolver::traits::resolvable::{resolution_output::DidResolutionOutput, DidResolvable}; -fn main() -> Result<(), Box> { - demo() +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<(), Box> { + demo().await } -fn demo() -> Result<(), Box> { - let recipient_key = KeyKind::Value("foo".to_string()); - let sov_service_extra = ExtraFieldsSov::DIDCommV1( - ExtraFieldsDidCommV1::builder() - .set_recipient_keys(vec![recipient_key]) - .build(), - ); - let service = ServiceBuilder::::new( - Uri::new("xyz://example.org")?, - Url::new("http://example.org")?, - sov_service_extra, - ) - .add_service_type("DIDCommMessaging".to_string())? - .build(); - +async fn demo() -> Result<(), Box> { let did_url = DidUrl::parse("did:foo:bar#key-1".into())?; let did = Did::parse("did:foo:bar".into())?; let verification_method = VerificationMethod::builder( @@ -44,7 +32,6 @@ fn demo() -> Result<(), Box> { let ddo = DidDocument::builder(did) .add_verification_method(verification_method) - .add_service(service) .build(); println!("Did document: \n{}", serde_json::to_string_pretty(&ddo)?); @@ -60,10 +47,24 @@ fn demo() -> Result<(), Box> { peer_did_3_v2 ); + let DidResolutionOutput { did_document, .. } = PeerDidResolver::new() + .resolve( + peer_did_2.did(), + &PeerDidResolutionOptions { + encoding: Some(PublicKeyEncoding::Base58), + }, + ) + .await + .unwrap(); + println!( + "Decoded did document: \n{}", + serde_json::to_string_pretty(&did_document)? + ); + Ok(()) } -#[test] -fn demo_test() -> Result<(), Box> { - demo() +#[tokio::test] +async fn demo_test() -> Result<(), Box> { + demo().await } diff --git a/did_core/did_methods/did_peer/src/error.rs b/did_core/did_methods/did_peer/src/error.rs index d47b5b74fa..11f1576110 100644 --- a/did_core/did_methods/did_peer/src/error.rs +++ b/did_core/did_methods/did_peer/src/error.rs @@ -1,24 +1,26 @@ use std::convert::Infallible; -use did_doc::schema::verification_method::VerificationMethodType; -use thiserror::Error; +use did_doc::schema::{ + types::uri::UriWrapperError, + verification_method::{error::KeyDecodingError, VerificationMethodType}, +}; use crate::peer_did::numalgos::kind::NumalgoKind; -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum DidPeerError { #[error("DID parser error: {0}")] DidParserError(#[from] did_parser::ParseError), + #[error("Parsing error: {0}")] + ParsingError(String), #[error("DID validation error: {0}")] DidValidationError(String), #[error("DID document builder error: {0}")] DidDocumentBuilderError(#[from] did_doc::error::DidDocumentBuilderError), #[error("Invalid key reference: {0}")] InvalidKeyReference(String), - #[error("Invalid service type")] - InvalidServiceType, - #[error("Sovrin DID document builder error: {0}")] - DidDocumentSovBuilderError(#[from] did_doc_sov::error::DidDocumentSovError), + #[error("Invalid service: {0}")] + InvalidService(String), #[error("Unsupported numalgo: {0}")] UnsupportedNumalgo(NumalgoKind), #[error("Invalid numalgo character: {0}")] @@ -29,6 +31,8 @@ pub enum DidPeerError { UnsupportedVerificationMethodType(VerificationMethodType), #[error("Base 64 decoding error")] Base64DecodingError(#[from] base64::DecodeError), + #[error("Key decoding error")] + KeyDecodingError(#[from] KeyDecodingError), #[error("JSON error: {0}")] JsonError(#[from] serde_json::Error), #[error("Regex error: {0}")] @@ -42,3 +46,9 @@ impl From for DidPeerError { panic!("Attempted to convert an Infallible error") } } + +impl From for DidPeerError { + fn from(error: UriWrapperError) -> Self { + DidPeerError::ParsingError(error.to_string()) + } +} diff --git a/did_core/did_methods/did_peer/src/helpers.rs b/did_core/did_methods/did_peer/src/helpers.rs new file mode 100644 index 0000000000..9c8725da97 --- /dev/null +++ b/did_core/did_methods/did_peer/src/helpers.rs @@ -0,0 +1,18 @@ +use std::collections::HashMap; + +use did_doc::error::DidDocumentBuilderError; +use serde::Serialize; +use serde_json::Value; + +pub fn convert_to_hashmap( + value: &T, +) -> Result, DidDocumentBuilderError> { + let serialized_value = serde_json::to_value(value)?; + + match serialized_value { + Value::Object(map) => Ok(map.into_iter().collect()), + _ => Err(DidDocumentBuilderError::CustomError( + "Expected JSON object".to_string(), + )), + } +} diff --git a/did_core/did_methods/did_peer/src/lib.rs b/did_core/did_methods/did_peer/src/lib.rs index 462a582971..7a7f8a038a 100644 --- a/did_core/did_methods/did_peer/src/lib.rs +++ b/did_core/did_methods/did_peer/src/lib.rs @@ -1,5 +1,6 @@ extern crate display_as_json; pub mod error; +pub mod helpers; pub mod peer_did; pub mod resolver; diff --git a/did_core/did_methods/did_peer/src/peer_did/generic.rs b/did_core/did_methods/did_peer/src/peer_did/generic.rs index aa34844282..b3e8d63005 100644 --- a/did_core/did_methods/did_peer/src/peer_did/generic.rs +++ b/did_core/did_methods/did_peer/src/peer_did/generic.rs @@ -75,10 +75,9 @@ mod tests { const VALID_PEER_DID_NUMALGO2: &str = "did:peer:2\ .Ez6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH\ .VzXwpBnMdCm1cLmKuzgESn29nqnonp1ioqrQMRHNsmjMyppzx8xB2pv7cw8q1PdDacSrdWE3dtB9f7Nxk886mdzNFoPtY\ - .SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0"; + .SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0"; - const INVALID_PEER_DID_NUMALGO2: &str = "did:peer:2\ - .SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX1"; + const INVALID_PEER_DID_NUMALGO2: &str = "did:peer:2.Qqqq"; const VALID_PEER_DID_NUMALGO3: &str = "did:peer:3.d8da5079c166b183cf815ee27747f34e116977103d8b23c96dcba9a9d9429688"; diff --git a/did_core/did_methods/did_peer/src/peer_did/mod.rs b/did_core/did_methods/did_peer/src/peer_did/mod.rs index a1e651d637..5a94d258b0 100644 --- a/did_core/did_methods/did_peer/src/peer_did/mod.rs +++ b/did_core/did_methods/did_peer/src/peer_did/mod.rs @@ -9,7 +9,6 @@ use core::fmt; use std::{fmt::Display, marker::PhantomData}; use did_doc::schema::did_doc::DidDocument; -use did_doc_sov::extra_fields::ExtraFieldsSov; use did_parser::Did; use serde::{ de::{self, Visitor}, @@ -43,17 +42,13 @@ impl PeerDid { } pub trait FromDidDoc: Numalgo { - fn from_did_doc( - did_document: DidDocument, - ) -> Result, DidPeerError> + fn from_did_doc(did_document: DidDocument) -> Result, DidPeerError> where Self: Sized; } impl PeerDid { - pub fn from_did_doc( - did_document: DidDocument, - ) -> Result, DidPeerError> { + pub fn from_did_doc(did_document: DidDocument) -> Result, DidPeerError> { N::from_did_doc(did_document) } } @@ -108,18 +103,29 @@ impl From> for Did { } } +impl AsRef for PeerDid { + fn as_ref(&self) -> &Did { + self.did() + } +} + #[cfg(test)] mod tests { - use super::*; - use crate::peer_did::numalgos::{numalgo2::Numalgo2, numalgo3::Numalgo3}; + use crate::{ + error::DidPeerError, + peer_did::{ + numalgos::{numalgo2::Numalgo2, numalgo3::Numalgo3}, + PeerDid, + }, + }; const VALID_PEER_DID_NUMALGO2: &str = "did:peer:2\ .Ez6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH\ .VzXwpBnMdCm1cLmKuzgESn29nqnonp1ioqrQMRHNsmjMyppzx8xB2pv7cw8q1PdDacSrdWE3dtB9f7Nxk886mdzNFoPtY\ - .SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0"; + .SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0"; const VALID_PEER_DID_NUMALGO3: &str = - "did:peer:3.d8da5079c166b183cf815ee27747f34e116977103d8b23c96dcba9a9d9429688"; + "did:peer:3.71ae6ec6f44acf6bc5dcd7ad1f3364a3a328f6f9c06da2be15786dcabbb18c2a"; fn peer_did_numalgo2() -> PeerDid { PeerDid { @@ -136,6 +142,8 @@ mod tests { } mod parse { + use pretty_assertions::assert_eq; + use super::*; macro_rules! generate_negative_parse_test { @@ -189,6 +197,8 @@ mod tests { } mod to_numalgo3 { + use pretty_assertions::assert_eq; + use super::*; #[test] @@ -221,6 +231,8 @@ mod tests { } mod deserialize { + use pretty_assertions::assert_eq; + use super::*; #[test] diff --git a/did_core/did_methods/did_peer/src/peer_did/numalgos/mod.rs b/did_core/did_methods/did_peer/src/peer_did/numalgos/mod.rs index 2fa91049c6..d7079be668 100644 --- a/did_core/did_methods/did_peer/src/peer_did/numalgos/mod.rs +++ b/did_core/did_methods/did_peer/src/peer_did/numalgos/mod.rs @@ -5,7 +5,6 @@ pub mod numalgo2; pub mod numalgo3; use did_doc::schema::did_doc::DidDocument; -use did_doc_sov::extra_fields::ExtraFieldsSov; use did_parser::Did; use crate::{ @@ -37,5 +36,5 @@ pub trait ResolvableNumalgo: Numalgo { &self, did: &Did, public_key_encoding: PublicKeyEncoding, - ) -> Result, DidPeerError>; + ) -> Result; } 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 96973af5c0..fceb6ecfd2 100644 --- a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/encoding.rs +++ b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/encoding.rs @@ -3,24 +3,22 @@ use std::cmp::Ordering; use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine}; use did_doc::schema::{ did_doc::DidDocument, - service::Service, - utils::OneOrList, verification_method::{VerificationMethod, VerificationMethodKind}, }; -use did_doc_sov::extra_fields::ExtraFieldsSov; use public_key::Key; use crate::{ error::DidPeerError, peer_did::numalgos::numalgo2::{ - purpose::ElementPurpose, service_abbreviated::ServiceAbbreviated, + purpose::ElementPurpose, + service_abbreviation::{abbreviate_service, ServiceAbbreviatedDidPeer2}, verification_method::get_key_by_verification_method, }, }; -pub fn append_encoded_key_segments( +pub(crate) fn append_encoded_key_segments( mut did: String, - did_document: &DidDocument, + did_document: &DidDocument, ) -> Result { for am in did_document.assertion_method() { did = append_encoded_key_segment(did, did_document, am, ElementPurpose::Assertion)?; @@ -64,15 +62,15 @@ pub fn append_encoded_key_segments( Ok(did) } -pub fn append_encoded_service_segment( +pub(crate) fn append_encoded_service_segment( mut did: String, - did_document: &DidDocument, + did_document: &DidDocument, ) -> Result { let services_abbreviated = did_document .service() .iter() .map(abbreviate_service) - .collect::, _>>()?; + .collect::, _>>()?; let service_encoded = match services_abbreviated.len().cmp(&1) { Ordering::Less => None, @@ -81,7 +79,10 @@ pub fn append_encoded_service_segment( Some(STANDARD_NO_PAD.encode(serde_json::to_vec(&service_abbreviated)?)) } Ordering::Greater => { - Some(STANDARD_NO_PAD.encode(serde_json::to_vec(&services_abbreviated)?)) + // todo: Easy fix; this should be implemented by iterating over each service and then + // appending the services in peer did, separated by a dot. + // See https://identity.foundation/peer-did-method-spec/ + unimplemented!("Multiple services are not supported yet") } }; @@ -95,7 +96,7 @@ pub fn append_encoded_service_segment( fn append_encoded_key_segment( did: String, - did_document: &DidDocument, + did_document: &DidDocument, vm: &VerificationMethodKind, purpose: ElementPurpose, ) -> Result { @@ -105,7 +106,7 @@ fn append_encoded_key_segment( } fn resolve_verification_method<'a>( - did_document: &'a DidDocument, + did_document: &'a DidDocument, vm: &'a VerificationMethodKind, ) -> Result<&'a VerificationMethod, DidPeerError> { match vm { @@ -122,54 +123,27 @@ fn append_key_to_did(mut did: String, key: Key, purpose: ElementPurpose) -> Stri did } -fn abbreviate_service( - service: &Service, -) -> Result { - let service_endpoint = service.service_endpoint().clone(); - let (routing_keys, accept) = match service.extra() { - ExtraFieldsSov::DIDCommV2(extra) => { - (extra.routing_keys().to_vec(), extra.accept().to_vec()) - } - ExtraFieldsSov::DIDCommV1(extra) => { - (extra.routing_keys().to_vec(), extra.accept().to_vec()) - } - _ => (vec![], vec![]), - }; - let service_type = match service.service_type() { - OneOrList::One(service_type) => service_type, - OneOrList::List(service_types) => { - if let Some(first_service) = service_types.first() { - first_service - } else { - return Err(DidPeerError::InvalidServiceType); - } - } - }; - - let service_type_abbr = if service_type.to_lowercase() == "didcommmessaging" { - "dm" - } else { - service_type - }; - - Ok(ServiceAbbreviated::builder() - .set_service_type(service_type_abbr.to_string()) - .set_service_endpoint(service_endpoint) - .set_routing_keys(routing_keys) - .set_accept_types(accept) - .build()) -} - #[cfg(test)] mod tests { use did_doc::schema::{ - service::Service, + service::{ + service_key_kind::ServiceKeyKind, + typed::{didcommv2::ExtraFieldsDidCommV2, ServiceType}, + Service, + }, + types::uri::Uri, + utils::OneOrList, verification_method::{VerificationMethod, VerificationMethodType}, }; - use did_doc_sov::extra_fields::{didcommv2::ExtraFieldsDidCommV2, KeyKind}; use did_parser::DidUrl; + use pretty_assertions::assert_eq; use super::*; + use crate::{ + helpers::convert_to_hashmap, + peer_did::{numalgos::numalgo2::Numalgo2, PeerDid}, + resolver::options::PublicKeyEncoding, + }; fn create_verification_method( did_full: String, @@ -185,15 +159,6 @@ mod tests { .build() } - fn create_did_document_with_service( - did_full: String, - service: Service, - ) -> DidDocument { - DidDocument::::builder(did_full.parse().unwrap()) - .add_service(service) - .build() - } - #[test] fn test_append_encoded_key_segments() { let did = "did:peer:2"; @@ -212,7 +177,7 @@ mod tests { VerificationMethodType::Ed25519VerificationKey2020, ); - let did_document = DidDocument::::builder(did_full.parse().unwrap()) + let did_document = DidDocument::builder(did_full.parse().unwrap()) .add_key_agreement(vm_0) .add_verification_method(vm_1) .build(); @@ -221,33 +186,46 @@ mod tests { assert_eq!(did, did_full); } - #[test] - fn test_append_encoded_service_segment() { + #[tokio::test] + async fn test_append_encoded_service_segment() { let did = "did:peer:2"; - let service = "eyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0"; - let did_full = format!("{}.S{}", did, service); - - let extra = ExtraFieldsSov::DIDCommV2( - ExtraFieldsDidCommV2::builder() - .set_routing_keys(vec![KeyKind::Reference( - "did:example:somemediator#somekey".parse().unwrap(), - )]) - .add_accept("didcomm/aip2;env=rfc587".into()) - .build(), - ); - let service = Service::::builder( - did_full.parse().unwrap(), + let service = "eyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0"; + let did_expected = format!("{}.S{}", did, service); + + let extra = ExtraFieldsDidCommV2::builder() + .routing_keys(vec![ServiceKeyKind::Reference( + "did:example:somemediator#somekey".parse().unwrap(), + )]) + .accept(vec!["didcomm/v2".into(), "didcomm/aip2;env=rfc587".into()]) + .build(); + + let service = Service::new( + Uri::new("#service-0").unwrap(), "https://example.com/endpoint".parse().unwrap(), - extra, - ) - .add_service_type("DIDCommMessaging".to_string()) - .unwrap() - .build(); + OneOrList::One(ServiceType::DIDCommV2), + convert_to_hashmap(&extra).unwrap(), + ); - let did_document = create_did_document_with_service(did_full.to_string(), service); + let did_document = DidDocument::builder(did_expected.parse().unwrap()) + .add_service(service) + .build(); let did = append_encoded_service_segment(did.to_string(), &did_document).unwrap(); - assert_eq!(did, did_full); + + let did_parsed = PeerDid::::parse(did.clone()).unwrap(); + let ddo = did_parsed + .to_did_doc_builder(PublicKeyEncoding::Base58) + .unwrap() + .build(); + + let did_expected_parsed = PeerDid::::parse(did_expected.clone()).unwrap(); + let ddo_expected = did_expected_parsed + .to_did_doc_builder(PublicKeyEncoding::Base58) + .unwrap() + .build(); + + assert_eq!(ddo, ddo_expected); + assert_eq!(did, did_expected); } #[test] @@ -262,7 +240,7 @@ mod tests { VerificationMethodType::X25519KeyAgreementKey2020, ); - let did_document = DidDocument::::builder(did_full.parse().unwrap()) + let did_document = DidDocument::builder(did_full.parse().unwrap()) .add_key_agreement(vm) .build(); @@ -294,7 +272,7 @@ mod tests { VerificationMethodType::Ed25519VerificationKey2020, ); - let did_document = DidDocument::::builder(did_full.parse().unwrap()) + let did_document = DidDocument::builder(did_full.parse().unwrap()) .add_assertion_method(vm_0) .add_key_agreement(vm_1) .add_verification_method(vm_2) @@ -317,7 +295,7 @@ mod tests { VerificationMethodType::X25519KeyAgreementKey2020, ); - let did_document = DidDocument::::builder(did_full.parse().unwrap()) + let did_document = DidDocument::builder(did_full.parse().unwrap()) .add_verification_method(vm) .add_key_agreement_reference(DidUrl::from_fragment(reference.to_string()).unwrap()) .build(); 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 new file mode 100644 index 0000000000..05e0758e25 --- /dev/null +++ b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/helpers.rs @@ -0,0 +1,241 @@ +use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine}; +use did_doc::schema::did_doc::DidDocumentBuilder; +use did_parser::Did; +use public_key::Key; + +use crate::{ + error::DidPeerError, + peer_did::numalgos::numalgo2::{ + purpose::ElementPurpose, + service_abbreviation::{deabbreviate_service, ServiceAbbreviatedDidPeer2}, + verification_method::get_verification_methods_by_key, + }, + resolver::options::PublicKeyEncoding, +}; + +pub fn didpeer_elements_to_diddoc( + mut did_doc_builder: DidDocumentBuilder, + did: &Did, + public_key_encoding: PublicKeyEncoding, +) -> Result { + let mut service_index: usize = 0; + + // Skipping one here because the first element is empty string + for element in did.id()[1..].split('.').skip(1) { + did_doc_builder = process_element( + element, + did_doc_builder, + &mut service_index, + did, + public_key_encoding, + )?; + } + + Ok(did_doc_builder) +} + +fn process_element( + element: &str, + mut did_doc_builder: DidDocumentBuilder, + service_index: &mut usize, + did: &Did, + public_key_encoding: PublicKeyEncoding, +) -> Result { + let purpose: ElementPurpose = element + .chars() + .next() + .ok_or(DidPeerError::DidValidationError(format!( + "No purpose code following element separator in '{}'", + element + )))? + .try_into()?; + let purposeless_element = &element[1..]; + + if purpose == ElementPurpose::Service { + did_doc_builder = + process_service_element(purposeless_element, did_doc_builder, service_index)?; + } else { + did_doc_builder = process_key_element( + purposeless_element, + did_doc_builder, + did, + public_key_encoding, + purpose, + )?; + } + + Ok(did_doc_builder) +} + +fn process_service_element( + element: &str, + mut did_doc_builder: DidDocumentBuilder, + service_index: &mut usize, +) -> Result { + let decoded = STANDARD_NO_PAD.decode(element)?; + let service: ServiceAbbreviatedDidPeer2 = serde_json::from_slice(&decoded)?; + + did_doc_builder = did_doc_builder.add_service(deabbreviate_service(service, *service_index)?); + *service_index += 1; + + Ok(did_doc_builder) +} + +fn process_key_element( + element: &str, + mut did_doc_builder: DidDocumentBuilder, + 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)?; + + for vm in vms.into_iter() { + match purpose { + ElementPurpose::Assertion => { + did_doc_builder = did_doc_builder.add_assertion_method(vm); + } + ElementPurpose::Encryption => { + did_doc_builder = did_doc_builder.add_key_agreement(vm); + } + ElementPurpose::Verification => { + did_doc_builder = did_doc_builder.add_verification_method(vm); + } + ElementPurpose::CapabilityInvocation => { + did_doc_builder = did_doc_builder.add_capability_invocation(vm) + } + ElementPurpose::CapabilityDelegation => { + did_doc_builder = did_doc_builder.add_capability_delegation(vm) + } + _ => return Err(DidPeerError::UnsupportedPurpose(purpose.into())), + } + } + + Ok(did_doc_builder) +} + +#[cfg(test)] +mod tests { + use did_doc::schema::service::typed::ServiceType; + use pretty_assertions::assert_eq; + + use super::*; + + #[test] + fn test_process_elements_empty_did() { + let did: Did = "did:peer:2".parse().unwrap(); + + let built_ddo = didpeer_elements_to_diddoc( + DidDocumentBuilder::new(did.clone()), + &did, + PublicKeyEncoding::Base58, + ) + .unwrap() + .build(); + assert_eq!(built_ddo.id().to_string(), did.to_string()); + } + + #[test] + fn test_process_elements_with_multiple_elements() { + let did: Did = + "did:peer:2.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.\ + SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9" + .parse() + .unwrap(); + + let processed_did_doc_builder = didpeer_elements_to_diddoc( + DidDocumentBuilder::new(did.clone()), + &did, + PublicKeyEncoding::Multibase, + ) + .unwrap(); + let built_ddo = processed_did_doc_builder.build(); + + assert_eq!(built_ddo.id().to_string(), did.to_string()); + assert_eq!(built_ddo.verification_method().len(), 1); + assert_eq!(built_ddo.service().len(), 1); + } + + #[test] + fn test_process_elements_error_on_invalid_element() { + let did: Did = + "did:peer:2.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.\ + SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9.\ + Xinvalid" + .parse() + .unwrap(); + + match didpeer_elements_to_diddoc( + DidDocumentBuilder::new(did.clone()), + &did, + PublicKeyEncoding::Multibase, + ) { + Ok(_) => panic!("Expected Err, got Ok"), + Err(e) => { + assert!(matches!(e, DidPeerError::UnsupportedPurpose('X'))); + } + } + } + + #[test] + fn test_process_service_element_one_service() { + let purposeless_service_element = + "eyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9"; + let did: Did = format!("did:peer:2.S{}", purposeless_service_element) + .parse() + .unwrap(); + let mut index = 0; + let ddo_builder = DidDocumentBuilder::new(did); + let built_ddo = + process_service_element(purposeless_service_element, ddo_builder, &mut index) + .unwrap() + .build(); + assert_eq!(built_ddo.service().len(), 1); + let service = built_ddo.service().first().unwrap(); + assert_eq!(service.id().to_string(), "#service-0".to_string()); + assert_eq!(service.service_types(), vec!(ServiceType::DIDCommV2)); + assert_eq!( + service.service_endpoint().to_string(), + "https://example.com/endpoint".to_string() + ); + } + + #[test] + fn test_process_key_element() { + let purposeless_key_element = "z6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V"; + let did: Did = format!("did:peer:2.V{}", purposeless_key_element) + .parse() + .unwrap(); + + let ddo_builder = DidDocumentBuilder::new(did.clone()); + let public_key_encoding = PublicKeyEncoding::Multibase; + let built_ddo = process_key_element( + purposeless_key_element, + ddo_builder, + &did, + public_key_encoding, + ElementPurpose::Verification, + ) + .unwrap() + .build(); + + assert_eq!(built_ddo.verification_method().len(), 1); + let vm = built_ddo.verification_method().first().unwrap(); + assert_eq!(vm.id().to_string(), "#6MkqRYqQ"); + assert_eq!(vm.controller().to_string(), did.to_string()); + } + + #[test] + fn test_process_key_element_negative() { + let did: Did = "did:peer:2".parse().unwrap(); + assert!(process_key_element( + "z6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V", + DidDocumentBuilder::new(did.clone()), + &did, + PublicKeyEncoding::Multibase, + ElementPurpose::Service + ) + .is_err()); + } +} 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 2eaf3d0f99..4afed44c4c 100644 --- a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/mod.rs +++ b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/mod.rs @@ -1,30 +1,24 @@ -use did_doc::schema::did_doc::DidDocument; -use did_doc_sov::extra_fields::ExtraFieldsSov; -use did_parser::Did; +use did_doc::schema::did_doc::{DidDocument, DidDocumentBuilder}; use encoding::{append_encoded_key_segments, append_encoded_service_segment}; use sha256::digest; use crate::{ error::DidPeerError, peer_did::{ - numalgos::{ - numalgo2::resolve::resolve_numalgo2, numalgo3::Numalgo3, Numalgo, ResolvableNumalgo, - }, + numalgos::{numalgo2::helpers::didpeer_elements_to_diddoc, numalgo3::Numalgo3, Numalgo}, FromDidDoc, PeerDid, }, resolver::options::PublicKeyEncoding, }; mod encoding; +mod helpers; mod purpose; -pub mod resolve; -mod service_abbreviated; +mod service_abbreviation; mod verification_method; impl FromDidDoc for Numalgo2 { - fn from_did_doc( - did_document: DidDocument, - ) -> Result, DidPeerError> { + fn from_did_doc(did_document: DidDocument) -> Result, DidPeerError> { let mut did = String::from("did:peer:2"); did = append_encoded_key_segments(did, &did_document)?; did = append_encoded_service_segment(did, &did_document)?; @@ -38,6 +32,16 @@ impl PeerDid { let numalgoless_id_hashed = digest(numalgoless_id); PeerDid::::parse(format!("did:peer:3.{}", numalgoless_id_hashed)) } + + pub(crate) fn to_did_doc_builder( + &self, + public_key_encoding: PublicKeyEncoding, + ) -> Result { + let mut did_doc_builder: DidDocumentBuilder = DidDocument::builder(self.did().clone()); + did_doc_builder = + didpeer_elements_to_diddoc(did_doc_builder, self.did(), public_key_encoding)?; + Ok(did_doc_builder) + } } #[derive(Clone, Copy, Default, Debug, PartialEq)] @@ -47,12 +51,60 @@ impl Numalgo for Numalgo2 { const NUMALGO_CHAR: char = '2'; } -impl ResolvableNumalgo for Numalgo2 { - fn resolve( - &self, - did: &Did, - public_key_encoding: PublicKeyEncoding, - ) -> Result, DidPeerError> { - resolve_numalgo2(did, public_key_encoding).map(|builder| builder.build()) +#[cfg(test)] +mod test { + use did_doc::schema::did_doc::DidDocument; + use pretty_assertions::assert_eq; + use serde_json::{from_value, json}; + + use crate::{ + peer_did::{numalgos::numalgo2::Numalgo2, PeerDid}, + resolver::options::PublicKeyEncoding, + }; + + #[test] + fn test_peer_did_2_encode_decode() { + let expected_did_peer = "did:peer:2.Ez6MkkukgyKAdBN46UAHvia2nxmioo74F6YdvW1nBT1wfKKha.Vz6MkfoapUdLHHgSMq5PYhdHYCoqGuRku2i17cQ9zAoR5cLSm.SeyJpZCI6IiNmb29iYXIiLCJ0IjpbImRpZC1jb21tdW5pY2F0aW9uIl0sInMiOiJodHRwOi8vZHVtbXl1cmwub3JnLyIsInIiOlsiIzZNa2t1a2d5Il0sImEiOlsiZGlkY29tbS9haXAyO2Vudj1yZmMxOSJdfQ"; + let value = json!({ + "id": expected_did_peer, + "verificationMethod": [ + { + "id": "#6MkfoapU", + "controller": expected_did_peer, + "type": "Ed25519VerificationKey2020", + "publicKeyBase58": "2MKmtP5qx8wtiaYr24KhMiHH5rV3cpkkvPF4LXT4h7fP" + } + ], + "keyAgreement": [ + { + "id": "#6Mkkukgy", + "controller": expected_did_peer, + "type": "Ed25519VerificationKey2020", + "publicKeyBase58": "7TVeP4vBqpZdMfTE314x7gAoyXnPgfPZozsFcjyeQ6vC" + } + ], + "service": [ + { + "id": "#foobar", + "type": [ + "did-communication" + ], + "serviceEndpoint": "http://dummyurl.org/", + "routingKeys": ["#6Mkkukgy"], + "accept": [ + "didcomm/aip2;env=rfc19" + ], + } + ] + }); + let ddo_original: DidDocument = from_value(value).unwrap(); + let did_peer: PeerDid = PeerDid::from_did_doc(ddo_original.clone()).unwrap(); + assert_eq!(did_peer.to_string(), expected_did_peer); + + let ddo_decoded: DidDocument = did_peer + .to_did_doc_builder(PublicKeyEncoding::Base58) + .unwrap() + .build(); + assert_eq!(ddo_original, ddo_decoded); } } diff --git a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/resolve/helpers.rs b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/resolve/helpers.rs deleted file mode 100644 index fdddb11082..0000000000 --- a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/resolve/helpers.rs +++ /dev/null @@ -1,471 +0,0 @@ -use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine}; -use did_doc::schema::{ - did_doc::DidDocumentBuilder, service::Service, types::uri::Uri, utils::OneOrList, -}; -use did_doc_sov::extra_fields::{ - aip1::ExtraFieldsAIP1, didcommv2::ExtraFieldsDidCommV2, ExtraFieldsSov, -}; -use did_parser::Did; -use public_key::Key; - -use crate::{ - error::DidPeerError, - peer_did::numalgos::numalgo2::{ - purpose::ElementPurpose, service_abbreviated::ServiceAbbreviated, - verification_method::get_verification_methods_by_key, - }, - resolver::options::PublicKeyEncoding, -}; - -pub fn process_elements( - mut did_doc_builder: DidDocumentBuilder, - did: &Did, - public_key_encoding: PublicKeyEncoding, -) -> Result, DidPeerError> { - let mut service_index: usize = 0; - - // Skipping one here because the first element is empty string - for element in did.id()[1..].split('.').skip(1) { - did_doc_builder = process_element( - element, - did_doc_builder, - &mut service_index, - did, - public_key_encoding, - )?; - } - - Ok(did_doc_builder) -} - -fn process_element( - element: &str, - mut did_doc_builder: DidDocumentBuilder, - service_index: &mut usize, - did: &Did, - public_key_encoding: PublicKeyEncoding, -) -> Result, DidPeerError> { - let purpose: ElementPurpose = element - .chars() - .next() - .ok_or(DidPeerError::DidValidationError(format!( - "No purpose code following element separator in '{}'", - element - )))? - .try_into()?; - let purposeless_element = &element[1..]; - - if purpose == ElementPurpose::Service { - did_doc_builder = - process_service_element(purposeless_element, did_doc_builder, service_index)?; - } else { - did_doc_builder = process_key_element( - purposeless_element, - did_doc_builder, - did, - public_key_encoding, - purpose, - )?; - } - - Ok(did_doc_builder) -} - -fn process_service_element( - element: &str, - mut did_doc_builder: DidDocumentBuilder, - service_index: &mut usize, -) -> Result, DidPeerError> { - let decoded = STANDARD_NO_PAD.decode(element)?; - let service: OneOrList = serde_json::from_slice(&decoded)?; - - match service { - OneOrList::One(service) => { - did_doc_builder = - did_doc_builder.add_service(deabbreviate_service(service, *service_index)?); - *service_index += 1; - } - OneOrList::List(services) => { - for service in services.into_iter() { - did_doc_builder = - did_doc_builder.add_service(deabbreviate_service(service, *service_index)?); - *service_index += 1; - } - } - } - - Ok(did_doc_builder) -} - -fn process_key_element( - element: &str, - mut did_doc_builder: DidDocumentBuilder, - did: &Did, - public_key_encoding: PublicKeyEncoding, - purpose: ElementPurpose, -) -> Result, DidPeerError> { - let key = Key::from_fingerprint(element)?; - let vms = get_verification_methods_by_key(&key, did, public_key_encoding)?; - - for vm in vms.into_iter() { - match purpose { - ElementPurpose::Assertion => { - did_doc_builder = did_doc_builder.add_assertion_method(vm); - } - ElementPurpose::Encryption => { - did_doc_builder = did_doc_builder.add_key_agreement(vm); - } - ElementPurpose::Verification => { - did_doc_builder = did_doc_builder.add_verification_method(vm); - } - ElementPurpose::CapabilityInvocation => { - did_doc_builder = did_doc_builder.add_capability_invocation(vm) - } - ElementPurpose::CapabilityDelegation => { - did_doc_builder = did_doc_builder.add_capability_delegation(vm) - } - _ => return Err(DidPeerError::UnsupportedPurpose(purpose.into())), - } - } - - Ok(did_doc_builder) -} - -fn deabbreviate_service( - service: ServiceAbbreviated, - index: usize, -) -> Result, DidPeerError> { - let service_type = match service.service_type() { - "dm" => "DIDCommMessaging".to_string(), - t => t.to_string(), - }; - - let id = format!("#{}-{}", service_type.to_lowercase(), index).parse()?; - - if service.routing_keys().is_empty() { - build_service_aip1(service, id, service_type) - } else { - build_service_didcommv2(service, id, service_type) - } -} - -fn build_service_aip1( - service: ServiceAbbreviated, - id: Uri, - service_type: String, -) -> Result, DidPeerError> { - Ok(Service::::builder( - id, - service.service_endpoint().parse()?, - ExtraFieldsSov::AIP1(ExtraFieldsAIP1::default()), - ) - .add_service_type(service_type)? - .build()) -} - -fn build_service_didcommv2( - service: ServiceAbbreviated, - id: Uri, - service_type: String, -) -> Result, DidPeerError> { - let extra_builder = ExtraFieldsDidCommV2::builder() - .set_routing_keys(service.routing_keys().to_owned()) - .set_accept(service.accept().to_owned()); - let extra = ExtraFieldsSov::DIDCommV2(extra_builder.build()); - Ok( - Service::::builder(id, service.service_endpoint().parse()?, extra) - .add_service_type(service_type)? - .build(), - ) -} - -#[cfg(test)] -mod tests { - use did_doc::schema::utils::OneOrList; - use did_doc_sov::extra_fields::{AcceptType, ExtraFieldsSov, KeyKind}; - - use super::*; - - #[test] - fn test_process_elements_empty_did() { - let did: Did = "did:peer:2".parse().unwrap(); - - let built_ddo = process_elements( - DidDocumentBuilder::::new(did.clone()), - &did, - PublicKeyEncoding::Base58, - ) - .unwrap() - .build(); - assert_eq!(built_ddo.id().to_string(), did.to_string()); - } - - #[test] - fn test_process_elements_with_multiple_elements() { - let did: Did = "did:peer:2.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.\ - SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9" - .parse() - .unwrap(); - - let processed_did_doc_builder = process_elements( - DidDocumentBuilder::::new(did.clone()), - &did, - PublicKeyEncoding::Multibase, - ) - .unwrap(); - let built_ddo = processed_did_doc_builder.build(); - - assert_eq!(built_ddo.id().to_string(), did.to_string()); - assert_eq!(built_ddo.verification_method().len(), 1); - assert_eq!(built_ddo.service().len(), 1); - } - - #[test] - fn test_process_elements_error_on_invalid_element() { - let did: Did = "did:peer:2.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.\ - SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9.Xinvalid" - .parse() - .unwrap(); - - match process_elements( - DidDocumentBuilder::::new(did.clone()), - &did, - PublicKeyEncoding::Multibase, - ) { - Ok(_) => panic!("Expected Err, got Ok"), - Err(e) => { - assert!(matches!(e, DidPeerError::UnsupportedPurpose('X'))); - } - } - } - - #[test] - fn test_process_service_element_one_service() { - let purposeless_service_element = - "eyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9"; - let did: Did = format!("did:peer:2.S{}", purposeless_service_element) - .parse() - .unwrap(); - let mut index = 0; - let ddo_builder = DidDocumentBuilder::::new(did); - let built_ddo = - process_service_element(purposeless_service_element, ddo_builder, &mut index) - .unwrap() - .build(); - assert_eq!(built_ddo.service().len(), 1); - let service = built_ddo.service().first().unwrap(); - assert_eq!(service.id().to_string(), "#didcommmessaging-0".to_string()); - assert_eq!( - service.service_type().to_string(), - "DIDCommMessaging".to_string() - ); - assert_eq!( - service.service_endpoint().to_string(), - "https://example.com/endpoint".to_string() - ); - } - - #[test] - fn test_process_service_element_multiple_services() { - let purposeless_service_element = "W3sidCI6ImRtIiwicyI6Imh0dHBzOi8vZXhhbXBsZS5jb20vZW5kcG9pbnQiLCJyIjpbImRpZDpleGFtcGxlOnNvbWVtZWRpYXRvciNzb21la2V5Il19LHsidCI6ImV4YW1wbGUiLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludDIiLCJyIjpbImRpZDpleGFtcGxlOnNvbWVtZWRpYXRvciNzb21la2V5MiJdLCJhIjpbImRpZGNvbW0vdjIiLCJkaWRjb21tL2FpcDI7ZW52PXJmYzU4NyJdfV0"; - let did: Did = format!("did:peer:2.S{}", purposeless_service_element) - .parse() - .unwrap(); - let mut index = 0; - let ddo_builder = DidDocumentBuilder::::new(did); - let built_ddo = - process_service_element(purposeless_service_element, ddo_builder, &mut index) - .unwrap() - .build(); - - assert_eq!(built_ddo.service().len(), 2); - - let first_service = built_ddo.service().first().unwrap(); - assert_eq!( - first_service.id().to_string(), - "#didcommmessaging-0".to_string() - ); - assert_eq!( - first_service.service_type().to_string(), - "DIDCommMessaging".to_string() - ); - assert_eq!( - first_service - .extra() - .first_routing_key() - .unwrap() - .to_string(), - "did:example:somemediator#somekey".to_string() - ); - assert_eq!( - first_service.service_endpoint().to_string(), - "https://example.com/endpoint".to_string() - ); - - let second_service = built_ddo.service().get(1).unwrap(); - assert_eq!(second_service.id().to_string(), "#example-1".to_string()); - assert_eq!( - second_service.service_type().to_string(), - "example".to_string() - ); - assert_eq!( - second_service.service_endpoint().to_string(), - "https://example.com/endpoint2".to_string() - ); - assert_eq!( - second_service.extra().accept().unwrap(), - vec![ - AcceptType::DIDCommV2, - AcceptType::Other("didcomm/aip2;env=rfc587".to_string()) - ] - ); - assert_eq!( - second_service - .extra() - .first_routing_key() - .unwrap() - .to_string(), - "did:example:somemediator#somekey2".to_string() - ); - } - - #[test] - fn test_process_key_element() { - let purposeless_key_element = "z6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V"; - let did: Did = format!("did:peer:2.V{}", purposeless_key_element) - .parse() - .unwrap(); - - let ddo_builder = DidDocumentBuilder::::new(did.clone()); - let public_key_encoding = PublicKeyEncoding::Multibase; - let built_ddo = process_key_element( - purposeless_key_element, - ddo_builder, - &did, - public_key_encoding, - ElementPurpose::Verification, - ) - .unwrap() - .build(); - - assert_eq!(built_ddo.verification_method().len(), 1); - let vm = built_ddo.verification_method().first().unwrap(); - assert_eq!(vm.id().to_string(), "#6MkqRYqQ"); - assert_eq!(vm.controller().to_string(), did.to_string()); - } - - #[test] - fn test_process_key_element_negative() { - let did: Did = "did:peer:2".parse().unwrap(); - assert!(process_key_element( - "z6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V", - DidDocumentBuilder::::new(did.clone()), - &did, - PublicKeyEncoding::Multibase, - ElementPurpose::Service - ) - .is_err()); - } - - #[test] - fn test_deabbreviate_service_aip1() { - let service_abbreviated = - ServiceAbbreviated::from_parts("dm", "https://example.com/endpoint", &[], &[]); - let index = 0; - - let service = deabbreviate_service(service_abbreviated, index).unwrap(); - - assert_eq!( - service.service_type().clone(), - OneOrList::One("DIDCommMessaging".to_string()) - ); - assert_eq!(service.id().to_string(), "#didcommmessaging-0"); - - assert!(matches!(service.extra(), ExtraFieldsSov::AIP1(_))); - } - - #[test] - fn test_deabbreviate_service_didcommv2() { - let routing_keys = vec![KeyKind::Value("key1".to_string())]; - let service_abbreviated = ServiceAbbreviated::from_parts( - "dm", - "https://example.com/endpoint", - &routing_keys, - &[], - ); - let index = 0; - - let service = deabbreviate_service(service_abbreviated, index).unwrap(); - - assert_eq!( - service.service_type().clone(), - OneOrList::One("DIDCommMessaging".to_string()) - ); - assert_eq!(service.id().to_string(), "#didcommmessaging-0"); - - match service.extra() { - ExtraFieldsSov::DIDCommV2(extra) => { - assert_eq!(extra.routing_keys(), &routing_keys); - } - _ => panic!("Expected ExtraFieldsSov::DIDCommV2"), - } - } - - #[test] - fn test_build_service_aip1() { - let routing_keys = vec![KeyKind::Value("key1".to_string())]; - let service_abbreviated = ServiceAbbreviated::from_parts( - "dm", - "https://example.com/endpoint", - routing_keys.as_ref(), - vec![].as_ref(), - ); - - let id = Uri::new("did:peer:2").unwrap(); - let service_type = "DIDCommMessaging".to_string(); - - let service = build_service_aip1(service_abbreviated, id, service_type).unwrap(); - - assert_eq!(service.id().to_string(), "did:peer:2"); - assert_eq!( - service.service_type().clone(), - OneOrList::One("DIDCommMessaging".to_string()) - ); - - match service.extra() { - ExtraFieldsSov::AIP1(_) => { /* This is expected */ } - _ => panic!("Expected ExtraFieldsSov::AIP1"), - } - } - - #[test] - fn test_build_service_didcommv2() { - let routing_keys = vec![KeyKind::Value("key1".to_string())]; - let accept = vec![AcceptType::DIDCommV2]; - let service_abbreviated = ServiceAbbreviated::from_parts( - "dm", - "https://example.com/endpoint", - routing_keys.as_ref(), - accept.as_ref(), - ); - - let id = Uri::new("did:peer:2").unwrap(); - let service_type = "DIDCommMessaging".to_string(); - - let service = build_service_didcommv2(service_abbreviated, id, service_type).unwrap(); - - assert_eq!(service.id().to_string(), "did:peer:2"); - assert_eq!( - service.service_type().clone(), - OneOrList::One("DIDCommMessaging".to_string()) - ); - - match service.extra() { - ExtraFieldsSov::DIDCommV2(extra) => { - assert_eq!(extra.routing_keys(), &routing_keys); - assert_eq!(extra.accept(), &accept); - } - _ => panic!("Expected ExtraFieldsSov::DIDCommV2"), - } - } -} diff --git a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/resolve/mod.rs b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/resolve/mod.rs deleted file mode 100644 index daea3c7a41..0000000000 --- a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/resolve/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -mod helpers; - -use did_doc::schema::did_doc::{DidDocument, DidDocumentBuilder}; -use did_doc_sov::extra_fields::ExtraFieldsSov; -use did_parser::Did; - -use self::helpers::process_elements; -use crate::{error::DidPeerError, resolver::options::PublicKeyEncoding}; - -pub fn resolve_numalgo2( - did: &Did, - public_key_encoding: PublicKeyEncoding, -) -> Result, DidPeerError> { - let mut did_doc_builder: DidDocumentBuilder = - DidDocument::builder(did.to_owned()); - - did_doc_builder = process_elements(did_doc_builder, did, public_key_encoding)?; - - Ok(did_doc_builder) -} diff --git a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/service_abbreviated.rs b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/service_abbreviated.rs deleted file mode 100644 index d7f0f7f25b..0000000000 --- a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/service_abbreviated.rs +++ /dev/null @@ -1,114 +0,0 @@ -use did_doc::schema::types::url::Url; -use did_doc_sov::extra_fields::{AcceptType, KeyKind}; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize)] -pub struct ServiceAbbreviated { - #[serde(rename = "t")] - service_type: String, - #[serde(rename = "s")] - service_endpoint: Url, - #[serde(rename = "r")] - #[serde(default)] - #[serde(skip_serializing_if = "Vec::is_empty")] - routing_keys: Vec, - #[serde(rename = "a")] - #[serde(default)] - #[serde(skip_serializing_if = "Vec::is_empty")] - accept: Vec, -} - -impl ServiceAbbreviated { - pub fn builder() -> ServiceAbbreviatedTypeBuilder { - ServiceAbbreviatedTypeBuilder - } - - pub fn service_type(&self) -> &str { - self.service_type.as_ref() - } - - pub fn service_endpoint(&self) -> &str { - self.service_endpoint.as_ref() - } - - pub fn routing_keys(&self) -> &[KeyKind] { - self.routing_keys.as_ref() - } - - pub fn accept(&self) -> &[AcceptType] { - self.accept.as_ref() - } - - #[cfg(test)] - pub(crate) fn from_parts( - service_type: &str, - service_endpoint: &str, - routing_keys: &[KeyKind], - accept: &[AcceptType], - ) -> Self { - Self { - service_type: service_type.to_string(), - service_endpoint: service_endpoint.parse().unwrap(), - routing_keys: routing_keys.to_vec(), - accept: accept.to_vec(), - } - } -} - -#[derive(Default)] -pub struct ServiceAbbreviatedTypeBuilder; - -pub struct ServiceAbbreviatedEndpointBuilder { - service_type: String, -} - -pub struct ServiceAbbreviatedCompleteBuilder { - service_type: String, - service_endpoint: Url, - routing_keys: Vec, - accept: Vec, -} - -impl ServiceAbbreviatedTypeBuilder { - pub fn set_service_type(self, service_type: String) -> ServiceAbbreviatedEndpointBuilder { - ServiceAbbreviatedEndpointBuilder { service_type } - } -} - -impl ServiceAbbreviatedEndpointBuilder { - pub fn set_service_endpoint(self, service_endpoint: Url) -> ServiceAbbreviatedCompleteBuilder { - ServiceAbbreviatedCompleteBuilder { - service_type: self.service_type, - service_endpoint, - routing_keys: Vec::new(), - accept: Vec::new(), - } - } -} - -impl ServiceAbbreviatedCompleteBuilder { - pub fn set_routing_keys( - &mut self, - routing_keys: Vec, - ) -> &mut ServiceAbbreviatedCompleteBuilder { - self.routing_keys = routing_keys; - self - } - - pub fn set_accept_types( - &mut self, - accept: Vec, - ) -> &mut ServiceAbbreviatedCompleteBuilder { - self.accept = accept; - self - } - - pub fn build(&self) -> ServiceAbbreviated { - ServiceAbbreviated { - service_type: self.service_type.to_owned(), - service_endpoint: self.service_endpoint.to_owned(), - routing_keys: self.routing_keys.to_owned(), - accept: self.accept.to_owned(), - } - } -} 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 new file mode 100644 index 0000000000..d3810e22e7 --- /dev/null +++ b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo2/service_abbreviation.rs @@ -0,0 +1,274 @@ +use std::{collections::HashMap, str::FromStr}; + +use did_doc::schema::{ + service::{ + service_accept_type::ServiceAcceptType, service_key_kind::ServiceKeyKind, + typed::ServiceType, Service, + }, + types::uri::Uri, + utils::OneOrList, +}; +use serde::{Deserialize, Serialize}; +use serde_json::from_value; +use url::Url; + +use crate::error::DidPeerError; + +#[derive(Serialize, Deserialize, Debug)] +pub struct ServiceAbbreviatedDidPeer2 { + // https://identity.foundation/peer-did-method-spec/#generating-a-didpeer2 + // > For use with did:peer:2, service id attributes MUST be relative. + // > The service MAY omit the id; however, this is NOT RECOMMEDED (clarified). + id: Option, + #[serde(rename = "t")] + service_type: OneOrList, + #[serde(rename = "s")] + service_endpoint: Url, + #[serde(rename = "r")] + #[serde(default)] + #[serde(skip_serializing_if = "Vec::is_empty")] + routing_keys: Vec, + #[serde(rename = "a")] + #[serde(default)] + #[serde(skip_serializing_if = "Vec::is_empty")] + accept: Vec, +} + +impl ServiceAbbreviatedDidPeer2 { + pub fn new( + id: Option, + service_type: OneOrList, + service_endpoint: Url, + routing_keys: Vec, + accept: Vec, + ) -> Self { + Self { + id, + service_type, + service_endpoint, + routing_keys, + accept, + } + } + + 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. +// Right now any unrecognized field will not be included in the abbreviated form +pub(crate) fn abbreviate_service( + service: &Service, +) -> Result { + let service_endpoint = service.service_endpoint().clone(); + let routing_keys = { + service + .extra() + .get("routingKeys") + .map(|value| { + from_value::>(value.clone()).map_err(|_| { + DidPeerError::ParsingError(format!( + "Could not parse routing keys as Vector of Strings. Value of \ + routing_keys: {}", + value + )) + }) + }) + .unwrap_or_else(|| Ok(vec![])) + }?; + let accept = { + service + .extra() + .get("accept") + .map(|value| { + from_value::>(value.clone()).map_err(|_| { + DidPeerError::ParsingError(format!( + "Could not parse accept as Vector of Strings. Value of accept: {}", + value + )) + }) + }) + .unwrap_or_else(|| Ok(vec![])) + }?; + let service_type = service.service_type().clone(); + let service_types_abbreviated = match service_type { + OneOrList::List(service_types) => { + let abbreviated_list = service_types + .iter() + .map(|value| { + if value == &ServiceType::DIDCommV2 { + "dm".to_string() + } else { + value.to_string() + } + }) + .collect(); + OneOrList::List(abbreviated_list) + } + OneOrList::One(service_type) => { + if service_type == ServiceType::DIDCommV2 { + OneOrList::One("dm".to_string()) + } else { + OneOrList::One(service_type.to_string()) + } + } + }; + Ok(ServiceAbbreviatedDidPeer2::new( + Some(service.id().clone()), + service_types_abbreviated, + service_endpoint, + routing_keys, + accept, + )) +} + +pub(crate) fn deabbreviate_service( + abbreviated: ServiceAbbreviatedDidPeer2, + index: usize, +) -> Result { + let service_type = match abbreviated.service_type().clone() { + OneOrList::One(service_type) => { + let typed = match service_type.as_str() { + "dm" => ServiceType::DIDCommV2, + _ => ServiceType::from_str(&service_type)?, + }; + OneOrList::One(typed) + } + OneOrList::List(service_types) => { + let mut typed = Vec::new(); + for service_type in service_types.iter() { + let service = match service_type.as_str() { + "dm" => ServiceType::DIDCommV2, + _ => ServiceType::from_str(service_type)?, + }; + typed.push(service); + } + OneOrList::List(typed) + } + }; + + let id = abbreviated + .id + .clone() + .unwrap_or(format!("#service-{}", index).parse()?); + + let mut service = Service::new( + id, + abbreviated.service_endpoint().clone(), + service_type, + HashMap::default(), + ); + let routing_keys = abbreviated.routing_keys(); + if !routing_keys.is_empty() { + service.add_extra_field_routing_keys(routing_keys.to_vec())?; + } + let accept = abbreviated.accept(); + if !accept.is_empty() { + service.add_extra_field_accept(accept.to_vec())?; + } + Ok(service) +} + +#[cfg(test)] +mod tests { + use did_doc::schema::{ + service::{ + service_accept_type::ServiceAcceptType, service_key_kind::ServiceKeyKind, + typed::ServiceType, Service, + }, + types::uri::Uri, + utils::OneOrList, + }; + use serde_json::json; + use url::Url; + + use crate::peer_did::numalgos::numalgo2::service_abbreviation::{ + abbreviate_service, deabbreviate_service, ServiceAbbreviatedDidPeer2, + }; + + #[test] + fn test_deabbreviate_service_type_value_dm() { + let service_abbreviated = ServiceAbbreviatedDidPeer2 { + id: Some(Uri::new("#service-0").unwrap()), + service_type: OneOrList::One("dm".to_string()), + service_endpoint: Url::parse("https://example.org").unwrap(), + routing_keys: vec![], + accept: vec![], + }; + let index = 0; + + let service = deabbreviate_service(service_abbreviated, index).unwrap(); + assert_eq!( + service.service_type().clone(), + OneOrList::One(ServiceType::DIDCommV2) + ); + } + + #[test] + fn test_deabbreviate_service() { + let routing_keys = vec![ServiceKeyKind::Value("key1".to_string())]; + let accept = vec![ServiceAcceptType::DIDCommV1]; + let service_endpoint = Url::parse("https://example.com/endpoint").unwrap(); + let service_type = OneOrList::One(ServiceType::Other("foobar".to_string())); + let service_id = Uri::new("#service-0").unwrap(); + let service_abbreviated = ServiceAbbreviatedDidPeer2 { + id: Some(service_id), + service_type: OneOrList::One("foobar".to_string()), + service_endpoint: service_endpoint.clone(), + routing_keys: routing_keys.clone(), + accept: accept.clone(), + }; + let index = 0; + + let service = deabbreviate_service(service_abbreviated, index).unwrap(); + assert_eq!(service.service_type().clone(), service_type); + assert_eq!(service.service_endpoint().clone(), service_endpoint); + assert_eq!(service.extra_field_routing_keys().unwrap(), routing_keys); + assert_eq!(service.extra_field_accept().unwrap(), accept); + } + + #[test] + fn test_abbreviate_deabbreviate_service() { + let service: Service = serde_json::from_value(json!({ + "id": "#0", + "type": [ + "did-communication" + ], + "serviceEndpoint": "http://dummyurl.org/", + "routingKeys": [], + "accept": [ + "didcomm/aip2;env=rfc19" + ], + "priority": 0, + "recipientKeys": [ + "did:key:z6MkkukgyKAdBN46UAHvia2nxmioo74F6YdvW1nBT1wfKKha" + ] + })) + .unwrap(); + let abbreviated = serde_json::to_value(abbreviate_service(&service).unwrap()).unwrap(); + // Note: the abbreviation is lossy! "recipient_keys", "priority" are legacy concept, we + // shouldn't mind + let expected = json!( + { + "id": "#0", + "t": ["did-communication"], + "s": "http://dummyurl.org/", + "a": ["didcomm/aip2;env=rfc19"] + } + ); + assert_eq!(abbreviated, expected); + } +} 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 7a19f733c1..1efca5b3e1 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 @@ -116,11 +116,6 @@ fn to_did_url_reference(key: &Key) -> Result { .map_err(Into::into) } -// TODO: post-rebase check if this is applicable version -// fn to_did_url_reference(key: &Key) -> Result { -// DidUrl::from_fragment(key.short_prefixless_fingerprint()).map_err(Into::into) -// } - #[cfg(test)] mod tests { use did_doc::schema::verification_method::{VerificationMethod, VerificationMethodType}; diff --git a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo3/mod.rs b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo3/mod.rs index 576838b30d..b50b080eff 100644 --- a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo3/mod.rs +++ b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo3/mod.rs @@ -1,5 +1,4 @@ use did_doc::schema::did_doc::DidDocument; -use did_doc_sov::extra_fields::ExtraFieldsSov; use crate::{ error::DidPeerError, @@ -17,9 +16,7 @@ impl Numalgo for Numalgo3 { } impl FromDidDoc for Numalgo3 { - fn from_did_doc( - did_document: DidDocument, - ) -> Result, DidPeerError> { + fn from_did_doc(did_document: DidDocument) -> Result, DidPeerError> { PeerDid::::from_did_doc(did_document)?.to_numalgo3() } } @@ -37,10 +34,10 @@ mod tests { .Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc\ .Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V\ .Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg\ - .SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0".to_string()).unwrap(); + .SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0".to_string()).unwrap(); assert_eq!( PeerDid::::parse( - "did:peer:3.0e857e93798921e83cfc2ef8bee9cafc25f15f4c9c7bee5ed9a9c62b56a62cca" + "did:peer:3.dc2ccfb083931f616e8967dd60017899bcf626134ee2e51a45ebf8d4f245f330" .to_string() ) .unwrap(), diff --git a/did_core/did_methods/did_peer/src/peer_did/numalgos/traits.rs b/did_core/did_methods/did_peer/src/peer_did/numalgos/traits.rs index 4aad8609c2..ac96e7e523 100644 --- a/did_core/did_methods/did_peer/src/peer_did/numalgos/traits.rs +++ b/did_core/did_methods/did_peer/src/peer_did/numalgos/traits.rs @@ -1,5 +1,4 @@ use did_doc::schema::did_doc::DidDocument; -use did_doc_sov::extra_fields::ExtraFieldsSov; use did_parser::Did; use crate::{ @@ -31,5 +30,5 @@ pub trait ResolvableNumalgo: Numalgo { &self, did: &Did, public_key_encoding: PublicKeyEncoding, - ) -> Result, DidPeerError>; + ) -> Result; } diff --git a/did_core/did_methods/did_peer/src/peer_did/parse.rs b/did_core/did_methods/did_peer/src/peer_did/parse.rs index f105479c29..2376838f08 100644 --- a/did_core/did_methods/did_peer/src/peer_did/parse.rs +++ b/did_core/did_methods/did_peer/src/peer_did/parse.rs @@ -6,6 +6,11 @@ pub fn parse_numalgo(did: &Did) -> Result { did.id() .chars() .next() - .ok_or_else(|| DidPeerError::DidValidationError(format!("Invalid did: {}", did.did())))? + .ok_or_else(|| { + DidPeerError::DidValidationError(format!( + "Invalid peer did: {} because numalgo couldn't be parsed", + did.did() + )) + })? .try_into() } diff --git a/did_core/did_methods/did_peer/src/peer_did/validate.rs b/did_core/did_methods/did_peer/src/peer_did/validate.rs index 6d80c22d61..bdff2d22eb 100644 --- a/did_core/did_methods/did_peer/src/peer_did/validate.rs +++ b/did_core/did_methods/did_peer/src/peer_did/validate.rs @@ -6,7 +6,7 @@ use crate::error::DidPeerError; static GROUP_NUMALGO_0_AND_1: &str = r"([01](z)([1-9a-km-zA-HJ-NP-Z]{5,200}))"; static GROUP_NUMALGO_2: &str = - r"(2((.[AEVID](z)([1-9a-km-zA-HJ-NP-Z]{5,200}))+(.(S)[0-9a-zA-Z=]*)?))"; + r"(2((.[AEVID](z)([1-9a-km-zA-HJ-NP-Z]{5,200}))*(.(S)[0-9a-zA-Z=]*)?))"; static GROUP_NUMALGO_3: &str = r"(3\.[0-9a-fA-F]{64})"; pub static PEER_DID_REGEX: Lazy = Lazy::new(|| { diff --git a/did_core/did_methods/did_peer/src/resolver/mod.rs b/did_core/did_methods/did_peer/src/resolver/mod.rs index 04cc37b1d7..d7b8c195c4 100644 --- a/did_core/did_methods/did_peer/src/resolver/mod.rs +++ b/did_core/did_methods/did_peer/src/resolver/mod.rs @@ -1,18 +1,17 @@ use async_trait::async_trait; -use did_doc_sov::extra_fields::ExtraFieldsSov; +use did_doc::schema::did_doc::DidDocumentBuilder; use did_parser::Did; use did_resolver::{ error::GenericError, traits::resolvable::{ - resolution_metadata::DidResolutionMetadata, resolution_options::DidResolutionOptions, - resolution_output::DidResolutionOutput, DidResolvable, + resolution_metadata::DidResolutionMetadata, resolution_output::DidResolutionOutput, + DidResolvable, }, }; +use serde::{Deserialize, Serialize}; use crate::{ - error::DidPeerError, - peer_did::{generic::AnyPeerDid, numalgos::numalgo2::resolve::resolve_numalgo2}, - resolver::options::ExtraFieldsOptions, + error::DidPeerError, peer_did::generic::AnyPeerDid, resolver::options::PublicKeyEncoding, }; pub mod options; @@ -26,23 +25,27 @@ impl PeerDidResolver { } } +#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct PeerDidResolutionOptions { + pub encoding: Option, +} #[async_trait] impl DidResolvable for PeerDidResolver { - type ExtraFieldsService = ExtraFieldsSov; - type ExtraFieldsOptions = ExtraFieldsOptions; + type DidResolutionOptions = PeerDidResolutionOptions; async fn resolve( &self, did: &Did, - options: &DidResolutionOptions, - ) -> Result, GenericError> { + options: &Self::DidResolutionOptions, + ) -> Result { let peer_did = AnyPeerDid::parse(did.to_owned())?; match peer_did { AnyPeerDid::Numalgo2(peer_did) => { - let did_doc = - resolve_numalgo2(peer_did.did(), options.extra().public_key_encoding())? - .add_also_known_as(peer_did.to_numalgo3()?.to_string().parse()?) - .build(); + let encoding = options.encoding.unwrap_or(PublicKeyEncoding::Multibase); + let builder: DidDocumentBuilder = peer_did.to_did_doc_builder(encoding)?; + let did_doc = builder + .add_also_known_as(peer_did.to_numalgo3()?.to_string().parse()?) + .build(); let resolution_metadata = DidResolutionMetadata::builder() .content_type("application/did+json".to_string()) .build(); diff --git a/did_core/did_methods/did_peer/tests/demo.rs b/did_core/did_methods/did_peer/tests/demo.rs index fccd47d863..a0668ea9d1 100644 --- a/did_core/did_methods/did_peer/tests/demo.rs +++ b/did_core/did_methods/did_peer/tests/demo.rs @@ -1,33 +1,27 @@ -use std::error::Error; +use std::{collections::HashMap, error::Error}; use did_doc::schema::{ did_doc::DidDocument, - service::ServiceBuilder, - types::{uri::Uri, url::Url}, + service::{typed::ServiceType, Service}, + types::uri::Uri, + utils::OneOrList, verification_method::{VerificationMethod, VerificationMethodType}, }; -use did_doc_sov::extra_fields::{didcommv1::ExtraFieldsDidCommV1, ExtraFieldsSov, KeyKind}; use did_parser::{Did, DidUrl}; use did_peer::peer_did::{ numalgos::{numalgo2::Numalgo2, numalgo3::Numalgo3}, PeerDid, }; +use url::Url; #[test] fn demo() -> Result<(), Box> { - let recipient_key = KeyKind::Value("foo".to_string()); - let sov_service_extra = ExtraFieldsSov::DIDCommV1( - ExtraFieldsDidCommV1::builder() - .set_recipient_keys(vec![recipient_key]) - .build(), - ); - let service = ServiceBuilder::::new( + let service = Service::new( Uri::new("xyz://example.org")?, - Url::new("http://example.org")?, - sov_service_extra, - ) - .add_service_type("DIDCommMessaging".to_string())? - .build(); + Url::parse("http://example.org")?, + OneOrList::One(ServiceType::DIDCommV2), + HashMap::new(), + ); let did_url = DidUrl::parse("did:foo:bar#key-1".into())?; let did = Did::parse("did:foo:bar".into())?; 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 6ac9cc3aa0..165a7ffe50 100644 --- a/did_core/did_methods/did_peer/tests/fixtures/basic.rs +++ b/did_core/did_methods/did_peer/tests/fixtures/basic.rs @@ -2,26 +2,26 @@ pub static PEER_DID_NUMALGO_2_BASIC: &str = "did:peer:2\ .Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc\ .Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V\ .Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg\ -.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0"; +.SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0"; pub static PEER_DID_NUMALGO_3_BASIC: &str = - "did:peer:3.0e857e93798921e83cfc2ef8bee9cafc25f15f4c9c7bee5ed9a9c62b56a62cca"; + "did:peer:3.dc2ccfb083931f616e8967dd60017899bcf626134ee2e51a45ebf8d4f245f330"; pub static DID_DOC_BASIC: &str = r##" { - "id": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0", - "alsoKnownAs": ["did:peer:3.0e857e93798921e83cfc2ef8bee9cafc25f15f4c9c7bee5ed9a9c62b56a62cca"], + "id": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0", + "alsoKnownAs": ["did:peer:3.dc2ccfb083931f616e8967dd60017899bcf626134ee2e51a45ebf8d4f245f330"], "verificationMethod": [ { "id": "#6MkqRYqQ", "type": "Ed25519VerificationKey2020", - "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0", + "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0", "publicKeyBase58": "ByHnpUCFb1vAfh9CFZ8ZkmUZguURW8nSw889hy6rD8L7" }, { "id": "#6MkgoLTn", "type": "Ed25519VerificationKey2020", - "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0", + "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0", "publicKeyBase58": "3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J" } ], @@ -31,7 +31,7 @@ pub static DID_DOC_BASIC: &str = r##" { "id": "#6LSbysY2", "type": "X25519KeyAgreementKey2020", - "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0", + "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0", "publicKeyBase58": "JhNWeSVLMYccCk7iopQW4guaSJTojqpMEELgSLhKwRr" } @@ -40,7 +40,7 @@ pub static DID_DOC_BASIC: &str = r##" "capabilityDelegation": [], "service": [ { - "id": "#didcommmessaging-0", + "id": "#service-0", "type": "DIDCommMessaging", "serviceEndpoint": "https://example.com/endpoint", "routingKeys": ["did:example:somemediator#somekey"], 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 c23fa14b46..5a9525c7b8 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 @@ -2,26 +2,26 @@ pub static PEER_DID_NUMALGO_2_NO_ROUTING_KEYS: &str = "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.\ Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.\ Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.\ - SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9"; + SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9"; pub static PEER_DID_NUMALGO_3_NO_ROUTING_KEYS: &str = - "did:peer:3.3bf7ef596b0452c475418259e4b9e1aac91da30d61fa2ee6403e7e5670f7ab6c"; + "did:peer:3.537227f12830fad78fde73345b56ddb248ff3f2d958496a5d7a0f9fbc04e4193"; pub static DID_DOC_NO_ROUTING_KEYS: &str = r##" { - "id": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9", - "alsoKnownAs": ["did:peer:3.3bf7ef596b0452c475418259e4b9e1aac91da30d61fa2ee6403e7e5670f7ab6c"], + "id": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9", + "alsoKnownAs": ["did:peer:3.537227f12830fad78fde73345b56ddb248ff3f2d958496a5d7a0f9fbc04e4193"], "verificationMethod": [ { "id": "#6MkqRYqQ", "type": "Ed25519VerificationKey2020", - "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9", + "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9", "publicKeyMultibase": "z6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V" }, { "id": "#6MkgoLTn", "type": "Ed25519VerificationKey2020", - "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9", + "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9", "publicKeyMultibase": "z6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg" } ], @@ -31,7 +31,7 @@ pub static DID_DOC_NO_ROUTING_KEYS: &str = r##" { "id": "#6LSbysY2", "type": "X25519KeyAgreementKey2020", - "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9", + "controller": "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCJ9", "publicKeyMultibase": "z6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc" } ], @@ -39,7 +39,7 @@ pub static DID_DOC_NO_ROUTING_KEYS: &str = r##" "capabilityDelegation": [], "service": [ { - "id": "#didcommmessaging-0", + "id": "#service-0", "type": "DIDCommMessaging", "serviceEndpoint": "https://example.com/endpoint" } diff --git a/did_core/did_methods/did_peer/tests/generate.rs b/did_core/did_methods/did_peer/tests/generate.rs index dc154045c1..f0d3259746 100644 --- a/did_core/did_methods/did_peer/tests/generate.rs +++ b/did_core/did_methods/did_peer/tests/generate.rs @@ -1,18 +1,14 @@ mod fixtures; use did_doc::schema::did_doc::DidDocument; -use did_doc_sov::extra_fields::ExtraFieldsSov; use did_peer::peer_did::{ numalgos::{numalgo2::Numalgo2, numalgo3::Numalgo3}, PeerDid, }; +use pretty_assertions::assert_eq; use crate::fixtures::{ basic::{DID_DOC_BASIC, PEER_DID_NUMALGO_2_BASIC, PEER_DID_NUMALGO_3_BASIC}, - multiple_services::{ - DID_DOC_MULTIPLE_SERVICES, PEER_DID_NUMALGO_2_MULTIPLE_SERVICES, - PEER_DID_NUMALGO_3_MULTIPLE_SERVICES, - }, no_routing_keys::{ DID_DOC_NO_ROUTING_KEYS, PEER_DID_NUMALGO_2_NO_ROUTING_KEYS, PEER_DID_NUMALGO_3_NO_ROUTING_KEYS, @@ -22,78 +18,48 @@ use crate::fixtures::{ }, }; -macro_rules! generate_test_numalgo2 { - ($test_name:ident, $did_doc:expr, $peer_did:expr) => { - #[test] - fn $test_name() { - let did_document = - serde_json::from_str::>($did_doc).unwrap(); - assert_eq!( - PeerDid::::parse($peer_did.to_string()).unwrap(), - PeerDid::::from_did_doc(did_document).unwrap() - ); - } - }; +fn test_numalgo2(did_doc: &str, expected_peer_did: &str) { + let did_document = serde_json::from_str::(did_doc).unwrap(); + assert_eq!( + PeerDid::::parse(expected_peer_did.to_string()).unwrap(), + PeerDid::::from_did_doc(did_document).unwrap() + ); } -macro_rules! generate_test_numalgo3 { - ($test_name:ident, $did_doc:expr, $peer_did:expr) => { - #[test] - fn $test_name() { - let did_document = - serde_json::from_str::>($did_doc).unwrap(); - assert_eq!( - PeerDid::::parse($peer_did.to_string()).unwrap(), - PeerDid::::from_did_doc(did_document).unwrap() - ); - } - }; +fn test_numalgo3(did_doc: &str, expected_peer_did: &str) { + let did_document = serde_json::from_str::(did_doc).unwrap(); + assert_eq!( + PeerDid::::parse(expected_peer_did.to_string()).unwrap(), + PeerDid::::from_did_doc(did_document).unwrap() + ); } -generate_test_numalgo2!( - test_generate_numalgo2_basic, - DID_DOC_BASIC, - PEER_DID_NUMALGO_2_BASIC -); - -generate_test_numalgo2!( - test_generate_numalgo2_multiple_services, - DID_DOC_MULTIPLE_SERVICES, - PEER_DID_NUMALGO_2_MULTIPLE_SERVICES -); - -generate_test_numalgo2!( - test_generate_numalgo2_no_services, - DID_DOC_NO_SERVICES, - PEER_DID_NUMALGO_2_NO_SERVICES -); +#[test] +fn test_generate_numalgo2_basic() { + test_numalgo2(DID_DOC_BASIC, PEER_DID_NUMALGO_2_BASIC); +} -generate_test_numalgo2!( - test_generate_numalgo2_no_routing_keys, - DID_DOC_NO_ROUTING_KEYS, - PEER_DID_NUMALGO_2_NO_ROUTING_KEYS -); +#[test] +fn test_generate_numalgo2_no_services() { + test_numalgo2(DID_DOC_NO_SERVICES, PEER_DID_NUMALGO_2_NO_SERVICES); +} -generate_test_numalgo3!( - test_generate_numalgo3_basic, - DID_DOC_BASIC, - PEER_DID_NUMALGO_3_BASIC -); +#[test] +fn test_generate_numalgo2_no_routing_keys() { + test_numalgo2(DID_DOC_NO_ROUTING_KEYS, PEER_DID_NUMALGO_2_NO_ROUTING_KEYS); +} -generate_test_numalgo3!( - test_generate_numalgo3_multiple_services, - DID_DOC_MULTIPLE_SERVICES, - PEER_DID_NUMALGO_3_MULTIPLE_SERVICES -); +#[test] +fn test_generate_numalgo3_basic() { + test_numalgo3(DID_DOC_BASIC, PEER_DID_NUMALGO_3_BASIC); +} -generate_test_numalgo3!( - test_generate_numalgo3_no_services, - DID_DOC_NO_SERVICES, - PEER_DID_NUMALGO_3_NO_SERVICES -); +#[test] +fn test_generate_numalgo3_no_services() { + test_numalgo3(DID_DOC_NO_SERVICES, PEER_DID_NUMALGO_3_NO_SERVICES); +} -generate_test_numalgo3!( - test_generate_numalgo3_no_routing_keys, - DID_DOC_NO_ROUTING_KEYS, - PEER_DID_NUMALGO_3_NO_ROUTING_KEYS -); +#[test] +fn test_generate_numalgo3_no_routing_keys() { + test_numalgo3(DID_DOC_NO_ROUTING_KEYS, PEER_DID_NUMALGO_3_NO_ROUTING_KEYS); +} diff --git a/did_core/did_methods/did_peer/tests/resolve_negative.rs b/did_core/did_methods/did_peer/tests/resolve_negative.rs index 64fca4925c..2bae39dc44 100644 --- a/did_core/did_methods/did_peer/tests/resolve_negative.rs +++ b/did_core/did_methods/did_peer/tests/resolve_negative.rs @@ -2,18 +2,15 @@ mod fixtures; use did_peer::{ error::DidPeerError, - resolver::{ - options::{ExtraFieldsOptions, PublicKeyEncoding}, - PeerDidResolver, - }, + resolver::{options::PublicKeyEncoding, PeerDidResolutionOptions, PeerDidResolver}, }; -use did_resolver::traits::resolvable::{resolution_options::DidResolutionOptions, DidResolvable}; +use did_resolver::traits::resolvable::DidResolvable; use tokio::test; async fn resolve_error(peer_did: &str) -> DidPeerError { - let options = DidResolutionOptions::new( - ExtraFieldsOptions::new().set_public_key_encoding(PublicKeyEncoding::Multibase), - ); + let options = PeerDidResolutionOptions { + encoding: Some(PublicKeyEncoding::Multibase), + }; *PeerDidResolver .resolve(&peer_did.parse().unwrap(), &options) .await diff --git a/did_core/did_methods/did_peer/tests/resolve_positive.rs b/did_core/did_methods/did_peer/tests/resolve_positive.rs index cff56f3fd6..6ef2d684ea 100644 --- a/did_core/did_methods/did_peer/tests/resolve_positive.rs +++ b/did_core/did_methods/did_peer/tests/resolve_positive.rs @@ -1,64 +1,51 @@ mod fixtures; use did_doc::schema::did_doc::DidDocument; -use did_doc_sov::extra_fields::ExtraFieldsSov; -use did_peer::resolver::{ - options::{ExtraFieldsOptions, PublicKeyEncoding}, - PeerDidResolver, -}; -use did_resolver::traits::resolvable::{resolution_options::DidResolutionOptions, DidResolvable}; +use did_peer::resolver::{options::PublicKeyEncoding, PeerDidResolutionOptions, PeerDidResolver}; +use did_resolver::traits::resolvable::DidResolvable; +use pretty_assertions::assert_eq; use tokio::test; use crate::fixtures::{ basic::{DID_DOC_BASIC, PEER_DID_NUMALGO_2_BASIC}, - multiple_services::{DID_DOC_MULTIPLE_SERVICES, PEER_DID_NUMALGO_2_MULTIPLE_SERVICES}, no_routing_keys::{DID_DOC_NO_ROUTING_KEYS, PEER_DID_NUMALGO_2_NO_ROUTING_KEYS}, no_services::{DID_DOC_NO_SERVICES, PEER_DID_NUMALGO_2_NO_SERVICES}, }; -macro_rules! resolve_positive_test { - ($test_name:ident, $did_doc:expr, $peer_did:expr, $encoding:expr) => { - #[test] - async fn $test_name() { - let options = DidResolutionOptions::new( - ExtraFieldsOptions::new().set_public_key_encoding($encoding), - ); - let did_document_expected = - serde_json::from_str::>($did_doc).unwrap(); - let ddo = PeerDidResolver - .resolve(&$peer_did.parse().unwrap(), &options) - .await - .unwrap(); - let did_document_actual = ddo.did_document().clone(); - assert_eq!(did_document_actual, did_document_expected); - } - }; +async fn resolve_positive_test(did_doc: &str, peer_did: &str, options: PeerDidResolutionOptions) { + let did_document_expected = serde_json::from_str::(did_doc).unwrap(); + let resolution = PeerDidResolver + .resolve(&peer_did.parse().unwrap(), &options) + .await + .unwrap(); + assert_eq!(resolution.did_document, did_document_expected); } -resolve_positive_test!( - test_resolve_numalgo2_basic, - DID_DOC_BASIC, - PEER_DID_NUMALGO_2_BASIC, - PublicKeyEncoding::Base58 -); - -resolve_positive_test!( - test_resolve_numalgo2_multiple_services, - DID_DOC_MULTIPLE_SERVICES, - PEER_DID_NUMALGO_2_MULTIPLE_SERVICES, - PublicKeyEncoding::Multibase -); +#[test] +async fn test_resolve_numalgo2_basic() { + let options = PeerDidResolutionOptions { + encoding: Some(PublicKeyEncoding::Base58), + }; + resolve_positive_test(DID_DOC_BASIC, PEER_DID_NUMALGO_2_BASIC, options).await; +} -resolve_positive_test!( - test_resolve_numalgo2_no_routing_keys, - DID_DOC_NO_ROUTING_KEYS, - PEER_DID_NUMALGO_2_NO_ROUTING_KEYS, - PublicKeyEncoding::Multibase -); +#[test] +async fn test_resolve_numalgo2_no_routing_keys() { + let options = PeerDidResolutionOptions { + encoding: Some(PublicKeyEncoding::Multibase), + }; + resolve_positive_test( + DID_DOC_NO_ROUTING_KEYS, + PEER_DID_NUMALGO_2_NO_ROUTING_KEYS, + options, + ) + .await; +} -resolve_positive_test!( - test_resolve_numalgo2_no_services, - DID_DOC_NO_SERVICES, - PEER_DID_NUMALGO_2_NO_SERVICES, - PublicKeyEncoding::Multibase -); +#[test] +async fn test_resolve_numalgo2_no_services() { + let options = PeerDidResolutionOptions { + encoding: Some(PublicKeyEncoding::Multibase), + }; + resolve_positive_test(DID_DOC_NO_SERVICES, PEER_DID_NUMALGO_2_NO_SERVICES, options).await; +} diff --git a/did_core/did_methods/did_resolver_sov/Cargo.toml b/did_core/did_methods/did_resolver_sov/Cargo.toml index 4612dae28a..d03f9b8dcb 100644 --- a/did_core/did_methods/did_resolver_sov/Cargo.toml +++ b/did_core/did_methods/did_resolver_sov/Cargo.toml @@ -6,7 +6,6 @@ edition = "2021" [dependencies] did_resolver = { path = "../../did_resolver" } aries_vcx_core = { path = "../../../aries/aries_vcx_core", default_features = false} -did_doc_sov = { path = "../../did_doc_sov" } async-trait = "0.1.68" mockall = "0.11.4" serde_json = "1.0.96" @@ -14,6 +13,7 @@ serde = { version = "1.0.160", features = ["derive"] } chrono = { version = "0.4.24", default-features = false } thiserror = "1.0.40" url = "2.3.1" +log = "0.4.16" [dev-dependencies] aries_vcx = { path = "../../../aries/aries_vcx" } diff --git a/did_core/did_methods/did_resolver_sov/src/dereferencing/dereferencer.rs b/did_core/did_methods/did_resolver_sov/src/dereferencing/dereferencer.rs index dcd85f07e0..fe57852af2 100644 --- a/did_core/did_methods/did_resolver_sov/src/dereferencing/dereferencer.rs +++ b/did_core/did_methods/did_resolver_sov/src/dereferencing/dereferencer.rs @@ -9,7 +9,7 @@ use did_resolver::{ dereferencing_options::DidDereferencingOptions, dereferencing_output::DidDereferencingOutput, DidDereferenceable, }, - resolvable::{resolution_options::DidResolutionOptions, DidResolvable}, + resolvable::DidResolvable, }, }; @@ -29,9 +29,7 @@ where did_url: &DidUrl, _options: &DidDereferencingOptions, ) -> Result, GenericError> { - let resolution_output = self - .resolve(&did_url.try_into()?, &DidResolutionOptions::default()) - .await?; + let resolution_output = self.resolve(&did_url.try_into()?, &()).await?; dereference_did_document(&resolution_output, did_url).map_err(|err| err.into()) } diff --git a/did_core/did_methods/did_resolver_sov/src/dereferencing/utils.rs b/did_core/did_methods/did_resolver_sov/src/dereferencing/utils.rs index 8a3194f1c4..3af8aef24e 100644 --- a/did_core/did_methods/did_resolver_sov/src/dereferencing/utils.rs +++ b/did_core/did_methods/did_resolver_sov/src/dereferencing/utils.rs @@ -13,11 +13,10 @@ use did_resolver::{ resolvable::resolution_output::DidResolutionOutput, }, }; -use serde::Serialize; use crate::error::DidSovError; -pub fn service_by_id(services: &[Service], predicate: F) -> Option<&Service> +pub fn service_by_id(services: &[Service], predicate: F) -> Option<&Service> where F: Fn(&str) -> bool, { @@ -36,8 +35,8 @@ where .find(|auth| predicate(auth.id().did_url())) } -fn content_stream_from( - did_document: &DidDocument, +fn content_stream_from( + did_document: &DidDocument, did_url: &DidUrl, ) -> Result>, DidSovError> { let fragment = did_url.fragment().ok_or_else(|| { @@ -71,13 +70,13 @@ fn content_stream_from( } // TODO: Currently, only fragment dereferencing is supported -pub(crate) fn dereference_did_document( - resolution_output: &DidResolutionOutput, +pub(crate) fn dereference_did_document( + resolution_output: &DidResolutionOutput, did_url: &DidUrl, ) -> Result>>, DidSovError> { - let content_stream = content_stream_from(resolution_output.did_document(), did_url)?; + let content_stream = content_stream_from(&resolution_output.did_document, did_url)?; - let content_metadata = resolution_output.did_document_metadata().clone(); + let content_metadata = resolution_output.did_document_metadata.clone(); let dereferencing_metadata = DidDereferencingMetadata::builder() .content_type("application/did+json".to_string()) @@ -93,7 +92,8 @@ pub(crate) fn dereference_did_document( mod tests { use did_resolver::{ did_doc::schema::{ - did_doc::DidDocumentBuilder, verification_method::VerificationMethodType, + did_doc::DidDocumentBuilder, service::typed::ServiceType, utils::OneOrList, + verification_method::VerificationMethodType, }, did_parser::DidUrl, traits::resolvable::resolution_output::DidResolutionOutput, @@ -102,7 +102,7 @@ mod tests { use super::*; - fn example_did_document_builder() -> DidDocumentBuilder<()> { + fn example_did_document_builder() -> DidDocumentBuilder { let verification_method = VerificationMethod::builder( DidUrl::parse("did:example:123456789abcdefghi#keys-1".to_string()).unwrap(), "did:example:123456789abcdefghi" @@ -114,23 +114,19 @@ mod tests { .add_public_key_base58("H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV".to_string()) .build(); - let agent_service = Service::builder( + let agent_service = Service::new( "did:example:123456789abcdefghi#agent".parse().unwrap(), "https://agent.example.com/8377464".try_into().unwrap(), - (), - ) - .add_service_type("AgentService".to_string()) - .unwrap() - .build(); + OneOrList::One(ServiceType::Other("AgentService".to_string())), + Default::default(), + ); - let messaging_service = Service::builder( + let messaging_service = Service::new( "did:example:123456789abcdefghi#messages".parse().unwrap(), "https://example.com/messages/8377464".try_into().unwrap(), - (), - ) - .add_service_type("MessagingService".to_string()) - .unwrap() - .build(); + OneOrList::One(ServiceType::Other("MessagingService".to_string())), + Default::default(), + ); DidDocument::builder(Default::default()) .add_verification_method(verification_method) @@ -138,7 +134,7 @@ mod tests { .add_service(messaging_service) } - fn example_resolution_output() -> DidResolutionOutput<()> { + fn example_resolution_output() -> DidResolutionOutput { DidResolutionOutput::builder(example_did_document_builder().build()).build() } @@ -180,7 +176,7 @@ mod tests { assert_eq!(content_value, expected); let content_metadata = dereferencing_output.content_metadata(); - assert_eq!(content_metadata, resolution_output.did_document_metadata()); + assert_eq!(content_metadata, &resolution_output.did_document_metadata); let dereferencing_metadata = dereferencing_output.dereferencing_metadata(); assert_eq!( @@ -202,14 +198,12 @@ mod tests { fn test_dereference_did_document_ambiguous() { let did_document = { let did_document_builder = example_did_document_builder(); - let additional_service = Service::builder( + let additional_service = Service::new( "did:example:123456789abcdefghi#keys-1".parse().unwrap(), "https://example.com/duplicated/8377464".try_into().unwrap(), - (), - ) - .add_service_type("DuplicatedService".to_string()) - .unwrap() - .build(); + OneOrList::One(ServiceType::Other("DuplicatedService".to_string())), + Default::default(), + ); did_document_builder.add_service(additional_service).build() }; diff --git a/did_core/did_methods/did_resolver_sov/src/error/mod.rs b/did_core/did_methods/did_resolver_sov/src/error/mod.rs index fc08f94bb9..815617ed89 100644 --- a/did_core/did_methods/did_resolver_sov/src/error/mod.rs +++ b/did_core/did_methods/did_resolver_sov/src/error/mod.rs @@ -2,10 +2,11 @@ pub mod parsing; mod resolution; use aries_vcx_core::errors::error::AriesVcxCoreError; -use did_resolver::did_doc::error::DidDocumentBuilderError; +use did_resolver::did_doc::{error::DidDocumentBuilderError, schema::types::uri::UriWrapperError}; use thiserror::Error; use self::parsing::ParsingErrorSource; +use crate::error::DidSovError::ParsingError; // TODO: DIDDocumentBuilderError should do key validation and the error // should me mapped accordingly @@ -21,7 +22,7 @@ pub enum DidSovError { RepresentationNotSupported(String), #[error("Internal error")] InternalError, - #[error("Invalid DID: {0}")] + #[error("Invalid DID {0}")] InvalidDid(String), #[error("AriesVCX Core error: {0}")] AriesVcxCoreError(#[from] AriesVcxCoreError), @@ -34,3 +35,9 @@ pub enum DidSovError { #[error(transparent)] Other(#[from] Box), } + +impl From for DidSovError { + fn from(error: UriWrapperError) -> Self { + ParsingError(ParsingErrorSource::DidDocumentParsingUriError(error)) + } +} diff --git a/did_core/did_methods/did_resolver_sov/src/error/parsing.rs b/did_core/did_methods/did_resolver_sov/src/error/parsing.rs index 623b8e0e91..40bd133cd0 100644 --- a/did_core/did_methods/did_resolver_sov/src/error/parsing.rs +++ b/did_core/did_methods/did_resolver_sov/src/error/parsing.rs @@ -1,4 +1,4 @@ -use did_resolver::did_parser; +use did_resolver::{did_doc::schema::types::uri::UriWrapperError, did_parser}; use thiserror::Error; use super::DidSovError; @@ -7,6 +7,8 @@ use super::DidSovError; pub enum ParsingErrorSource { #[error("DID document parsing error: {0}")] DidDocumentParsingError(#[from] did_parser::ParseError), + #[error("DID document parsing URI error: {0}")] + DidDocumentParsingUriError(#[from] UriWrapperError), #[error("Serde error: {0}")] SerdeError(#[from] serde_json::Error), #[error("Ledger response parsing error: {0}")] diff --git a/did_core/did_methods/did_resolver_sov/src/resolution/resolver.rs b/did_core/did_methods/did_resolver_sov/src/resolution/resolver.rs index f0ce5d93f1..0e70df9f8f 100644 --- a/did_core/did_methods/did_resolver_sov/src/resolution/resolver.rs +++ b/did_core/did_methods/did_resolver_sov/src/resolution/resolver.rs @@ -1,15 +1,10 @@ use std::{borrow::Borrow, marker::PhantomData}; use async_trait::async_trait; -use did_doc_sov::extra_fields::ExtraFieldsSov; use did_resolver::{ did_parser::Did, error::GenericError, - shared_types::media_type::MediaType, - traits::resolvable::{ - resolution_options::DidResolutionOptions, resolution_output::DidResolutionOutput, - DidResolvable, - }, + traits::resolvable::{resolution_output::DidResolutionOutput, DidResolvable}, }; use serde_json::Value; @@ -50,21 +45,14 @@ where T: Borrow + Sync + Send, A: AttrReader, { - type ExtraFieldsService = ExtraFieldsSov; - type ExtraFieldsOptions = (); + type DidResolutionOptions = (); async fn resolve( &self, parsed_did: &Did, - options: &DidResolutionOptions<()>, - ) -> Result, GenericError> { - if let Some(accept) = options.accept() { - if accept != &MediaType::DidJson { - return Err(Box::new(DidSovError::RepresentationNotSupported( - accept.to_string(), - ))); - } - } + _options: &Self::DidResolutionOptions, + ) -> Result { + log::info!("DidSovResolver::resolve >> Resolving did: {}", parsed_did); let method = parsed_did.method().ok_or_else(|| { DidSovError::InvalidDid("Attempted to resolve unqualified did".to_string()) })?; @@ -74,9 +62,10 @@ where ))); } if !is_valid_sovrin_did_id(parsed_did.id()) { - return Err(Box::new(DidSovError::InvalidDid( - parsed_did.id().to_string(), - ))); + return Err(Box::new(DidSovError::InvalidDid(format!( + "Sovrin DID: {} contains invalid DID ID.", + parsed_did.id() + )))); } let ledger_response = self .ledger @@ -97,6 +86,7 @@ where { async fn get_verkey(&self, did: &Did) -> Result { let nym_response = self.ledger.borrow().get_nym(did).await?; + log::info!("get_verkey >> nym_response: {}", nym_response); let nym_json: Value = serde_json::from_str(&nym_response)?; let nym_data = nym_json["result"]["data"] .as_str() diff --git a/did_core/did_methods/did_resolver_sov/src/resolution/utils.rs b/did_core/did_methods/did_resolver_sov/src/resolution/utils.rs index a895a159ce..eca2da3423 100644 --- a/did_core/did_methods/did_resolver_sov/src/resolution/utils.rs +++ b/did_core/did_methods/did_resolver_sov/src/resolution/utils.rs @@ -2,11 +2,12 @@ use chrono::{DateTime, NaiveDateTime, Utc}; use did_resolver::{ did_doc::schema::{ did_doc::DidDocument, - service::Service, + service::{typed::ServiceType, Service}, types::uri::Uri, + utils::OneOrList, verification_method::{VerificationMethod, VerificationMethodType}, }, - did_parser::Did, + did_parser::{Did, DidUrl}, shared_types::did_document_metadata::DidDocumentMetadata, traits::resolvable::{ resolution_metadata::DidResolutionMetadata, resolution_output::DidResolutionOutput, @@ -61,40 +62,41 @@ pub(super) fn is_valid_sovrin_did_id(id: &str) -> bool { id.chars().all(|c| base58_chars.contains(c)) } -pub(super) async fn ledger_response_to_ddo( +pub(super) async fn ledger_response_to_ddo( did: &str, resp: &str, verkey: String, -) -> Result, DidSovError> { +) -> Result { + log::info!("ledger_response_to_ddo >> did: {did}, verkey: {verkey}, resp: {resp}"); let (service_id, ddo_id) = prepare_ids(did)?; let service_data = get_data_from_response(resp)?; + log::info!("ledger_response_to_ddo >> service_data: {service_data:?}"); let endpoint: EndpointDidSov = serde_json::from_value(service_data["endpoint"].clone())?; let txn_time = get_txn_time_from_response(resp)?; let datetime = unix_to_datetime(txn_time); - let service = { - let service_types: Vec = endpoint - .types - .into_iter() - .filter(|t| *t != DidSovServiceType::Unknown) - .map(|t| t.to_string()) - .collect(); - let mut builder = Service::builder( - service_id, - endpoint.endpoint.as_str().try_into()?, - Default::default(), - ); - for service_type in service_types { - builder = builder.add_service_type(service_type)?; - } - builder.build() - }; + let service_types: Vec = endpoint + .types + .into_iter() + .map(|t| match t { + DidSovServiceType::Endpoint => ServiceType::AIP1, + DidSovServiceType::DidCommunication => ServiceType::DIDCommV1, + DidSovServiceType::DIDComm => ServiceType::DIDCommV2, + DidSovServiceType::Unknown => ServiceType::Other("Unknown".to_string()), + }) + .collect(); + let service = Service::new( + service_id, + endpoint.endpoint, + OneOrList::List(service_types), + Default::default(), + ); // TODO: Use multibase instead of base58 let verification_method = VerificationMethod::builder( - did.to_string().try_into()?, + DidUrl::parse("#1".to_string())?, did.to_string().try_into()?, VerificationMethodType::Ed25519VerificationKey2018, ) @@ -104,6 +106,7 @@ pub(super) async fn ledger_response_to_ddo( let ddo = DidDocument::builder(ddo_id) .add_service(service) .add_verification_method(verification_method) + .add_key_agreement_reference(DidUrl::parse("#1".to_string())?) .build(); let ddo_metadata = { @@ -184,10 +187,11 @@ mod tests { } }"#; let verkey = "9wvq2i4xUa5umXoThe83CDgx1e5bsjZKJL4DEWvTP9qe".to_string(); - let resolution_output = ledger_response_to_ddo::<()>(did, resp, verkey) - .await - .unwrap(); - let ddo = resolution_output.did_document(); + let DidResolutionOutput { + did_document: ddo, + did_resolution_metadata, + did_document_metadata, + } = ledger_response_to_ddo(did, resp, verkey).await.unwrap(); assert_eq!(ddo.id().to_string(), "did:example:1234567890"); assert_eq!(ddo.service()[0].id().to_string(), "did:example:1234567890"); assert_eq!( @@ -195,14 +199,11 @@ mod tests { "https://example.com/" ); assert_eq!( - resolution_output.did_document_metadata().updated().unwrap(), - chrono::Utc.timestamp_opt(1629272938, 0).unwrap() + did_document_metadata.updated().unwrap(), + Utc.timestamp_opt(1629272938, 0).unwrap() ); assert_eq!( - resolution_output - .did_resolution_metadata() - .content_type() - .unwrap(), + did_resolution_metadata.content_type().unwrap(), "application/did+json" ); if let PublicKeyField::Base58 { public_key_base58 } = diff --git a/did_core/did_methods/did_resolver_sov/tests/resolution.rs b/did_core/did_methods/did_resolver_sov/tests/resolution.rs index 9f65461e82..9062d76696 100644 --- a/did_core/did_methods/did_resolver_sov/tests/resolution.rs +++ b/did_core/did_methods/did_resolver_sov/tests/resolution.rs @@ -1,13 +1,11 @@ use std::{thread, time::Duration}; -use aries_vcx::common::ledger::{ - service_didsov::{DidSovServiceType, EndpointDidSov}, - transactions::write_endpoint, -}; +use aries_vcx::common::ledger::{service_didsov::EndpointDidSov, transactions::write_endpoint}; use aries_vcx_core::{ledger::base_ledger::IndyLedgerWrite, wallet::base_wallet::BaseWallet}; use did_resolver::{ + did_doc::schema::service::typed::ServiceType, did_parser::Did, - traits::resolvable::{resolution_options::DidResolutionOptions, DidResolvable}, + traits::resolvable::{resolution_output::DidResolutionOutput, DidResolvable}, }; use did_resolver_sov::resolution::DidSovResolver; use test_utils::devsetup::build_setup_profile; @@ -20,7 +18,7 @@ async fn write_test_endpoint( let endpoint = EndpointDidSov::create() .set_service_endpoint("http://localhost:8080".parse().unwrap()) .set_routing_keys(Some(vec!["key1".to_string(), "key2".to_string()])) - .set_types(Some(vec![DidSovServiceType::Endpoint])); + .set_types(Some(vec![ServiceType::AIP1.to_string()])); write_endpoint(wallet, ledger_write, did, &endpoint) .await .unwrap(); @@ -39,15 +37,12 @@ async fn write_service_on_ledger_and_resolve_did_doc() { let resolver = DidSovResolver::new(profile.ledger_read); let did = format!("did:sov:{}", profile.institution_did); - let did_doc = resolver - .resolve( - &Did::parse(did.clone()).unwrap(), - &DidResolutionOptions::default(), - ) + let DidResolutionOutput { did_document, .. } = resolver + .resolve(&Did::parse(did.clone()).unwrap(), &()) .await .unwrap(); - assert_eq!(did_doc.did_document().id().to_string(), did); + assert_eq!(did_document.id().to_string(), did); } #[tokio::test] @@ -57,10 +52,7 @@ async fn test_error_handling_during_resolution() { let did = format!("did:unknownmethod:{}", profile.institution_did); let result = resolver - .resolve( - &Did::parse(did.clone()).unwrap(), - &DidResolutionOptions::default(), - ) + .resolve(&Did::parse(did.clone()).unwrap(), &()) .await; assert!(result.is_err()); diff --git a/did_core/did_methods/did_resolver_web/src/resolution/resolver.rs b/did_core/did_methods/did_resolver_web/src/resolution/resolver.rs index d2b0796eaa..f82aa899de 100644 --- a/did_core/did_methods/did_resolver_web/src/resolution/resolver.rs +++ b/did_core/did_methods/did_resolver_web/src/resolution/resolver.rs @@ -2,10 +2,10 @@ use async_trait::async_trait; use did_resolver::{ did_parser::Did, error::GenericError, - shared_types::{did_document_metadata::DidDocumentMetadata, media_type::MediaType}, + shared_types::did_document_metadata::DidDocumentMetadata, traits::resolvable::{ - resolution_metadata::DidResolutionMetadata, resolution_options::DidResolutionOptions, - resolution_output::DidResolutionOutput, DidResolvable, + resolution_metadata::DidResolutionMetadata, resolution_output::DidResolutionOutput, + DidResolvable, }, }; use hyper::{ @@ -65,14 +65,13 @@ impl DidResolvable for DidWebResolver where C: Connect + Send + Sync + Clone + 'static, { - type ExtraFieldsService = (); - type ExtraFieldsOptions = (); + type DidResolutionOptions = (); async fn resolve( &self, did: &Did, - options: &DidResolutionOptions, - ) -> Result, GenericError> { + _options: &Self::DidResolutionOptions, + ) -> Result { let method = did.method().ok_or_else(|| { DidWebError::InvalidDid("Attempted to resolve unqualified did".to_string()) })?; @@ -82,14 +81,6 @@ where ))); } - if let Some(accept) = options.accept() { - if accept != &MediaType::DidJson { - return Err(Box::new(DidWebError::RepresentationNotSupported( - accept.to_string(), - ))); - } - } - let did_parts: Vec<&str> = did.id().split(':').collect(); if did_parts.is_empty() { diff --git a/did_core/did_methods/did_resolver_web/tests/resolution.rs b/did_core/did_methods/did_resolver_web/tests/resolution.rs index a1bb348c94..a415ed92f1 100644 --- a/did_core/did_methods/did_resolver_web/tests/resolution.rs +++ b/did_core/did_methods/did_resolver_web/tests/resolution.rs @@ -3,7 +3,7 @@ use std::{convert::Infallible, net::SocketAddr}; use did_resolver::{ did_doc::schema::did_doc::DidDocument, did_parser::Did, - traits::resolvable::{resolution_options::DidResolutionOptions, DidResolvable}, + traits::resolvable::{resolution_output::DidResolutionOutput, DidResolvable}, }; use did_resolver_web::resolution::resolver::DidWebResolver; use hyper::{ @@ -94,7 +94,7 @@ async fn create_mock_server(port: u16) -> String { #[tokio::test] async fn test_did_web_resolver() { - fn verify_did_document(did_document: &DidDocument<()>) { + fn verify_did_document(did_document: &DidDocument) { assert_eq!( did_document.id().to_string(), "did:web:example.com".to_string() @@ -113,17 +113,13 @@ async fn test_did_web_resolver() { let did_example_1 = Did::parse(format!("did:web:{}%3A{}", host, port)).unwrap(); let did_example_2 = Did::parse(format!("did:web:{}%3A{}:user:alice", host, port)).unwrap(); - let result_1 = assert_ok!( - did_web_resolver - .resolve(&did_example_1, &DidResolutionOptions::default()) - .await - ); - verify_did_document(result_1.did_document()); + let DidResolutionOutput { + did_document: ddo1, .. + } = assert_ok!(did_web_resolver.resolve(&did_example_1, &()).await); + verify_did_document(&ddo1); - let result_2 = assert_ok!( - did_web_resolver - .resolve(&did_example_2, &DidResolutionOptions::default()) - .await - ); - verify_did_document(result_2.did_document()); + let DidResolutionOutput { + did_document: ddo2, .. + } = assert_ok!(did_web_resolver.resolve(&did_example_2, &()).await); + verify_did_document(&ddo2); } diff --git a/did_core/did_parser/src/did.rs b/did_core/did_parser/src/did.rs index c9a4a92e42..ce8d58fa34 100644 --- a/did_core/did_parser/src/did.rs +++ b/did_core/did_parser/src/did.rs @@ -48,6 +48,12 @@ impl Did { } } +impl AsRef for Did { + fn as_ref(&self) -> &Did { + self + } +} + impl TryFrom for Did { type Error = ParseError; diff --git a/did_core/did_resolver/Cargo.toml b/did_core/did_resolver/Cargo.toml index d254e407a9..fd3c0e50fe 100644 --- a/did_core/did_resolver/Cargo.toml +++ b/did_core/did_resolver/Cargo.toml @@ -12,3 +12,4 @@ did_doc = { path = "../did_doc" } async-trait = "0.1.68" chrono = { version = "0.4.24", default-features = false, features = ["serde"] } serde = { version = "1.0.160", default-features = false, features = ["derive"] } +serde_json = "1.0.103" diff --git a/did_core/did_resolver/src/traits/resolvable/mod.rs b/did_core/did_resolver/src/traits/resolvable/mod.rs index fce98a0676..9f48e02ba7 100644 --- a/did_core/did_resolver/src/traits/resolvable/mod.rs +++ b/did_core/did_resolver/src/traits/resolvable/mod.rs @@ -1,22 +1,20 @@ pub mod resolution_error; pub mod resolution_metadata; -pub mod resolution_options; pub mod resolution_output; use async_trait::async_trait; use did_parser::Did; -use self::{resolution_options::DidResolutionOptions, resolution_output::DidResolutionOutput}; +use self::resolution_output::DidResolutionOutput; use crate::error::GenericError; #[async_trait] pub trait DidResolvable { - type ExtraFieldsService: Default; - type ExtraFieldsOptions: Default; + type DidResolutionOptions: Default; async fn resolve( &self, did: &Did, - options: &DidResolutionOptions, - ) -> Result, GenericError>; + options: &Self::DidResolutionOptions, + ) -> Result; } diff --git a/did_core/did_resolver/src/traits/resolvable/resolution_options.rs b/did_core/did_resolver/src/traits/resolvable/resolution_options.rs deleted file mode 100644 index 8a0bc92e2d..0000000000 --- a/did_core/did_resolver/src/traits/resolvable/resolution_options.rs +++ /dev/null @@ -1,31 +0,0 @@ -use serde::Deserialize; - -use crate::shared_types::media_type::MediaType; - -#[derive(Debug, Clone, PartialEq, Default, Deserialize)] -pub struct DidResolutionOptions { - accept: Option, - extra: E, -} - -impl DidResolutionOptions { - pub fn new(extra: E) -> Self { - Self { - accept: None, - extra, - } - } - - pub fn set_accept(mut self, accept: MediaType) -> Self { - self.accept = Some(accept); - self - } - - pub fn accept(&self) -> Option<&MediaType> { - self.accept.as_ref() - } - - pub fn extra(&self) -> &E { - &self.extra - } -} diff --git a/did_core/did_resolver/src/traits/resolvable/resolution_output.rs b/did_core/did_resolver/src/traits/resolvable/resolution_output.rs index 20ca157ddb..7c1f96223f 100644 --- a/did_core/did_resolver/src/traits/resolvable/resolution_output.rs +++ b/did_core/did_resolver/src/traits/resolvable/resolution_output.rs @@ -9,41 +9,29 @@ use crate::shared_types::did_document_metadata::DidDocumentMetadata; // non-empty field in DidResolutionOutput in the error case. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(rename_all = "camelCase")] -pub struct DidResolutionOutput { - pub did_document: DidDocument, +pub struct DidResolutionOutput { + pub did_document: DidDocument, pub did_resolution_metadata: DidResolutionMetadata, pub did_document_metadata: DidDocumentMetadata, } -impl DidResolutionOutput { - pub fn builder(did_document: DidDocument) -> DidResolutionOutputBuilder { +impl DidResolutionOutput { + pub fn builder(did_document: DidDocument) -> DidResolutionOutputBuilder { DidResolutionOutputBuilder { did_document, did_resolution_metadata: None, did_document_metadata: None, } } - - pub fn did_document(&self) -> &DidDocument { - &self.did_document - } - - pub fn did_resolution_metadata(&self) -> &DidResolutionMetadata { - &self.did_resolution_metadata - } - - pub fn did_document_metadata(&self) -> &DidDocumentMetadata { - &self.did_document_metadata - } } -pub struct DidResolutionOutputBuilder { - did_document: DidDocument, +pub struct DidResolutionOutputBuilder { + did_document: DidDocument, did_resolution_metadata: Option, did_document_metadata: Option, } -impl DidResolutionOutputBuilder { +impl DidResolutionOutputBuilder { pub fn did_resolution_metadata( mut self, did_resolution_metadata: DidResolutionMetadata, @@ -57,7 +45,7 @@ impl DidResolutionOutputBuilder { self } - pub fn build(self) -> DidResolutionOutput { + pub fn build(self) -> DidResolutionOutput { DidResolutionOutput { did_document: self.did_document, did_resolution_metadata: self.did_resolution_metadata.unwrap_or_default(), diff --git a/did_core/did_resolver_registry/src/lib.rs b/did_core/did_resolver_registry/src/lib.rs index 8de6818b72..20da57f687 100644 --- a/did_core/did_resolver_registry/src/lib.rs +++ b/did_core/did_resolver_registry/src/lib.rs @@ -7,17 +7,12 @@ use did_resolver::{ did_doc::schema::did_doc::DidDocument, did_parser::Did, error::GenericError, - traits::resolvable::{ - resolution_options::DidResolutionOptions, resolution_output::DidResolutionOutput, - DidResolvable, - }, + traits::resolvable::{resolution_output::DidResolutionOutput, DidResolvable}, }; use error::DidResolverRegistryError; use serde::{Deserialize, Serialize}; use serde_json::Value; -// TODO: Use serde_json::Map instead -pub type GenericMap = HashMap; pub type GenericResolver = dyn DidResolvableAdaptorTrait + Send + Sync; #[derive(Default)] @@ -34,43 +29,40 @@ pub trait DidResolvableAdaptorTrait: Send + Sync { async fn resolve( &self, did: &Did, - options: &DidResolutionOptions>, - ) -> Result>, GenericError>; + options: HashMap, + ) -> Result; } #[async_trait] impl DidResolvableAdaptorTrait for DidResolvableAdaptor where - T::ExtraFieldsService: Send + Sync + Serialize + for<'de> Deserialize<'de>, - T::ExtraFieldsOptions: Send + Sync + Serialize + for<'de> Deserialize<'de>, + T::DidResolutionOptions: Send + Sync + Serialize + for<'de> Deserialize<'de>, { async fn resolve( &self, did: &Did, - options: &DidResolutionOptions>, - ) -> Result>, GenericError> { - let options_inner: T::ExtraFieldsOptions = if options.extra().is_empty() { + options: HashMap, + ) -> Result { + let options: T::DidResolutionOptions = if options.is_empty() { Default::default() } else { - serde_json::from_value(Value::Object(options.extra().clone().into_iter().collect()))? + let json_map = options.into_iter().collect(); + serde_json::from_value(Value::Object(json_map))? }; - let result_inner = self - .inner - .resolve(did, &DidResolutionOptions::new(options_inner)) - .await?; + let result_inner = self.inner.resolve(did, &options).await?; - let did_document_inner_hashmap = serde_json::to_value(result_inner.did_document()) + let did_document_inner_hashmap = serde_json::to_value(result_inner.did_document) .unwrap() .as_object() .unwrap() .clone(); - let did_document: DidDocument> = + let did_document: DidDocument = serde_json::from_value(Value::Object(did_document_inner_hashmap))?; Ok(DidResolutionOutput::builder(did_document) - .did_resolution_metadata(result_inner.did_resolution_metadata().clone()) - .did_document_metadata(result_inner.did_document_metadata().clone()) + .did_resolution_metadata(result_inner.did_resolution_metadata) + .did_document_metadata(result_inner.did_document_metadata) .build()) } } @@ -83,9 +75,7 @@ impl ResolverRegistry { pub fn register_resolver(mut self, method: String, resolver: T) -> Self where T: DidResolvable + 'static + Send + Sync, - for<'de> ::ExtraFieldsService: - Send + Sync + Serialize + Deserialize<'de>, - for<'de> ::ExtraFieldsOptions: + for<'de> ::DidResolutionOptions: Send + Sync + Serialize + Deserialize<'de>, { let adaptor = DidResolvableAdaptor { inner: resolver }; @@ -101,13 +91,13 @@ impl ResolverRegistry { pub async fn resolve( &self, did: &Did, - options: &DidResolutionOptions, - ) -> Result, GenericError> { + options: &HashMap, + ) -> Result { let method = did .method() .ok_or(DidResolverRegistryError::UnsupportedMethod)?; match self.resolvers.get(method) { - Some(resolver) => resolver.resolve(did, options).await, + Some(resolver) => resolver.resolve(did, options.clone()).await, None => Err(Box::new(DidResolverRegistryError::UnsupportedMethod)), } } @@ -119,7 +109,7 @@ mod tests { use async_trait::async_trait; use did_resolver::did_doc::schema::did_doc::DidDocumentBuilder; - use mockall::{automock, predicate::eq}; + use mockall::automock; use super::*; @@ -128,14 +118,13 @@ mod tests { #[async_trait] #[automock] impl DidResolvable for DummyDidResolver { - type ExtraFieldsService = (); - type ExtraFieldsOptions = (); + type DidResolutionOptions = (); async fn resolve( &self, did: &Did, - _options: &DidResolutionOptions<()>, - ) -> Result, GenericError> { + _options: &Self::DidResolutionOptions, + ) -> Result { Ok(DidResolutionOutput::builder( DidDocumentBuilder::new(Did::parse(did.did().to_string()).unwrap()).build(), ) @@ -162,11 +151,11 @@ mod tests { let mut mock_resolver = MockDummyDidResolver::new(); mock_resolver .expect_resolve() - .with(eq(did.clone()), eq(DidResolutionOptions::default())) + // .with(eq(did.clone()), eq(DidResolutionOptions::default())) .times(1) .return_once(move |_, _| { let future = async move { - Err::, GenericError>(Box::new(DummyResolverError)) + Err::(Box::new(DummyResolverError)) }; Pin::from(Box::new(future)) }); @@ -174,9 +163,7 @@ mod tests { let registry = ResolverRegistry::new() .register_resolver::(method, mock_resolver); - let result = registry - .resolve(&did, &DidResolutionOptions::default()) - .await; + let result = registry.resolve(&did, &HashMap::new()).await; assert!(result.is_err()); let error = result.unwrap_err(); @@ -195,11 +182,11 @@ mod tests { let mut mock_resolver = MockDummyDidResolver::new(); mock_resolver .expect_resolve() - .with(eq(parsed_did.clone()), eq(DidResolutionOptions::default())) + // .with(eq(parsed_did.clone()), eq(DidResolutionOptions::default())) .times(1) .return_once(move |_, _| { let future = async move { - Ok::, GenericError>( + Ok::( DidResolutionOutput::builder( DidDocumentBuilder::new(Did::parse(did.to_string()).unwrap()).build(), ) @@ -212,9 +199,7 @@ mod tests { let registry = ResolverRegistry::new() .register_resolver::(method, mock_resolver); - let result = registry - .resolve(&parsed_did, &DidResolutionOptions::default()) - .await; + let result = registry.resolve(&parsed_did, &HashMap::new()).await; assert!(result.is_ok()); } @@ -241,9 +226,7 @@ mod tests { let did = Did::parse("did:unknown:1234".to_string()).unwrap(); let registry = ResolverRegistry::new(); - let result = registry - .resolve(&did, &DidResolutionOptions::default()) - .await; + let result = registry.resolve(&did, &HashMap::new()).await; assert!(result.is_err()); let error = result.unwrap_err(); @@ -265,11 +248,11 @@ mod tests { let mut mock_resolver = MockDummyDidResolver::new(); mock_resolver .expect_resolve() - .with(eq(parsed_did.clone()), eq(DidResolutionOptions::default())) + // .with(eq(parsed_did.clone()), eq(DidResolutionOptions::default())) .times(1) .return_once(move |_, _| { let future = async move { - Ok::, GenericError>( + Ok::( DidResolutionOutput::builder( DidDocumentBuilder::new(Did::parse(did.to_string()).unwrap()).build(), ) @@ -281,9 +264,7 @@ mod tests { let mut registry = ResolverRegistry::new(); - let result_before = registry - .resolve(&parsed_did, &DidResolutionOptions::default()) - .await; + let result_before = registry.resolve(&parsed_did, &HashMap::new()).await; assert!(result_before.is_err()); let error_before = result_before.unwrap_err(); assert!( @@ -296,9 +277,7 @@ mod tests { registry = registry.register_resolver::(method, mock_resolver); - let result_after = registry - .resolve(&parsed_did, &DidResolutionOptions::default()) - .await; + let result_after = registry.resolve(&parsed_did, &HashMap::new()).await; assert!(result_after.is_ok()); } } diff --git a/did_core/public_key/src/key.rs b/did_core/public_key/src/key.rs index 6b8607d9fa..8edd3b74b6 100644 --- a/did_core/public_key/src/key.rs +++ b/did_core/public_key/src/key.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use super::KeyType; use crate::error::PublicKeyError; +/// Represents raw public key data along with information about the key type #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Key { key_type: KeyType, @@ -46,8 +47,6 @@ impl Key { bs58::encode(&self.key).into_string() } - // TODO: base64, ... - pub fn multibase58(&self) -> String { multibase::encode(multibase::Base::Base58Btc, &self.key) } @@ -69,7 +68,6 @@ impl Key { }) } - // TODO: A better name? pub fn short_prefixless_fingerprint(&self) -> String { self.prefixless_fingerprint() .chars() diff --git a/justfile b/justfile index ba94ed6076..589bf33007 100644 --- a/justfile +++ b/justfile @@ -59,4 +59,4 @@ test-integration-libvcx wallet test_name="": RUST_TEST_THREADS=1 cargo test --manifest-path="aries/misc/legacy/libvcx_core/Cargo.toml" -F {{wallet}} -- --include-ignored {{test_name}} test-integration-did-crate test_name="": - cargo test --examples -p did_doc -p did_parser -p did_resolver -p did_resolver_registry -p did_resolver_sov -p did_resolver_web -p did_doc_sov -p did_key -p did_peer --test "*" + cargo test --examples -p did_doc -p did_parser -p did_resolver -p did_resolver_registry -p did_resolver_sov -p did_resolver_web -p did_key -p did_peer --test "*"