Skip to main content

agent_governance/
identity.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Ed25519-based agent identity with DID support.
5
6use ed25519_dalek::{Signer, SigningKey, Verifier, VerifyingKey};
7use rand::rngs::OsRng;
8use serde::{Deserialize, Serialize};
9
10/// Maximum delegation depth to prevent Sybil attacks via infinite chains.
11pub const MAX_DELEGATION_DEPTH: u32 = 10;
12
13/// An agent's cryptographic identity (Ed25519 key pair + DID).
14#[derive(Debug)]
15pub struct AgentIdentity {
16    /// Decentralised identifier, e.g. `did:agentmesh:my-agent`.
17    pub did: String,
18    /// Ed25519 public key.
19    pub public_key: VerifyingKey,
20    /// Capabilities declared by this agent.
21    pub capabilities: Vec<String>,
22    /// Parent agent DID if this identity was created via delegation.
23    pub parent_did: Option<String>,
24    /// Depth in the delegation chain (0 = root).
25    pub delegation_depth: u32,
26    signing_key: SigningKey,
27}
28
29impl AgentIdentity {
30    /// Generate a new Ed25519-based identity for the given agent.
31    pub fn generate(agent_id: &str, capabilities: Vec<String>) -> Result<Self, IdentityError> {
32        let signing_key = SigningKey::generate(&mut OsRng);
33        let public_key = signing_key.verifying_key();
34        Ok(Self {
35            did: format!("did:agentmesh:{}", agent_id),
36            public_key,
37            capabilities,
38            parent_did: None,
39            delegation_depth: 0,
40            signing_key,
41        })
42    }
43
44    /// Sign arbitrary data with the agent's private key.
45    pub fn sign(&self, data: &[u8]) -> Vec<u8> {
46        self.signing_key.sign(data).to_bytes().to_vec()
47    }
48
49    /// Verify a signature against data using this identity's public key.
50    pub fn verify(&self, data: &[u8], signature: &[u8]) -> bool {
51        if signature.len() != 64 {
52            return false;
53        }
54        let sig_bytes: [u8; 64] = signature.try_into().unwrap();
55        let sig = ed25519_dalek::Signature::from_bytes(&sig_bytes);
56        self.public_key.verify(data, &sig).is_ok()
57    }
58
59    /// Delegate to a child agent with narrowed capabilities.
60    ///
61    /// The child's capabilities **must** be a subset of the parent's.
62    /// Delegation depth is incremented; exceeding [`MAX_DELEGATION_DEPTH`]
63    /// returns an error.
64    pub fn delegate(&self, name: &str, capabilities: Vec<String>) -> Result<Self, IdentityError> {
65        if self.delegation_depth >= MAX_DELEGATION_DEPTH {
66            return Err(IdentityError::DelegationDepthExceeded {
67                current: self.delegation_depth,
68                max: MAX_DELEGATION_DEPTH,
69            });
70        }
71
72        // Capabilities must be a subset of parent's
73        for cap in &capabilities {
74            if !self.capabilities.contains(cap) {
75                return Err(IdentityError::CapabilityNotInParent {
76                    capability: cap.clone(),
77                });
78            }
79        }
80
81        let signing_key = SigningKey::generate(&mut OsRng);
82        let public_key = signing_key.verifying_key();
83
84        Ok(Self {
85            did: format!("did:agentmesh:{}", name),
86            public_key,
87            capabilities,
88            parent_did: Some(self.did.clone()),
89            delegation_depth: self.delegation_depth + 1,
90            signing_key,
91        })
92    }
93
94    /// Serialise the public portion of the identity to JSON.
95    pub fn to_json(&self) -> Result<String, IdentityError> {
96        let public = PublicIdentity {
97            did: self.did.clone(),
98            public_key: self.public_key.to_bytes().to_vec(),
99            capabilities: self.capabilities.clone(),
100        };
101        serde_json::to_string(&public).map_err(IdentityError::Serialization)
102    }
103
104    /// Deserialise a public identity from JSON.
105    ///
106    /// The returned identity can verify signatures but cannot sign.
107    pub fn from_json(json: &str) -> Result<PublicIdentity, IdentityError> {
108        serde_json::from_str(json).map_err(IdentityError::Serialization)
109    }
110}
111
112/// The public (verifiable) portion of an agent identity.
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct PublicIdentity {
115    pub did: String,
116    pub public_key: Vec<u8>,
117    #[serde(default)]
118    pub capabilities: Vec<String>,
119}
120
121impl PublicIdentity {
122    /// Verify a signature using this public identity.
123    pub fn verify(&self, data: &[u8], signature: &[u8]) -> bool {
124        if self.public_key.len() != 32 || signature.len() != 64 {
125            return false;
126        }
127        let key_bytes: [u8; 32] = self.public_key.as_slice().try_into().unwrap();
128        let sig_bytes: [u8; 64] = signature.try_into().unwrap();
129        if let Ok(verifying_key) = VerifyingKey::from_bytes(&key_bytes) {
130            let sig = ed25519_dalek::Signature::from_bytes(&sig_bytes);
131            verifying_key.verify(data, &sig).is_ok()
132        } else {
133            false
134        }
135    }
136}
137
138/// Errors returned by identity operations.
139#[derive(Debug, thiserror::Error)]
140pub enum IdentityError {
141    #[error("serialization error: {0}")]
142    Serialization(serde_json::Error),
143
144    #[error("maximum delegation depth ({max}) exceeded (current depth: {current})")]
145    DelegationDepthExceeded { current: u32, max: u32 },
146
147    #[error("cannot delegate capability '{capability}' — not in parent's capabilities")]
148    CapabilityNotInParent { capability: String },
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn test_generate_and_did() {
157        let id = AgentIdentity::generate("test-agent", vec!["data.read".into()]).unwrap();
158        assert_eq!(id.did, "did:agentmesh:test-agent");
159        assert_eq!(id.capabilities, vec!["data.read"]);
160    }
161
162    #[test]
163    fn test_sign_and_verify() {
164        let id = AgentIdentity::generate("signer", vec![]).unwrap();
165        let data = b"hello world";
166        let sig = id.sign(data);
167        assert!(id.verify(data, &sig));
168        assert!(!id.verify(b"wrong data", &sig));
169    }
170
171    #[test]
172    fn test_json_roundtrip() {
173        let id = AgentIdentity::generate("json-agent", vec!["cap1".into()]).unwrap();
174        let json = id.to_json().unwrap();
175        let public = AgentIdentity::from_json(&json).unwrap();
176        assert_eq!(public.did, "did:agentmesh:json-agent");
177        assert_eq!(public.capabilities, vec!["cap1"]);
178
179        // Public identity can verify signatures
180        let sig = id.sign(b"payload");
181        assert!(public.verify(b"payload", &sig));
182    }
183
184    #[test]
185    fn test_bad_signature_rejected() {
186        let id = AgentIdentity::generate("agent", vec![]).unwrap();
187        assert!(!id.verify(b"data", &[0u8; 64]));
188        assert!(!id.verify(b"data", &[0u8; 32])); // wrong length
189    }
190
191    #[test]
192    fn test_multiple_identities_different_dids() {
193        let id1 = AgentIdentity::generate("agent-1", vec![]).unwrap();
194        let id2 = AgentIdentity::generate("agent-2", vec![]).unwrap();
195        assert_ne!(id1.did, id2.did);
196    }
197
198    #[test]
199    fn test_multiple_identities_different_key_pairs() {
200        let id1 = AgentIdentity::generate("agent-a", vec![]).unwrap();
201        let id2 = AgentIdentity::generate("agent-b", vec![]).unwrap();
202        assert_ne!(id1.public_key.to_bytes(), id2.public_key.to_bytes());
203    }
204
205    #[test]
206    fn test_sign_empty_data() {
207        let id = AgentIdentity::generate("empty-signer", vec![]).unwrap();
208        let sig = id.sign(b"");
209        assert_eq!(sig.len(), 64);
210        assert!(id.verify(b"", &sig));
211    }
212
213    #[test]
214    fn test_cross_identity_verification_fails() {
215        let id1 = AgentIdentity::generate("signer-1", vec![]).unwrap();
216        let id2 = AgentIdentity::generate("signer-2", vec![]).unwrap();
217        let sig = id1.sign(b"test data");
218        // id2 should NOT verify a signature produced by id1
219        assert!(!id2.verify(b"test data", &sig));
220    }
221
222    #[test]
223    fn test_public_identity_from_json_verifies_signatures() {
224        let id = AgentIdentity::generate("json-verify", vec!["read".into()]).unwrap();
225        let json = id.to_json().unwrap();
226        let public = AgentIdentity::from_json(&json).unwrap();
227        let data = b"important payload";
228        let sig = id.sign(data);
229        assert!(public.verify(data, &sig));
230        // Should fail with wrong data
231        assert!(!public.verify(b"wrong data", &sig));
232    }
233
234    #[test]
235    fn test_invalid_json_returns_error() {
236        let result = AgentIdentity::from_json("not valid json {{{");
237        assert!(result.is_err());
238        assert!(matches!(
239            result.unwrap_err(),
240            IdentityError::Serialization(_)
241        ));
242    }
243
244    #[test]
245    fn test_public_identity_empty_public_key_rejects() {
246        let public = PublicIdentity {
247            did: "did:agentmesh:test".to_string(),
248            public_key: vec![], // empty
249            capabilities: vec![],
250        };
251        assert!(!public.verify(b"data", &[0u8; 64]));
252    }
253
254    #[test]
255    fn test_capabilities_roundtrip_json() {
256        let caps = vec![
257            "data.read".to_string(),
258            "data.write".to_string(),
259            "admin".to_string(),
260        ];
261        let id = AgentIdentity::generate("cap-agent", caps.clone()).unwrap();
262        let json = id.to_json().unwrap();
263        let public = AgentIdentity::from_json(&json).unwrap();
264        assert_eq!(public.capabilities, caps);
265    }
266
267    #[test]
268    fn test_did_format() {
269        let id = AgentIdentity::generate("my-agent", vec![]).unwrap();
270        assert!(id.did.starts_with("did:agentmesh:"));
271        assert_eq!(id.did, "did:agentmesh:my-agent");
272    }
273
274    // ------------------------------------------------------------------
275    // Delegation tests (Issue #607)
276    // ------------------------------------------------------------------
277
278    #[test]
279    fn test_delegate_creates_child_with_parent_did() {
280        let parent =
281            AgentIdentity::generate("parent", vec!["read".into(), "write".into()]).unwrap();
282        let child = parent.delegate("child", vec!["read".into()]).unwrap();
283        assert_eq!(child.parent_did, Some("did:agentmesh:parent".to_string()));
284        assert_eq!(child.delegation_depth, 1);
285        assert_eq!(child.capabilities, vec!["read"]);
286    }
287
288    #[test]
289    fn test_delegate_narrows_capabilities() {
290        let parent =
291            AgentIdentity::generate("parent", vec!["read".into(), "write".into()]).unwrap();
292        let child = parent.delegate("child", vec!["read".into()]).unwrap();
293        assert!(!child.capabilities.contains(&"write".to_string()));
294    }
295
296    #[test]
297    fn test_delegate_rejects_superset() {
298        let parent = AgentIdentity::generate("parent", vec!["read".into()]).unwrap();
299        let result = parent.delegate("child", vec!["read".into(), "admin".into()]);
300        assert!(result.is_err());
301        match result.unwrap_err() {
302            IdentityError::CapabilityNotInParent { capability } => {
303                assert_eq!(capability, "admin");
304            }
305            other => panic!("expected CapabilityNotInParent, got {:?}", other),
306        }
307    }
308
309    #[test]
310    fn test_delegate_depth_increments() {
311        let root = AgentIdentity::generate("root", vec!["read".into()]).unwrap();
312        let d1 = root.delegate("d1", vec!["read".into()]).unwrap();
313        let d2 = d1.delegate("d2", vec!["read".into()]).unwrap();
314        assert_eq!(d2.delegation_depth, 2);
315        assert_eq!(d2.parent_did, Some("did:agentmesh:d1".to_string()));
316    }
317
318    #[test]
319    fn test_delegate_max_depth_enforced() {
320        let mut current = AgentIdentity::generate("root", vec!["read".into()]).unwrap();
321        for i in 0..MAX_DELEGATION_DEPTH {
322            current = current
323                .delegate(&format!("child-{}", i), vec!["read".into()])
324                .unwrap();
325        }
326        let result = current.delegate("one-too-many", vec!["read".into()]);
327        assert!(result.is_err());
328        assert!(matches!(
329            result.unwrap_err(),
330            IdentityError::DelegationDepthExceeded { .. }
331        ));
332    }
333
334    #[test]
335    fn test_delegate_child_has_own_keypair() {
336        let parent = AgentIdentity::generate("parent", vec!["read".into()]).unwrap();
337        let child = parent.delegate("child", vec!["read".into()]).unwrap();
338        assert_ne!(parent.public_key.to_bytes(), child.public_key.to_bytes());
339    }
340
341    #[test]
342    fn test_delegate_child_can_sign_and_verify() {
343        let parent = AgentIdentity::generate("parent", vec!["read".into()]).unwrap();
344        let child = parent.delegate("child", vec!["read".into()]).unwrap();
345        let data = b"delegation payload";
346        let sig = child.sign(data);
347        assert!(child.verify(data, &sig));
348        // Parent should NOT verify child's signature
349        assert!(!parent.verify(data, &sig));
350    }
351
352    #[test]
353    fn test_root_identity_has_no_parent() {
354        let root = AgentIdentity::generate("root", vec![]).unwrap();
355        assert!(root.parent_did.is_none());
356        assert_eq!(root.delegation_depth, 0);
357    }
358
359    #[test]
360    fn test_delegate_empty_capabilities_allowed() {
361        let parent = AgentIdentity::generate("parent", vec!["read".into()]).unwrap();
362        let child = parent.delegate("child", vec![]).unwrap();
363        assert!(child.capabilities.is_empty());
364        assert_eq!(child.delegation_depth, 1);
365    }
366}