Skip to main content

agent_id_core/
document.rs

1//! DID Document structure and signing.
2//!
3//! A DID Document contains the public keys and service endpoints for an agent.
4//! Documents are self-signed to prevent tampering.
5
6use crate::{signing, Did, Error, Result, RootKey};
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9
10/// A verification method (public key) in a DID Document.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct VerificationMethod {
14    /// Full ID of this key (e.g., "did:key:...#root")
15    pub id: String,
16    /// Key type
17    #[serde(rename = "type")]
18    pub type_: String,
19    /// Controller DID
20    pub controller: String,
21    /// Public key in multibase format
22    pub public_key_multibase: String,
23}
24
25/// A service endpoint in a DID Document.
26#[derive(Debug, Clone, Serialize, Deserialize)]
27#[serde(rename_all = "camelCase")]
28pub struct Service {
29    /// Service ID (e.g., "did:key:...#handshake")
30    pub id: String,
31    /// Service type
32    #[serde(rename = "type")]
33    pub type_: String,
34    /// Service endpoint URL
35    pub service_endpoint: String,
36}
37
38/// Proof attached to a DID Document.
39#[derive(Debug, Clone, Serialize, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct DocumentProof {
42    /// Proof type
43    #[serde(rename = "type")]
44    pub type_: String,
45    /// When the proof was created
46    pub created: DateTime<Utc>,
47    /// Which key was used to sign
48    pub verification_method: String,
49    /// The signature value (base64)
50    pub proof_value: String,
51}
52
53/// A DID Document describing an agent's keys and services.
54#[derive(Debug, Clone, Serialize, Deserialize)]
55#[serde(rename_all = "camelCase")]
56pub struct DidDocument {
57    /// JSON-LD context
58    #[serde(rename = "@context")]
59    pub context: Vec<String>,
60    /// The DID this document describes
61    pub id: String,
62    /// Controller of this DID (usually self)
63    pub controller: String,
64    /// Verification methods (public keys)
65    pub verification_method: Vec<VerificationMethod>,
66    /// Keys that can authenticate as this DID
67    pub authentication: Vec<String>,
68    /// Keys that can make assertions
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub assertion_method: Option<Vec<String>>,
71    /// Service endpoints
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub service: Option<Vec<Service>>,
74    /// When this document was created
75    pub created: DateTime<Utc>,
76    /// When this document was last updated
77    pub updated: DateTime<Utc>,
78    /// Proof of authenticity
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub proof: Option<DocumentProof>,
81}
82
83impl DidDocument {
84    /// Create a new unsigned DID Document for a root key.
85    pub fn new(root_key: &RootKey) -> Self {
86        let did = root_key.did();
87        let did_str = did.to_string();
88        let now = Utc::now();
89
90        // Encode public key as multibase (z = base58btc)
91        let pubkey_multibase = format!("z{}", did.key_id());
92
93        Self {
94            context: vec![
95                "https://www.w3.org/ns/did/v1".to_string(),
96                "https://w3id.org/security/suites/ed25519-2020/v1".to_string(),
97            ],
98            id: did_str.clone(),
99            controller: did_str.clone(),
100            verification_method: vec![VerificationMethod {
101                id: format!("{}#root", did_str),
102                type_: "Ed25519VerificationKey2020".to_string(),
103                controller: did_str.clone(),
104                public_key_multibase: pubkey_multibase,
105            }],
106            authentication: vec![format!("{}#root", did_str)],
107            assertion_method: Some(vec![format!("{}#root", did_str)]),
108            service: None,
109            created: now,
110            updated: now,
111            proof: None,
112        }
113    }
114
115    /// Add a service endpoint.
116    pub fn add_service(mut self, id: &str, type_: &str, endpoint: &str) -> Self {
117        let service = Service {
118            id: format!("{}#{}", self.id, id),
119            type_: type_.to_string(),
120            service_endpoint: endpoint.to_string(),
121        };
122
123        match &mut self.service {
124            Some(services) => services.push(service),
125            None => self.service = Some(vec![service]),
126        }
127
128        self.updated = Utc::now();
129        self
130    }
131
132    /// Add the handshake service endpoint.
133    pub fn with_handshake_endpoint(self, endpoint: &str) -> Self {
134        self.add_service("handshake", "AIPHandshake", endpoint)
135    }
136
137    /// Sign this document with the root key.
138    pub fn sign(mut self, root_key: &RootKey) -> Result<Self> {
139        // Clear any existing proof before signing
140        self.proof = None;
141        self.updated = Utc::now();
142
143        // Canonicalize and sign
144        let canonical = signing::canonicalize(&self)?;
145        let signature = root_key.sign(&canonical);
146        let sig_b64 = base64::Engine::encode(
147            &base64::engine::general_purpose::STANDARD,
148            signature.to_bytes(),
149        );
150
151        self.proof = Some(DocumentProof {
152            type_: "Ed25519Signature2020".to_string(),
153            created: Utc::now(),
154            verification_method: format!("{}#root", self.id),
155            proof_value: sig_b64,
156        });
157
158        Ok(self)
159    }
160
161    /// Verify this document's signature.
162    pub fn verify(&self) -> Result<()> {
163        let proof = self.proof.as_ref().ok_or(Error::InvalidSignature)?;
164
165        // Parse the DID to get the expected public key
166        let did: Did = self.id.parse()?;
167        let public_key = did.public_key()?;
168
169        // Create unsigned version for verification
170        let mut unsigned = self.clone();
171        unsigned.proof = None;
172
173        let canonical = signing::canonicalize(&unsigned)?;
174
175        // Decode and verify signature
176        let sig_bytes = base64::Engine::decode(
177            &base64::engine::general_purpose::STANDARD,
178            &proof.proof_value,
179        )
180        .map_err(|_| Error::InvalidSignature)?;
181
182        let signature = ed25519_dalek::Signature::from_bytes(
183            &sig_bytes.try_into().map_err(|_| Error::InvalidSignature)?,
184        );
185
186        crate::keys::verify(&public_key, &canonical, &signature)?;
187
188        Ok(())
189    }
190
191    /// Get the handshake endpoint if present.
192    pub fn handshake_endpoint(&self) -> Option<&str> {
193        self.service.as_ref()?.iter().find_map(|s| {
194            if s.type_ == "AIPHandshake" {
195                Some(s.service_endpoint.as_str())
196            } else {
197                None
198            }
199        })
200    }
201
202    /// Get the DID this document describes.
203    pub fn did(&self) -> Result<Did> {
204        self.id.parse()
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211
212    #[test]
213    fn test_document_creation() {
214        let key = RootKey::generate();
215        let doc = DidDocument::new(&key);
216
217        assert!(doc.id.starts_with("did:key:"));
218        assert_eq!(doc.verification_method.len(), 1);
219        assert!(doc.proof.is_none());
220    }
221
222    #[test]
223    fn test_document_signing() {
224        let key = RootKey::generate();
225        let doc = DidDocument::new(&key)
226            .with_handshake_endpoint("https://example.com/handshake")
227            .sign(&key)
228            .unwrap();
229
230        assert!(doc.proof.is_some());
231        doc.verify().unwrap();
232    }
233
234    #[test]
235    fn test_document_tamper_detection() {
236        let key = RootKey::generate();
237        let mut doc = DidDocument::new(&key).sign(&key).unwrap();
238
239        // Tamper with the document
240        doc.controller = "did:key:ATTACKER".to_string();
241
242        // Verification should fail
243        assert!(doc.verify().is_err());
244    }
245
246    #[test]
247    fn test_handshake_endpoint() {
248        let key = RootKey::generate();
249        let doc = DidDocument::new(&key).with_handshake_endpoint("https://example.com/hs");
250
251        assert_eq!(doc.handshake_endpoint(), Some("https://example.com/hs"));
252    }
253}