use crate::{
DIDWebVHError, DIDWebVHState, Multibase, Signer, create::sign_witness_proofs,
ensure_object_mut, log_entry::LogEntry, parameters::Parameters, url::WebVHURL,
witness::Witnesses,
};
use affinidi_secrets_resolver::secrets::Secret;
use ahash::HashMap;
use serde_json::Value;
use std::sync::Arc;
use url::Url;
pub struct UpdateDIDConfig<A: Signer = Secret, W: Signer = Secret> {
pub state: DIDWebVHState,
pub signing_key: A,
pub document: Option<Value>,
pub update_keys: Option<Vec<Multibase>>,
pub next_key_hashes: Option<Vec<Multibase>>,
pub witness: Option<Witnesses>,
pub watchers: Option<Vec<String>>,
pub ttl: Option<u32>,
pub portable: Option<bool>,
pub deactivated: bool,
pub migrate_to: Option<String>,
pub witness_secrets: HashMap<String, W>,
}
pub struct UpdateDIDConfigBuilder<A: Signer = Secret, W: Signer = Secret> {
state: Option<DIDWebVHState>,
signing_key: Option<A>,
document: Option<Value>,
update_keys: Option<Vec<Multibase>>,
next_key_hashes: Option<Vec<Multibase>>,
witness: Option<Witnesses>,
watchers: Option<Vec<String>>,
ttl: Option<u32>,
portable: Option<bool>,
deactivated: bool,
migrate_to: Option<String>,
witness_secrets: HashMap<String, W>,
}
impl<A: Signer, W: Signer> UpdateDIDConfigBuilder<A, W> {
fn new() -> Self {
Self {
state: None,
signing_key: None,
document: None,
update_keys: None,
next_key_hashes: None,
witness: None,
watchers: None,
ttl: None,
portable: None,
deactivated: false,
migrate_to: None,
witness_secrets: HashMap::default(),
}
}
pub fn state(mut self, state: DIDWebVHState) -> Self {
self.state = Some(state);
self
}
pub fn signing_key(mut self, key: A) -> Self {
self.signing_key = Some(key);
self
}
pub fn document(mut self, doc: Value) -> Self {
self.document = Some(doc);
self
}
pub fn update_keys(mut self, keys: Vec<Multibase>) -> Self {
self.update_keys = Some(keys);
self
}
pub fn next_key_hashes(mut self, hashes: Vec<Multibase>) -> Self {
self.next_key_hashes = Some(hashes);
self
}
pub fn witness(mut self, witnesses: Witnesses) -> Self {
self.witness = Some(witnesses);
self
}
pub fn watchers(mut self, watchers: Vec<String>) -> Self {
self.watchers = Some(watchers);
self
}
pub fn ttl(mut self, ttl: u32) -> Self {
self.ttl = Some(ttl);
self
}
pub fn disable_portability(mut self) -> Self {
self.portable = Some(false);
self
}
pub fn deactivate(mut self, deactivated: bool) -> Self {
self.deactivated = deactivated;
self
}
pub fn migrate_to(mut self, address: impl Into<String>) -> Self {
self.migrate_to = Some(address.into());
self
}
pub fn witness_secret(mut self, did: impl Into<String>, secret: W) -> Self {
self.witness_secrets.insert(did.into(), secret);
self
}
pub fn witness_secrets(mut self, secrets: HashMap<String, W>) -> Self {
self.witness_secrets = secrets;
self
}
pub fn build(self) -> Result<UpdateDIDConfig<A, W>, DIDWebVHError> {
let state = self
.state
.ok_or_else(|| DIDWebVHError::DIDError("state is required".to_string()))?;
let signing_key = self
.signing_key
.ok_or_else(|| DIDWebVHError::DIDError("signing_key is required".to_string()))?;
if state.log_entries().is_empty() {
return Err(DIDWebVHError::LogEntryError(
"State must have at least one log entry to update".to_string(),
));
}
Ok(UpdateDIDConfig {
state,
signing_key,
document: self.document,
update_keys: self.update_keys,
next_key_hashes: self.next_key_hashes,
witness: self.witness,
watchers: self.watchers,
ttl: self.ttl,
portable: self.portable,
deactivated: self.deactivated,
migrate_to: self.migrate_to,
witness_secrets: self.witness_secrets,
})
}
}
impl UpdateDIDConfig {
pub fn builder() -> UpdateDIDConfigBuilder {
UpdateDIDConfigBuilder::new()
}
}
impl<A: Signer, W: Signer> UpdateDIDConfig<A, W> {
pub fn builder_generic() -> UpdateDIDConfigBuilder<A, W> {
UpdateDIDConfigBuilder::new()
}
}
#[derive(Debug)]
pub struct UpdateDIDResult {
did: String,
log_entry: LogEntry,
state: DIDWebVHState,
}
impl UpdateDIDResult {
pub fn did(&self) -> &str {
&self.did
}
pub fn log_entry(&self) -> &LogEntry {
&self.log_entry
}
pub fn state(&self) -> &DIDWebVHState {
&self.state
}
pub fn into_state(self) -> DIDWebVHState {
self.state
}
}
pub async fn update_did<A: Signer, W: Signer>(
mut config: UpdateDIDConfig<A, W>,
) -> Result<UpdateDIDResult, DIDWebVHError> {
if config.migrate_to.is_some() {
let new_address = config.migrate_to.clone().unwrap();
return do_migrate(config, new_address).await;
}
if config.deactivated {
return do_deactivate(config).await;
}
let last_params = config
.state
.log_entries()
.last()
.map(|e| e.validated_parameters.clone())
.ok_or_else(|| DIDWebVHError::LogEntryError("No log entries exist".to_string()))?;
let document = config.document.unwrap_or_else(|| {
config
.state
.log_entries()
.last()
.unwrap()
.get_state()
.clone()
});
let mut params = last_params;
if let Some(keys) = config.update_keys {
params.update_keys = Some(Arc::new(keys));
}
if let Some(hashes) = config.next_key_hashes {
params.next_key_hashes = Some(Arc::new(hashes));
}
if let Some(witness) = config.witness {
params.witness = Some(Arc::new(witness));
}
if let Some(watchers) = config.watchers {
params.watchers = Some(Arc::new(watchers));
}
if let Some(ttl) = config.ttl {
params.ttl = Some(ttl);
}
if let Some(portable) = config.portable {
params.portable = Some(portable);
}
config
.state
.create_log_entry(None, &document, ¶ms, &config.signing_key)
.await?;
sign_new_entry_witnesses(&mut config.state, &config.witness_secrets).await?;
build_result(config.state)
}
async fn do_migrate<A: Signer, W: Signer>(
mut config: UpdateDIDConfig<A, W>,
new_address: String,
) -> Result<UpdateDIDResult, DIDWebVHError> {
let last_entry = config
.state
.log_entries()
.last()
.ok_or_else(|| DIDWebVHError::LogEntryError("No log entries exist".to_string()))?;
if last_entry.validated_parameters.portable != Some(true) {
return Err(DIDWebVHError::ParametersError(
"DID must have portable=true to migrate".to_string(),
));
}
let did = last_entry
.get_state()
.get("id")
.and_then(|v| v.as_str())
.ok_or_else(|| DIDWebVHError::DIDError("DID not found in log entry state".to_string()))?
.to_string();
let did_url = WebVHURL::parse_did_url(&did)?;
let mut new_did_url = if new_address.starts_with("did:") {
WebVHURL::parse_did_url(&new_address)?
} else {
let url = Url::parse(&new_address)
.map_err(|e| DIDWebVHError::DIDError(format!("Invalid URL: {e}")))?;
WebVHURL::parse_url(&url)?
};
new_did_url.scid = did_url.scid.clone();
let doc_str = serde_json::to_string(last_entry.get_state())
.map_err(|e| DIDWebVHError::DIDError(format!("Failed to serialize document: {e}")))?;
let new_doc_str = doc_str.replace(&did_url.to_string(), &new_did_url.to_string());
let mut new_doc: Value = serde_json::from_str(&new_doc_str)
.map_err(|e| DIDWebVHError::DIDError(format!("Failed to parse document: {e}")))?;
if let Some(alias) = new_doc.get_mut("alsoKnownAs") {
if let Some(arr) = alias.as_array_mut() {
arr.push(Value::String(did));
}
} else {
ensure_object_mut(&mut new_doc)?.insert(
"alsoKnownAs".to_string(),
Value::Array(vec![Value::String(did)]),
);
}
let mut params = last_entry.validated_parameters.clone();
if let Some(keys) = config.update_keys {
params.update_keys = Some(Arc::new(keys));
}
if let Some(hashes) = config.next_key_hashes {
params.next_key_hashes = Some(Arc::new(hashes));
}
if let Some(witness) = config.witness {
params.witness = Some(Arc::new(witness));
}
if let Some(watchers) = config.watchers {
params.watchers = Some(Arc::new(watchers));
}
if let Some(ttl) = config.ttl {
params.ttl = Some(ttl);
}
config
.state
.create_log_entry(None, &new_doc, ¶ms, &config.signing_key)
.await?;
sign_new_entry_witnesses(&mut config.state, &config.witness_secrets).await?;
build_result(config.state)
}
async fn do_deactivate<A: Signer, W: Signer>(
mut config: UpdateDIDConfig<A, W>,
) -> Result<UpdateDIDResult, DIDWebVHError> {
let last_entry = config
.state
.log_entries()
.last()
.ok_or_else(|| DIDWebVHError::LogEntryError("No log entries exist".to_string()))?;
if last_entry.validated_parameters.pre_rotation_active {
let doc = last_entry.get_state().clone();
let vm = config.signing_key.verification_method();
let pk = vm.split('#').next().unwrap_or(vm);
let pk = pk.strip_prefix("did:key:").unwrap_or(pk);
let disable_params = Parameters {
update_keys: Some(Arc::new(vec![Multibase::new(pk.to_string())])),
next_key_hashes: Some(Arc::new(Vec::new())),
..Default::default()
};
config
.state
.create_log_entry(None, &doc, &disable_params, &config.signing_key)
.await?;
sign_new_entry_witnesses(&mut config.state, &config.witness_secrets).await?;
}
let doc = config
.state
.log_entries()
.last()
.unwrap()
.get_state()
.clone();
let deactivate_params = Parameters {
deactivated: Some(true),
update_keys: Some(Arc::new(Vec::new())),
..Default::default()
};
config
.state
.create_log_entry(None, &doc, &deactivate_params, &config.signing_key)
.await?;
sign_new_entry_witnesses(&mut config.state, &config.witness_secrets).await?;
build_result(config.state)
}
async fn sign_new_entry_witnesses<W: Signer>(
state: &mut DIDWebVHState,
witness_secrets: &HashMap<String, W>,
) -> Result<(), DIDWebVHError> {
let (log_entries, witness_proofs) = state.log_entries_and_witness_proofs_mut();
let entry = log_entries
.last()
.ok_or_else(|| DIDWebVHError::LogEntryError("No log entries after update".to_string()))?;
sign_witness_proofs(
witness_proofs,
entry,
&entry.get_active_witnesses(),
witness_secrets,
)
.await?;
Ok(())
}
fn build_result(state: DIDWebVHState) -> Result<UpdateDIDResult, DIDWebVHError> {
let last_entry = state
.log_entries()
.last()
.ok_or_else(|| DIDWebVHError::LogEntryError("No log entries after update".to_string()))?;
let did = last_entry
.get_state()
.get("id")
.and_then(|v| v.as_str())
.unwrap_or_default()
.to_string();
let log_entry = last_entry.log_entry.clone();
Ok(UpdateDIDResult {
did,
log_entry,
state,
})
}