1use std::collections::HashMap;
7
8use base64::{Engine, prelude::BASE64_URL_SAFE_NO_PAD};
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11
12use crate::one_or_many::OneOrMany;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16pub enum PeerNumAlgo {
17 InceptionKey = 0,
19 GenesisDoc = 1,
21 MultipleKeys = 2,
23}
24
25impl PeerNumAlgo {
26 pub fn from_char(c: char) -> Option<Self> {
28 match c {
29 '0' => Some(PeerNumAlgo::InceptionKey),
30 '1' => Some(PeerNumAlgo::GenesisDoc),
31 '2' => Some(PeerNumAlgo::MultipleKeys),
32 _ => None,
33 }
34 }
35
36 pub fn to_char(self) -> char {
38 match self {
39 PeerNumAlgo::InceptionKey => '0',
40 PeerNumAlgo::GenesisDoc => '1',
41 PeerNumAlgo::MultipleKeys => '2',
42 }
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum PeerPurpose {
49 Assertion,
51 Delegation,
53 Encryption,
55 Invocation,
57 Verification,
59 Service,
61}
62
63impl PeerPurpose {
64 pub fn from_char(c: char) -> Option<Self> {
66 match c {
67 'A' => Some(PeerPurpose::Assertion),
68 'D' => Some(PeerPurpose::Delegation),
69 'E' => Some(PeerPurpose::Encryption),
70 'I' => Some(PeerPurpose::Invocation),
71 'V' => Some(PeerPurpose::Verification),
72 'S' => Some(PeerPurpose::Service),
73 _ => None,
74 }
75 }
76
77 pub fn to_char(self) -> char {
79 match self {
80 PeerPurpose::Assertion => 'A',
81 PeerPurpose::Delegation => 'D',
82 PeerPurpose::Encryption => 'E',
83 PeerPurpose::Invocation => 'I',
84 PeerPurpose::Verification => 'V',
85 PeerPurpose::Service => 'S',
86 }
87 }
88
89 pub fn is_key(&self) -> bool {
91 !matches!(self, PeerPurpose::Service)
92 }
93}
94
95#[derive(Error, Debug)]
101pub enum PeerError {
102 #[error("Unsupported key type")]
103 UnsupportedKeyType,
104
105 #[error("Unsupported curve: {0}")]
106 UnsupportedCurve(String),
107
108 #[error("Syntax error in service definition: {0}")]
109 ServiceSyntaxError(String),
110
111 #[error("Unsupported numalgo. Only 0 and 2 are supported")]
112 UnsupportedNumalgo,
113
114 #[error("Key parsing error: {0}")]
115 KeyParsingError(String),
116
117 #[error("Encoding error: {0}")]
118 EncodingError(String),
119
120 #[error("Internal error: {0}")]
121 InternalError(String),
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
130pub enum PeerKeyPurpose {
131 Verification,
133 Encryption,
135}
136
137impl PeerKeyPurpose {
138 pub fn to_char(self) -> char {
140 match self {
141 PeerKeyPurpose::Verification => 'V',
142 PeerKeyPurpose::Encryption => 'E',
143 }
144 }
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
149pub enum PeerKeyType {
150 Ed25519,
151 Secp256k1,
152 P256,
153}
154
155impl PeerKeyType {
156 pub fn to_crypto_key_type(self) -> affinidi_crypto::KeyType {
158 match self {
159 PeerKeyType::Ed25519 => affinidi_crypto::KeyType::Ed25519,
160 PeerKeyType::Secp256k1 => affinidi_crypto::KeyType::Secp256k1,
161 PeerKeyType::P256 => affinidi_crypto::KeyType::P256,
162 }
163 }
164}
165
166#[derive(Debug, Clone)]
168pub struct PeerCreateKey {
169 pub purpose: PeerKeyPurpose,
171 pub key_type: Option<PeerKeyType>,
173 pub public_key_multibase: Option<String>,
176}
177
178impl PeerCreateKey {
179 pub fn new(purpose: PeerKeyPurpose, key_type: PeerKeyType) -> Self {
181 Self {
182 purpose,
183 key_type: Some(key_type),
184 public_key_multibase: None,
185 }
186 }
187
188 pub fn from_multibase(purpose: PeerKeyPurpose, multibase: String) -> Self {
190 Self {
191 purpose,
192 key_type: None,
193 public_key_multibase: Some(multibase),
194 }
195 }
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct PeerCreatedKey {
201 pub key_multibase: String,
203 pub curve: String,
205 pub d: String,
207 pub x: String,
209 pub y: Option<String>,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct PeerService {
222 #[serde(rename = "t")]
224 pub type_: String,
225
226 #[serde(rename = "s")]
228 pub endpoint: PeerServiceEndpoint,
229
230 #[serde(skip_serializing_if = "Option::is_none")]
232 pub id: Option<String>,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
237#[serde(untagged)]
238pub enum PeerServiceEndpoint {
239 Uri(String),
241 Long(OneOrMany<PeerServiceEndpointLong>),
243 Short(OneOrMany<PeerServiceEndpointShort>),
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize)]
249pub struct PeerServiceEndpointShort {
250 pub uri: String,
252 #[serde(default, skip_serializing_if = "Vec::is_empty")]
254 pub a: Vec<String>,
255 #[serde(default, skip_serializing_if = "Vec::is_empty")]
257 pub r: Vec<String>,
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct PeerServiceEndpointLong {
263 pub uri: String,
265 #[serde(default, skip_serializing_if = "Vec::is_empty")]
267 pub accept: Vec<String>,
268 #[serde(default, skip_serializing_if = "Vec::is_empty")]
270 pub routing_keys: Vec<String>,
271}
272
273impl PeerServiceEndpointShort {
274 pub fn to_long(&self) -> PeerServiceEndpointLong {
276 PeerServiceEndpointLong {
277 uri: self.uri.clone(),
278 accept: self.a.clone(),
279 routing_keys: self.r.clone(),
280 }
281 }
282}
283
284impl PeerServiceEndpointLong {
285 pub fn to_short(&self) -> PeerServiceEndpointShort {
287 PeerServiceEndpointShort {
288 uri: self.uri.clone(),
289 a: self.accept.clone(),
290 r: self.routing_keys.clone(),
291 }
292 }
293}
294
295impl PeerService {
300 pub fn encode(&self) -> Result<String, PeerError> {
302 let json = serde_json::to_string(self).map_err(|e| {
303 PeerError::ServiceSyntaxError(format!("Failed to serialize service: {e}"))
304 })?;
305 Ok(format!(
306 "S{}",
307 BASE64_URL_SAFE_NO_PAD.encode(json.as_bytes())
308 ))
309 }
310
311 pub fn decode(encoded: &str) -> Result<Self, PeerError> {
313 let encoded = encoded.strip_prefix('S').unwrap_or(encoded);
314 let bytes = BASE64_URL_SAFE_NO_PAD
315 .decode(encoded)
316 .map_err(|e| PeerError::ServiceSyntaxError(format!("Base64 decode failed: {e}")))?;
317
318 serde_json::from_slice(&bytes)
319 .map_err(|e| PeerError::ServiceSyntaxError(format!("JSON parse failed: {e}")))
320 }
321
322 pub fn to_did_service(
324 &self,
325 did: &str,
326 index: u32,
327 ) -> Result<crate::service::Service, PeerError> {
328 use std::str::FromStr;
329 use url::Url;
330
331 let id_fragment = if let Some(id) = &self.id {
333 id.clone()
334 } else if index == 0 {
335 "#service".to_string()
336 } else {
337 format!("#service-{index}")
338 };
339
340 let id = Url::from_str(&format!("{did}{id_fragment}"))
341 .map_err(|e| PeerError::ServiceSyntaxError(format!("Invalid service ID: {e}")))?;
342
343 let service_endpoint = match &self.endpoint {
345 PeerServiceEndpoint::Uri(uri) => {
346 let url = Url::from_str(uri)
347 .map_err(|e| PeerError::ServiceSyntaxError(format!("Invalid URI: {e}")))?;
348 crate::service::Endpoint::Url(url)
349 }
350 PeerServiceEndpoint::Short(endpoints) => {
351 let value = match endpoints {
352 OneOrMany::One(ep) => serde_json::to_value(ep.to_long())
353 .map_err(|e| PeerError::ServiceSyntaxError(e.to_string()))?,
354 OneOrMany::Many(eps) => {
355 let long: Vec<_> = eps.iter().map(|e| e.to_long()).collect();
356 serde_json::to_value(long)
357 .map_err(|e| PeerError::ServiceSyntaxError(e.to_string()))?
358 }
359 };
360 crate::service::Endpoint::Map(value)
361 }
362 PeerServiceEndpoint::Long(endpoints) => {
363 let value = serde_json::to_value(endpoints)
364 .map_err(|e| PeerError::ServiceSyntaxError(e.to_string()))?;
365 crate::service::Endpoint::Map(value)
366 }
367 };
368
369 Ok(crate::service::Service {
370 id: Some(id),
371 type_: vec!["DIDCommMessaging".to_string()],
372 service_endpoint,
373 property_set: HashMap::new(),
374 })
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381
382 #[test]
385 fn numalgo_from_char_valid() {
386 assert_eq!(PeerNumAlgo::from_char('0'), Some(PeerNumAlgo::InceptionKey));
387 assert_eq!(PeerNumAlgo::from_char('1'), Some(PeerNumAlgo::GenesisDoc));
388 assert_eq!(PeerNumAlgo::from_char('2'), Some(PeerNumAlgo::MultipleKeys));
389 }
390
391 #[test]
392 fn numalgo_from_char_invalid() {
393 assert_eq!(PeerNumAlgo::from_char('3'), None);
394 assert_eq!(PeerNumAlgo::from_char('x'), None);
395 }
396
397 #[test]
398 fn numalgo_to_char() {
399 assert_eq!(PeerNumAlgo::InceptionKey.to_char(), '0');
400 assert_eq!(PeerNumAlgo::GenesisDoc.to_char(), '1');
401 assert_eq!(PeerNumAlgo::MultipleKeys.to_char(), '2');
402 }
403
404 #[test]
405 fn numalgo_roundtrip() {
406 for c in ['0', '1', '2'] {
407 let algo = PeerNumAlgo::from_char(c).unwrap();
408 assert_eq!(algo.to_char(), c);
409 }
410 }
411
412 #[test]
415 fn purpose_from_char_valid() {
416 assert_eq!(PeerPurpose::from_char('A'), Some(PeerPurpose::Assertion));
417 assert_eq!(PeerPurpose::from_char('D'), Some(PeerPurpose::Delegation));
418 assert_eq!(PeerPurpose::from_char('E'), Some(PeerPurpose::Encryption));
419 assert_eq!(PeerPurpose::from_char('I'), Some(PeerPurpose::Invocation));
420 assert_eq!(PeerPurpose::from_char('V'), Some(PeerPurpose::Verification));
421 assert_eq!(PeerPurpose::from_char('S'), Some(PeerPurpose::Service));
422 }
423
424 #[test]
425 fn purpose_from_char_invalid() {
426 assert_eq!(PeerPurpose::from_char('X'), None);
427 assert_eq!(PeerPurpose::from_char('a'), None);
428 }
429
430 #[test]
431 fn purpose_to_char() {
432 assert_eq!(PeerPurpose::Assertion.to_char(), 'A');
433 assert_eq!(PeerPurpose::Delegation.to_char(), 'D');
434 assert_eq!(PeerPurpose::Encryption.to_char(), 'E');
435 assert_eq!(PeerPurpose::Invocation.to_char(), 'I');
436 assert_eq!(PeerPurpose::Verification.to_char(), 'V');
437 assert_eq!(PeerPurpose::Service.to_char(), 'S');
438 }
439
440 #[test]
441 fn purpose_roundtrip() {
442 for c in ['A', 'D', 'E', 'I', 'V', 'S'] {
443 let purpose = PeerPurpose::from_char(c).unwrap();
444 assert_eq!(purpose.to_char(), c);
445 }
446 }
447
448 #[test]
449 fn purpose_is_key() {
450 assert!(PeerPurpose::Assertion.is_key());
451 assert!(PeerPurpose::Delegation.is_key());
452 assert!(PeerPurpose::Encryption.is_key());
453 assert!(PeerPurpose::Invocation.is_key());
454 assert!(PeerPurpose::Verification.is_key());
455 assert!(!PeerPurpose::Service.is_key());
456 }
457
458 #[test]
461 fn key_purpose_to_char() {
462 assert_eq!(PeerKeyPurpose::Verification.to_char(), 'V');
463 assert_eq!(PeerKeyPurpose::Encryption.to_char(), 'E');
464 }
465
466 #[test]
469 fn key_type_to_crypto_key_type() {
470 assert_eq!(
471 PeerKeyType::Ed25519.to_crypto_key_type(),
472 affinidi_crypto::KeyType::Ed25519
473 );
474 assert_eq!(
475 PeerKeyType::Secp256k1.to_crypto_key_type(),
476 affinidi_crypto::KeyType::Secp256k1
477 );
478 assert_eq!(
479 PeerKeyType::P256.to_crypto_key_type(),
480 affinidi_crypto::KeyType::P256
481 );
482 }
483
484 #[test]
487 fn peer_create_key_new() {
488 let k = PeerCreateKey::new(PeerKeyPurpose::Verification, PeerKeyType::Ed25519);
489 assert_eq!(k.purpose, PeerKeyPurpose::Verification);
490 assert_eq!(k.key_type, Some(PeerKeyType::Ed25519));
491 assert!(k.public_key_multibase.is_none());
492 }
493
494 #[test]
495 fn peer_create_key_from_multibase() {
496 let k = PeerCreateKey::from_multibase(
497 PeerKeyPurpose::Encryption,
498 "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string(),
499 );
500 assert_eq!(k.purpose, PeerKeyPurpose::Encryption);
501 assert!(k.key_type.is_none());
502 assert_eq!(
503 k.public_key_multibase.as_deref(),
504 Some("z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK")
505 );
506 }
507
508 #[test]
511 fn short_to_long_conversion() {
512 let short = PeerServiceEndpointShort {
513 uri: "https://example.com/didcomm".to_string(),
514 a: vec!["didcomm/v2".to_string()],
515 r: vec!["did:example:123#key-1".to_string()],
516 };
517 let long = short.to_long();
518 assert_eq!(long.uri, "https://example.com/didcomm");
519 assert_eq!(long.accept, vec!["didcomm/v2"]);
520 assert_eq!(long.routing_keys, vec!["did:example:123#key-1"]);
521 }
522
523 #[test]
524 fn long_to_short_conversion() {
525 let long = PeerServiceEndpointLong {
526 uri: "https://example.com/didcomm".to_string(),
527 accept: vec!["didcomm/v2".to_string()],
528 routing_keys: vec!["did:example:123#key-1".to_string()],
529 };
530 let short = long.to_short();
531 assert_eq!(short.uri, "https://example.com/didcomm");
532 assert_eq!(short.a, vec!["didcomm/v2"]);
533 assert_eq!(short.r, vec!["did:example:123#key-1"]);
534 }
535
536 #[test]
537 fn short_long_roundtrip() {
538 let short = PeerServiceEndpointShort {
539 uri: "https://example.com".to_string(),
540 a: vec!["a".to_string(), "b".to_string()],
541 r: vec![],
542 };
543 let roundtripped = short.to_long().to_short();
544 assert_eq!(roundtripped.uri, short.uri);
545 assert_eq!(roundtripped.a, short.a);
546 assert_eq!(roundtripped.r, short.r);
547 }
548
549 #[test]
552 fn service_encode_decode_roundtrip() {
553 let svc = PeerService {
554 type_: "dm".to_string(),
555 endpoint: PeerServiceEndpoint::Uri("https://example.com/didcomm".to_string()),
556 id: None,
557 };
558 let encoded = svc.encode().unwrap();
559 assert!(encoded.starts_with('S'));
560
561 let decoded = PeerService::decode(&encoded).unwrap();
562 assert_eq!(decoded.type_, "dm");
563 if let PeerServiceEndpoint::Uri(uri) = &decoded.endpoint {
564 assert_eq!(uri, "https://example.com/didcomm");
565 } else {
566 panic!("expected Uri endpoint");
567 }
568 }
569
570 #[test]
571 fn service_decode_without_s_prefix() {
572 let svc = PeerService {
573 type_: "dm".to_string(),
574 endpoint: PeerServiceEndpoint::Uri("https://example.com".to_string()),
575 id: None,
576 };
577 let encoded = svc.encode().unwrap();
578 let without_prefix = &encoded[1..];
580 let decoded = PeerService::decode(without_prefix).unwrap();
581 assert_eq!(decoded.type_, "dm");
582 }
583
584 #[test]
585 fn service_decode_invalid_base64() {
586 assert!(PeerService::decode("S!!!invalid!!!").is_err());
587 }
588
589 #[test]
590 fn service_decode_invalid_json() {
591 let encoded = format!("S{}", BASE64_URL_SAFE_NO_PAD.encode(b"not json"));
592 assert!(PeerService::decode(&encoded).is_err());
593 }
594
595 #[test]
598 fn to_did_service_with_uri_endpoint() {
599 let svc = PeerService {
600 type_: "dm".to_string(),
601 endpoint: PeerServiceEndpoint::Uri("https://example.com/didcomm".to_string()),
602 id: None,
603 };
604 let did_svc = svc.to_did_service("did:peer:2abc", 0).unwrap();
605 assert_eq!(did_svc.id.unwrap().as_str(), "did:peer:2abc#service");
606 assert_eq!(did_svc.type_, vec!["DIDCommMessaging"]);
607 }
608
609 #[test]
610 fn to_did_service_with_index() {
611 let svc = PeerService {
612 type_: "dm".to_string(),
613 endpoint: PeerServiceEndpoint::Uri("https://example.com".to_string()),
614 id: None,
615 };
616 let did_svc = svc.to_did_service("did:peer:2abc", 3).unwrap();
617 assert_eq!(did_svc.id.unwrap().as_str(), "did:peer:2abc#service-3");
618 }
619
620 #[test]
621 fn to_did_service_with_custom_id() {
622 let svc = PeerService {
623 type_: "dm".to_string(),
624 endpoint: PeerServiceEndpoint::Uri("https://example.com".to_string()),
625 id: Some("#my-svc".to_string()),
626 };
627 let did_svc = svc.to_did_service("did:peer:2abc", 0).unwrap();
628 assert_eq!(did_svc.id.unwrap().as_str(), "did:peer:2abc#my-svc");
629 }
630
631 #[test]
632 fn to_did_service_with_short_endpoint() {
633 let short = PeerServiceEndpointShort {
634 uri: "https://example.com/didcomm".to_string(),
635 a: vec!["didcomm/v2".to_string()],
636 r: vec![],
637 };
638 let svc = PeerService {
639 type_: "dm".to_string(),
640 endpoint: PeerServiceEndpoint::Short(OneOrMany::One(short)),
641 id: None,
642 };
643 let did_svc = svc.to_did_service("did:peer:2abc", 0).unwrap();
644 assert!(matches!(
645 did_svc.service_endpoint,
646 crate::service::Endpoint::Map(_)
647 ));
648 }
649}