From 05c92f2ab4b26abce56b624637fa953fc168628f Mon Sep 17 00:00:00 2001 From: Patrik Date: Thu, 21 Mar 2024 05:57:15 +0100 Subject: [PATCH] Feature/didpeer4 (#1161) Implement did:peer:4 (#1161) Signed-off-by: Patrik Stas --- Cargo.lock | 3 + did_core/did_doc/src/schema/did_doc.rs | 11 + .../did_doc/src/schema/types/multibase.rs | 14 +- .../src/schema/verification_method/mod.rs | 12 ++ did_core/did_methods/did_peer/Cargo.toml | 1 + .../did_methods/did_peer/examples/demo.rs | 59 +++++- did_core/did_methods/did_peer/src/error.rs | 2 + did_core/did_methods/did_peer/src/lib.rs | 1 + .../did_peer/src/peer_did/generic.rs | 48 ++--- .../did_methods/did_peer/src/peer_did/mod.rs | 19 -- .../did_peer/src/peer_did/numalgos/kind.rs | 6 +- .../did_peer/src/peer_did/numalgos/mod.rs | 4 +- .../numalgos/numalgo4/encoded_document.rs | 153 ++++++++++++++ .../src/peer_did/numalgos/numalgo4/mod.rs | 189 ++++++++++++++++++ .../did_peer/src/peer_did/parse.rs | 1 + .../did_peer/src/peer_did/validate.rs | 29 --- .../did_methods/did_peer/src/resolver/mod.rs | 23 ++- did_core/did_methods/did_peer/tests/demo.rs | 50 ----- .../did_peer/tests/resolve_negative.rs | 12 -- did_core/did_parser_nom/Cargo.toml | 2 + did_core/did_parser_nom/src/did/mod.rs | 10 +- .../src/did/parsing/did_core.rs | 67 +++++-- .../did_parser_nom/src/did/parsing/did_key.rs | 6 +- .../src/did/parsing/did_peer_4.rs | 24 +++ .../did_parser_nom/src/did/parsing/did_sov.rs | 30 ++- .../did_parser_nom/src/did/parsing/did_web.rs | 6 +- .../did_parser_nom/src/did/parsing/mod.rs | 4 +- did_core/did_parser_nom/tests/did/negative.rs | 1 - did_core/did_parser_nom/tests/did/positive.rs | 75 +++---- .../did_parser_nom/tests/did_url/negative.rs | 1 - .../did_parser_nom/tests/did_url/positive.rs | 85 ++++---- 31 files changed, 663 insertions(+), 285 deletions(-) create mode 100644 did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo4/encoded_document.rs create mode 100644 did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo4/mod.rs delete mode 100644 did_core/did_methods/did_peer/src/peer_did/validate.rs delete mode 100644 did_core/did_methods/did_peer/tests/demo.rs create mode 100644 did_core/did_parser_nom/src/did/parsing/did_peer_4.rs diff --git a/Cargo.lock b/Cargo.lock index aa2194841d..5020cd71c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1820,6 +1820,8 @@ dependencies = [ name = "did_parser_nom" version = "0.1.0" dependencies = [ + "env_logger 0.10.0", + "log", "nom", "serde", "serde_test", @@ -1832,6 +1834,7 @@ dependencies = [ "async-trait", "base64", "bs58 0.5.0", + "derive_builder", "did_doc", "did_parser_nom", "did_resolver", diff --git a/did_core/did_doc/src/schema/did_doc.rs b/did_core/did_doc/src/schema/did_doc.rs index 3147903086..52e89843e4 100644 --- a/did_core/did_doc/src/schema/did_doc.rs +++ b/did_core/did_doc/src/schema/did_doc.rs @@ -142,6 +142,12 @@ impl DidDocumentBuilder { self } + // This was needed fo peer:did:4 implementation, but it's assymetric with other methods here + // TODO: Find better approach for the builder + pub fn add_verification_method_2(&mut self, verification_method: VerificationMethod) { + self.verification_method.push(verification_method); + } + pub fn add_authentication_method(mut self, method: VerificationMethod) -> Self { self.authentication .push(VerificationMethodKind::Resolved(method)); @@ -207,6 +213,11 @@ impl DidDocumentBuilder { self } + pub fn set_service(mut self, service: Vec) -> Self { + self.service = service; + self + } + pub fn add_extra_field(mut self, key: String, value: Value) -> Self { self.extra.insert(key, value); self diff --git a/did_core/did_doc/src/schema/types/multibase.rs b/did_core/did_doc/src/schema/types/multibase.rs index 2509411d68..464f08fc0b 100644 --- a/did_core/did_doc/src/schema/types/multibase.rs +++ b/did_core/did_doc/src/schema/types/multibase.rs @@ -4,7 +4,7 @@ use std::{ str::FromStr, }; -use multibase::{decode, Base}; +use multibase::decode; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -28,7 +28,7 @@ impl Display for MultibaseWrapperError { // https://datatracker.ietf.org/doc/html/draft-multiformats-multibase-07 #[derive(Clone, Debug, PartialEq)] pub struct Multibase { - base: Base, + base: multibase::Base, bytes: Vec, } @@ -41,7 +41,7 @@ impl Multibase { Ok(Self { base, bytes }) } - pub fn base_to_multibase(base: Base, encoded: &str) -> Self { + pub fn base_to_multibase(base: multibase::Base, encoded: &str) -> Self { let multibase_encoded = format!("{}{}", base.code(), encoded); Self { base, @@ -91,6 +91,8 @@ impl AsRef<[u8]> for Multibase { #[cfg(test)] mod tests { + use multibase::Base::Base58Btc; + use super::*; #[test] @@ -113,7 +115,7 @@ mod tests { assert_eq!( multibase, Multibase { - base: Base::Base58Btc, + base: Base58Btc, bytes: decode("zQmWvQxTqbG2Z9HPJgG57jjwR154cKhbtJenbyYTWkjgF3e") .unwrap() .1 @@ -159,13 +161,13 @@ mod tests { #[test] fn test_base_to_multibase() { let multibase = Multibase::base_to_multibase( - Base::Base58Btc, + Base58Btc, "QmWvQxTqbG2Z9HPJgG57jjwR154cKhbtJenbyYTWkjgF3e", ); assert_eq!( multibase, Multibase { - base: Base::Base58Btc, + base: Base58Btc, bytes: "zQmWvQxTqbG2Z9HPJgG57jjwR154cKhbtJenbyYTWkjgF3e" .as_bytes() .to_vec() 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 87ad93fec6..bf76b9ec8f 100644 --- a/did_core/did_doc/src/schema/verification_method/mod.rs +++ b/did_core/did_doc/src/schema/verification_method/mod.rs @@ -84,6 +84,18 @@ impl IncompleteVerificationMethodBuilder { } } + pub fn add_public_key_field( + self, + public_key: PublicKeyField, + ) -> CompleteVerificationMethodBuilder { + CompleteVerificationMethodBuilder { + id: self.id, + controller: self.controller, + verification_method_type: self.verification_method_type, + public_key: Some(public_key), + } + } + pub fn add_public_key_multibase( self, public_key_multibase: String, diff --git a/did_core/did_methods/did_peer/Cargo.toml b/did_core/did_methods/did_peer/Cargo.toml index 57dbcb9bcd..91279a4887 100644 --- a/did_core/did_methods/did_peer/Cargo.toml +++ b/did_core/did_methods/did_peer/Cargo.toml @@ -24,6 +24,7 @@ sha256 = "1.1.4" log = "0.4.16" url = { version = "2.3.1", features = ["serde"] } display_as_json = { path = "../../../misc/display_as_json" } +derive_builder = "0.12.0" [dev-dependencies] tokio = { version = "1.27.0", default-features = false, features = ["macros", "rt"] } diff --git a/did_core/did_methods/did_peer/examples/demo.rs b/did_core/did_methods/did_peer/examples/demo.rs index c543e7ee2c..3fe6cfc0de 100644 --- a/did_core/did_methods/did_peer/examples/demo.rs +++ b/did_core/did_methods/did_peer/examples/demo.rs @@ -1,13 +1,20 @@ -use std::error::Error; +use std::{collections::HashMap, error::Error}; use did_doc::schema::{ did_doc::DidDocument, + service::{typed::ServiceType, Service}, + types::uri::Uri, + utils::OneOrList, verification_method::{VerificationMethod, VerificationMethodType}, }; use did_parser_nom::{Did, DidUrl}; use did_peer::{ peer_did::{ - numalgos::{numalgo2::Numalgo2, numalgo3::Numalgo3}, + numalgos::{ + numalgo2::Numalgo2, + numalgo3::Numalgo3, + numalgo4::{encoded_document::DidPeer4EncodedDocumentBuilder, Numalgo4}, + }, PeerDid, }, resolver::{options::PublicKeyEncoding, PeerDidResolutionOptions, PeerDidResolver}, @@ -19,7 +26,7 @@ async fn main() -> Result<(), Box> { demo().await } -async fn demo() -> Result<(), Box> { +async fn demo_did_peer_2_and_3() -> 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( @@ -33,16 +40,16 @@ async fn demo() -> Result<(), Box> { let ddo = DidDocument::builder(did) .add_verification_method(verification_method) .build(); - println!("Did document: \n{}", serde_json::to_string_pretty(&ddo)?); + log::info!("Did document: \n{}", serde_json::to_string_pretty(&ddo)?); let peer_did_2 = PeerDid::::from_did_doc(ddo.clone())?; - println!("as did:peer numalgo(2): {}", peer_did_2); + log::info!("as did:peer numalgo(2): {}", peer_did_2); let peer_did_3 = PeerDid::::from_did_doc(ddo)?; - println!("as did:peer numalgo(3): {}", peer_did_3); + log::info!("as did:peer numalgo(3): {}", peer_did_3); let peer_did_3_v2 = peer_did_2.to_numalgo3()?; - println!( + log::info!( "as did:peer numalgo(2) converted to numalgo(3): {}", peer_did_3_v2 ); @@ -56,10 +63,46 @@ async fn demo() -> Result<(), Box> { ) .await .unwrap(); - println!( + log::info!( "Decoded did document: \n{}", serde_json::to_string_pretty(&did_document)? ); + Ok(()) +} + +async fn demo_did_peer_4() -> Result<(), Box> { + let service = Service::new( + Uri::new("#service-0").unwrap(), + "https://example.com/endpoint".parse().unwrap(), + OneOrList::One(ServiceType::DIDCommV2), + HashMap::default(), + ); + let encoded_document = DidPeer4EncodedDocumentBuilder::default() + .service(vec![service]) + .build() + .unwrap(); + log::info!( + "Pseudo did document as input for did:peer:4 construction: {}", + serde_json::to_string_pretty(&encoded_document)? + ); + + let peer_did_4 = PeerDid::::new(encoded_document)?; + log::info!("Instance of peer did: {}", peer_did_4); + + let did_document = peer_did_4.resolve_did_doc()?; + log::info!( + "Resolved did document: {}", + serde_json::to_string_pretty(&did_document)? + ); + Ok(()) +} + +async fn demo() -> Result<(), Box> { + let env = env_logger::Env::default().default_filter_or("info"); + env_logger::init_from_env(env); + + demo_did_peer_2_and_3().await?; + demo_did_peer_4().await?; Ok(()) } diff --git a/did_core/did_methods/did_peer/src/error.rs b/did_core/did_methods/did_peer/src/error.rs index d84c30e440..fea9c5045e 100644 --- a/did_core/did_methods/did_peer/src/error.rs +++ b/did_core/did_methods/did_peer/src/error.rs @@ -39,6 +39,8 @@ pub enum DidPeerError { RegexError(#[from] regex::Error), #[error("Public key error: {0}")] PublicKeyError(#[from] public_key::PublicKeyError), + #[error("General error: {0}")] + GeneralError(String), } impl From for DidPeerError { diff --git a/did_core/did_methods/did_peer/src/lib.rs b/did_core/did_methods/did_peer/src/lib.rs index 7a7f8a038a..4534edf1f5 100644 --- a/did_core/did_methods/did_peer/src/lib.rs +++ b/did_core/did_methods/did_peer/src/lib.rs @@ -1,3 +1,4 @@ +extern crate derive_builder; extern crate display_as_json; pub mod error; 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 12e8ff36e4..12f5c093ce 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 @@ -1,3 +1,5 @@ +use std::fmt::Display; + use did_parser_nom::Did; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -5,9 +7,8 @@ use super::PeerDid; use crate::{ error::DidPeerError, peer_did::{ - numalgos::{kind::NumalgoKind, numalgo2::Numalgo2, numalgo3::Numalgo3}, + numalgos::{kind::NumalgoKind, numalgo2::Numalgo2, numalgo3::Numalgo3, numalgo4::Numalgo4}, parse::parse_numalgo, - validate::validate, }, }; @@ -15,25 +16,35 @@ use crate::{ pub enum AnyPeerDid { Numalgo2(PeerDid), Numalgo3(PeerDid), + Numalgo4(PeerDid), } impl AnyPeerDid { pub fn parse(did: T) -> Result where + T: Display, Did: TryFrom, >::Error: Into, { + log::info!("AnyPeerDid >> parsing input {} as peer:did", did); let did: Did = did.try_into().map_err(Into::into)?; + log::info!("AnyPeerDid >> parsed did {}", did); let numalgo = parse_numalgo(&did)?; - validate(&did)?; + log::info!("AnyPeerDid >> parsed numalgo {}", numalgo.to_char()); let parsed = match numalgo { - NumalgoKind::MultipleInceptionKeys(numalgo) => { - AnyPeerDid::Numalgo2(PeerDid { did, numalgo }) - } - _ => AnyPeerDid::Numalgo3(PeerDid { + NumalgoKind::MultipleInceptionKeys(numalgo2) => AnyPeerDid::Numalgo2(PeerDid { + did, + numalgo: numalgo2, + }), + NumalgoKind::DidShortening(numalgo3) => AnyPeerDid::Numalgo3(PeerDid { did, - numalgo: Numalgo3, + numalgo: numalgo3, }), + NumalgoKind::DidPeer4(numalgo4) => AnyPeerDid::Numalgo4(PeerDid { + did, + numalgo: numalgo4, + }), + o => unimplemented!("Parsing numalgo {} is not supported", o.to_char()), }; Ok(parsed) } @@ -42,6 +53,7 @@ impl AnyPeerDid { match self { AnyPeerDid::Numalgo2(peer_did) => NumalgoKind::MultipleInceptionKeys(peer_did.numalgo), AnyPeerDid::Numalgo3(peer_did) => NumalgoKind::DidShortening(peer_did.numalgo), + AnyPeerDid::Numalgo4(peer_did) => NumalgoKind::DidPeer4(peer_did.numalgo), } } } @@ -54,6 +66,7 @@ impl Serialize for AnyPeerDid { match &self { AnyPeerDid::Numalgo2(peer_did) => serializer.serialize_str(peer_did.did().did()), AnyPeerDid::Numalgo3(peer_did) => serializer.serialize_str(peer_did.did().did()), + AnyPeerDid::Numalgo4(peer_did) => serializer.serialize_str(peer_did.did().did()), } } } @@ -77,14 +90,9 @@ mod tests { .VzXwpBnMdCm1cLmKuzgESn29nqnonp1ioqrQMRHNsmjMyppzx8xB2pv7cw8q1PdDacSrdWE3dtB9f7Nxk886mdzNFoPtY\ .SeyJpZCI6IiNzZXJ2aWNlLTAiLCJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0"; - const INVALID_PEER_DID_NUMALGO2: &str = "did:peer:2.Qqqq"; - const VALID_PEER_DID_NUMALGO3: &str = "did:peer:3.d8da5079c166b183cf815ee27747f34e116977103d8b23c96dcba9a9d9429688"; - const INVALID_PEER_DID_NUMALGO3: &str = - "did:peer:3.d8da5079c166b183cfz15ee27747f34e116977103d8b23c96dcba9a9d9429689"; - fn generic_peer_did_numalgo2() -> AnyPeerDid { AnyPeerDid::Numalgo2(PeerDid { did: VALID_PEER_DID_NUMALGO2.parse().unwrap(), @@ -125,25 +133,11 @@ mod tests { assert_eq!(deserialized, generic_peer_did_numalgo2()); } - #[test] - fn numalgo2_invalid() { - let deserialized: Result = - serde_json::from_str(&format!("\"{}\"", INVALID_PEER_DID_NUMALGO2)); - assert!(deserialized.is_err()); - } - #[test] fn numalgo3() { let deserialized: AnyPeerDid = serde_json::from_str(&format!("\"{}\"", VALID_PEER_DID_NUMALGO3)).unwrap(); assert_eq!(deserialized, generic_peer_did_numalgo3()); } - - #[test] - fn numalgo3_invalid() { - let deserialized: Result = - serde_json::from_str(&format!("\"{}\"", INVALID_PEER_DID_NUMALGO3)); - assert!(deserialized.is_err()); - } } } 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 c6cbfb5c5d..7af489a279 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 @@ -1,7 +1,6 @@ pub mod numalgos; mod parse; -mod validate; pub mod generic; @@ -156,24 +155,6 @@ mod tests { }; } - generate_negative_parse_test!( - unsupported_transform_code, - "did:peer:2\ - .Ea6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc\ - .Va6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V\ - .SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXX0", - DidPeerError::DidValidationError(_) - ); - - generate_negative_parse_test!( - malformed_base58_encoding_signing, - "did:peer:2\ - .Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc\ - .Vz6MkqRYqQiSgvZQdnBytw86Qbs0ZWUkGv22od935YF4s8M7V\ - .SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXX0", - DidPeerError::DidValidationError(_) - ); - generate_negative_parse_test!( malformed_base58_encoding_encryption, "did:peer:2\ diff --git a/did_core/did_methods/did_peer/src/peer_did/numalgos/kind.rs b/did_core/did_methods/did_peer/src/peer_did/numalgos/kind.rs index 732ea2fa7c..b2d9e68b57 100644 --- a/did_core/did_methods/did_peer/src/peer_did/numalgos/kind.rs +++ b/did_core/did_methods/did_peer/src/peer_did/numalgos/kind.rs @@ -3,7 +3,8 @@ use std::fmt::Display; use crate::{ error::DidPeerError, peer_did::numalgos::{ - numalgo0::Numalgo0, numalgo1::Numalgo1, numalgo2::Numalgo2, numalgo3::Numalgo3, Numalgo, + numalgo0::Numalgo0, numalgo1::Numalgo1, numalgo2::Numalgo2, numalgo3::Numalgo3, + numalgo4::Numalgo4, Numalgo, }, }; @@ -13,6 +14,7 @@ pub enum NumalgoKind { GenesisDoc(Numalgo1), MultipleInceptionKeys(Numalgo2), DidShortening(Numalgo3), + DidPeer4(Numalgo4), } impl NumalgoKind { @@ -22,6 +24,7 @@ impl NumalgoKind { NumalgoKind::GenesisDoc(_) => Numalgo1::NUMALGO_CHAR, NumalgoKind::MultipleInceptionKeys(_) => Numalgo2::NUMALGO_CHAR, NumalgoKind::DidShortening(_) => Numalgo3::NUMALGO_CHAR, + NumalgoKind::DidPeer4(_) => Numalgo4::NUMALGO_CHAR, } } } @@ -41,6 +44,7 @@ impl TryFrom for NumalgoKind { Numalgo1::NUMALGO_CHAR => Ok(NumalgoKind::GenesisDoc(Numalgo1)), Numalgo2::NUMALGO_CHAR => Ok(NumalgoKind::MultipleInceptionKeys(Numalgo2)), Numalgo3::NUMALGO_CHAR => Ok(NumalgoKind::DidShortening(Numalgo3)), + Numalgo4::NUMALGO_CHAR => Ok(NumalgoKind::DidPeer4(Numalgo4)), c => Err(DidPeerError::InvalidNumalgoCharacter(c)), } } 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 91054f76ab..616a7c7399 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 @@ -3,13 +3,14 @@ pub mod numalgo0; pub mod numalgo1; pub mod numalgo2; pub mod numalgo3; +pub mod numalgo4; use did_doc::schema::did_doc::DidDocument; use did_parser_nom::Did; use crate::{ error::DidPeerError, - peer_did::{parse::parse_numalgo, validate::validate, PeerDid}, + peer_did::{parse::parse_numalgo, PeerDid}, resolver::options::PublicKeyEncoding, }; @@ -26,7 +27,6 @@ pub trait Numalgo: Sized + Default { if numalgo_char != Self::NUMALGO_CHAR { return Err(DidPeerError::InvalidNumalgoCharacter(numalgo_char)); } - validate(&did)?; Ok(PeerDid::from_parts(did, Self::default())) } } diff --git a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo4/encoded_document.rs b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo4/encoded_document.rs new file mode 100644 index 0000000000..d0cf9b96c5 --- /dev/null +++ b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo4/encoded_document.rs @@ -0,0 +1,153 @@ +use std::collections::HashMap; + +use did_doc::schema::{ + did_doc::DidDocument, + service::Service, + types::uri::Uri, + verification_method::{ + PublicKeyField, VerificationMethod, VerificationMethodKind, VerificationMethodType, + }, +}; +use did_parser_nom::DidUrl; +use display_as_json::Display; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use crate::peer_did::{numalgos::numalgo4::Numalgo4, PeerDid}; + +/// The following DidPeer4* structs are similar to those defined in did_doc crate, +/// however with minor differences defined by https://identity.foundation/peer-did-method-spec/#creating-a-did +/// In nutshell: +/// - The document MUST NOT include an id at the root. +/// - All identifiers within this document MUST be relative +/// - All references pointing to resources within this document MUST be relative +/// - For verification methods, the controller MUST be omitted if the controller is the document +/// owner. +/// +/// These structures are **only** used for construction of did:peer:4 DIDs +#[derive( + Serialize, Deserialize, Clone, Debug, PartialEq, Default, Display, derive_builder::Builder, +)] +#[serde(default)] +#[serde(rename_all = "camelCase")] +#[builder(default)] +pub struct DidPeer4EncodedDocument { + #[serde(skip_serializing_if = "Vec::is_empty")] + also_known_as: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + verification_method: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + authentication: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + assertion_method: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + key_agreement: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + capability_invocation: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + capability_delegation: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + service: Vec, + #[serde(skip_serializing_if = "HashMap::is_empty")] + #[serde(flatten)] + extra: HashMap, +} + +impl DidPeer4EncodedDocument { + // - Performs DidDocument "contextualization" as described here: https://identity.foundation/peer-did-method-spec/#resolving-a-did + pub(crate) fn contextualize_to_did_doc(&self, did_peer_4: &PeerDid) -> DidDocument { + let mut builder = + DidDocument::builder(did_peer_4.did().clone()).set_service(self.service.clone()); + for vm in &self.verification_method { + builder.add_verification_method_2(vm.contextualize(did_peer_4)); + } + builder.build() + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(untagged)] +#[allow(clippy::large_enum_variant)] // todo: revisit this +pub enum DidPeer4VerificationMethodKind { + Resolved(DidPeer4VerificationMethod), + Resolvable(DidUrl), /* MUST be relative, + TODO: Should we have subtype such as RelativeDidUrl? */ +} + +impl DidPeer4VerificationMethodKind { + pub fn contextualize(&self, did_peer_4: &PeerDid) -> VerificationMethodKind { + match self { + DidPeer4VerificationMethodKind::Resolved(vm) => { + VerificationMethodKind::Resolved(vm.contextualize(did_peer_4)) + } + DidPeer4VerificationMethodKind::Resolvable(did_url) => { + VerificationMethodKind::Resolvable(did_url.clone()) + } + } + } +} + +// TODO: use builder instead of pub(crate) ? +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct DidPeer4VerificationMethod { + pub(crate) id: DidUrl, + // - Controller MUST be relative, can we break down DidUrl into new type RelativeDidUrl? + // - Controller MUST be omitted, if the controller is the document owner (main reason why this + // is different from did_doc::schema::verification_method::VerificationMethod) + // - TODO: add support for controller different than the document owner (how does that work for + // peer DIDs?) + // controller: Option, + #[serde(rename = "type")] + pub(crate) verification_method_type: VerificationMethodType, + #[serde(flatten)] + pub(crate) public_key: PublicKeyField, +} + +impl DidPeer4VerificationMethod { + pub(crate) fn contextualize(&self, did_peer_4: &PeerDid) -> VerificationMethod { + VerificationMethod::builder( + self.id.clone(), + did_peer_4.did().clone(), + self.verification_method_type, + ) + .add_public_key_field(self.public_key.clone()) + .build() + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use did_doc::schema::{ + service::{typed::ServiceType, Service}, + types::uri::Uri, + utils::OneOrList, + }; + + use crate::peer_did::numalgos::numalgo4::encoded_document::DidPeer4EncodedDocumentBuilder; + + #[test] + fn test_encoded_document_has_builder_api() { + let service = Service::new( + Uri::new("#service-0").unwrap(), + "https://example.com/endpoint".parse().unwrap(), + OneOrList::One(ServiceType::DIDCommV2), + HashMap::default(), + ); + let encoded_document = DidPeer4EncodedDocumentBuilder::default() + .service(vec![service]) + .build() + .unwrap(); + assert_eq!(encoded_document.service.len(), 1); + assert_eq!(encoded_document.assertion_method.len(), 0); + assert_eq!(encoded_document.authentication.len(), 0); + assert_eq!(encoded_document.key_agreement.len(), 0); + assert_eq!(encoded_document.capability_invocation.len(), 0); + assert_eq!(encoded_document.capability_delegation.len(), 0); + assert_eq!(encoded_document.verification_method.len(), 0); + assert_eq!(encoded_document.also_known_as.len(), 0); + assert_eq!(encoded_document.extra.len(), 0); + } +} diff --git a/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo4/mod.rs b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo4/mod.rs new file mode 100644 index 0000000000..95d3fd20a1 --- /dev/null +++ b/did_core/did_methods/did_peer/src/peer_did/numalgos/numalgo4/mod.rs @@ -0,0 +1,189 @@ +use did_doc::schema::did_doc::DidDocument; +use did_parser_nom::Did; + +use crate::{ + error::DidPeerError, + peer_did::{ + numalgos::{numalgo4::encoded_document::DidPeer4EncodedDocument, Numalgo}, + PeerDid, + }, +}; + +pub mod encoded_document; + +#[derive(Clone, Copy, Default, Debug, PartialEq)] +pub struct Numalgo4; + +impl Numalgo for Numalgo4 { + const NUMALGO_CHAR: char = '4'; +} + +impl PeerDid { + pub fn new(encoded_document: DidPeer4EncodedDocument) -> Result { + let serialized = serde_json::to_string(&encoded_document)?; + let mut prefixed_bytes = Vec::new(); + prefixed_bytes.push(0x02u8); // multi-codec prefix for json is 0x0200, see https://github.com/multiformats/multicodec/blob/master/table.csv + prefixed_bytes.push(0x00u8); + prefixed_bytes.extend_from_slice(serialized.as_bytes()); + let encoded_document = multibase::encode(multibase::Base::Base58Btc, prefixed_bytes); + // Take SHA2-256 digest of the encoded document (encode the bytes as utf-8) + // Prefix these bytes with the multihash prefix for SHA2-256 and the hash length (varint + // 0x12 for prefix, varint 0x20 for 32 bytes in length) Multibase encode the bytes + // as base58btc (base58 encode the value and prefix with a z) Consider this value + // the hash + let hash_raw = sha256::digest(&encoded_document); + let prefix = vec![0x12u8, 0x20u8]; + let hash = multibase::encode( + multibase::Base::Base58Btc, + [prefix.as_slice(), hash_raw.as_bytes()].concat(), + ); + let did = Did::parse(format!("did:peer:4{}:{}", hash, encoded_document))?; + Ok(Self { + did, + numalgo: Numalgo4, + }) + } + + pub fn long_form(&self) -> Result { + self.encoded_did_peer_4_document() + .ok_or(DidPeerError::GeneralError(format!( + "Long form is not available for peer did: {}", + self.did + )))?; + Ok(self.did().clone()) + } + + pub fn short_form(&self) -> Result { + let short_id = self + .did() + .id() + .to_string() + .split(':') + .collect::>()[0] + .to_string(); + Did::parse(format!("did:peer:{}", short_id)).map_err(|e| { + DidPeerError::GeneralError(format!("Failed to parse short form of PeerDid: {}", e)) + }) + } + + pub fn hash(&self) -> Result { + let short_form_did = self.short_form()?; + let hash = short_form_did.id()[1..].to_string(); // the first character of id did:peer:4 ID is always "4", followed by hash + Ok(hash) + } + + fn encoded_did_peer_4_document(&self) -> Option<&str> { + let did = self.did(); + did.id().split(':').collect::>().get(1).copied() + } + + fn to_did_peer_4_encoded_diddoc(&self) -> Result { + let encoded_did_doc = + self.encoded_did_peer_4_document() + .ok_or(DidPeerError::GeneralError(format!( + "to_did_peer_4_encoded_diddoc >> Long form is not available for peer did: {}", + self.did + )))?; + let (_base, diddoc_with_multibase_prefix) = + multibase::decode(encoded_did_doc).map_err(|e| { + DidPeerError::GeneralError(format!( + "Failed to decode multibase prefix from encoded did doc: {}", + e + )) + })?; + // without first 2 bytes + let peer4_did_doc: &[u8] = &diddoc_with_multibase_prefix[2..]; + let encoded_document: DidPeer4EncodedDocument = + serde_json::from_slice(peer4_did_doc).unwrap(); + Ok(encoded_document) + } + + pub fn resolve_did_doc(&self) -> Result { + let did_doc_peer4_encoded = self.to_did_peer_4_encoded_diddoc()?; + Ok(did_doc_peer4_encoded.contextualize_to_did_doc(self)) + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use did_doc::schema::{ + service::{typed::ServiceType, Service}, + types::uri::Uri, + utils::OneOrList, + verification_method::{PublicKeyField, VerificationMethodType}, + }; + use did_parser_nom::DidUrl; + + use crate::peer_did::{ + numalgos::numalgo4::{ + encoded_document::{DidPeer4EncodedDocumentBuilder, DidPeer4VerificationMethod}, + Numalgo4, + }, + PeerDid, + }; + + #[test] + fn test_create_did_peer_4() { + let service = Service::new( + Uri::new("#service-0").unwrap(), + "https://example.com/endpoint".parse().unwrap(), + OneOrList::One(ServiceType::DIDCommV2), + HashMap::default(), + ); + let vm = DidPeer4VerificationMethod { + id: DidUrl::parse("#key-1".to_string()).unwrap(), + verification_method_type: VerificationMethodType::Ed25519VerificationKey2020, + public_key: PublicKeyField::Base58 { + public_key_base58: "z27uFkiq".to_string(), + }, + }; + let encoded_document = DidPeer4EncodedDocumentBuilder::default() + .service(vec![service]) + .verification_method(vec![vm]) + .build() + .unwrap(); + log::info!( + "original didpeer4 document: {}", + serde_json::to_string_pretty(&encoded_document).unwrap() + ); + let did = PeerDid::::new(encoded_document).unwrap(); + assert_eq!(did.to_string(), "did:peer:4z84Vmeih9kTUrnxVanw9DhiVX9JNuW5cEz1RJx9dwrKcqh4bq96Z6zuc9m6oPV4gc6tafguyzd8dYih4N153Gh3XmWK:z2FrKwFgfDgrV5fdpSvPvBThURtNvDa3RWfoueUsEVQQmzJpMxXhAiutkPRRbuvVVeJDMZd2wdjeeNsRPx1csnDyQsoyhQWviaBd2LRen8fp9vZSkzmFmP1sgoKDXztkREhiUnKbXCiArA6t2nKed2NoGALYXFw1D72NbSgEhcMVzLL2wwgovV4D1HhEcvzXJQDKXwqUDaW1B3YgCMBKeEvy4vsaYhxf7JFcZzS5Ga8mSSUk3nAC9nXMWG3GT8XxzviQWxdfB2fwyKoy3bC3ihxwwjkpxVNuB72mJ"); + + let resolved_did_doc = did.resolve_did_doc().unwrap(); + assert_eq!(resolved_did_doc.id().to_string(), did.did().to_string()); + log::info!( + "resolved document: {}", + serde_json::to_string_pretty(&resolved_did_doc).unwrap() + ); + } + + #[test] + fn long_form_to_short_form() { + let peer_did = "did:peer:4z84UjLJ6ugExV8TJ5gJUtZap5q67uD34LU26m1Ljo2u9PZ4xHa9XnknHLc3YMST5orPXh3LKi6qEYSHdNSgRMvassKP:z27uFkiqJVwvvn2ke5M19UCvByS79r5NppqwjiGAJzkj1EM4sf2JmiUySkANKy4YNu8M7yKjSmvPJTqbcyhPrJs9TASzDs2fWE1vFegmaRJxHRF5M9wGTPwGR1NbPkLGsvcnXum7aN2f8kX3BnhWWWp"; + let peer_did = PeerDid::::parse(peer_did).unwrap(); + assert_eq!(peer_did.short_form().unwrap().to_string(), "did:peer:4z84UjLJ6ugExV8TJ5gJUtZap5q67uD34LU26m1Ljo2u9PZ4xHa9XnknHLc3YMST5orPXh3LKi6qEYSHdNSgRMvassKP".to_string()); + } + + #[test] + fn short_form_to_short_form() { + let peer_did = "did:peer:4z84UjLJ6ugExV8TJ5gJUtZap5q67uD34LU26m1Ljo2u9PZ4xHa9XnknHLc3YMST5orPXh3LKi6qEYSHdNSgRMvassKP"; + let peer_did = PeerDid::::parse(peer_did).unwrap(); + assert_eq!(peer_did.short_form().unwrap().to_string(), "did:peer:4z84UjLJ6ugExV8TJ5gJUtZap5q67uD34LU26m1Ljo2u9PZ4xHa9XnknHLc3YMST5orPXh3LKi6qEYSHdNSgRMvassKP".to_string()); + } + + #[test] + fn long_form_to_long_form() { + let peer_did = "did:peer:4z84UjLJ6ugExV8TJ5gJUtZap5q67uD34LU26m1Ljo2u9PZ4xHa9XnknHLc3YMST5orPXh3LKi6qEYSHdNSgRMvassKP:z27uFkiqJVwvvn2ke5M19UCvByS79r5NppqwjiGAJzkj1EM4sf2JmiUySkANKy4YNu8M7yKjSmvPJTqbcyhPrJs9TASzDs2fWE1vFegmaRJxHRF5M9wGTPwGR1NbPkLGsvcnXum7aN2f8kX3BnhWWWp"; + let peer_did = PeerDid::::parse(peer_did).unwrap(); + assert_eq!(peer_did.long_form().unwrap().to_string(), "did:peer:4z84UjLJ6ugExV8TJ5gJUtZap5q67uD34LU26m1Ljo2u9PZ4xHa9XnknHLc3YMST5orPXh3LKi6qEYSHdNSgRMvassKP:z27uFkiqJVwvvn2ke5M19UCvByS79r5NppqwjiGAJzkj1EM4sf2JmiUySkANKy4YNu8M7yKjSmvPJTqbcyhPrJs9TASzDs2fWE1vFegmaRJxHRF5M9wGTPwGR1NbPkLGsvcnXum7aN2f8kX3BnhWWWp".to_string()); + } + + #[test] + fn short_form_to_long_form_fails() { + let peer_did = "did:peer:4z84UjLJ6ugExV8TJ5gJUtZap5q67uD34LU26m1Ljo2u9PZ4xHa9XnknHLc3YMST5orPXh3LKi6qEYSHdNSgRMvassKP"; + let peer_did = PeerDid::::parse(peer_did).unwrap(); + peer_did.long_form().unwrap_err(); + } +} 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 4c7e610260..54c89b849f 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 @@ -3,6 +3,7 @@ use did_parser_nom::Did; use crate::{error::DidPeerError, peer_did::numalgos::kind::NumalgoKind}; pub fn parse_numalgo(did: &Did) -> Result { + log::info!("did.id() >> {}", did.id()); did.id() .chars() .next() 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 deleted file mode 100644 index b86fb2265f..0000000000 --- a/did_core/did_methods/did_peer/src/peer_did/validate.rs +++ /dev/null @@ -1,29 +0,0 @@ -use did_parser_nom::Did; -use once_cell::sync::Lazy; -use regex::Regex; - -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=]*)?))"; -static GROUP_NUMALGO_3: &str = r"(3\.[0-9a-fA-F]{64})"; - -pub static PEER_DID_REGEX: Lazy = Lazy::new(|| { - Regex::new(&format!( - r"^did:peer:({GROUP_NUMALGO_0_AND_1}|{GROUP_NUMALGO_2}|{GROUP_NUMALGO_3})$" - )) - .unwrap() -}); - -pub fn validate(did: &Did) -> Result<(), DidPeerError> { - if !PEER_DID_REGEX.is_match(did.did()) { - Err(DidPeerError::DidValidationError(format!( - "Invalid did: {} because it's not matching peer did regex {}", - did.did(), - *PEER_DID_REGEX - ))) - } else { - Ok(()) - } -} 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 ddd6e80cd5..845514564b 100644 --- a/did_core/did_methods/did_peer/src/resolver/mod.rs +++ b/did_core/did_methods/did_peer/src/resolver/mod.rs @@ -39,21 +39,22 @@ impl DidResolvable for PeerDidResolver { options: &Self::DidResolutionOptions, ) -> Result { let peer_did = AnyPeerDid::parse(did.to_owned())?; - match peer_did { + let did_doc = match peer_did { AnyPeerDid::Numalgo2(peer_did) => { let encoding = options.encoding.unwrap_or(PublicKeyEncoding::Multibase); let builder: DidDocumentBuilder = peer_did.to_did_doc_builder(encoding)?; - let did_doc = builder + 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(); - let builder = DidResolutionOutput::builder(did_doc) - .did_resolution_metadata(resolution_metadata); - Ok(builder.build()) + .build() } - n => Err(Box::new(DidPeerError::UnsupportedNumalgo(n.numalgo()))), - } + AnyPeerDid::Numalgo4(peer_did) => peer_did.resolve_did_doc()?, + n => return Err(Box::new(DidPeerError::UnsupportedNumalgo(n.numalgo()))), + }; + let resolution_metadata = DidResolutionMetadata::builder() + .content_type("application/did+json".to_string()) + .build(); + let builder = + DidResolutionOutput::builder(did_doc).did_resolution_metadata(resolution_metadata); + Ok(builder.build()) } } diff --git a/did_core/did_methods/did_peer/tests/demo.rs b/did_core/did_methods/did_peer/tests/demo.rs deleted file mode 100644 index f996b3d188..0000000000 --- a/did_core/did_methods/did_peer/tests/demo.rs +++ /dev/null @@ -1,50 +0,0 @@ -use std::{collections::HashMap, error::Error}; - -use did_doc::schema::{ - did_doc::DidDocument, - service::{typed::ServiceType, Service}, - types::uri::Uri, - utils::OneOrList, - verification_method::{VerificationMethod, VerificationMethodType}, -}; -use did_parser_nom::{Did, DidUrl}; -use did_peer::peer_did::{ - numalgos::{numalgo2::Numalgo2, numalgo3::Numalgo3}, - PeerDid, -}; -use url::Url; - -#[test] -fn demo() -> Result<(), Box> { - let service = Service::new( - Uri::new("xyz://example.org")?, - 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())?; - let verification_method = VerificationMethod::builder( - did_url, - did.clone(), - VerificationMethodType::Ed25519VerificationKey2018, - ) - .add_public_key_base64("Zm9vYmFyCg".to_string()) - .build(); - - let ddo = DidDocument::builder(did) - .add_verification_method(verification_method) - .add_service(service) - .build(); - println!("diddoc: {}", ddo); - - let peer_did_2 = PeerDid::::from_did_doc(ddo.clone())?; - println!("PeerDid numalgo(2): {}", peer_did_2); - let peer_did_3 = PeerDid::::from_did_doc(ddo)?; - println!("PeerDid numalgo(3): {}", peer_did_3); - let peer_did_3_v2 = peer_did_2.to_numalgo3()?; - println!("Converted PeerDid numalgo(3): {}", peer_did_3_v2); - - Ok(()) -} 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 2bae39dc44..d10a0e7993 100644 --- a/did_core/did_methods/did_peer/tests/resolve_negative.rs +++ b/did_core/did_methods/did_peer/tests/resolve_negative.rs @@ -54,18 +54,6 @@ async fn test_resolve_numalgo_2_invalid_2() { )); } -#[test] -async fn test_resolve_numalgo_2_invalid_3() { - let peer_did = "did:peer:2\ - .Cz6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc\ - .Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg\ - .SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXX0"; - assert!(matches!( - resolve_error(peer_did).await, - DidPeerError::DidValidationError(_) - )); -} - #[test] async fn test_resolve_numalgo_2_invalid_4() { let peer_did = "did:peer:2\ diff --git a/did_core/did_parser_nom/Cargo.toml b/did_core/did_parser_nom/Cargo.toml index d2561fcf7c..5cea174995 100644 --- a/did_core/did_parser_nom/Cargo.toml +++ b/did_core/did_parser_nom/Cargo.toml @@ -6,6 +6,8 @@ edition = "2021" [dependencies] nom = "7.1.3" serde = "1.0.160" +log = "0.4.16" [dev-dependencies] serde_test = "1.0.176" +env_logger = "0.10" diff --git a/did_core/did_parser_nom/src/did/mod.rs b/did_core/did_parser_nom/src/did/mod.rs index 39e3d332a0..5ca481e175 100644 --- a/did_core/did_parser_nom/src/did/mod.rs +++ b/did_core/did_parser_nom/src/did/mod.rs @@ -40,7 +40,7 @@ impl Did { } pub fn id(&self) -> &str { - self.did[self.id.clone()].as_ref() + self.did[self.id.start..self.id.end].as_ref() } pub(crate) fn from_parts( @@ -66,6 +66,14 @@ impl TryFrom for Did { } } +impl TryFrom<&str> for Did { + type Error = ParseError; + + fn try_from(did: &str) -> Result { + Did::from_str(did) + } +} + impl FromStr for Did { type Err = ParseError; diff --git a/did_core/did_parser_nom/src/did/parsing/did_core.rs b/did_core/did_parser_nom/src/did/parsing/did_core.rs index 8b520a051e..9930d374b0 100644 --- a/did_core/did_parser_nom/src/did/parsing/did_core.rs +++ b/did_core/did_parser_nom/src/did/parsing/did_core.rs @@ -3,9 +3,9 @@ use nom::{ branch::alt, bytes::complete::{tag, take_while1}, character::complete::{alphanumeric1, char, satisfy}, - combinator::{all_consuming, cut, opt, recognize}, - multi::many1, - sequence::{delimited, tuple}, + combinator::recognize, + multi::{many0, many1}, + sequence::{delimited, terminated, tuple}, AsChar, IResult, }; @@ -19,6 +19,11 @@ fn hexadecimal_digit(input: &str) -> IResult<&str, &str> { recognize(satisfy(is_hexadecimal_digit))(input) } +// todo: is this better? +// fn hexadecimal_digit(input: &str) -> IResult<&str, &str> { +// recognize(alt((nom::character::is_hex_digit, nom::character::is_hex_digit)))(input) +// } + fn is_lowercase_alphanumeric(c: char) -> bool { c.is_ascii_lowercase() || c.is_dec_digit() } @@ -29,7 +34,7 @@ fn pct_encoded(input: &str) -> IResult<&str, &str> { } // idchar = ALPHA / DIGIT / "." / "-" / "_" / pct-encoded -fn idchar(input: &str) -> IResult<&str, &str> { +pub(super) fn idchar(input: &str) -> IResult<&str, &str> { alt((alphanumeric1, tag("."), tag("-"), tag("_"), pct_encoded))(input) } @@ -39,27 +44,47 @@ fn method_name(input: &str) -> IResult<&str, &str> { delimited(char(':'), take_while1(is_lowercase_alphanumeric), char(':'))(input) } -// method-specific-id = *namespace 1*idchar -fn method_specific_id(input: &str) -> IResult<&str, &str> { - recognize(many1(idchar))(input) +fn method_specific_id_optional_repeat(input: &str) -> IResult<&str, &str> { + log::trace!( + "did_core::method_specific_id_optional_repeat >> input: {:?}", + input + ); + let ret = recognize(many0(terminated(many0(idchar), char(':'))))(input); // First half of DID Syntax ABNF rule method-specific-id: *( *idchar ":" + // )recognize(many1(idchar))(input) + log::trace!( + "did_core::method_specific_id_optional_repeat >> ret: {:?}", + ret + ); + ret } -// namespace = *idchar ":" -pub(super) fn namespace(input: &str) -> IResult<&str, &str> { - if let Some((before_last_colon, after_last_colon)) = input.rsplit_once(':') { - match cut(all_consuming(many1(alt((idchar, tag(":"))))))(before_last_colon) { - Ok(_) => Ok((after_last_colon, before_last_colon)), - Err(err) => Err(err), - } - } else { - Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Tag, - ))) - } +fn method_specific_id_required_characters(input: &str) -> IResult<&str, &str> { + log::trace!( + "did_core::method_specific_id_required_characters >> input: {:?}", + input + ); + let ret = recognize(many1(idchar))(input); // Second half of DID Syntax ABNF rule method-specific-id: 1*idchar + log::trace!( + "did_core::method_specific_id_required_characters >> ret: {:?}", + ret + ); + ret +} + +pub(super) fn general_did_id(input: &str) -> IResult<&str, &str> { + log::trace!("did_core::general_did_id >> input: {:?}", input); + let (input, did_id) = recognize(tuple(( + method_specific_id_optional_repeat, + method_specific_id_required_characters, + )))(input)?; + log::trace!("did_core::general_did_id >> did_id: {:?}", did_id); + Ok((input, did_id)) } // did = "did:" method-name ":" method-specific-id pub(super) fn parse_qualified_did(input: &str) -> IResult<&str, DidPart> { - tuple((tag("did"), method_name, opt(namespace), method_specific_id))(input) + let (input_left, (prefix, method, id)) = + tuple((tag("did"), method_name, general_did_id))(input)?; + + Ok((input_left, (prefix, method, None, id))) } diff --git a/did_core/did_parser_nom/src/did/parsing/did_key.rs b/did_core/did_parser_nom/src/did/parsing/did_key.rs index b2c69a9941..4d0eaf5066 100644 --- a/did_core/did_parser_nom/src/did/parsing/did_key.rs +++ b/did_core/did_parser_nom/src/did/parsing/did_key.rs @@ -23,10 +23,12 @@ pub(super) fn parse_did_key(input: &str) -> IResult<&str, DidPart> { delimited(char(':'), tag("key"), char(':'))(input) } - tuple(( + let (input_left, (prefix, method, namespace, id)) = tuple(( tag("did"), did_key_method, opt(fail::<_, &str, _>), cut(parse_mb_value), - ))(input) + ))(input)?; + + Ok((input_left, (prefix, method, namespace, id))) } diff --git a/did_core/did_parser_nom/src/did/parsing/did_peer_4.rs b/did_core/did_parser_nom/src/did/parsing/did_peer_4.rs new file mode 100644 index 0000000000..3ed9f2ebfc --- /dev/null +++ b/did_core/did_parser_nom/src/did/parsing/did_peer_4.rs @@ -0,0 +1,24 @@ +use nom::{ + bytes::complete::tag, + character::complete::char, + combinator::{cut, peek}, + sequence::{delimited, tuple}, + IResult, +}; + +use super::DidPart; +use crate::did::parsing::did_core::general_did_id; + +fn did_peer_method(input: &str) -> IResult<&str, &str> { + delimited(char(':'), tag("peer"), char(':'))(input) +} + +fn check_4(input: &str) -> IResult<&str, &str> { + peek(tag("4"))(input) +} + +pub(super) fn parse_did_peer_4(input: &str) -> IResult<&str, DidPart> { + let ret = tuple((tag("did"), did_peer_method, check_4, cut(general_did_id)))(input); + let (input_left, (prefix, method, _peek, id)) = ret?; + Ok((input_left, (prefix, method, None, id))) +} diff --git a/did_core/did_parser_nom/src/did/parsing/did_sov.rs b/did_core/did_parser_nom/src/did/parsing/did_sov.rs index 7f91b6d7ce..376fd328e3 100644 --- a/did_core/did_parser_nom/src/did/parsing/did_sov.rs +++ b/did_core/did_parser_nom/src/did/parsing/did_sov.rs @@ -1,19 +1,35 @@ use nom::{ + branch::alt, bytes::complete::tag, character::complete::{char, one_of}, - combinator::{cut, opt, recognize}, - multi::many_m_n, + combinator::{all_consuming, cut, opt, recognize}, + multi::{many1, many_m_n}, sequence::{delimited, tuple}, IResult, }; use super::{DidPart, BASE58CHARS}; -use crate::did::parsing::did_core::namespace; +use crate::did::parsing::did_core::idchar; fn base58char(input: &str) -> IResult<&str, &str> { recognize(one_of(BASE58CHARS))(input) } +// namespace = *idchar ":" +fn did_sov_namespace(input: &str) -> IResult<&str, &str> { + if let Some((before_last_colon, after_last_colon)) = input.rsplit_once(':') { + match cut(all_consuming(many1(alt((idchar, tag(":"))))))(before_last_colon) { + Ok(_) => Ok((after_last_colon, before_last_colon)), + Err(err) => Err(err), + } + } else { + Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Tag, + ))) + } +} + // idstring = 21*22(base58char) pub(super) fn parse_unqualified_sovrin_did(input: &str) -> IResult<&str, &str> { recognize(many_m_n(21, 22, base58char))(input) @@ -26,10 +42,12 @@ pub(super) fn parse_qualified_sovrin_did(input: &str) -> IResult<&str, DidPart> fn did_sov_method(input: &str) -> IResult<&str, &str> { delimited(char(':'), tag("sov"), char(':'))(input) } - tuple(( + let (input_left, (prefix, method, namespace, id)) = tuple(( tag("did"), did_sov_method, - opt(namespace), + opt(did_sov_namespace), cut(parse_unqualified_sovrin_did), - ))(input) + ))(input)?; + + Ok((input_left, (prefix, method, namespace, id))) } diff --git a/did_core/did_parser_nom/src/did/parsing/did_web.rs b/did_core/did_parser_nom/src/did/parsing/did_web.rs index df4d5d05b7..2a8f9fa521 100644 --- a/did_core/did_parser_nom/src/did/parsing/did_web.rs +++ b/did_core/did_parser_nom/src/did/parsing/did_web.rs @@ -13,10 +13,12 @@ pub(super) fn parse_did_web(input: &str) -> IResult<&str, DidPart> { delimited(char(':'), tag("web"), char(':'))(input) } - tuple(( + let (input_left, (prefix, method, namespace, id)) = tuple(( tag("did"), did_web_method, opt(fail::<_, &str, _>), take_till(|c| "?/#".contains(c)), - ))(input) + ))(input)?; + + Ok((input_left, (prefix, method, namespace, id))) } diff --git a/did_core/did_parser_nom/src/did/parsing/mod.rs b/did_core/did_parser_nom/src/did/parsing/mod.rs index 33b775f4e3..148ef6d17d 100644 --- a/did_core/did_parser_nom/src/did/parsing/mod.rs +++ b/did_core/did_parser_nom/src/did/parsing/mod.rs @@ -1,5 +1,6 @@ mod did_core; mod did_key; +mod did_peer_4; mod did_sov; mod did_web; @@ -15,7 +16,7 @@ use self::{ did_sov::{parse_qualified_sovrin_did, parse_unqualified_sovrin_did}, did_web::parse_did_web, }; -use crate::{Did, DidRange, ParseError}; +use crate::{did::parsing::did_peer_4::parse_did_peer_4, Did, DidRange, ParseError}; type DidPart<'a> = (&'a str, &'a str, Option<&'a str>, &'a str); pub type DidRanges = (Option, Option, Option); @@ -62,6 +63,7 @@ fn to_did_ranges((did_prefix, method, namespace, id): DidPart) -> DidRanges { pub fn parse_did_ranges(input: &str) -> IResult<&str, DidRanges> { alt(( + map(parse_did_peer_4, to_did_ranges), map(parse_did_web, to_did_ranges), map(parse_did_key, to_did_ranges), map(parse_qualified_sovrin_did, to_did_ranges), diff --git a/did_core/did_parser_nom/tests/did/negative.rs b/did_core/did_parser_nom/tests/did/negative.rs index 10bd57bb25..6a52f6a33b 100644 --- a/did_core/did_parser_nom/tests/did/negative.rs +++ b/did_core/did_parser_nom/tests/did/negative.rs @@ -5,7 +5,6 @@ macro_rules! test_cases_negative { $( #[test] fn $name() { - println!("Testing {}", $input); assert!(Did::parse($input.to_string()).is_err()); } )* diff --git a/did_core/did_parser_nom/tests/did/positive.rs b/did_core/did_parser_nom/tests/did/positive.rs index aa6b29f408..6a0a34fa62 100644 --- a/did_core/did_parser_nom/tests/did/positive.rs +++ b/did_core/did_parser_nom/tests/did/positive.rs @@ -1,14 +1,27 @@ +use std::sync::Once; + use did_parser_nom::Did; +static TEST_LOGGING_INIT: Once = Once::new(); + +pub fn init_logger() { + TEST_LOGGING_INIT.call_once(|| { + let env = env_logger::Env::default().default_filter_or("info"); + env_logger::init_from_env(env); + }) +} + macro_rules! test_cases_positive { - ($($name:ident: $input:expr, $expected_did:expr, $expected_method:expr, $expected_namespace:expr, $expected_id:expr)*) => { + ($($name:ident: $input_did:expr, $expected_method:expr, $expected_namespace:expr, $expected_id:expr)*) => { $( #[test] fn $name() { - println!("Testing {}", $input); - let parsed_did = Did::parse($input.to_string()).unwrap(); + init_logger(); + + log::debug!("Testing parsing of {}", $input_did); + let parsed_did = Did::parse($input_did.to_string()).unwrap(); - assert_eq!(parsed_did.did(), $expected_did, "DID"); + assert_eq!(parsed_did.did(), $input_did, "DID"); assert_eq!(parsed_did.method(), $expected_method, "Method"); assert_eq!(parsed_did.namespace(), $expected_namespace, "Namespace"); assert_eq!(parsed_did.id(), $expected_id, "ID"); @@ -18,76 +31,54 @@ macro_rules! test_cases_positive { } test_cases_positive! { - test_case1: - "did:example:123456789abcdefghi", + test_did_unknown_method: "did:example:123456789abcdefghi", Some("example"), None, "123456789abcdefghi" - test_case2: - "did:web:w3c-ccg.github.io", + test_did_web: "did:web:w3c-ccg.github.io", Some("web"), None, "w3c-ccg.github.io" - test_case3: - "2ZHFFhzA2XtTD6hJqzL7ux", + test_did_sov_unqualified: "2ZHFFhzA2XtTD6hJqzL7ux", None, None, "2ZHFFhzA2XtTD6hJqzL7ux" - test_case4: - "did:sov:2wJPyULfLLnYTEFYzByfUR", + test_did_sov: "did:sov:2wJPyULfLLnYTEFYzByfUR", Some("sov"), None, "2wJPyULfLLnYTEFYzByfUR" - test_case5: - "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", + test_did_key: "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", Some("key"), None, "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK" - test_case6: - "did:indy:sovrin:7Tqg6BwSSWapxgUDm9KKgg", + test_did_indy: "did:indy:sovrin:7Tqg6BwSSWapxgUDm9KKgg", Some("indy"), - Some("sovrin"), - "7Tqg6BwSSWapxgUDm9KKgg" - test_case7: - "did:indy:sovrin:alpha:7Tqg6BwSSWapxgUDm9KKgg", - "did:indy:sovrin:alpha:7Tqg6BwSSWapxgUDm9KKgg", - Some("indy"), - Some("sovrin:alpha"), - "7Tqg6BwSSWapxgUDm9KKgg" - test_case8: - "did:indy:sovrin:alpha:%0Aqg6BwS.Wapxg-Dm9K_gg", - "did:indy:sovrin:alpha:%0Aqg6BwS.Wapxg-Dm9K_gg", - Some("indy"), - Some("sovrin:alpha"), - "%0Aqg6BwS.Wapxg-Dm9K_gg" - test_case9: - "did:sov:builder:VbPQNHsvoLZdaNU7fTBeFx", + None, + "sovrin:7Tqg6BwSSWapxgUDm9KKgg" + test_did_sov_namespaced: "did:sov:builder:VbPQNHsvoLZdaNU7fTBeFx", Some("sov"), Some("builder"), "VbPQNHsvoLZdaNU7fTBeFx" - test_case10: - "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", - "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", - Some("key"), - None, - "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK" - test_case11: - "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9kaWRjb21tIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0xIl19fQ.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9hbm90aGVyIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0yIl19fQ", + test_did_peer_2: "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9kaWRjb21tIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0xIl19fQ.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9hbm90aGVyIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0yIl19fQ", Some("peer"), None, "2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9kaWRjb21tIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0xIl19fQ.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly9leGFtcGxlLmNvbS9hbm90aGVyIiwiYSI6WyJkaWRjb21tL3YyIl0sInIiOlsiZGlkOmV4YW1wbGU6MTIzNDU2Nzg5YWJjZGVmZ2hpI2tleS0yIl19fQ" - test_case12: - "did:peer:3zQmS19jtYDvGtKVrJhQnRFpBQAx3pJ9omx2HpNrcXFuRCz9", + test_did_peer_3: "did:peer:3zQmS19jtYDvGtKVrJhQnRFpBQAx3pJ9omx2HpNrcXFuRCz9", Some("peer"), None, "3zQmS19jtYDvGtKVrJhQnRFpBQAx3pJ9omx2HpNrcXFuRCz9" + test_did_peer_4: + "did:peer:4z84UjLJ6ugExV8TJ5gJUtZap5q67uD34LU26m1Ljo2u9PZ4xHa9XnknHLc3YMST5orPXh3LKi6qEYSHdNSgRMvassKP:z27uFkiqJVwvvn2ke5M19UCvByS79r5NppqwjiGAJzkj1EM4sf2JmiUySkANKy4YNu8M7yKjSmvPJTqbcyhPrJs9TASzDs2fWE1vFegmaRJxHRF5M9wGTPwGR1NbPkLGsvcnXum7aN2f8kX3BnhWWWp", + Some("peer"), + None, + "4z84UjLJ6ugExV8TJ5gJUtZap5q67uD34LU26m1Ljo2u9PZ4xHa9XnknHLc3YMST5orPXh3LKi6qEYSHdNSgRMvassKP:z27uFkiqJVwvvn2ke5M19UCvByS79r5NppqwjiGAJzkj1EM4sf2JmiUySkANKy4YNu8M7yKjSmvPJTqbcyhPrJs9TASzDs2fWE1vFegmaRJxHRF5M9wGTPwGR1NbPkLGsvcnXum7aN2f8kX3BnhWWWp" } diff --git a/did_core/did_parser_nom/tests/did_url/negative.rs b/did_core/did_parser_nom/tests/did_url/negative.rs index fd4fcccd69..d2e55533c9 100644 --- a/did_core/did_parser_nom/tests/did_url/negative.rs +++ b/did_core/did_parser_nom/tests/did_url/negative.rs @@ -5,7 +5,6 @@ macro_rules! test_cases_negative { $( #[test] fn $name() { - println!("Testing {}", $input); assert!(DidUrl::parse($input.to_string()).is_err()); } )* diff --git a/did_core/did_parser_nom/tests/did_url/positive.rs b/did_core/did_parser_nom/tests/did_url/positive.rs index 19f2969871..fea2099559 100644 --- a/did_core/did_parser_nom/tests/did_url/positive.rs +++ b/did_core/did_parser_nom/tests/did_url/positive.rs @@ -7,7 +7,6 @@ macro_rules! test_cases_positive { $( #[test] fn $name() { - println!("Testing {}", $input); let parsed_did = DidUrl::parse($input.to_string()).unwrap(); assert_eq!(parsed_did.did(), $expected_did, "DID"); @@ -27,8 +26,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), None, None, HashMap::new() @@ -37,8 +36,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi/path", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), Some("/path"), None, HashMap::new() @@ -47,8 +46,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi/path?query1=value1&query2=value2", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), Some("/path"), None, { @@ -62,8 +61,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi/path?query=value#fragment", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), Some("/path"), Some("fragment"), { @@ -76,8 +75,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi?query1=value1&query2=value2#fragment", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), None, Some("fragment"), { @@ -91,8 +90,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi#fragment", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), None, Some("fragment"), HashMap::new() @@ -101,8 +100,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi?query=value", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), None, None, { @@ -115,8 +114,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi/path#fragment", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), Some("/path"), Some("fragment"), HashMap::new() @@ -125,8 +124,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi#fragment", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), None, Some("fragment"), HashMap::new() @@ -135,8 +134,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi?query=value", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), None, None, { @@ -149,8 +148,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi/path?query=value", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), Some("/path"), None, { @@ -163,8 +162,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi/path?query1=value1&query2=value2#fragment", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), Some("/path"), Some("fragment"), { @@ -178,8 +177,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi?query1=value1?query2=value2", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), None, None, { @@ -193,8 +192,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi?query=", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), None, None, { @@ -207,8 +206,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi?query1=value1&query2=#fragment", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), None, Some("fragment"), { @@ -222,8 +221,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi?query1=value1&query2=value2#fragment", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), None, Some("fragment"), { @@ -315,8 +314,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi?query1=val;ue1", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), None, None, { @@ -329,8 +328,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi?quer;y1=value1", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), None, None, { @@ -343,8 +342,8 @@ test_cases_positive! { "did:example:namespace:123456789abcdefghi?query1=val=ue1", Some("did:example:namespace:123456789abcdefghi"), Some("example"), - Some("namespace"), - Some("123456789abcdefghi"), + None, + Some("namespace:123456789abcdefghi"), None, None, { @@ -354,10 +353,10 @@ test_cases_positive! { } test_case27: - "did:indy:sovrin:5nDyJVP1NrcPAttP3xwMB9/anoncreds/v0/REV_REG_DEF/56495/npdb/TAG1", - Some("did:indy:sovrin:5nDyJVP1NrcPAttP3xwMB9"), - Some("indy"), - Some("sovrin"), + "did:sov:5nDyJVP1NrcPAttP3xwMB9/anoncreds/v0/REV_REG_DEF/56495/npdb/TAG1", + Some("did:sov:5nDyJVP1NrcPAttP3xwMB9"), + Some("sov"), + None, Some("5nDyJVP1NrcPAttP3xwMB9"), Some("/anoncreds/v0/REV_REG_DEF/56495/npdb/TAG1"), None,