did_utils/methods/peer/
method.rs

1use multibase::Base::{Base58Btc, Base64Url};
2use regex::Regex;
3use serde::{Deserialize, Serialize};
4
5use super::{
6    errors::DIDPeerMethodError,
7    util::{abbreviate_service_for_did_peer_2, validate_input_document},
8};
9
10use crate::{
11    crypto::{
12        alg::decode_multikey,
13        sha256_multihash, Algorithm, Ed25519KeyPair, PublicKeyFormat, {Generate, KeyMaterial},
14    },
15    didcore::{self, Document as DIDDocument, KeyFormat, Service, VerificationMethod},
16    ldmodel::Context,
17    methods::{errors::DIDResolutionError, peer::util, traits::DIDMethod, DidKey},
18};
19
20lazy_static::lazy_static!(
21    pub static ref DID_PEER_0_REGEX: Regex = Regex::new("^did:peer:(0(z)([1-9a-km-zA-HJ-NP-Z]+))$").unwrap();
22    pub static ref DID_PEER_2_REGEX: Regex = Regex::new("^did:peer:(2((\\.[AEVID](z)([1-9a-km-zA-HJ-NP-Z]+))+(\\.(S)[0-9a-zA-Z]*)*))$").unwrap();
23);
24
25const MULTICODEC_JSON: [u8; 2] = [0x80, 0x04];
26
27#[derive(Default)]
28pub struct DidPeer {
29    /// Key format to consider during DID expansion into a DID document
30    key_format: PublicKeyFormat,
31}
32
33#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
34#[serde(rename_all = "camelCase")]
35pub enum Purpose {
36    Assertion,
37    Encryption,   // Key Agreement
38    Verification, // Authentication
39    CapabilityInvocation,
40    CapabilityDelegation,
41    Service,
42}
43
44#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
45#[serde(rename_all = "camelCase")]
46pub struct PurposedKey {
47    pub purpose: Purpose,
48    pub public_key_multibase: String,
49}
50
51impl DIDMethod for DidPeer {
52    fn name() -> String {
53        "did:peer".to_string()
54    }
55}
56
57impl Purpose {
58    /// Converts purpose to normalized one-letter code
59    pub fn code(&self) -> char {
60        match self {
61            Purpose::Assertion => 'A',
62            Purpose::Encryption => 'E',
63            Purpose::Verification => 'V',
64            Purpose::CapabilityInvocation => 'I',
65            Purpose::CapabilityDelegation => 'D',
66            Purpose::Service => 'S',
67        }
68    }
69
70    /// Derives purpose from normalized one-letter code
71    pub fn from_code(c: &char) -> Result<Self, DIDPeerMethodError> {
72        match c {
73            'A' => Ok(Purpose::Assertion),
74            'E' => Ok(Purpose::Encryption),
75            'V' => Ok(Purpose::Verification),
76            'I' => Ok(Purpose::CapabilityInvocation),
77            'D' => Ok(Purpose::CapabilityDelegation),
78            'S' => Ok(Purpose::Service),
79            _ => Err(DIDPeerMethodError::InvalidPurposeCode),
80        }
81    }
82}
83
84impl DidPeer {
85    /// Creates new instance of DidPeer.
86    pub fn new() -> Self {
87        Self::default()
88    }
89
90    /// Creates new instance of DidPeer with given key format.
91    pub fn with_format(key_format: PublicKeyFormat) -> Self {
92        Self { key_format }
93    }
94
95    // ---------------------------------------------------------------------------
96    // Generating did:peer addresses
97    // ---------------------------------------------------------------------------
98
99    /// Method 0: Generates did:peer address from ed25519 inception key without doc
100    ///
101    /// See https://identity.foundation/peer-did-method-spec/#method-0-inception-key-without-doc
102    pub fn create_did_peer_0_from_ed25519_keypair(keypair: &Ed25519KeyPair) -> Result<String, DIDPeerMethodError> {
103        let did_key = DidKey::from_ed25519_keypair(keypair)?;
104
105        Ok(did_key.replace("did:key:", "did:peer:0"))
106    }
107
108    /// Method 0: Generates did:peer address from inception key without doc
109    ///
110    /// See https://identity.foundation/peer-did-method-spec/#method-0-inception-key-without-doc
111    pub fn create_did_peer_0_from_raw_public_key(alg: Algorithm, bytes: &[u8]) -> Result<String, DIDPeerMethodError> {
112        let did_key = DidKey::from_raw_public_key(alg, bytes)?;
113
114        Ok(did_key.replace("did:key:", "did:peer:0"))
115    }
116
117    /// Method 1: Generates did:peer address from DID document
118    ///
119    /// See https://identity.foundation/peer-did-method-spec/#method-1-genesis-doc
120    pub fn create_did_peer_1_from_stored_variant(diddoc: &DIDDocument) -> Result<String, DIDPeerMethodError> {
121        if !diddoc.id.is_empty() {
122            return Err(DIDPeerMethodError::InvalidStoredVariant);
123        }
124
125        let json = json_canon::to_string(diddoc)?;
126        let multihash = sha256_multihash(json.as_bytes());
127
128        Ok(format!("did:peer:1{multihash}"))
129    }
130
131    /// Method 2: Generates did:peer address from multiple inception key
132    ///
133    /// See https://identity.foundation/peer-did-method-spec/#method-2-multiple-inception-key-without-doc
134    pub fn create_did_peer_2(keys: &[PurposedKey], services: &[Service]) -> Result<String, DIDPeerMethodError> {
135        if keys.is_empty() && services.is_empty() {
136            return Err(DIDPeerMethodError::EmptyArguments);
137        }
138
139        // Initialization
140        let mut chain = vec![];
141
142        // Chain keys
143        for key in keys {
144            if matches!(key.purpose, Purpose::Service) {
145                return Err(DIDPeerMethodError::UnexpectedPurpose);
146            }
147
148            chain.push(format!(".{}{}", key.purpose.code(), key.public_key_multibase));
149        }
150
151        // Chain services
152        for service in services {
153            let abbreviated_service = abbreviate_service_for_did_peer_2(service)?;
154            let encoded_service = Base64Url.encode(abbreviated_service);
155
156            chain.push(format!(".{}{}", Purpose::Service.code(), encoded_service));
157        }
158
159        Ok(format!("did:peer:2{}", chain.join("")))
160    }
161
162    /// Method 3: DID Shortening with SHA-256 Hash
163    ///
164    /// See https://identity.foundation/peer-did-method-spec/#method-3-did-shortening-with-sha-256-hash
165    pub fn create_did_peer_3(did: &str) -> Result<String, DIDPeerMethodError> {
166        let stripped = match did.strip_prefix("did:peer:2") {
167            Some(stripped) => stripped,
168            None => return Err(DIDPeerMethodError::IllegalArgument),
169        };
170
171        // Multihash with SHA256
172        let multihash = sha256_multihash(stripped.as_bytes());
173
174        Ok(format!("did:peer:3{multihash}"))
175    }
176
177    /// Method 4: Generates did:peer address from DID document (embedding long form)
178    ///
179    /// See https://identity.foundation/peer-did-method-spec/#method-4-short-form-and-long-form
180    pub fn create_did_peer_4_from_stored_variant(diddoc: &DIDDocument) -> Result<String, DIDPeerMethodError> {
181        // Validate input documment
182        validate_input_document(diddoc)?;
183
184        // Encode document
185        let json = json_canon::to_string(diddoc)?;
186        let encoded = multibase::encode(Base58Btc, [&MULTICODEC_JSON, json.as_bytes()].concat());
187
188        // Hashing
189        let hash = sha256_multihash(encoded.as_bytes());
190
191        Ok(format!("did:peer:4{hash}:{encoded}"))
192    }
193
194    /// Method 4: DID shortening for did:peer:4 addresses
195    ///
196    /// See https://identity.foundation/peer-did-method-spec/#method-4-short-form-and-long-form
197    pub fn shorten_did_peer_4(did: &str) -> Result<String, DIDPeerMethodError> {
198        let stripped = match did.strip_prefix("did:peer:4") {
199            Some(stripped) => stripped,
200            None => return Err(DIDPeerMethodError::IllegalArgument),
201        };
202
203        // Split hash and encoded segments
204        let segments: Vec<_> = stripped.split(':').collect();
205        if segments.len() != 2 || segments[1].is_empty() {
206            return Err(DIDPeerMethodError::MalformedLongPeerDID);
207        }
208
209        let (hash, encoded) = (segments[0], segments[1]);
210
211        // Verify hash
212        if hash != sha256_multihash(encoded.as_bytes()) {
213            return Err(DIDPeerMethodError::InvalidHash);
214        }
215
216        Ok(format!("did:peer:4{hash}"))
217    }
218
219    // ---------------------------------------------------------------------------
220    // Expanding did:peer addresses
221    // ---------------------------------------------------------------------------
222
223    /// Expands `did:peer` address into DID document
224    pub fn expand(&self, did: &str) -> Result<DIDDocument, DIDResolutionError> {
225        if !did.starts_with("did:peer:") {
226            return Err(DIDResolutionError::InvalidDid);
227        }
228
229        match did {
230        s if s.starts_with("did:peer:0") => self.expand_did_peer_0(did).map_err(Into::into),
231        s if s.starts_with("did:peer:2") => self.expand_did_peer_2(did).map_err(Into::into),
232        s if s.starts_with("did:peer:4") => self.expand_did_peer_4(did).map_err(Into::into),
233        _ => Err(DIDResolutionError::MethodNotSupported), 
234    }
235}
236
237
238    /// Expands did:peer:0 address
239    ///
240    /// See https://identity.foundation/peer-did-method-spec/#method-0-inception-key-without-doc
241    pub fn expand_did_peer_0(&self, did: &str) -> Result<DIDDocument, DIDPeerMethodError> {
242        if !DID_PEER_0_REGEX.is_match(did) {
243            return Err(DIDPeerMethodError::RegexMismatch);
244        }
245
246        // Decode multikey in did:peer
247        let multikey = did.strip_prefix("did:peer:0").unwrap();
248        let (alg, key) = decode_multikey(multikey).map_err(|_| DIDPeerMethodError::MalformedPeerDID)?;
249
250        // Run algorithm for signature verification method expansion
251        let signature_verification_method = self.derive_verification_method(did, multikey, alg, &key)?;
252
253        // Build DID document
254        let mut diddoc = DIDDocument {
255            context: Context::SetOfString(vec![
256                String::from("https://www.w3.org/ns/did/v1"),
257                match self.key_format {
258                    PublicKeyFormat::Multikey => String::from("https://w3id.org/security/multikey/v1"),
259                    PublicKeyFormat::Jwk => String::from("https://w3id.org/security/suites/jws-2020/v1"),
260                },
261            ]),
262            id: did.to_string(),
263            controller: None,
264            also_known_as: None,
265            verification_method: Some(vec![signature_verification_method.clone()]),
266            authentication: Some(vec![didcore::VerificationMethodType::Reference(
267                signature_verification_method.id.clone(), //
268            )]),
269            assertion_method: Some(vec![didcore::VerificationMethodType::Reference(
270                signature_verification_method.id.clone(), //
271            )]),
272            capability_delegation: Some(vec![didcore::VerificationMethodType::Reference(
273                signature_verification_method.id.clone(), //
274            )]),
275            capability_invocation: Some(vec![didcore::VerificationMethodType::Reference(
276                signature_verification_method.id.clone(), //
277            )]),
278            key_agreement: None,
279            service: None,
280            additional_properties: None,
281            proof: None,
282        };
283
284        // Derive X25519 key agreement if Ed25519
285        if alg == Algorithm::Ed25519 {
286            // Run algorithm for encryption verification method derivation
287            let encryption_verification_method = self.derive_encryption_verification_method(did, &key)?;
288
289            // Amend DID document accordingly
290            let verification_method = diddoc.verification_method.as_mut().unwrap();
291            verification_method.push(encryption_verification_method.clone());
292            diddoc.key_agreement = Some(vec![didcore::VerificationMethodType::Reference(
293                encryption_verification_method.id.clone(), //
294            )]);
295        }
296
297        // Output
298        Ok(diddoc)
299    }
300
301    /// Derives verification method from multikey constituents
302    fn derive_verification_method(&self, did: &str, multikey: &str, alg: Algorithm, key: &[u8]) -> Result<VerificationMethod, DIDPeerMethodError> {
303        if let Some(required_length) = alg.public_key_length() {
304            if required_length != key.len() {
305                return Err(DIDResolutionError::InvalidPublicKeyLength.into());
306            }
307        }
308
309        Ok(VerificationMethod {
310            id: format!("#{multikey}"),
311            key_type: String::from(match self.key_format {
312                PublicKeyFormat::Multikey => "Multikey",
313                PublicKeyFormat::Jwk => "JsonWebKey2020",
314            }),
315            controller: did.to_string(),
316            public_key: Some(match self.key_format {
317                PublicKeyFormat::Multikey => KeyFormat::Multibase(String::from(multikey)),
318                PublicKeyFormat::Jwk => KeyFormat::Jwk(alg.build_jwk(key).map_err(|_| DIDResolutionError::InternalError)?),
319            }),
320            ..Default::default()
321        })
322    }
323
324    /// Derives X25519 key agreement verification method indirectly from Ed25519 key
325    fn derive_encryption_verification_method(&self, did: &str, key: &[u8]) -> Result<VerificationMethod, DIDPeerMethodError> {
326        let key: [u8; 32] = key.try_into().map_err(|_| DIDResolutionError::InvalidPublicKeyLength)?;
327        let ed25519_keypair = Ed25519KeyPair::from_public_key(&key).map_err(|_| DIDResolutionError::InternalError)?;
328        let x25519_keypair = ed25519_keypair.get_x25519().map_err(|_| DIDResolutionError::InternalError)?;
329
330        let alg = Algorithm::X25519;
331        let enc_key = &x25519_keypair.public_key_bytes().unwrap()[..];
332        let enc_multikey = multibase::encode(Base58Btc, [&alg.muticodec_prefix(), enc_key].concat());
333
334        self.derive_verification_method(did, &enc_multikey, alg, enc_key)
335    }
336
337    /// Expands did:peer:2 address
338    ///
339    /// See https://identity.foundation/peer-did-method-spec/#resolving-a-didpeer2
340    pub fn expand_did_peer_2(&self, did: &str) -> Result<DIDDocument, DIDPeerMethodError> {
341        if !DID_PEER_2_REGEX.is_match(did) {
342            return Err(DIDPeerMethodError::RegexMismatch);
343        }
344
345        // Compute did:peer:3 alias
346
347        let alias = Self::create_did_peer_3(did)?;
348
349        // Dissecting did address
350
351        let chain = did.strip_prefix("did:peer:2.").unwrap();
352        let chain: Vec<(Purpose, &str)> = chain
353            .split('.')
354            .map(|e| {
355                (
356                    Purpose::from_code(&e.chars().nth(0).unwrap()).expect("invalid purpose prefix bypasses regex check"),
357                    &e[1..],
358                )
359            })
360            .collect();
361
362        // Define LD-JSON Context
363
364        let context = Context::SetOfString(vec![
365            String::from("https://www.w3.org/ns/did/v1"),
366            match self.key_format {
367                PublicKeyFormat::Multikey => String::from("https://w3id.org/security/multikey/v1"),
368                PublicKeyFormat::Jwk => String::from("https://w3id.org/security/suites/jws-2020/v1"),
369            },
370        ]);
371
372        // Initialize relationships
373
374        let mut authentication = vec![];
375        let mut assertion_method = vec![];
376        let mut key_agreement = vec![];
377        let mut capability_delegation = vec![];
378        let mut capability_invocation = vec![];
379
380        // Resolve verification methods
381
382        let key_chain = chain.iter().filter(|(purpose, _)| purpose != &Purpose::Service);
383
384        let mut methods: Vec<VerificationMethod> = vec![];
385
386        for (method_current_id, (purpose, multikey)) in key_chain.enumerate() {
387            let id = format!("#key-{}", method_current_id + 1);
388
389            match purpose {
390                Purpose::Assertion => assertion_method.push(didcore::VerificationMethodType::Reference(id.clone())),
391                Purpose::Encryption => key_agreement.push(didcore::VerificationMethodType::Reference(id.clone())),
392                Purpose::Verification => authentication.push(didcore::VerificationMethodType::Reference(id.clone())),
393                Purpose::CapabilityDelegation => capability_delegation.push(didcore::VerificationMethodType::Reference(id.clone())),
394                Purpose::CapabilityInvocation => capability_invocation.push(didcore::VerificationMethodType::Reference(id.clone())),
395                Purpose::Service => unreachable!(),
396            }
397
398            let method = VerificationMethod {
399                id,
400                key_type: String::from(match self.key_format {
401                    PublicKeyFormat::Multikey => "Multikey",
402                    PublicKeyFormat::Jwk => "JsonWebKey2020",
403                }),
404                controller: did.to_string(),
405                public_key: Some(match self.key_format {
406                    PublicKeyFormat::Multikey => KeyFormat::Multibase(multikey.to_string()),
407                    PublicKeyFormat::Jwk => {
408                        let (alg, key) = decode_multikey(multikey).map_err(|_| DIDPeerMethodError::MalformedPeerDID)?;
409                        KeyFormat::Jwk(alg.build_jwk(&key).map_err(|_| DIDResolutionError::InternalError)?)
410                    }
411                }),
412                ..Default::default()
413            };
414
415            methods.push(method);
416        }
417
418        // Resolve services
419
420        let service_chain = chain
421            .iter()
422            .filter_map(|(purpose, multikey)| (purpose == &Purpose::Service).then_some(multikey));
423
424        let mut services: Vec<Service> = vec![];
425        let mut service_next_id = 0;
426
427        for encoded_service in service_chain {
428            let decoded_service_bytes = Base64Url.decode(encoded_service).map_err(|_| DIDPeerMethodError::DIDParseError)?;
429            let decoded_service = String::from_utf8(decoded_service_bytes).map_err(|_| DIDPeerMethodError::DIDParseError)?;
430
431            // Reverse service abbreviation
432            let mut service = util::reverse_abbreviate_service_for_did_peer_2(&decoded_service)?;
433
434            if service.id.is_empty() {
435                service.id = if service_next_id == 0 {
436                    String::from("#service")
437                } else {
438                    format!("#service-{service_next_id}")
439                };
440                service_next_id += 1;
441            }
442
443            services.push(service);
444        }
445
446        // Build DIDDocument
447
448        let diddoc = DIDDocument {
449            context,
450            id: did.to_string(),
451            controller: None,
452            also_known_as: Some(vec![alias]),
453            verification_method: Some(methods),
454            authentication: (!authentication.is_empty()).then_some(authentication),
455            assertion_method: (!assertion_method.is_empty()).then_some(assertion_method),
456            capability_delegation: (!capability_delegation.is_empty()).then_some(capability_delegation),
457            capability_invocation: (!capability_invocation.is_empty()).then_some(capability_invocation),
458            key_agreement: (!key_agreement.is_empty()).then_some(key_agreement),
459            service: (!services.is_empty()).then_some(services),
460            additional_properties: None,
461            proof: None,
462        };
463
464        // Output
465
466        Ok(diddoc)
467    }
468
469    /// Expands did:peer:4 address
470    ///
471    /// See https://identity.foundation/peer-did-method-spec/#resolving-a-did
472    pub fn expand_did_peer_4(&self, did: &str) -> Result<DIDDocument, DIDPeerMethodError> {
473        // Ensure long format by computing did:peer:4 short form alias
474        // This also ensures that the hash is valid before shortening the did.
475        let alias = Self::shorten_did_peer_4(did)?;
476
477        // Extract encoded document
478        let encoded_document = did.split(':').nth(3).ok_or(DIDPeerMethodError::DIDParseError)?;
479
480        // Decode document
481        let (base, decoded_bytes) = multibase::decode(encoded_document).map_err(|_| DIDPeerMethodError::DIDParseError)?;
482
483        if Base58Btc != base || decoded_bytes.len() < 2 || decoded_bytes[..2] != MULTICODEC_JSON {
484            return Err(DIDPeerMethodError::MalformedLongPeerDID);
485        }
486        // Deserialize the document
487        let mut diddoc: DIDDocument = serde_json::from_slice(&decoded_bytes[2..]).map_err(DIDPeerMethodError::SerdeError)?;
488
489        // Contextualize decoded document
490        diddoc.id = did.to_string();
491
492        if diddoc.also_known_as.is_none() {
493            diddoc.also_known_as = Some(vec![alias]);
494        }
495
496        diddoc.verification_method = diddoc.verification_method.map(|arr| {
497            arr.into_iter()
498                .map(|vm| VerificationMethod {
499                    controller: if !vm.controller.is_empty() { vm.controller } else { did.to_string() },
500                    ..vm
501                })
502                .collect()
503        });
504
505        // Output DIDDocument
506        Ok(diddoc)
507    }
508}
509
510#[cfg(test)]
511mod tests {
512    use serde_json::Value;
513
514    use super::*;
515    use crate::jwk::Jwk;
516
517    #[test]
518    fn test_did_peer_0_generation_from_given_jwk() {
519        let jwk: Jwk = serde_json::from_str(
520            r#"{
521                "kty": "OKP",
522                "crv": "Ed25519",
523                "x": "O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik"
524            }"#,
525        )
526        .unwrap();
527        let keypair: Ed25519KeyPair = jwk.try_into().unwrap();
528
529        let did = DidPeer::create_did_peer_0_from_ed25519_keypair(&keypair);
530        assert_eq!(did.unwrap(), "did:peer:0z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp");
531    }
532
533    #[test]
534    fn test_did_peer_0_generation_from_given_raw_public_key_bytes() {
535        let entries = [
536            (
537                Algorithm::Ed25519,
538                hex::decode("3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29").unwrap(),
539                "did:peer:0z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
540            ),
541            (
542                Algorithm::X25519,
543                hex::decode("2fe57da347cd62431528daac5fbb290730fff684afc4cfc2ed90995f58cb3b74").unwrap(),
544                "did:peer:0z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F",
545            ),
546        ];
547
548        for entry in entries {
549            let (alg, bytes, expected) = entry;
550            let did = DidPeer::create_did_peer_0_from_raw_public_key(alg, &bytes);
551            assert_eq!(did.unwrap(), expected);
552        }
553    }
554
555    #[test]
556    fn test_did_peer_1_generation_from_did_document() {
557        let diddoc = _stored_variant_v0();
558        let did = DidPeer::create_did_peer_1_from_stored_variant(&diddoc);
559        assert_eq!(did.unwrap(), "did:peer:1zQmbEB1EqP7PnNVaHiSpXhkatAA6kNyQK9mWkvrMx2eckgq");
560    }
561
562    #[test]
563    fn test_did_peer_1_generation_fails_from_did_document_with_id() {
564        let diddoc = _invalid_stored_variant_v0();
565        let did = DidPeer::create_did_peer_1_from_stored_variant(&diddoc);
566        assert!(matches!(did.unwrap_err(), DIDPeerMethodError::InvalidStoredVariant));
567    }
568
569    #[test]
570    fn test_did_peer_2_generation() {
571        let keys = vec![
572            PurposedKey {
573                purpose: Purpose::Verification,
574                public_key_multibase: String::from("z6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc"),
575            },
576            PurposedKey {
577                purpose: Purpose::Encryption,
578                public_key_multibase: String::from("z6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR"),
579            },
580        ];
581
582        let did = DidPeer::create_did_peer_2(&keys, &[]).unwrap();
583        assert_eq!(
584            &did,
585            "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR"
586        );
587    }
588
589    #[test]
590    fn test_did_peer_2_generation_with_service() {
591        let keys = vec![PurposedKey {
592            purpose: Purpose::Verification,
593            public_key_multibase: String::from("z6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc"),
594        }];
595
596        let services = vec![Service {
597            id: String::from("#didcomm"),
598            service_type: String::from("DIDCommMessaging"),
599            service_endpoint: Value::String(String::from("http://example.com/didcomm")),
600            additional_properties: None,
601        }];
602
603        assert_eq!(
604            &DidPeer::create_did_peer_2(&keys, &services).unwrap(),
605            concat!(
606                "did:peer:2",
607                ".Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc",
608                ".SeyJpZCI6IiNkaWRjb21tIiwicyI6Imh0dHA6Ly9leGFtcGxlLmNvbS9kaWRjb21tIiwidCI6ImRtIn0"
609            )
610        );
611    }
612
613    #[test]
614    fn test_did_peer_2_generation_with_services() {
615        let keys = vec![PurposedKey {
616            purpose: Purpose::Verification,
617            public_key_multibase: String::from("z6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc"),
618        }];
619
620        let services = vec![
621            Service {
622                id: String::from("#didcomm-1"),
623                service_type: String::from("DIDCommMessaging"),
624                service_endpoint: Value::String(String::from("http://example.com/didcomm-1")),
625                additional_properties: None,
626            },
627            Service {
628                id: String::from("#didcomm-2"),
629                service_type: String::from("DIDCommMessaging"),
630                service_endpoint: Value::String(String::from("http://example.com/didcomm-2")),
631                additional_properties: None,
632            },
633        ];
634
635        assert_eq!(
636            &DidPeer::create_did_peer_2(&keys, &services).unwrap(),
637            concat!(
638                "did:peer:2",
639                ".Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc",
640                ".SeyJpZCI6IiNkaWRjb21tLTEiLCJzIjoiaHR0cDovL2V4YW1wbGUuY29tL2RpZGNvbW0tMSIsInQiOiJkbSJ9",
641                ".SeyJpZCI6IiNkaWRjb21tLTIiLCJzIjoiaHR0cDovL2V4YW1wbGUuY29tL2RpZGNvbW0tMiIsInQiOiJkbSJ9"
642            )
643        );
644    }
645
646    #[test]
647    fn test_did_peer_2_generation_should_err_on_key_associated_with_service_purpose() {
648        let keys = vec![PurposedKey {
649            purpose: Purpose::Service,
650            public_key_multibase: String::from("z6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc"),
651        }];
652
653        assert!(matches!(
654            DidPeer::create_did_peer_2(&keys, &[]).unwrap_err(),
655            DIDPeerMethodError::UnexpectedPurpose
656        ));
657    }
658
659    #[test]
660    fn test_did_peer_2_generation_should_err_on_empty_key_and_service_args() {
661        assert!(matches!(
662            DidPeer::create_did_peer_2(&[], &[]).unwrap_err(),
663            DIDPeerMethodError::EmptyArguments
664        ));
665    }
666
667    #[test]
668    fn test_did_peer_3_generation() {
669        let did = concat!(
670            "did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQi",
671            "SgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF",
672            "4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2",
673            "ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaW",
674            "Rjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0",
675        );
676
677        assert_eq!(
678            &DidPeer::create_did_peer_3(did).unwrap(),
679            "did:peer:3zQmS19jtYDvGtKVrJhQnRFpBQAx3pJ9omx2HpNrcXFuRCz9"
680        );
681    }
682
683    #[test]
684    fn test_did_peer_3_generation_fails_on_non_did_peer_2_arg() {
685        let dids = [
686            "",
687            "did:peer:0z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
688            "did:peer:1zQmbEB1EqP7PnNVaHiSpXhkatAA6kNyQK9mWkvrMx2eckgq",
689        ];
690
691        for did in dids {
692            assert!(matches!(
693                DidPeer::create_did_peer_3(did).unwrap_err(),
694                DIDPeerMethodError::IllegalArgument
695            ));
696        }
697    }
698
699    #[test]
700    fn test_did_peer_4_generation() {
701        let diddoc = _stored_variant_v0();
702        assert_eq!(
703            &DidPeer::create_did_peer_4_from_stored_variant(&diddoc).unwrap(),
704            concat!(
705                "did:peer:4zQmePYVawceZsPSxpLRp54z4Q5DCZXeyyGKwoDMc2NqgZXZ:z2yS424R5nAoSu",
706                "CezPTvBHybrvByZRD9g8L4oMe4ctq9UwPksVskxJFiars33RRyKz3z7RbwwQRAo9ByoXmBhg",
707                "7UCMkvmSHBeXWF44tQJfLjiXieCtXgxASzPJ5UsgPLAWX2vdjNFfmiLVh1WLe3RdBPvQoMuM",
708                "EiPLFGiKhbzX66dT21qDwZusRC4uDzQa7XpsLBS7rBjZZ9sLMRzjpG4rYpjgLUmUF2D1ixeW",
709                "ZFMqy7fVfPUUGyt4N6R4aLAjMLgcJzAQKb1uFiBYe2ZCTmsjtazWkHypgJetLysv7AwasYDV",
710                "4MMNPY5AbM4p3TGtdpJZaxaXzSKRZexuQ4tWsfGuHXEDiaABj5YtjbNjWh4f5M4sn7D9AAAS",
711                "StG593VkLFaPxG4VnFR4tKPiWeN9AJXRWPQ2XRnsD7U3mCHpRSb2f1HT5KeSHTU8zNAn6vFc",
712                "4fstgf2j71Uo8tngcUBkxdqkHKmpvZ1Fs27sWh7JvWAeiehsW3aBe4CbU4WGjzmusaKVb2HS",
713                "7iY5hbYngYrpwcZ5Sse",
714            )
715        );
716    }
717
718    #[test]
719    fn test_did_peer_4_generation_fails_from_did_document_with_id() {
720        let diddoc = _invalid_stored_variant_v0();
721        let did = DidPeer::create_did_peer_4_from_stored_variant(&diddoc);
722        assert!(matches!(did.unwrap_err(), DIDPeerMethodError::InvalidStoredVariant));
723    }
724
725    #[test]
726    fn test_did_peer_4_shortening() {
727        let did = concat!(
728            "did:peer:4zQmePYVawceZsPSxpLRp54z4Q5DCZXeyyGKwoDMc2NqgZXZ:z2yS424R5nAoSu",
729            "CezPTvBHybrvByZRD9g8L4oMe4ctq9UwPksVskxJFiars33RRyKz3z7RbwwQRAo9ByoXmBhg",
730            "7UCMkvmSHBeXWF44tQJfLjiXieCtXgxASzPJ5UsgPLAWX2vdjNFfmiLVh1WLe3RdBPvQoMuM",
731            "EiPLFGiKhbzX66dT21qDwZusRC4uDzQa7XpsLBS7rBjZZ9sLMRzjpG4rYpjgLUmUF2D1ixeW",
732            "ZFMqy7fVfPUUGyt4N6R4aLAjMLgcJzAQKb1uFiBYe2ZCTmsjtazWkHypgJetLysv7AwasYDV",
733            "4MMNPY5AbM4p3TGtdpJZaxaXzSKRZexuQ4tWsfGuHXEDiaABj5YtjbNjWh4f5M4sn7D9AAAS",
734            "StG593VkLFaPxG4VnFR4tKPiWeN9AJXRWPQ2XRnsD7U3mCHpRSb2f1HT5KeSHTU8zNAn6vFc",
735            "4fstgf2j71Uo8tngcUBkxdqkHKmpvZ1Fs27sWh7JvWAeiehsW3aBe4CbU4WGjzmusaKVb2HS",
736            "7iY5hbYngYrpwcZ5Sse",
737        );
738
739        assert_eq!(
740            &DidPeer::shorten_did_peer_4(did).unwrap(),
741            "did:peer:4zQmePYVawceZsPSxpLRp54z4Q5DCZXeyyGKwoDMc2NqgZXZ"
742        );
743    }
744
745    #[test]
746    fn test_did_peer_4_shortening_fails_on_non_did_peer_4_arg() {
747        let dids = [
748            "",
749            "did:peer:0z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
750            "did:peer:1zQmbEB1EqP7PnNVaHiSpXhkatAA6kNyQK9mWkvrMx2eckgq",
751            "did:peer:3zQmS19jtYDvGtKVrJhQnRFpBQAx3pJ9omx2HpNrcXFuRCz9",
752        ];
753
754        for did in dids {
755            assert!(matches!(
756                DidPeer::shorten_did_peer_4(did).unwrap_err(),
757                DIDPeerMethodError::IllegalArgument
758            ));
759        }
760    }
761
762    #[test]
763    fn test_did_peer_4_shortening_fails_on_malformed_long_peer_did() {
764        let dids = [
765            "did:peer:4zQmePYVawceZsPSxpLRp54z4Q5DCZXeyyGKwoDMc2NqgZXZz2yS424R5nAoSu",
766            "did:peer:4zQmePYVawceZsPSxpLRp54z4Q5DCZXeyyGKwoDMc2NqgZXZ:z2yS424:R5nAoSu",
767            "did:peer:4zQmePYVawceZsPSxpLRp54z4Q5DCZXeyyGKwoDMc2NqgZXZ:",
768        ];
769
770        for did in dids {
771            assert!(matches!(
772                DidPeer::shorten_did_peer_4(did).unwrap_err(),
773                DIDPeerMethodError::MalformedLongPeerDID
774            ));
775        }
776    }
777
778    #[test]
779    fn test_did_peer_4_shortening_fails_on_invalid_hash_in_long_peer_did() {
780        let valid_did = concat!(
781            "did:peer:4zQmePYVawceZsPSxpLRp54z4Q5DCZXeyyGKwoDMc2NqgZXZ:z2yS424R5nAoSu",
782            "CezPTvBHybrvByZRD9g8L4oMe4ctq9UwPksVskxJFiars33RRyKz3z7RbwwQRAo9ByoXmBhg",
783            "7UCMkvmSHBeXWF44tQJfLjiXieCtXgxASzPJ5UsgPLAWX2vdjNFfmiLVh1WLe3RdBPvQoMuM",
784            "EiPLFGiKhbzX66dT21qDwZusRC4uDzQa7XpsLBS7rBjZZ9sLMRzjpG4rYpjgLUmUF2D1ixeW",
785            "ZFMqy7fVfPUUGyt4N6R4aLAjMLgcJzAQKb1uFiBYe2ZCTmsjtazWkHypgJetLysv7AwasYDV",
786            "4MMNPY5AbM4p3TGtdpJZaxaXzSKRZexuQ4tWsfGuHXEDiaABj5YtjbNjWh4f5M4sn7D9AAAS",
787            "StG593VkLFaPxG4VnFR4tKPiWeN9AJXRWPQ2XRnsD7U3mCHpRSb2f1HT5KeSHTU8zNAn6vFc",
788            "4fstgf2j71Uo8tngcUBkxdqkHKmpvZ1Fs27sWh7JvWAeiehsW3aBe4CbU4WGjzmusaKVb2HS",
789            "7iY5hbYngYrpwcZ5Sse",
790        );
791
792        // Invalidate hash
793        let mut did = valid_did.to_string();
794        did.insert_str(20, "blurg");
795
796        assert!(matches!(DidPeer::shorten_did_peer_4(&did).unwrap_err(), DIDPeerMethodError::InvalidHash));
797
798        // Invalidate hash by tampering with encoded document
799        let did = format!("{valid_did}blurg");
800
801        assert!(matches!(DidPeer::shorten_did_peer_4(&did).unwrap_err(), DIDPeerMethodError::InvalidHash));
802    }
803
804    #[test]
805    fn test_expand_fails_on_non_did_peer() {
806        let did_method = DidPeer::default();
807
808        let did = "did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F";
809        assert!(matches!(did_method.expand(did).unwrap_err(), DIDResolutionError::InvalidDid));
810    }
811
812    #[test]
813    fn test_expand_fails_on_unsupported_did_peer() {
814        let did_method = DidPeer::default();
815
816        let did = "did:peer:1zQmbEB1EqP7PnNVaHiSpXhkatAA6kNyQK9mWkvrMx2eckgq";
817        assert!(matches!(
818            did_method.expand(did).unwrap_err(),
819            DIDResolutionError::MethodNotSupported
820        ));
821    }
822
823    #[test]
824    fn test_expand_did_peer_0_v1() {
825        let did_method = DidPeer::default();
826
827        let did = "did:peer:0z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
828        let expected: Value = serde_json::from_str(
829            r##"{
830                "@context": [
831                    "https://www.w3.org/ns/did/v1",
832                    "https://w3id.org/security/multikey/v1"
833                ],
834                "id": "did:peer:0z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
835                "verificationMethod": [
836                    {
837                        "id": "#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
838                        "type": "Multikey",
839                        "controller": "did:peer:0z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
840                        "publicKeyMultibase": "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
841                    },
842                    {
843                        "id": "#z6LSj72tK8brWgZja8NLRwPigth2T9QRiG1uH9oKZuKjdh9p",
844                        "type": "Multikey",
845                        "controller": "did:peer:0z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
846                        "publicKeyMultibase": "z6LSj72tK8brWgZja8NLRwPigth2T9QRiG1uH9oKZuKjdh9p"
847                    }
848                ],
849                "authentication": ["#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"],
850                "assertionMethod": ["#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"],
851                "capabilityDelegation": ["#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"],
852                "capabilityInvocation": ["#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"],
853                "keyAgreement": ["#z6LSj72tK8brWgZja8NLRwPigth2T9QRiG1uH9oKZuKjdh9p"]
854            }"##,
855        )
856        .unwrap();
857
858        let diddoc = did_method.expand(did).unwrap();
859
860        assert_eq!(
861            json_canon::to_string(&diddoc).unwrap(),   //
862            json_canon::to_string(&expected).unwrap(), //
863        );
864    }
865
866    #[test]
867    fn test_expand_did_peer_0_v2() {
868        let did_method = DidPeer::default();
869
870        let did = "did:peer:0z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F";
871        let expected: Value = serde_json::from_str(
872            r##"{
873                "@context": [
874                    "https://www.w3.org/ns/did/v1",
875                    "https://w3id.org/security/multikey/v1"
876                ],
877                "id": "did:peer:0z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F",
878                "verificationMethod": [
879                    {
880                        "id": "#z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F",
881                        "type": "Multikey",
882                        "controller": "did:peer:0z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F",
883                        "publicKeyMultibase": "z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F"
884                    }
885                ],
886                "authentication": ["#z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F"],
887                "assertionMethod": ["#z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F"],
888                "capabilityDelegation": ["#z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F"],
889                "capabilityInvocation": ["#z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F"]
890            }"##,
891        )
892        .unwrap();
893
894        let diddoc = did_method.expand(did).unwrap();
895
896        assert_eq!(
897            json_canon::to_string(&diddoc).unwrap(),   //
898            json_canon::to_string(&expected).unwrap(), //
899        );
900    }
901
902    #[test]
903    fn test_expand_did_peer_0_jwk_format() {
904        let did_method = DidPeer {
905            key_format: PublicKeyFormat::Jwk,
906        };
907
908        let did = "did:peer:0z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
909        let expected: Value = serde_json::from_str(
910            r##"{
911                "@context": [
912                    "https://www.w3.org/ns/did/v1",
913                    "https://w3id.org/security/suites/jws-2020/v1"
914                ],
915                "id": "did:peer:0z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
916                "verificationMethod": [
917                    {
918                        "id": "#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
919                        "type": "JsonWebKey2020",
920                        "controller": "did:peer:0z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
921                        "publicKeyJwk": {
922                            "kty": "OKP",
923                            "crv": "Ed25519",
924                            "x": "Lm_M42cB3HkUiODQsXRcweM6TByfzEHGO9ND274JcOY"
925                        }
926                    },
927                    {
928                        "id": "#z6LSj72tK8brWgZja8NLRwPigth2T9QRiG1uH9oKZuKjdh9p",
929                        "type": "JsonWebKey2020",
930                        "controller": "did:peer:0z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
931                        "publicKeyJwk": {
932                            "kty": "OKP",
933                            "crv": "X25519",
934                            "x": "bl_3kgKpz9jgsg350CNuHa_kQL3B60Gi-98WmdQW2h8"
935                        }
936                    }
937                ],
938                "authentication": ["#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"],
939                "assertionMethod": ["#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"],
940                "capabilityDelegation": ["#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"],
941                "capabilityInvocation": ["#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"],
942                "keyAgreement": ["#z6LSj72tK8brWgZja8NLRwPigth2T9QRiG1uH9oKZuKjdh9p"]
943            }"##,
944        )
945        .unwrap();
946
947        let diddoc = did_method.expand(did).unwrap();
948
949        assert_eq!(
950            json_canon::to_string(&diddoc).unwrap(),   //
951            json_canon::to_string(&expected).unwrap(), //
952        );
953    }
954
955    #[test]
956    fn test_expand_did_peer_0_fails_for_regex_mismatch() {
957        let did_method = DidPeer::default();
958
959        let dids = [
960            // Must be '0z' not '0Z'
961            "did:peer:0Z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
962            // '#' is not a valid Base58 character
963            "did:peer:0z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDoo###",
964        ];
965
966        for did in dids {
967            assert!(matches!(did_method.expand(did).unwrap_err(), DIDResolutionError::InvalidDid));
968        }
969    }
970
971    #[test]
972    fn test_expand_did_peer_0_fails_on_malformed_dids() {
973        let did_method = DidPeer::default();
974
975        let dids = ["did:peer:0z6", "did:peer:0z7MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWpd"];
976
977        for did in dids {
978            assert!(matches!(did_method.expand(did).unwrap_err(), DIDResolutionError::InvalidDid));
979        }
980    }
981
982    #[test]
983    fn test_expand_did_peer_0_fails_on_too_long_did() {
984        let did_method = DidPeer::default();
985        let did = "did:peer:0zQebt6zPwbE4Vw5GFAjjARHrNXFALofERVv4q6Z4db8cnDRQm";
986        assert!(matches!(
987            did_method.expand(did).unwrap_err(),
988            DIDResolutionError::InvalidPublicKeyLength
989        ));
990    }
991
992    #[test]
993    fn test_expand_did_peer_2() {
994        let did_method = DidPeer::default();
995
996        let did = concat!(
997            "did:peer:2",
998            ".Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc",
999            ".Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR",
1000            ".SeyJpZCI6IiNkaWRjb21tIiwicyI6Imh0dHA6Ly9leGFtcGxlLmNvbS8xMjMiLCJ0IjoiZG0ifQ",
1001            ".SeyJpZCI6IiIsInMiOiJodHRwOi8vZXhhbXBsZS5jb20vYWJjIiwidCI6ImRtIn0",
1002            ".SeyJpZCI6IiIsInMiOiJodHRwOi8vZXhhbXBsZS5jb20veHl6IiwidCI6ImRtIn0",
1003        );
1004
1005        let expected: Value = serde_json::from_str(
1006            r##"{
1007                "@context": [
1008                    "https://www.w3.org/ns/did/v1",
1009                    "https://w3id.org/security/multikey/v1"
1010                ],
1011                "id": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJpZCI6IiNkaWRjb21tIiwicyI6Imh0dHA6Ly9leGFtcGxlLmNvbS8xMjMiLCJ0IjoiZG0ifQ.SeyJpZCI6IiIsInMiOiJodHRwOi8vZXhhbXBsZS5jb20vYWJjIiwidCI6ImRtIn0.SeyJpZCI6IiIsInMiOiJodHRwOi8vZXhhbXBsZS5jb20veHl6IiwidCI6ImRtIn0",
1012                "alsoKnownAs": ["did:peer:3zQmR9j6bEaydJuXDfzYaW4f3EEPQFxz2Zy1iPZuchgeF63h"],
1013                "verificationMethod": [
1014                    {
1015                        "id": "#key-1",
1016                        "type": "Multikey",
1017                        "controller": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJpZCI6IiNkaWRjb21tIiwicyI6Imh0dHA6Ly9leGFtcGxlLmNvbS8xMjMiLCJ0IjoiZG0ifQ.SeyJpZCI6IiIsInMiOiJodHRwOi8vZXhhbXBsZS5jb20vYWJjIiwidCI6ImRtIn0.SeyJpZCI6IiIsInMiOiJodHRwOi8vZXhhbXBsZS5jb20veHl6IiwidCI6ImRtIn0",
1018                        "publicKeyMultibase": "z6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc"
1019                    },
1020                    {
1021                        "id": "#key-2",
1022                        "type": "Multikey",
1023                        "controller": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJpZCI6IiNkaWRjb21tIiwicyI6Imh0dHA6Ly9leGFtcGxlLmNvbS8xMjMiLCJ0IjoiZG0ifQ.SeyJpZCI6IiIsInMiOiJodHRwOi8vZXhhbXBsZS5jb20vYWJjIiwidCI6ImRtIn0.SeyJpZCI6IiIsInMiOiJodHRwOi8vZXhhbXBsZS5jb20veHl6IiwidCI6ImRtIn0",
1024                        "publicKeyMultibase": "z6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR"
1025                    }
1026                ],
1027                "authentication": ["#key-1"],
1028                "keyAgreement": ["#key-2"],
1029                "service": [
1030                    {
1031                        "id": "#didcomm",
1032                        "type": "DIDCommMessaging",
1033                        "serviceEndpoint": "http://example.com/123"
1034                    },
1035                    {
1036                        "id": "#service",
1037                        "type": "DIDCommMessaging",
1038                        "serviceEndpoint": "http://example.com/abc"
1039                    },
1040                    {
1041                        "id": "#service-1",
1042                        "type": "DIDCommMessaging",
1043                        "serviceEndpoint": "http://example.com/xyz"
1044                    }
1045                ]
1046            }"##,
1047        )
1048        .unwrap();
1049
1050        let diddoc = did_method.expand(did).unwrap();
1051        assert_eq!(
1052            json_canon::to_string(&diddoc).unwrap(),   //
1053            json_canon::to_string(&expected).unwrap(), //
1054        );
1055    }
1056
1057    #[test]
1058    fn test_expand_did_peer_2_jwk_format() {
1059        let did_method = DidPeer {
1060            key_format: PublicKeyFormat::Jwk,
1061        };
1062
1063        let did = concat!(
1064            "did:peer:2",
1065            ".Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc",
1066            ".Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR",
1067            ".SeyJpZCI6IiIsInMiOiJodHRwOi8vZXhhbXBsZS5jb20vYWJjIiwidCI6ImRtIn0",
1068        );
1069
1070        let expected: Value = serde_json::from_str(
1071            r##"{
1072                "@context": [
1073                    "https://www.w3.org/ns/did/v1",
1074                    "https://w3id.org/security/suites/jws-2020/v1"
1075                ],
1076                "id": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJpZCI6IiIsInMiOiJodHRwOi8vZXhhbXBsZS5jb20vYWJjIiwidCI6ImRtIn0",
1077                "alsoKnownAs": ["did:peer:3zQmWdmF5Lgads1v6qeV9x6PJEWrfUaKQ5D8tf7up9a5xwDi"],
1078                "verificationMethod": [
1079                    {
1080                        "id": "#key-1",
1081                        "type": "JsonWebKey2020",
1082                        "controller": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJpZCI6IiIsInMiOiJodHRwOi8vZXhhbXBsZS5jb20vYWJjIiwidCI6ImRtIn0",
1083                        "publicKeyJwk": {
1084                            "kty": "OKP",
1085                            "crv": "Ed25519",
1086                            "x": "RCzl6iYBsyD4aK8Yzmo8r_6eBriu0XmnDj64xOQ3d6M"
1087                        }
1088                    },
1089                    {
1090                        "id": "#key-2",
1091                        "type": "JsonWebKey2020",
1092                        "controller": "did:peer:2.Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc.Ez6LSg8zQom395jKLrGiBNruB9MM6V8PWuf2FpEy4uRFiqQBR.SeyJpZCI6IiIsInMiOiJodHRwOi8vZXhhbXBsZS5jb20vYWJjIiwidCI6ImRtIn0",
1093                        "publicKeyJwk": {
1094                            "kty": "OKP",
1095                            "crv": "X25519",
1096                            "x": "Qk1FMFvAv5Ihlgjm_SJIqNRU3kqhb_RWQZrPUh3mNWg"
1097                        }
1098                    }
1099                ],
1100                "authentication": ["#key-1"],
1101                "keyAgreement": ["#key-2"],
1102                "service": [{
1103                    "id": "#service",
1104                    "type": "DIDCommMessaging",
1105                    "serviceEndpoint": "http://example.com/abc"
1106                }]
1107            }"##,
1108        )
1109        .unwrap();
1110
1111        let diddoc = did_method.expand(did).unwrap();
1112        assert_eq!(
1113            json_canon::to_string(&diddoc).unwrap(),   //
1114            json_canon::to_string(&expected).unwrap(), //
1115        );
1116    }
1117
1118    #[test]
1119    fn test_expand_did_peer_2_fails_on_malformed_encoded_service() {
1120        let did_method = DidPeer::default();
1121        let did = concat!(
1122            "did:peer:2",
1123            ".Vz6Mkj3PUd1WjvaDhNZhhhXQdz5UnZXmS7ehtx8bsPpD47kKc",
1124            // {"s":"http://example.com/xyz","t":"dm" (missing closing brace)
1125            ".SeyJzIjoiaHR0cDovL2V4YW1wbGUuY29tL3h5eiIsInQiOiJkbSI",
1126        );
1127
1128        assert!(matches!(did_method.expand(did).unwrap_err(), DIDResolutionError::InvalidDid));
1129    }
1130
1131    #[test]
1132    fn test_expand_did_peer_4() {
1133        let did_method = DidPeer::new();
1134
1135        let did = concat!(
1136            "did:peer:4zQmePYVawceZsPSxpLRp54z4Q5DCZXeyyGKwoDMc2NqgZXZ:z2yS424R5nAoSu",
1137            "CezPTvBHybrvByZRD9g8L4oMe4ctq9UwPksVskxJFiars33RRyKz3z7RbwwQRAo9ByoXmBhg",
1138            "7UCMkvmSHBeXWF44tQJfLjiXieCtXgxASzPJ5UsgPLAWX2vdjNFfmiLVh1WLe3RdBPvQoMuM",
1139            "EiPLFGiKhbzX66dT21qDwZusRC4uDzQa7XpsLBS7rBjZZ9sLMRzjpG4rYpjgLUmUF2D1ixeW",
1140            "ZFMqy7fVfPUUGyt4N6R4aLAjMLgcJzAQKb1uFiBYe2ZCTmsjtazWkHypgJetLysv7AwasYDV",
1141            "4MMNPY5AbM4p3TGtdpJZaxaXzSKRZexuQ4tWsfGuHXEDiaABj5YtjbNjWh4f5M4sn7D9AAAS",
1142            "StG593VkLFaPxG4VnFR4tKPiWeN9AJXRWPQ2XRnsD7U3mCHpRSb2f1HT5KeSHTU8zNAn6vFc",
1143            "4fstgf2j71Uo8tngcUBkxdqkHKmpvZ1Fs27sWh7JvWAeiehsW3aBe4CbU4WGjzmusaKVb2HS",
1144            "7iY5hbYngYrpwcZ5Sse",
1145        );
1146
1147        let expected = DIDDocument {
1148            id: did.to_string(),
1149            also_known_as: Some(vec![String::from("did:peer:4zQmePYVawceZsPSxpLRp54z4Q5DCZXeyyGKwoDMc2NqgZXZ")]),
1150            .._stored_variant_v0()
1151        };
1152
1153        let diddoc = did_method.expand(did).unwrap();
1154        assert_eq!(
1155            json_canon::to_string(&diddoc).unwrap(),   //
1156            json_canon::to_string(&expected).unwrap(), //
1157        );
1158    }
1159
1160    fn _stored_variant_v0() -> DIDDocument {
1161        serde_json::from_str(
1162            r##"{
1163                "@context": [
1164                    "https://www.w3.org/ns/did/v1",
1165                    "https://w3id.org/security/suites/ed25519-2020/v1"
1166                ],
1167                "verificationMethod": [{
1168                    "id": "#key1",
1169                    "type": "Ed25519VerificationKey2020",
1170                    "controller": "#id",
1171                    "publicKeyMultibase": "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
1172                }],
1173                "authentication": ["#key1"],
1174                "assertionMethod": ["#key1"],
1175                "capabilityDelegation": ["#key1"],
1176                "capabilityInvocation": ["#key1"]
1177            }"##,
1178        )
1179        .unwrap()
1180    }
1181
1182    fn _invalid_stored_variant_v0() -> DIDDocument {
1183        serde_json::from_str(
1184            r##"{
1185                "@context": [
1186                    "https://www.w3.org/ns/did/v1",
1187                    "https://w3id.org/security/suites/ed25519-2020/v1"
1188                ],
1189                "id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
1190                "verificationMethod": [{
1191                    "id": "#key1",
1192                    "type": "Ed25519VerificationKey2020",
1193                    "controller": "#id",
1194                    "publicKeyMultibase": "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
1195                }],
1196                "authentication": ["#key1"],
1197                "assertionMethod": ["#key1"],
1198                "capabilityDelegation": ["#key1"],
1199                "capabilityInvocation": ["#key1"]
1200            }"##,
1201        )
1202        .unwrap()
1203    }
1204}