diff --git a/Cargo.lock b/Cargo.lock index 5bea505d..36afda75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2984,7 +2984,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.118", ] [[package]] @@ -3258,9 +3258,9 @@ dependencies = [ [[package]] name = "didwebvh-rs" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad5c39bda74ba720b8aaee3921552be256e773ab8dafc345967a4bdec8208dc" +checksum = "994a31640b6bc1c4e59cde5efbd2e7b1565f2666b29c603ae51ce22ecdbf9e7d" dependencies = [ "affinidi-data-integrity", "affinidi-did-common", @@ -4736,7 +4736,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.4", "system-configuration 0.7.0", "tokio", "tower-service", @@ -6926,7 +6926,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.40", - "socket2 0.5.10", + "socket2 0.6.4", "thiserror 2.0.18", "tokio", "tracing", @@ -6964,7 +6964,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.4", "tracing", "windows-sys 0.60.2", ] @@ -10832,7 +10832,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 762b556e..fe19ca18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -136,7 +136,7 @@ chrono = { version = "0.4", features = ["serde"] } rand = "0.10" # DID Web with Verifiable History -didwebvh-rs = "0.5" +didwebvh-rs = "0.5.6" # URL parsing url = "2" diff --git a/vta-service/src/operations/did_webvh/mod.rs b/vta-service/src/operations/did_webvh/mod.rs index b25206a1..77a23fcc 100644 --- a/vta-service/src/operations/did_webvh/mod.rs +++ b/vta-service/src/operations/did_webvh/mod.rs @@ -437,6 +437,20 @@ async fn load_key_as_secret( Ok((secret, record.public_key.clone(), record)) } +/// A synthetic, strictly-increasing, backdated `versionTime` for the VTA's next +/// did:webvh log entry. did:webvh serialises `versionTime` at second granularity +/// and requires each entry to be strictly later than the previous and not in the +/// future; the real wall-clock value is irrelevant for resolution. We backdate a +/// day and space entries a minute apart by their index, so the VTA can create +/// then update its DID back-to-back (e.g. `setup` then `services didcomm enable`) +/// without producing same-second timestamps that serialise identically and make +/// the DID unresolvable. `existing_entry_count` is the number of log entries +/// already in the chain (0 for the genesis entry). +fn backdated_version_time(existing_entry_count: usize) -> chrono::DateTime { + use chrono::Duration; + Utc::now().fixed_offset() - Duration::days(1) + Duration::minutes(existing_entry_count as i64) +} + /// Check whether a DID document (JSON) contains any DIDCommMessaging service. fn document_has_didcomm_service(doc: &serde_json::Value) -> bool { doc.get("service") @@ -919,6 +933,9 @@ pub async fn create_did_webvh( .authorization_key(derived.signing_secret.clone()) .did_document(did_document.clone()) .parameters(parameters) + // Backdated genesis timestamp (entry index 0) so a follow-on update in + // the same second doesn't collide — see `backdated_version_time`. + .version_time(backdated_version_time(0)) .build() .map_err(|e| AppError::Internal(format!("failed to build DID config: {e}")))?; diff --git a/vta-service/src/operations/did_webvh/update/orchestrator.rs b/vta-service/src/operations/did_webvh/update/orchestrator.rs index 1da9c40b..07e932a3 100644 --- a/vta-service/src/operations/did_webvh/update/orchestrator.rs +++ b/vta-service/src/operations/did_webvh/update/orchestrator.rs @@ -106,6 +106,8 @@ pub async fn update_did_webvh( let last_state = state.log_entries().last().ok_or_else(|| { UpdateDidWebvhError::Library(format!("DID {} has no log entries", record.did)) })?; + // Index for the new entry's backdated versionTime (count already in the chain). + let new_entry_index = state.log_entries().len(); // 4a. Optimistic-concurrency precondition. Check BEFORE key // derivation / signing so a stale `get → edit → save` cycle @@ -203,7 +205,10 @@ pub async fn update_did_webvh( // 9. Build the library config. let mut builder = UpdateDIDConfig::::builder_generic() .state(state) - .signing_key(signing_secret); + .signing_key(signing_secret) + // Backdated, index-spaced timestamp so a back-to-back update doesn't + // collide with the previous entry's second — see `backdated_version_time`. + .version_time(super::super::backdated_version_time(new_entry_index)); if let Some(doc) = new_doc { builder = builder.document(doc); let new_keys: Vec = if pre_rotation_active {