1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(deny_unknown_fields)]
13pub struct CredentialBundle {
14 pub did: String,
15 #[serde(rename = "privateKeyMultibase")]
16 pub private_key_multibase: String,
17 #[serde(rename = "vtaDid")]
18 pub vta_did: String,
19 #[serde(rename = "vtaUrl", default, skip_serializing_if = "Option::is_none")]
20 pub vta_url: Option<String>,
21}
22
23impl CredentialBundle {
24 pub fn new(
26 did: impl Into<String>,
27 private_key_multibase: impl Into<String>,
28 vta_did: impl Into<String>,
29 ) -> Self {
30 Self {
31 did: did.into(),
32 private_key_multibase: private_key_multibase.into(),
33 vta_did: vta_did.into(),
34 vta_url: None,
35 }
36 }
37
38 pub fn vta_url(mut self, url: impl Into<String>) -> Self {
40 self.vta_url = Some(url.into());
41 self
42 }
43
44 #[cfg(feature = "sealed-transfer")]
62 pub fn from_ed25519_seed_multibase(
63 private_key_multibase: &str,
64 vta_did: &str,
65 vta_url: Option<&str>,
66 ) -> Result<(Self, String), crate::did_key::DidKeyError> {
67 let seed = crate::did_key::decode_private_key_multibase(private_key_multibase)?;
68 let public_key = ed25519_dalek::SigningKey::from_bytes(&seed)
69 .verifying_key()
70 .to_bytes();
71 let admin_did = format!(
72 "did:key:{}",
73 crate::did_key::ed25519_multibase_pubkey(&public_key)
74 );
75 let bundle = Self {
76 did: admin_did.clone(),
77 private_key_multibase: private_key_multibase.to_string(),
78 vta_did: vta_did.to_string(),
79 vta_url: vta_url.map(String::from),
80 };
81 Ok((bundle, admin_did))
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn test_credential_bundle_full() {
91 let json = r#"{
92 "did": "did:key:z6Mk123",
93 "privateKeyMultibase": "z1234567890",
94 "vtaDid": "did:key:z6MkVTA",
95 "vtaUrl": "https://vta.example.com"
96 }"#;
97 let bundle: CredentialBundle = serde_json::from_str(json).unwrap();
98 assert_eq!(bundle.did, "did:key:z6Mk123");
99 assert_eq!(bundle.private_key_multibase, "z1234567890");
100 assert_eq!(bundle.vta_did, "did:key:z6MkVTA");
101 assert_eq!(bundle.vta_url.as_deref(), Some("https://vta.example.com"));
102 }
103
104 #[test]
105 fn test_credential_bundle_without_url() {
106 let json = r#"{
107 "did": "did:key:z6Mk123",
108 "privateKeyMultibase": "z1234567890",
109 "vtaDid": "did:key:z6MkVTA"
110 }"#;
111 let bundle: CredentialBundle = serde_json::from_str(json).unwrap();
112 assert!(bundle.vta_url.is_none());
113 }
114
115 #[test]
116 fn test_credential_bundle_missing_did_fails() {
117 let json = r#"{
118 "privateKeyMultibase": "z1234567890",
119 "vtaDid": "did:key:z6MkVTA"
120 }"#;
121 assert!(serde_json::from_str::<CredentialBundle>(json).is_err());
122 }
123
124 #[test]
125 fn test_serde_json_roundtrip() {
126 let bundle = CredentialBundle {
127 did: "did:key:z6Mk123".to_string(),
128 private_key_multibase: "z1234567890".to_string(),
129 vta_did: "did:key:z6MkVTA".to_string(),
130 vta_url: Some("https://vta.example.com".to_string()),
131 };
132 let json = serde_json::to_string(&bundle).unwrap();
133 let decoded: CredentialBundle = serde_json::from_str(&json).unwrap();
134 assert_eq!(decoded.did, bundle.did);
135 assert_eq!(decoded.private_key_multibase, bundle.private_key_multibase);
136 assert_eq!(decoded.vta_did, bundle.vta_did);
137 assert_eq!(decoded.vta_url, bundle.vta_url);
138 }
139
140 #[test]
141 fn test_serde_json_roundtrip_without_url() {
142 let bundle = CredentialBundle {
143 did: "did:key:z6Mk123".to_string(),
144 private_key_multibase: "z1234567890".to_string(),
145 vta_did: "did:key:z6MkVTA".to_string(),
146 vta_url: None,
147 };
148 let json = serde_json::to_string(&bundle).unwrap();
149 assert!(!json.contains("vtaUrl"));
151 let decoded: CredentialBundle = serde_json::from_str(&json).unwrap();
152 assert!(decoded.vta_url.is_none());
153 }
154}