use crate::{signing, Did, Error, Result, RootKey};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VerificationMethod {
pub id: String,
#[serde(rename = "type")]
pub type_: String,
pub controller: String,
pub public_key_multibase: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Service {
pub id: String,
#[serde(rename = "type")]
pub type_: String,
pub service_endpoint: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DocumentProof {
#[serde(rename = "type")]
pub type_: String,
pub created: DateTime<Utc>,
pub verification_method: String,
pub proof_value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DidDocument {
#[serde(rename = "@context")]
pub context: Vec<String>,
pub id: String,
pub controller: String,
pub verification_method: Vec<VerificationMethod>,
pub authentication: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub assertion_method: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub service: Option<Vec<Service>>,
pub created: DateTime<Utc>,
pub updated: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub proof: Option<DocumentProof>,
}
impl DidDocument {
pub fn new(root_key: &RootKey) -> Self {
let did = root_key.did();
let did_str = did.to_string();
let now = Utc::now();
let pubkey_multibase = format!("z{}", did.key_id());
Self {
context: vec![
"https://www.w3.org/ns/did/v1".to_string(),
"https://w3id.org/security/suites/ed25519-2020/v1".to_string(),
],
id: did_str.clone(),
controller: did_str.clone(),
verification_method: vec![VerificationMethod {
id: format!("{}#root", did_str),
type_: "Ed25519VerificationKey2020".to_string(),
controller: did_str.clone(),
public_key_multibase: pubkey_multibase,
}],
authentication: vec![format!("{}#root", did_str)],
assertion_method: Some(vec![format!("{}#root", did_str)]),
service: None,
created: now,
updated: now,
proof: None,
}
}
pub fn add_service(mut self, id: &str, type_: &str, endpoint: &str) -> Self {
let service = Service {
id: format!("{}#{}", self.id, id),
type_: type_.to_string(),
service_endpoint: endpoint.to_string(),
};
match &mut self.service {
Some(services) => services.push(service),
None => self.service = Some(vec![service]),
}
self.updated = Utc::now();
self
}
pub fn with_handshake_endpoint(self, endpoint: &str) -> Self {
self.add_service("handshake", "AIPHandshake", endpoint)
}
pub fn sign(mut self, root_key: &RootKey) -> Result<Self> {
self.proof = None;
self.updated = Utc::now();
let canonical = signing::canonicalize(&self)?;
let signature = root_key.sign(&canonical);
let sig_b64 = base64::Engine::encode(
&base64::engine::general_purpose::STANDARD,
signature.to_bytes(),
);
self.proof = Some(DocumentProof {
type_: "Ed25519Signature2020".to_string(),
created: Utc::now(),
verification_method: format!("{}#root", self.id),
proof_value: sig_b64,
});
Ok(self)
}
pub fn verify(&self) -> Result<()> {
let proof = self.proof.as_ref().ok_or(Error::InvalidSignature)?;
let did: Did = self.id.parse()?;
let public_key = did.public_key()?;
let mut unsigned = self.clone();
unsigned.proof = None;
let canonical = signing::canonicalize(&unsigned)?;
let sig_bytes = base64::Engine::decode(
&base64::engine::general_purpose::STANDARD,
&proof.proof_value,
)
.map_err(|_| Error::InvalidSignature)?;
let signature = ed25519_dalek::Signature::from_bytes(
&sig_bytes.try_into().map_err(|_| Error::InvalidSignature)?,
);
crate::keys::verify(&public_key, &canonical, &signature)?;
Ok(())
}
pub fn handshake_endpoint(&self) -> Option<&str> {
self.service.as_ref()?.iter().find_map(|s| {
if s.type_ == "AIPHandshake" {
Some(s.service_endpoint.as_str())
} else {
None
}
})
}
pub fn did(&self) -> Result<Did> {
self.id.parse()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_document_creation() {
let key = RootKey::generate();
let doc = DidDocument::new(&key);
assert!(doc.id.starts_with("did:key:"));
assert_eq!(doc.verification_method.len(), 1);
assert!(doc.proof.is_none());
}
#[test]
fn test_document_signing() {
let key = RootKey::generate();
let doc = DidDocument::new(&key)
.with_handshake_endpoint("https://example.com/handshake")
.sign(&key)
.unwrap();
assert!(doc.proof.is_some());
doc.verify().unwrap();
}
#[test]
fn test_document_tamper_detection() {
let key = RootKey::generate();
let mut doc = DidDocument::new(&key).sign(&key).unwrap();
doc.controller = "did:key:ATTACKER".to_string();
assert!(doc.verify().is_err());
}
#[test]
fn test_handshake_endpoint() {
let key = RootKey::generate();
let doc = DidDocument::new(&key).with_handshake_endpoint("https://example.com/hs");
assert_eq!(doc.handshake_endpoint(), Some("https://example.com/hs"));
}
}