Skip to main content

ma_did/
identity.rs

1use libp2p_identity::PeerId;
2
3use crate::{Did, Document, EncryptionKey, MaError, Result, SigningKey, VerificationMethod};
4
5/// A generated DID identity with keys and a signed document.
6///
7/// Private keys are hex-encoded for storage. Use [`SigningKey::from_private_key_bytes`]
8/// and [`EncryptionKey::from_private_key_bytes`] to reconstruct key objects.
9#[derive(Debug, Clone)]
10pub struct GeneratedIdentity {
11    pub subject_url: Did,
12    pub document: Document,
13    pub signing_private_key_hex: String,
14    pub encryption_private_key_hex: String,
15}
16
17fn build_identity(ipns: &str) -> Result<GeneratedIdentity> {
18    let subject_url = Did::new_url(ipns, None::<String>)?;
19    let sign_url = Did::new_url(ipns, None::<String>)?;
20    let enc_url = Did::new_url(ipns, None::<String>)?;
21
22    let signing_key = SigningKey::generate(sign_url)?;
23    let encryption_key = EncryptionKey::generate(enc_url)?;
24
25    let mut document = Document::new(&subject_url, &subject_url);
26
27    let assertion_vm = VerificationMethod::new(
28        subject_url.base_id(),
29        subject_url.base_id(),
30        signing_key.key_type.clone(),
31        signing_key.did.fragment.as_deref().unwrap_or_default(),
32        signing_key.public_key_multibase.clone(),
33    )?;
34
35    let key_agreement_vm = VerificationMethod::new(
36        subject_url.base_id(),
37        subject_url.base_id(),
38        encryption_key.key_type.clone(),
39        encryption_key.did.fragment.as_deref().unwrap_or_default(),
40        encryption_key.public_key_multibase.clone(),
41    )?;
42
43    let assertion_vm_id = assertion_vm.id.clone();
44    document.add_verification_method(assertion_vm.clone())?;
45    document.add_verification_method(key_agreement_vm.clone())?;
46    document.assertion_method = vec![assertion_vm_id];
47    document.key_agreement = vec![key_agreement_vm.id.clone()];
48    document.sign(&signing_key, &assertion_vm)?;
49
50    Ok(GeneratedIdentity {
51        subject_url,
52        document,
53        signing_private_key_hex: hex::encode(signing_key.private_key_bytes()),
54        encryption_private_key_hex: hex::encode(encryption_key.private_key_bytes()),
55    })
56}
57
58/// Derive the `did:ma` IPNS identifier from a caller-managed Ed25519 secret.
59///
60/// The secret must be a valid 32-byte libp2p Ed25519 secret key. Secret storage,
61/// rotation, and recovery remain the caller's responsibility.
62pub fn ipns_from_secret(secret: [u8; 32]) -> Result<String> {
63    let keypair = libp2p_identity::Keypair::ed25519_from_bytes(secret)
64        .map_err(|_| MaError::InvalidIdentitySecret)?;
65    let peer_id = PeerId::from_public_key(&keypair.public());
66    Ok(peer_id.to_string())
67}
68
69/// Generate a base DID identity with keys and a signed document.
70///
71/// Use this when the application already has an IPNS identifier and wants to
72/// supply it explicitly. For the default flow where the application owns only
73/// the secret material, prefer [`generate_identity_from_secret`].
74///
75/// No `ma` extension fields are set — those are application-specific.
76///
77/// # Examples
78///
79/// ```
80/// use ma_did::generate_identity;
81///
82/// let id = generate_identity(
83///     "k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr"
84/// ).unwrap();
85///
86/// // Document is signed and valid
87/// id.document.verify().unwrap();
88/// id.document.validate().unwrap();
89///
90/// // Private keys available for storage
91/// assert!(!id.signing_private_key_hex.is_empty());
92/// assert!(!id.encryption_private_key_hex.is_empty());
93/// ```
94pub fn generate_identity(ipns: &str) -> Result<GeneratedIdentity> {
95    build_identity(ipns)
96}
97
98/// Generate a base DID identity where the `did:ma` IPNS identifier is derived
99/// from a caller-managed Ed25519 secret.
100///
101/// This keeps the secret under application control while removing the need for
102/// callers to precompute the IPNS/PeerId string themselves.
103///
104/// # Examples
105///
106/// ```
107/// use ma_did::{generate_identity_from_secret, ipns_from_secret};
108///
109/// let secret = [7u8; 32];
110/// let identity = generate_identity_from_secret(secret).unwrap();
111/// let expected_ipns = ipns_from_secret(secret).unwrap();
112///
113/// assert_eq!(identity.subject_url.ipns, expected_ipns);
114/// identity.document.verify().unwrap();
115/// ```
116pub fn generate_identity_from_secret(secret: [u8; 32]) -> Result<GeneratedIdentity> {
117    let ipns = ipns_from_secret(secret)?;
118    build_identity(&ipns)
119}