Skip to main content

co_identity/resolvers/
did_key.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2// Copyright (C) 2026 1io BRANDGUARDIAN GmbH
3
4use crate::{
5	library::from_did_key_verification_method::from_did_key_verification_method,
6	types::didcomm::context::DidCommContext, DidCommPrivateContext, DidCommPublicContext, Identity, IdentityBox,
7	IdentityResolver, IdentityResolverError, PrivateIdentity, SignError,
8};
9use anyhow::anyhow;
10use async_trait::async_trait;
11use co_primitives::{tags, Network, Secret};
12use did_key::{
13	from_existing_key, generate, resolve, CoreSign, DIDCore, Ed25519KeyPair, KeyMaterial, PatchedKeyPair, X25519KeyPair,
14};
15use std::{collections::BTreeSet, fmt::Debug, sync::Arc};
16
17#[derive(Clone)]
18pub struct DidKeyIdentity {
19	did: String,
20	key: Arc<PatchedKeyPair>,
21	private: bool,
22}
23impl DidKeyIdentity {
24	/// Generate new identity.
25	///
26	/// # Arguments
27	/// - `seed` - The seed usedt to genreate the identity. If `None` is passed it will be generated using `getrandom`
28	///   crate.
29	pub fn generate(seed: Option<&[u8]>) -> Self {
30		Self::from_key(generate::<Ed25519KeyPair>(seed))
31	}
32
33	pub fn generate_x25519(seed: Option<&[u8]>) -> Self {
34		Self::from_key(generate::<X25519KeyPair>(seed))
35	}
36
37	pub fn from_identity(identity: &str) -> Result<Self, anyhow::Error> {
38		Self::try_from(identity)
39	}
40
41	pub fn from_key(key: PatchedKeyPair) -> Self {
42		let private = !key.private_key_bytes().is_empty();
43		Self { did: key.get_did_document(Default::default()).id, key: Arc::new(key), private }
44	}
45
46	pub fn from_bytes(bytes: &[u8]) -> Result<Self, anyhow::Error> {
47		Self::try_from(bytes)
48	}
49
50	pub fn to_bytes(&self) -> &[u8] {
51		self.identity().as_bytes()
52	}
53
54	// pub fn key(&self) -> Arc<PatchedKeyPair> {
55	// 	self.key.clone()
56	// }
57
58	pub fn public_key_bytes(&self) -> Vec<u8> {
59		self.key.as_ref().public_key_bytes()
60	}
61
62	pub fn private_key_bytes(&self) -> Secret {
63		self.key.as_ref().private_key_bytes().into()
64	}
65
66	pub fn import(key: &co_core_keystore::Key) -> Result<Self, anyhow::Error> {
67		match (key.tags.string("format"), &key.secret) {
68			(Some("Ed25519"), co_core_keystore::Secret::PrivateKey(secret)) => {
69				Ok(Self::from_key(from_existing_key::<Ed25519KeyPair>(&[], Some(secret.divulge()))))
70			},
71			(Some("X25519"), co_core_keystore::Secret::PrivateKey(secret)) => {
72				Ok(Self::from_key(from_existing_key::<X25519KeyPair>(&[], Some(secret.divulge()))))
73			},
74			_ => Err(anyhow!("Invalid identity format or key")),
75		}
76	}
77
78	pub fn export(&self) -> Result<co_core_keystore::Key, anyhow::Error> {
79		Ok(co_core_keystore::Key {
80			description: "did:key identitiy".to_owned(),
81			name: self.identity().to_owned(),
82			tags: tags!("type": "co-identity", "format": "Ed25519"), // TODO: detect format alg
83			uri: self.identity().to_owned(),
84			secret: co_core_keystore::Secret::PrivateKey(self.key.private_key_bytes().into()),
85		})
86	}
87}
88impl Debug for DidKeyIdentity {
89	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90		f.debug_struct("DidKeyIdentity")
91			.field("did", &self.did)
92			.field("public_key", &format_args!("{:02X?}", self.key.public_key_bytes()))
93			.finish()
94	}
95}
96impl TryFrom<&[u8]> for DidKeyIdentity {
97	type Error = anyhow::Error;
98
99	fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
100		Ok(Self::from_key(resolve(std::str::from_utf8(value)?).map_err(|e| anyhow!("resolve failed: {:?}", e))?))
101	}
102}
103impl TryFrom<&str> for DidKeyIdentity {
104	type Error = anyhow::Error;
105
106	fn try_from(value: &str) -> Result<Self, Self::Error> {
107		Ok(Self::from_key(resolve(value).map_err(|e| anyhow!("resolve failed: {:?}", e))?))
108	}
109}
110impl Identity for DidKeyIdentity {
111	fn identity(&self) -> &str {
112		&self.did
113	}
114
115	fn public_key(&self) -> Option<Vec<u8>> {
116		// Some(self.key.public_key_bytes())
117		None
118	}
119
120	fn verify(&self, signature: &[u8], data: &[u8], public_key: Option<&[u8]>) -> bool {
121		// if key is provided verify its our key
122		if let Some(key) = public_key {
123			if key != self.key.public_key_bytes() {
124				return false;
125			}
126		}
127
128		// verify signature
129		self.key.verify(data, signature).is_ok()
130	}
131
132	fn didcomm_public(&self) -> Option<DidCommPublicContext> {
133		let doc = self
134			.key
135			.get_did_document(did_key::Config { use_jose_format: false, serialize_secrets: false });
136		let verfication_method = from_did_key_verification_method(doc.verification_method.first()?.clone(), None);
137		let key_aggrements = doc.key_agreement?;
138		let key_aggrement_id = key_aggrements.first()?;
139		let key_agreement = from_did_key_verification_method(
140			doc.verification_method
141				.iter()
142				.find(|item| &item.id == key_aggrement_id)?
143				.clone(),
144			None,
145		);
146		Some(DidCommPublicContext::new(self.identity().to_owned(), verfication_method, key_agreement))
147	}
148
149	fn networks(&self) -> BTreeSet<Network> {
150		Default::default()
151	}
152}
153impl PrivateIdentity for DidKeyIdentity {
154	fn sign(&self, data: &[u8]) -> Result<Vec<u8>, SignError> {
155		if !self.private {
156			return Err(SignError::Unauthorized);
157		}
158		Ok(self.key.sign(data))
159	}
160
161	fn didcomm_private(&self) -> Option<DidCommPrivateContext> {
162		let public = self.didcomm_public()?;
163		let doc = self
164			.key
165			.get_did_document(did_key::Config { use_jose_format: false, serialize_secrets: true });
166		let verfication_method_private = doc.verification_method.iter().find_map(|vm| {
167			if vm.id == public.verification_method().id && vm.private_key.is_some() {
168				from_did_key_verification_method(vm.clone(), vm.private_key.clone())
169					.public_key_bytes()
170					.ok()
171			} else {
172				None
173			}
174		})?;
175		let key_agreement_private = doc.verification_method.iter().find_map(|vm| {
176			if vm.id == public.key_agreement().id && vm.private_key.is_some() {
177				from_did_key_verification_method(vm.clone(), vm.private_key.clone())
178					.public_key_bytes()
179					.ok()
180			} else {
181				None
182			}
183		})?;
184		Some(DidCommPrivateContext::new(public, verfication_method_private.into(), key_agreement_private.into()))
185	}
186}
187
188#[derive(Debug, Clone)]
189pub struct DidKeyIdentityResolver {}
190impl Default for DidKeyIdentityResolver {
191	fn default() -> Self {
192		Self::new()
193	}
194}
195impl DidKeyIdentityResolver {
196	pub fn new() -> DidKeyIdentityResolver {
197		Self {}
198	}
199}
200#[async_trait]
201impl IdentityResolver for DidKeyIdentityResolver {
202	async fn resolve(&self, identity: &str) -> Result<IdentityBox, IdentityResolverError> {
203		if identity.starts_with("did:key:") {
204			if let Ok(did_key_identity) = DidKeyIdentity::try_from(identity) {
205				return Ok(IdentityBox::new(did_key_identity));
206			}
207		}
208		Err(IdentityResolverError::NotFound)
209	}
210}
211
212#[cfg(test)]
213mod tests {
214	use crate::{DidCommHeader, DidKeyIdentity, Identity, IdentityBox, MemoryIdentityResolver, PrivateIdentity};
215	use co_primitives::StaticCoDate;
216
217	#[test]
218	fn it_should_sign_and_verfiy() {
219		let data = "hello world".as_bytes();
220		let identity = DidKeyIdentity::generate(None);
221		let signature = identity.sign(data).unwrap();
222		assert!(identity.verify(signature.as_slice(), data, None));
223	}
224
225	#[test]
226	fn it_should_sign_and_verfiy_with_public_key() {
227		let data = "hello world".as_bytes();
228		let identity = DidKeyIdentity::generate(None);
229		let public_key = identity.public_key();
230		let signature = identity.sign(data).unwrap();
231		assert!(identity.verify(signature.as_slice(), data, public_key.as_deref()));
232	}
233
234	#[test]
235	fn it_should_have_public_context() {
236		let identity = DidKeyIdentity::generate(None);
237		identity.try_didcomm_public().unwrap();
238	}
239
240	#[test]
241	fn it_should_have_private_context() {
242		let identity = DidKeyIdentity::generate(None);
243		identity.try_didcomm_private().unwrap();
244	}
245
246	#[test]
247	fn test_import_export() {
248		let identity = DidKeyIdentity::generate(None);
249		let export = identity.export().unwrap();
250		let import = DidKeyIdentity::import(&export).unwrap();
251		import.try_didcomm_public().unwrap();
252		import.try_didcomm_private().unwrap();
253	}
254
255	#[tokio::test]
256	async fn test_jwe_roundtrip() {
257		// let from = DidKeyIdentity::generate(None);
258		// let to = DidKeyIdentity::generate(None);
259		let from = DidKeyIdentity::import(&DidKeyIdentity::generate(None).export().unwrap()).unwrap();
260		let to = DidKeyIdentity::import(&DidKeyIdentity::generate(None).export().unwrap()).unwrap();
261
262		// resolver
263		let mut resolver = MemoryIdentityResolver::default();
264		resolver.insert(IdentityBox::new(from.clone())).await;
265		resolver.insert(IdentityBox::new(to.clone())).await;
266
267		// "send"
268		let (from_private, to_public, header) = DidCommHeader::create(&StaticCoDate(0), &from, &to, "test").unwrap();
269		let message = from_private.jwe(&to_public, header.clone(), "null").unwrap();
270
271		// "recv"
272		let to_private = to.try_didcomm_private().unwrap();
273		let (received_header, received_body) = to_private.receive(&resolver, &message).await.unwrap();
274		assert_eq!(received_header, header);
275		assert_eq!(received_body, "null");
276	}
277}